ratatui_ruby 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.builds/ruby-3.2.yml +1 -1
- data/.builds/ruby-3.3.yml +1 -1
- data/.builds/ruby-3.4.yml +1 -1
- data/.builds/ruby-4.0.0.yml +1 -1
- data/AGENTS.md +87 -171
- data/CHANGELOG.md +38 -1
- data/README.md +8 -3
- data/REUSE.toml +20 -0
- data/doc/application_architecture.md +105 -45
- data/doc/application_testing.md +5 -3
- data/doc/contributors/design/ruby_frontend.md +9 -5
- data/doc/contributors/developing_examples.md +76 -18
- data/doc/contributors/documentation_style.md +7 -0
- data/doc/contributors/index.md +2 -0
- data/doc/event_handling.md +10 -4
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_barchart_demo.png +0 -0
- data/doc/images/widget_block_padding.png +0 -0
- data/doc/images/widget_block_titles.png +0 -0
- data/doc/images/widget_box_demo.png +0 -0
- data/doc/images/widget_calendar_demo.png +0 -0
- data/doc/images/widget_cell_demo.png +0 -0
- data/doc/images/widget_chart_demo.png +0 -0
- data/doc/images/widget_gauge_demo.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge_demo.png +0 -0
- data/doc/images/widget_list_demo.png +0 -0
- data/doc/images/widget_ratatui_logo_demo.png +0 -0
- data/doc/images/widget_ratatui_mascot_demo.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_scrollbar_demo.png +0 -0
- data/doc/images/widget_sparkline_demo.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table_flex.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/interactive_design.md +25 -30
- data/doc/quickstart.md +147 -120
- data/examples/app_all_events/README.md +81 -0
- data/examples/app_all_events/app.rb +93 -0
- data/examples/app_all_events/model/event_color_cycle.rb +41 -0
- data/examples/app_all_events/model/event_entry.rb +75 -0
- data/examples/app_all_events/model/events.rb +180 -0
- data/examples/app_all_events/model/highlight.rb +57 -0
- data/examples/app_all_events/model/timestamp.rb +54 -0
- data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
- data/examples/app_all_events/view/app_view.rb +78 -0
- data/examples/app_all_events/view/controls_view.rb +50 -0
- data/examples/app_all_events/view/counts_view.rb +55 -0
- data/examples/app_all_events/view/live_view.rb +69 -0
- data/examples/app_all_events/view/log_view.rb +60 -0
- data/examples/app_all_events/view.rb +7 -0
- data/examples/app_all_events/view_state.rb +42 -0
- data/examples/app_color_picker/README.md +94 -0
- data/examples/app_color_picker/app.rb +112 -0
- data/examples/app_color_picker/clipboard.rb +84 -0
- data/examples/app_color_picker/color.rb +191 -0
- data/examples/app_color_picker/copy_dialog.rb +170 -0
- data/examples/app_color_picker/harmony.rb +56 -0
- data/examples/app_color_picker/input.rb +142 -0
- data/examples/app_color_picker/palette.rb +80 -0
- data/examples/app_color_picker/scene.rb +201 -0
- data/examples/{login_form → app_login_form}/app.rb +39 -42
- data/examples/{map_demo → app_map_demo}/app.rb +24 -21
- data/examples/{table_select → app_table_select}/app.rb +68 -65
- data/examples/{quickstart_dsl → verify_quickstart_dsl}/app.rb +15 -6
- data/examples/verify_quickstart_layout/app.rb +69 -0
- data/examples/{quickstart_lifecycle → verify_quickstart_lifecycle}/app.rb +19 -10
- data/examples/verify_readme_usage/app.rb +34 -0
- data/examples/widget_barchart_demo/app.rb +238 -0
- data/examples/{block_padding → widget_block_padding}/app.rb +17 -13
- data/examples/{block_titles → widget_block_titles}/app.rb +25 -17
- data/examples/{box_demo → widget_box_demo}/app.rb +99 -65
- data/examples/widget_calendar_demo/app.rb +109 -0
- data/examples/widget_cell_demo/app.rb +104 -0
- data/examples/widget_chart_demo/app.rb +213 -0
- data/examples/widget_gauge_demo/app.rb +212 -0
- data/examples/widget_layout_split/app.rb +246 -0
- data/examples/widget_line_gauge_demo/app.rb +217 -0
- data/examples/widget_list_demo/app.rb +382 -0
- data/examples/widget_list_styles/app.rb +141 -0
- data/examples/widget_popup_demo/app.rb +104 -0
- data/examples/widget_ratatui_logo_demo/app.rb +103 -0
- data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
- data/examples/widget_rect/app.rb +205 -0
- data/examples/widget_render/app.rb +184 -0
- data/examples/widget_rich_text/app.rb +137 -0
- data/examples/widget_scroll_text/app.rb +108 -0
- data/examples/widget_scrollbar_demo/app.rb +153 -0
- data/examples/widget_sparkline_demo/app.rb +274 -0
- data/examples/widget_style_colors/app.rb +19 -21
- data/examples/widget_table_flex/app.rb +95 -0
- data/examples/widget_tabs_demo/app.rb +167 -0
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/ext/ratatui_ruby/src/events.rs +121 -36
- data/ext/ratatui_ruby/src/frame.rs +115 -0
- data/ext/ratatui_ruby/src/lib.rs +79 -26
- data/ext/ratatui_ruby/src/rendering.rs +8 -4
- data/ext/ratatui_ruby/src/style.rs +138 -57
- data/ext/ratatui_ruby/src/terminal.rs +5 -9
- data/ext/ratatui_ruby/src/text.rs +13 -6
- data/ext/ratatui_ruby/src/widgets/barchart.rs +56 -54
- data/ext/ratatui_ruby/src/widgets/block.rs +7 -6
- data/ext/ratatui_ruby/src/widgets/canvas.rs +21 -3
- data/ext/ratatui_ruby/src/widgets/chart.rs +20 -10
- data/ext/ratatui_ruby/src/widgets/layout.rs +9 -4
- data/ext/ratatui_ruby/src/widgets/list.rs +32 -9
- data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
- data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +19 -8
- data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +17 -10
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +4 -2
- data/ext/ratatui_ruby/src/widgets/sparkline.rs +14 -11
- data/ext/ratatui_ruby/src/widgets/table.rs +8 -4
- data/ext/ratatui_ruby/src/widgets/tabs.rs +11 -11
- data/lib/ratatui_ruby/cell.rb +3 -3
- data/lib/ratatui_ruby/event/key.rb +1 -1
- data/lib/ratatui_ruby/event/none.rb +43 -0
- data/lib/ratatui_ruby/event.rb +56 -4
- data/lib/ratatui_ruby/frame.rb +87 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +11 -11
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +1 -5
- data/lib/ratatui_ruby/schema/bar_chart.rb +217 -217
- data/lib/ratatui_ruby/schema/block.rb +163 -168
- data/lib/ratatui_ruby/schema/calendar.rb +66 -67
- data/lib/ratatui_ruby/schema/canvas.rb +63 -63
- data/lib/ratatui_ruby/schema/center.rb +46 -46
- data/lib/ratatui_ruby/schema/chart.rb +135 -143
- data/lib/ratatui_ruby/schema/clear.rb +42 -42
- data/lib/ratatui_ruby/schema/constraint.rb +76 -76
- data/lib/ratatui_ruby/schema/cursor.rb +25 -25
- data/lib/ratatui_ruby/schema/gauge.rb +53 -53
- data/lib/ratatui_ruby/schema/layout.rb +87 -87
- data/lib/ratatui_ruby/schema/line_gauge.rb +62 -62
- data/lib/ratatui_ruby/schema/list.rb +86 -84
- data/lib/ratatui_ruby/schema/overlay.rb +31 -31
- data/lib/ratatui_ruby/schema/paragraph.rb +80 -80
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +10 -6
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +10 -5
- data/lib/ratatui_ruby/schema/rect.rb +60 -60
- data/lib/ratatui_ruby/schema/scrollbar.rb +119 -119
- data/lib/ratatui_ruby/schema/shape/label.rb +1 -1
- data/lib/ratatui_ruby/schema/sparkline.rb +111 -110
- data/lib/ratatui_ruby/schema/style.rb +46 -46
- data/lib/ratatui_ruby/schema/table.rb +112 -119
- data/lib/ratatui_ruby/schema/tabs.rb +66 -67
- data/lib/ratatui_ruby/session/autodoc.rb +417 -0
- data/lib/ratatui_ruby/session.rb +40 -23
- data/lib/ratatui_ruby/test_helper.rb +185 -19
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +65 -39
- data/{examples/sparkline_demo → sig/examples/app_all_events}/app.rbs +3 -2
- data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
- data/sig/examples/app_all_events/model/events.rbs +15 -0
- data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
- data/sig/examples/app_all_events/view/app_view.rbs +8 -0
- data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
- data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
- data/sig/examples/app_all_events/view/live_view.rbs +6 -0
- data/sig/examples/app_all_events/view/log_view.rbs +6 -0
- data/sig/examples/app_all_events/view.rbs +8 -0
- data/sig/examples/app_all_events/view_state.rbs +15 -0
- data/{examples/list_demo → sig/examples/app_color_picker}/app.rbs +2 -2
- data/sig/examples/app_login_form/app.rbs +11 -0
- data/sig/examples/app_map_demo/app.rbs +11 -0
- data/sig/examples/app_table_select/app.rbs +11 -0
- data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
- data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
- data/sig/examples/verify_readme_usage/app.rbs +11 -0
- data/sig/examples/widget_block_padding/app.rbs +11 -0
- data/sig/examples/widget_block_titles/app.rbs +11 -0
- data/sig/examples/widget_box_demo/app.rbs +11 -0
- data/sig/examples/widget_calendar_demo/app.rbs +11 -0
- data/sig/examples/widget_cell_demo/app.rbs +11 -0
- data/sig/examples/widget_chart_demo/app.rbs +11 -0
- data/{examples/gauge_demo → sig/examples/widget_gauge_demo}/app.rbs +4 -0
- data/sig/examples/widget_layout_split/app.rbs +10 -0
- data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
- data/sig/examples/widget_list_demo/app.rbs +12 -0
- data/sig/examples/widget_list_styles/app.rbs +11 -0
- data/sig/examples/widget_popup_demo/app.rbs +11 -0
- data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
- data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
- data/sig/examples/widget_rect/app.rbs +12 -0
- data/sig/examples/widget_render/app.rbs +10 -0
- data/sig/examples/widget_rich_text/app.rbs +11 -0
- data/sig/examples/widget_scroll_text/app.rbs +11 -0
- data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
- data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
- data/{examples → sig/examples}/widget_style_colors/app.rbs +1 -1
- data/sig/examples/widget_table_flex/app.rbs +11 -0
- data/sig/ratatui_ruby/frame.rbs +9 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +3 -2
- data/sig/ratatui_ruby/schema/draw.rbs +4 -0
- data/sig/ratatui_ruby/schema/layout.rbs +1 -1
- data/sig/ratatui_ruby/session.rbs +94 -0
- data/tasks/autodoc/inventory.rb +61 -0
- data/tasks/autodoc/member.rb +56 -0
- data/tasks/autodoc/name.rb +19 -0
- data/tasks/autodoc/notice.rb +26 -0
- data/tasks/autodoc/rbs.rb +38 -0
- data/tasks/autodoc/rdoc.rb +45 -0
- data/tasks/autodoc.rake +47 -0
- data/tasks/bump/history.rb +2 -2
- data/tasks/doc.rake +600 -6
- data/tasks/example_viewer.html.erb +172 -0
- data/tasks/lint.rake +8 -4
- data/tasks/resources/index.html.erb +6 -0
- data/tasks/sourcehut.rake +4 -4
- data/tasks/terminal_preview/app_screenshot.rb +1 -3
- data/tasks/terminal_preview/crash_report.rb +7 -9
- data/tasks/terminal_preview/launcher_script.rb +4 -6
- data/tasks/terminal_preview/preview_collection.rb +4 -6
- data/tasks/terminal_preview/safety_confirmation.rb +3 -5
- data/tasks/terminal_preview/saved_screenshot.rb +7 -9
- data/tasks/terminal_preview/terminal_window.rb +7 -9
- data/tasks/test.rake +1 -1
- data/tasks/website/index_page.rb +3 -3
- data/tasks/website/version.rb +10 -10
- data/tasks/website/version_menu.rb +10 -12
- data/tasks/website/versioned_documentation.rb +49 -17
- data/tasks/website/website.rb +6 -8
- data/tasks/website.rake +4 -4
- metadata +156 -125
- data/LICENSES/BSD-2-Clause.txt +0 -9
- data/doc/contributors/better_dx.md +0 -543
- data/doc/contributors/example_analysis.md +0 -82
- data/doc/images/all_events.png +0 -0
- data/doc/images/block_padding.png +0 -0
- data/doc/images/block_titles.png +0 -0
- data/doc/images/box_demo.png +0 -0
- data/doc/images/calendar_demo.png +0 -0
- data/doc/images/cell_demo.png +0 -0
- data/doc/images/chart_demo.png +0 -0
- data/doc/images/flex_layout.png +0 -0
- data/doc/images/gauge_demo.png +0 -0
- data/doc/images/line_gauge_demo.png +0 -0
- data/doc/images/list_demo.png +0 -0
- data/doc/images/readme_usage.png +0 -0
- data/doc/images/scrollbar_demo.png +0 -0
- data/doc/images/sparkline_demo.png +0 -0
- data/doc/images/table_flex.png +0 -0
- data/examples/all_events/app.rb +0 -169
- data/examples/all_events/app.rbs +0 -7
- data/examples/all_events/test_app.rb +0 -139
- data/examples/analytics/app.rb +0 -258
- data/examples/analytics/app.rbs +0 -7
- data/examples/analytics/test_app.rb +0 -132
- data/examples/block_padding/app.rbs +0 -7
- data/examples/block_padding/test_app.rb +0 -31
- data/examples/block_titles/app.rbs +0 -7
- data/examples/block_titles/test_app.rb +0 -34
- data/examples/box_demo/app.rbs +0 -7
- data/examples/box_demo/test_app.rb +0 -88
- data/examples/calendar_demo/app.rb +0 -101
- data/examples/calendar_demo/app.rbs +0 -7
- data/examples/calendar_demo/test_app.rb +0 -108
- data/examples/cell_demo/app.rb +0 -108
- data/examples/cell_demo/app.rbs +0 -7
- data/examples/cell_demo/test_app.rb +0 -36
- data/examples/chart_demo/app.rb +0 -203
- data/examples/chart_demo/app.rbs +0 -7
- data/examples/chart_demo/test_app.rb +0 -102
- data/examples/custom_widget/app.rb +0 -51
- data/examples/custom_widget/app.rbs +0 -7
- data/examples/custom_widget/test_app.rb +0 -30
- data/examples/flex_layout/app.rb +0 -156
- data/examples/flex_layout/app.rbs +0 -7
- data/examples/flex_layout/test_app.rb +0 -65
- data/examples/gauge_demo/app.rb +0 -182
- data/examples/gauge_demo/test_app.rb +0 -120
- data/examples/hit_test/app.rb +0 -175
- data/examples/hit_test/app.rbs +0 -7
- data/examples/hit_test/test_app.rb +0 -102
- data/examples/line_gauge_demo/app.rb +0 -190
- data/examples/line_gauge_demo/app.rbs +0 -7
- data/examples/line_gauge_demo/test_app.rb +0 -129
- data/examples/list_demo/app.rb +0 -253
- data/examples/list_demo/test_app.rb +0 -237
- data/examples/list_styles/app.rb +0 -140
- data/examples/list_styles/app.rbs +0 -7
- data/examples/list_styles/test_app.rb +0 -157
- data/examples/login_form/app.rbs +0 -7
- data/examples/login_form/test_app.rb +0 -51
- data/examples/map_demo/app.rbs +0 -7
- data/examples/map_demo/test_app.rb +0 -149
- data/examples/mouse_events/app.rb +0 -97
- data/examples/mouse_events/app.rbs +0 -7
- data/examples/mouse_events/test_app.rb +0 -53
- data/examples/popup_demo/app.rb +0 -103
- data/examples/popup_demo/app.rbs +0 -7
- data/examples/popup_demo/test_app.rb +0 -54
- data/examples/quickstart_dsl/app.rbs +0 -7
- data/examples/quickstart_dsl/test_app.rb +0 -29
- data/examples/quickstart_lifecycle/app.rbs +0 -7
- data/examples/quickstart_lifecycle/test_app.rb +0 -29
- data/examples/ratatui_logo_demo/app.rb +0 -79
- data/examples/ratatui_logo_demo/app.rbs +0 -7
- data/examples/ratatui_logo_demo/test_app.rb +0 -51
- data/examples/ratatui_mascot_demo/app.rb +0 -84
- data/examples/ratatui_mascot_demo/app.rbs +0 -7
- data/examples/ratatui_mascot_demo/test_app.rb +0 -47
- data/examples/readme_usage/app.rb +0 -29
- data/examples/readme_usage/app.rbs +0 -7
- data/examples/readme_usage/test_app.rb +0 -29
- data/examples/rich_text/app.rb +0 -141
- data/examples/rich_text/app.rbs +0 -7
- data/examples/rich_text/test_app.rb +0 -166
- data/examples/scroll_text/app.rb +0 -103
- data/examples/scroll_text/app.rbs +0 -7
- data/examples/scroll_text/test_app.rb +0 -110
- data/examples/scrollbar_demo/app.rb +0 -143
- data/examples/scrollbar_demo/app.rbs +0 -7
- data/examples/scrollbar_demo/test_app.rb +0 -77
- data/examples/sparkline_demo/app.rb +0 -240
- data/examples/sparkline_demo/test_app.rb +0 -107
- data/examples/table_flex/app.rb +0 -65
- data/examples/table_flex/app.rbs +0 -7
- data/examples/table_flex/test_app.rb +0 -36
- data/examples/table_select/app.rbs +0 -7
- data/examples/table_select/test_app.rb +0 -180
- data/examples/widget_style_colors/test_app.rb +0 -48
- /data/doc/images/{analytics.png → app_analytics.png} +0 -0
- /data/doc/images/{custom_widget.png → app_custom_widget.png} +0 -0
- /data/doc/images/{login_form.png → app_login_form.png} +0 -0
- /data/doc/images/{map_demo.png → app_map_demo.png} +0 -0
- /data/doc/images/{mouse_events.png → app_mouse_events.png} +0 -0
- /data/doc/images/{table_select.png → app_table_select.png} +0 -0
- /data/doc/images/{quickstart_dsl.png → verify_quickstart_dsl.png} +0 -0
- /data/doc/images/{ratatui_logo_demo.png → verify_quickstart_layout.png} +0 -0
- /data/doc/images/{quickstart_lifecycle.png → verify_quickstart_lifecycle.png} +0 -0
- /data/doc/images/{list_styles.png → widget_list_styles.png} +0 -0
- /data/doc/images/{popup_demo.png → widget_popup_demo.png} +0 -0
- /data/doc/images/{hit_test.png → widget_rect.png} +0 -0
- /data/doc/images/{rich_text.png → widget_rich_text.png} +0 -0
- /data/doc/images/{scroll_text.png → widget_scroll_text.png} +0 -0
|
@@ -35,13 +35,15 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
35
35
|
let mut data_storage: Vec<Vec<(f64, f64)>> = Vec::new();
|
|
36
36
|
|
|
37
37
|
for i in 0..datasets_val.len() {
|
|
38
|
-
let index = isize::try_from(i)
|
|
38
|
+
let index = isize::try_from(i)
|
|
39
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
39
40
|
let ds_val: Value = datasets_val.entry(index)?;
|
|
40
41
|
let data_array: magnus::RArray = ds_val.funcall("data", ())?;
|
|
41
42
|
|
|
42
43
|
let mut points = Vec::new();
|
|
43
44
|
for j in 0..data_array.len() {
|
|
44
|
-
let index = isize::try_from(j)
|
|
45
|
+
let index = isize::try_from(j)
|
|
46
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
45
47
|
let point_array_val: Value = data_array.entry(index)?;
|
|
46
48
|
let point_array = magnus::RArray::from_value(point_array_val).ok_or_else(|| {
|
|
47
49
|
Error::new(ruby.exception_type_error(), "expected array for point")
|
|
@@ -54,7 +56,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
for (i, points) in data_storage.iter().enumerate() {
|
|
57
|
-
let index = isize::try_from(i)
|
|
59
|
+
let index = isize::try_from(i)
|
|
60
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
58
61
|
let ds_val: Value = datasets_val.entry(index)?;
|
|
59
62
|
let name: String = ds_val.funcall("name", ())?;
|
|
60
63
|
let marker_sym: Symbol = ds_val.funcall("marker", ())?;
|
|
@@ -112,7 +115,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
if !hidden_legend_constraints_val.is_nil() {
|
|
115
|
-
let constraints_array: magnus::RArray =
|
|
118
|
+
let constraints_array: magnus::RArray =
|
|
119
|
+
hidden_legend_constraints_val.funcall("to_a", ())?;
|
|
116
120
|
if constraints_array.len() == 2 {
|
|
117
121
|
let width_val: Value = constraints_array.entry(0)?;
|
|
118
122
|
let height_val: Value = constraints_array.entry(1)?;
|
|
@@ -138,7 +142,8 @@ fn parse_axis(axis_val: Value) -> Result<Axis<'static>, Error> {
|
|
|
138
142
|
|
|
139
143
|
let mut labels = Vec::new();
|
|
140
144
|
for i in 0..labels_val.len() {
|
|
141
|
-
let index = isize::try_from(i)
|
|
145
|
+
let index = isize::try_from(i)
|
|
146
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
142
147
|
let label: String = labels_val.entry(index)?;
|
|
143
148
|
labels.push(Span::from(label));
|
|
144
149
|
}
|
|
@@ -175,13 +180,15 @@ fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
175
180
|
let mut data_storage: Vec<Vec<(f64, f64)>> = Vec::new();
|
|
176
181
|
|
|
177
182
|
for i in 0..datasets_val.len() {
|
|
178
|
-
let index = isize::try_from(i)
|
|
183
|
+
let index = isize::try_from(i)
|
|
184
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
179
185
|
let ds_val: Value = datasets_val.entry(index)?;
|
|
180
186
|
let data_array: magnus::RArray = ds_val.funcall("data", ())?;
|
|
181
187
|
|
|
182
188
|
let mut points = Vec::new();
|
|
183
189
|
for j in 0..data_array.len() {
|
|
184
|
-
let index = isize::try_from(j)
|
|
190
|
+
let index = isize::try_from(j)
|
|
191
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
185
192
|
let point_array_val: Value = data_array.entry(index)?;
|
|
186
193
|
let point_array = magnus::RArray::from_value(point_array_val).ok_or_else(|| {
|
|
187
194
|
Error::new(ruby.exception_type_error(), "expected array for point")
|
|
@@ -194,7 +201,8 @@ fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
194
201
|
}
|
|
195
202
|
|
|
196
203
|
for (i, points) in data_storage.iter().enumerate() {
|
|
197
|
-
let index = isize::try_from(i)
|
|
204
|
+
let index = isize::try_from(i)
|
|
205
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
198
206
|
let ds_val: Value = datasets_val.entry(index)?;
|
|
199
207
|
let name: String = ds_val.funcall("name", ())?;
|
|
200
208
|
|
|
@@ -214,14 +222,16 @@ fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
214
222
|
|
|
215
223
|
let mut x_labels = Vec::new();
|
|
216
224
|
for i in 0..x_labels_val.len() {
|
|
217
|
-
let index = isize::try_from(i)
|
|
225
|
+
let index = isize::try_from(i)
|
|
226
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
218
227
|
let label: String = x_labels_val.entry(index)?;
|
|
219
228
|
x_labels.push(Span::from(label));
|
|
220
229
|
}
|
|
221
230
|
|
|
222
231
|
let mut y_labels = Vec::new();
|
|
223
232
|
for i in 0..y_labels_val.len() {
|
|
224
|
-
let index = isize::try_from(i)
|
|
233
|
+
let index = isize::try_from(i)
|
|
234
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
225
235
|
let label: String = y_labels_val.entry(index)?;
|
|
226
236
|
y_labels.push(Span::from(label));
|
|
227
237
|
}
|
|
@@ -42,7 +42,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
42
42
|
|
|
43
43
|
if let Some(arr) = constraints_array {
|
|
44
44
|
for i in 0..arr.len() {
|
|
45
|
-
let index = isize::try_from(i)
|
|
45
|
+
let index = isize::try_from(i)
|
|
46
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
46
47
|
let constraint_obj: Value = arr.entry(index)?;
|
|
47
48
|
if let Ok(constraint) = parse_constraint(constraint_obj) {
|
|
48
49
|
ratatui_constraints.push(constraint);
|
|
@@ -53,7 +54,9 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
53
54
|
// If constraints don't match children, adjust or default
|
|
54
55
|
if ratatui_constraints.len() != len {
|
|
55
56
|
ratatui_constraints = (0..len)
|
|
56
|
-
.map(|_|
|
|
57
|
+
.map(|_| {
|
|
58
|
+
Constraint::Percentage(100 / u16::try_from(len).unwrap_or(u16::MAX).max(1))
|
|
59
|
+
})
|
|
57
60
|
.collect();
|
|
58
61
|
}
|
|
59
62
|
|
|
@@ -64,7 +67,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
64
67
|
.split(area);
|
|
65
68
|
|
|
66
69
|
for i in 0..len {
|
|
67
|
-
let index = isize::try_from(i)
|
|
70
|
+
let index = isize::try_from(i)
|
|
71
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
68
72
|
let child: Value = children_array.entry(index)?;
|
|
69
73
|
if let Err(e) = render_node(frame, chunks[i], child) {
|
|
70
74
|
eprintln!("Error rendering child {i}: {e:?}");
|
|
@@ -168,7 +172,8 @@ pub fn split_layout(
|
|
|
168
172
|
// Parse constraints
|
|
169
173
|
let mut ratatui_constraints = Vec::new();
|
|
170
174
|
for i in 0..constraints.len() {
|
|
171
|
-
let index = isize::try_from(i)
|
|
175
|
+
let index = isize::try_from(i)
|
|
176
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
172
177
|
let constraint_obj: Value = constraints.entry(index)?;
|
|
173
178
|
if let Ok(constraint) = parse_constraint(constraint_obj) {
|
|
174
179
|
ratatui_constraints.push(constraint);
|
|
@@ -29,7 +29,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
29
29
|
|
|
30
30
|
let mut items: Vec<String> = Vec::new();
|
|
31
31
|
for i in 0..items_array.len() {
|
|
32
|
-
let index = isize::try_from(i)
|
|
32
|
+
let index = isize::try_from(i)
|
|
33
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
33
34
|
let item: String = items_array.entry(index)?;
|
|
34
35
|
items.push(item);
|
|
35
36
|
}
|
|
@@ -142,21 +143,41 @@ mod tests {
|
|
|
142
143
|
fn test_repeat_highlight_symbol() {
|
|
143
144
|
let items = vec!["Item 1", "Item 2"];
|
|
144
145
|
let list_without_repeat = List::new(items.clone()).highlight_symbol(Line::from(">> "));
|
|
145
|
-
let list_with_repeat = List::new(items)
|
|
146
|
-
|
|
146
|
+
let list_with_repeat = List::new(items)
|
|
147
|
+
.highlight_symbol(Line::from(">> "))
|
|
148
|
+
.repeat_highlight_symbol(true);
|
|
149
|
+
|
|
147
150
|
let mut state = ListState::default();
|
|
148
151
|
state.select(Some(0));
|
|
149
152
|
|
|
150
153
|
let mut buf1 = Buffer::empty(Rect::new(0, 0, 10, 2));
|
|
151
154
|
use ratatui::widgets::StatefulWidget;
|
|
152
|
-
StatefulWidget::render(
|
|
155
|
+
StatefulWidget::render(
|
|
156
|
+
list_without_repeat,
|
|
157
|
+
Rect::new(0, 0, 10, 2),
|
|
158
|
+
&mut buf1,
|
|
159
|
+
&mut state,
|
|
160
|
+
);
|
|
153
161
|
|
|
154
162
|
let mut buf2 = Buffer::empty(Rect::new(0, 0, 10, 2));
|
|
155
|
-
StatefulWidget::render(
|
|
163
|
+
StatefulWidget::render(
|
|
164
|
+
list_with_repeat,
|
|
165
|
+
Rect::new(0, 0, 10, 2),
|
|
166
|
+
&mut buf2,
|
|
167
|
+
&mut state,
|
|
168
|
+
);
|
|
156
169
|
|
|
157
170
|
// Both should render, but the behavior might differ based on content width
|
|
158
|
-
let content1 = buf1
|
|
159
|
-
|
|
171
|
+
let content1 = buf1
|
|
172
|
+
.content()
|
|
173
|
+
.iter()
|
|
174
|
+
.map(|c| c.symbol())
|
|
175
|
+
.collect::<String>();
|
|
176
|
+
let content2 = buf2
|
|
177
|
+
.content()
|
|
178
|
+
.iter()
|
|
179
|
+
.map(|c| c.symbol())
|
|
180
|
+
.collect::<String>();
|
|
160
181
|
assert!(!content1.is_empty());
|
|
161
182
|
assert!(!content2.is_empty());
|
|
162
183
|
}
|
|
@@ -164,8 +185,10 @@ mod tests {
|
|
|
164
185
|
#[test]
|
|
165
186
|
fn test_scroll_padding() {
|
|
166
187
|
let items = vec!["Item 1", "Item 2", "Item 3", "Item 4"];
|
|
167
|
-
let list = List::new(items)
|
|
168
|
-
|
|
188
|
+
let list = List::new(items)
|
|
189
|
+
.scroll_padding(1)
|
|
190
|
+
.highlight_symbol(Line::from(">> "));
|
|
191
|
+
|
|
169
192
|
let mut state = ListState::default();
|
|
170
193
|
state.select(Some(1));
|
|
171
194
|
|
|
@@ -12,7 +12,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
12
12
|
.ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for layers"))?;
|
|
13
13
|
|
|
14
14
|
for i in 0..layers_array.len() {
|
|
15
|
-
let index = isize::try_from(i)
|
|
15
|
+
let index = isize::try_from(i)
|
|
16
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
16
17
|
let layer: Value = layers_array.entry(index)?;
|
|
17
18
|
if let Err(e) = render_node(frame, area, layer) {
|
|
18
19
|
eprintln!("Error rendering overlay layer {i}: {e:?}");
|
|
@@ -12,7 +12,7 @@ use ratatui::{
|
|
|
12
12
|
|
|
13
13
|
use crate::text::parse_text;
|
|
14
14
|
|
|
15
|
-
fn create_paragraph
|
|
15
|
+
fn create_paragraph(node: Value, bump: &Bump) -> Result<Paragraph<'_>, Error> {
|
|
16
16
|
let text_val: Value = node.funcall("text", ())?;
|
|
17
17
|
let style_val: Value = node.funcall("style", ())?;
|
|
18
18
|
let block_val: Value = node.funcall("block", ())?;
|
|
@@ -1,27 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
use magnus::Value;
|
|
6
|
+
use ratatui::{
|
|
7
|
+
layout::Rect,
|
|
8
|
+
widgets::{RatatuiLogo, RatatuiLogoSize},
|
|
9
|
+
Frame,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
pub fn render(frame: &mut Frame, area: Rect, _node: Value) {
|
|
5
13
|
// RatatuiLogo does not support custom styling (it has fixed colors).
|
|
6
14
|
// It requires a size argument.
|
|
7
15
|
let widget = RatatuiLogo::new(RatatuiLogoSize::Small);
|
|
8
16
|
frame.render_widget(widget, area);
|
|
9
|
-
Ok(())
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
#[cfg(test)]
|
|
13
20
|
mod tests {
|
|
14
|
-
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
|
15
21
|
use super::*;
|
|
22
|
+
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
|
16
23
|
|
|
17
24
|
#[test]
|
|
18
25
|
fn test_render() {
|
|
19
26
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 50, 20));
|
|
20
27
|
let widget = RatatuiLogo::new(RatatuiLogoSize::Small);
|
|
21
28
|
widget.render(Rect::new(0, 0, 50, 20), &mut buffer);
|
|
22
|
-
|
|
23
|
-
let content = buffer
|
|
24
|
-
|
|
29
|
+
|
|
30
|
+
let content = buffer
|
|
31
|
+
.content()
|
|
32
|
+
.iter()
|
|
33
|
+
.map(|c| c.symbol())
|
|
34
|
+
.collect::<String>();
|
|
35
|
+
|
|
25
36
|
// The logo uses block characters for rendering
|
|
26
37
|
assert!(content.contains('█'));
|
|
27
38
|
assert!(!content.trim().is_empty());
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
|
|
1
5
|
use crate::style::parse_block;
|
|
2
6
|
use bumpalo::Bump;
|
|
3
7
|
use magnus::{prelude::*, Error, Value};
|
|
4
|
-
use ratatui::{
|
|
5
|
-
layout::Rect,
|
|
6
|
-
widgets::RatatuiMascot,
|
|
7
|
-
Frame,
|
|
8
|
-
};
|
|
8
|
+
use ratatui::{layout::Rect, widgets::RatatuiMascot, Frame};
|
|
9
9
|
|
|
10
10
|
pub fn render_ratatui_mascot(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
11
11
|
let block_val: Value = node.funcall("block", ())?;
|
|
@@ -26,19 +26,26 @@ pub fn render_ratatui_mascot(frame: &mut Frame, area: Rect, node: Value) -> Resu
|
|
|
26
26
|
|
|
27
27
|
#[cfg(test)]
|
|
28
28
|
mod tests {
|
|
29
|
-
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
|
30
29
|
use super::*;
|
|
30
|
+
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
|
31
31
|
|
|
32
32
|
#[test]
|
|
33
33
|
fn test_render() {
|
|
34
34
|
let mut buffer = Buffer::empty(Rect::new(0, 0, 50, 20));
|
|
35
35
|
let widget = RatatuiMascot::new();
|
|
36
36
|
widget.render(Rect::new(0, 0, 50, 20), &mut buffer);
|
|
37
|
-
|
|
38
|
-
let content = buffer
|
|
39
|
-
|
|
37
|
+
|
|
38
|
+
let content = buffer
|
|
39
|
+
.content()
|
|
40
|
+
.iter()
|
|
41
|
+
.map(|c| c.symbol())
|
|
42
|
+
.collect::<String>();
|
|
43
|
+
|
|
40
44
|
// The mascot uses block drawing characters
|
|
41
|
-
assert!(
|
|
45
|
+
assert!(
|
|
46
|
+
content.contains("█"),
|
|
47
|
+
"Mascot rendering should contain block characters"
|
|
48
|
+
);
|
|
42
49
|
assert!(!content.trim().is_empty());
|
|
43
50
|
}
|
|
44
51
|
}
|
|
@@ -14,7 +14,7 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
14
14
|
let content_length: usize = node.funcall("content_length", ())?;
|
|
15
15
|
let position: usize = node.funcall("position", ())?;
|
|
16
16
|
let orientation_sym: Symbol = node.funcall("orientation", ())?;
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
let thumb_symbol_val: Value = node.funcall("thumb_symbol", ())?;
|
|
19
19
|
let thumb_style_val: Value = node.funcall("thumb_style", ())?;
|
|
20
20
|
let track_symbol_val: Value = node.funcall("track_symbol", ())?;
|
|
@@ -32,7 +32,9 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
32
32
|
|
|
33
33
|
scrollbar = match orientation_sym.to_string().as_str() {
|
|
34
34
|
"vertical_left" => scrollbar.orientation(ScrollbarOrientation::VerticalLeft),
|
|
35
|
-
"horizontal_bottom" | "horizontal" =>
|
|
35
|
+
"horizontal_bottom" | "horizontal" => {
|
|
36
|
+
scrollbar.orientation(ScrollbarOrientation::HorizontalBottom)
|
|
37
|
+
}
|
|
36
38
|
"horizontal_top" => scrollbar.orientation(ScrollbarOrientation::HorizontalTop),
|
|
37
39
|
_ => scrollbar.orientation(ScrollbarOrientation::VerticalRight),
|
|
38
40
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
|
-
use crate::style::{parse_block, parse_style
|
|
4
|
+
use crate::style::{parse_bar_set, parse_block, parse_style};
|
|
5
5
|
use bumpalo::Bump;
|
|
6
6
|
use magnus::{prelude::*, Error, RString, Value};
|
|
7
|
-
use ratatui::{layout::Rect, widgets::
|
|
7
|
+
use ratatui::{layout::Rect, widgets::RenderDirection, widgets::Sparkline, Frame};
|
|
8
8
|
|
|
9
9
|
pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
10
10
|
let bump = Bump::new();
|
|
@@ -20,7 +20,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
20
20
|
|
|
21
21
|
let mut data_vec = Vec::new();
|
|
22
22
|
for i in 0..data_val.len() {
|
|
23
|
-
let index = isize::try_from(i)
|
|
23
|
+
let index = isize::try_from(i)
|
|
24
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
24
25
|
let val: Value = data_val.entry(index)?;
|
|
25
26
|
if val.is_nil() {
|
|
26
27
|
data_vec.push(None);
|
|
@@ -100,22 +101,24 @@ mod tests {
|
|
|
100
101
|
fn test_sparkline_absent_value_symbol() {
|
|
101
102
|
// Data with absent (None) and present values: [Some(5), None, Some(8), None]
|
|
102
103
|
let data = vec![Some(5), None, Some(8), None];
|
|
103
|
-
let sparkline = Sparkline::default()
|
|
104
|
-
.data(&data)
|
|
105
|
-
.absent_value_symbol("-");
|
|
104
|
+
let sparkline = Sparkline::default().data(&data).absent_value_symbol("-");
|
|
106
105
|
let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
|
|
107
106
|
sparkline.render(Rect::new(0, 0, 4, 1), &mut buf);
|
|
108
|
-
|
|
107
|
+
|
|
109
108
|
// Collect all rendered symbols
|
|
110
109
|
let symbols: Vec<&str> = buf.content().iter().map(|c| c.symbol()).collect();
|
|
111
|
-
|
|
110
|
+
|
|
112
111
|
// Check that we have 4 cells rendered
|
|
113
|
-
assert_eq!(
|
|
114
|
-
|
|
112
|
+
assert_eq!(
|
|
113
|
+
symbols.len(),
|
|
114
|
+
4,
|
|
115
|
+
"Should have 4 cells rendered for 4 data points"
|
|
116
|
+
);
|
|
117
|
+
|
|
115
118
|
// Absent values (None) should render as "-"
|
|
116
119
|
assert_eq!(symbols[1], "-", "Second value (None) should render as dash");
|
|
117
120
|
assert_eq!(symbols[3], "-", "Fourth value (None) should render as dash");
|
|
118
|
-
|
|
121
|
+
|
|
119
122
|
// Present values should not be dashes
|
|
120
123
|
assert_ne!(symbols[0], "-", "First value (Some(5)) should not be dash");
|
|
121
124
|
assert_ne!(symbols[2], "-", "Third value (Some(8)) should not be dash");
|
|
@@ -33,7 +33,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
33
33
|
|
|
34
34
|
let mut rows = Vec::new();
|
|
35
35
|
for i in 0..rows_array.len() {
|
|
36
|
-
let index = isize::try_from(i)
|
|
36
|
+
let index = isize::try_from(i)
|
|
37
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
37
38
|
let row_val: Value = rows_array.entry(index)?;
|
|
38
39
|
rows.push(parse_row(row_val)?);
|
|
39
40
|
}
|
|
@@ -120,7 +121,8 @@ fn parse_row(row_val: Value) -> Result<Row<'static>, Error> {
|
|
|
120
121
|
|
|
121
122
|
let mut cells = Vec::new();
|
|
122
123
|
for i in 0..row_array.len() {
|
|
123
|
-
let index = isize::try_from(i)
|
|
124
|
+
let index = isize::try_from(i)
|
|
125
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
124
126
|
let cell_val: Value = row_array.entry(index)?;
|
|
125
127
|
cells.push(parse_cell(cell_val)?);
|
|
126
128
|
}
|
|
@@ -159,7 +161,8 @@ fn parse_cell(cell_val: Value) -> Result<Cell<'static>, Error> {
|
|
|
159
161
|
if let Some(mods_array) = magnus::RArray::from_value(modifiers_val) {
|
|
160
162
|
let ruby = magnus::Ruby::get().unwrap();
|
|
161
163
|
for i in 0..mods_array.len() {
|
|
162
|
-
let index = isize::try_from(i)
|
|
164
|
+
let index = isize::try_from(i)
|
|
165
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
163
166
|
let mod_str: String = mods_array.entry::<String>(index)?;
|
|
164
167
|
if let Some(modifier) = crate::style::parse_modifier_str(&mod_str) {
|
|
165
168
|
style = style.add_modifier(modifier);
|
|
@@ -177,7 +180,8 @@ fn parse_constraints(widths_array: magnus::RArray) -> Result<Vec<Constraint>, Er
|
|
|
177
180
|
let ruby = magnus::Ruby::get().unwrap();
|
|
178
181
|
let mut constraints = Vec::new();
|
|
179
182
|
for i in 0..widths_array.len() {
|
|
180
|
-
let index = isize::try_from(i)
|
|
183
|
+
let index = isize::try_from(i)
|
|
184
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
181
185
|
let constraint_obj: Value = widths_array.entry(index)?;
|
|
182
186
|
let type_sym: Symbol = constraint_obj.funcall("type", ())?;
|
|
183
187
|
let value_obj: Value = constraint_obj.funcall("value", ())?;
|
|
@@ -15,7 +15,7 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
15
15
|
|
|
16
16
|
use crate::text::parse_line;
|
|
17
17
|
|
|
18
|
-
fn create_tabs
|
|
18
|
+
fn create_tabs(node: Value, bump: &Bump) -> Result<Tabs<'_>, Error> {
|
|
19
19
|
let ruby = magnus::Ruby::get().unwrap();
|
|
20
20
|
let titles_val: Value = node.funcall("titles", ())?;
|
|
21
21
|
let selected_index: usize = node.funcall("selected_index", ())?;
|
|
@@ -30,7 +30,8 @@ fn create_tabs<'a>(node: Value, bump: &'a Bump) -> Result<Tabs<'a>, Error> {
|
|
|
30
30
|
|
|
31
31
|
let mut titles = Vec::new();
|
|
32
32
|
for i in 0..titles_array.len() {
|
|
33
|
-
let index = isize::try_from(i)
|
|
33
|
+
let index = isize::try_from(i)
|
|
34
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
34
35
|
let val: Value = titles_array.entry(index)?;
|
|
35
36
|
if let Ok(line) = parse_line(val) {
|
|
36
37
|
titles.push(line);
|
|
@@ -81,10 +82,11 @@ pub fn width(node: Value) -> Result<usize, Error> {
|
|
|
81
82
|
.ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for titles"))?;
|
|
82
83
|
|
|
83
84
|
let mut total_width = padding_left + padding_right;
|
|
84
|
-
|
|
85
|
+
|
|
85
86
|
let mut titles_count = 0;
|
|
86
87
|
for i in 0..titles_array.len() {
|
|
87
|
-
let index = isize::try_from(i)
|
|
88
|
+
let index = isize::try_from(i)
|
|
89
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
88
90
|
let val: Value = titles_array.entry(index)?;
|
|
89
91
|
let line_width = if let Ok(line) = parse_line(val) {
|
|
90
92
|
line.width()
|
|
@@ -97,11 +99,11 @@ pub fn width(node: Value) -> Result<usize, Error> {
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
if titles_count > 1 {
|
|
100
|
-
let divider_width = if
|
|
102
|
+
let divider_width = if divider_val.is_nil() {
|
|
103
|
+
1 // Default divider is "|"
|
|
104
|
+
} else {
|
|
101
105
|
let d: String = divider_val.funcall("to_s", ())?;
|
|
102
106
|
ratatui::text::Span::raw(d).width()
|
|
103
|
-
} else {
|
|
104
|
-
1 // Default divider is "|"
|
|
105
107
|
};
|
|
106
108
|
total_width += (titles_count - 1) * divider_width;
|
|
107
109
|
}
|
|
@@ -134,10 +136,8 @@ mod tests {
|
|
|
134
136
|
fn test_tabs_highlight_style() {
|
|
135
137
|
let titles = vec![Line::from("Tab1"), Line::from("Tab2")];
|
|
136
138
|
let highlight_style = Style::default().fg(Color::Red).add_modifier(Modifier::BOLD);
|
|
137
|
-
let tabs = Tabs::new(titles)
|
|
138
|
-
|
|
139
|
-
.highlight_style(highlight_style);
|
|
140
|
-
|
|
139
|
+
let tabs = Tabs::new(titles).select(0).highlight_style(highlight_style);
|
|
140
|
+
|
|
141
141
|
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 1));
|
|
142
142
|
tabs.render(Rect::new(0, 0, 15, 1), &mut buf);
|
|
143
143
|
|
data/lib/ratatui_ruby/cell.rb
CHANGED
|
@@ -67,7 +67,7 @@ module RatatuiRuby
|
|
|
67
67
|
# Cell.symbol("X") # => #<RatatuiRuby::Cell symbol="X">
|
|
68
68
|
#
|
|
69
69
|
def self.symbol(symbol)
|
|
70
|
-
new(symbol
|
|
70
|
+
new(symbol:, fg: nil, bg: nil, modifiers: [])
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# Alias for Rubyists who prefer a shorter name.
|
|
@@ -149,7 +149,7 @@ module RatatuiRuby
|
|
|
149
149
|
parts << "fg=#{fg.inspect}" if fg
|
|
150
150
|
parts << "bg=#{bg.inspect}" if bg
|
|
151
151
|
parts << "modifiers=#{modifiers.inspect}" unless modifiers.empty?
|
|
152
|
-
"#<#{self.class} #{parts.join(
|
|
152
|
+
"#<#{self.class} #{parts.join(' ')}>"
|
|
153
153
|
end
|
|
154
154
|
|
|
155
155
|
# Returns the cell's character.
|
|
@@ -160,7 +160,7 @@ module RatatuiRuby
|
|
|
160
160
|
# Support for pattern matching.
|
|
161
161
|
# Supports both <tt>:symbol</tt> and <tt>:char</tt> keys.
|
|
162
162
|
def deconstruct_keys(keys)
|
|
163
|
-
{ symbol
|
|
163
|
+
{ symbol:, char: symbol, fg:, bg:, modifiers: }
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
|
|
6
|
+
module RatatuiRuby
|
|
7
|
+
class Event
|
|
8
|
+
# {Null object}[https://en.wikipedia.org/wiki/Null_object_pattern] for absent events.
|
|
9
|
+
#
|
|
10
|
+
# Event loops poll for input 60 times per second. Usually nothing is happening.
|
|
11
|
+
# If <tt>RatatuiRuby.poll_event</tt> returned <tt>nil</tt>, you would need
|
|
12
|
+
# nil-checks: <tt>event&.key?</tt>, <tt>next unless event</tt>.
|
|
13
|
+
#
|
|
14
|
+
# This class eliminates that friction. It responds to every predicate with
|
|
15
|
+
# <tt>false</tt>. Call <tt>none?</tt> to detect it explicitly. Pattern-match on
|
|
16
|
+
# <tt>type: :none</tt> for exhaustive dispatch.
|
|
17
|
+
#
|
|
18
|
+
# Use it to simplify your event loop. No guards. Optional `else` clauses.
|
|
19
|
+
#
|
|
20
|
+
# See Martin Fowler's {Special Case}[https://martinfowler.com/eaaCatalog/specialCase.html] pattern.
|
|
21
|
+
#
|
|
22
|
+
# === Predicate Example
|
|
23
|
+
#
|
|
24
|
+
# event = RatatuiRuby.poll_event
|
|
25
|
+
# break if event.ctrl_c?
|
|
26
|
+
# redraw if event.none?
|
|
27
|
+
#
|
|
28
|
+
# === Pattern Matching Example
|
|
29
|
+
#
|
|
30
|
+
# redraw if RatatuiRuby.poll_event in type: :none
|
|
31
|
+
class None < Event
|
|
32
|
+
# Returns true for None events.
|
|
33
|
+
def none?
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Deconstructs the event for pattern matching.
|
|
38
|
+
def deconstruct_keys(keys)
|
|
39
|
+
{ type: :none }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/ratatui_ruby/event.rb
CHANGED
|
@@ -6,11 +6,63 @@
|
|
|
6
6
|
module RatatuiRuby
|
|
7
7
|
# Base class for all RatatuiRuby events.
|
|
8
8
|
#
|
|
9
|
-
# Events
|
|
10
|
-
# All events support Ruby 3.0+ pattern matching
|
|
9
|
+
# Events represent terminal input: keyboard, mouse, resize, paste, focus changes.
|
|
10
|
+
# Returned by RatatuiRuby.poll_event. All events support Ruby 3.0+ pattern matching.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
12
|
+
# == Event Types
|
|
13
|
+
#
|
|
14
|
+
# * <tt>Key</tt> — keyboard input
|
|
15
|
+
# * <tt>Mouse</tt> — mouse clicks, movement, wheel
|
|
16
|
+
# * <tt>Resize</tt> — terminal resized
|
|
17
|
+
# * <tt>Paste</tt> — clipboard paste
|
|
18
|
+
# * <tt>FocusGained</tt> — terminal gained focus
|
|
19
|
+
# * <tt>FocusLost</tt> — terminal lost focus
|
|
20
|
+
# * <tt>None</tt> — no event available (Null Object)
|
|
21
|
+
#
|
|
22
|
+
# == Pattern Matching (Exhaustive)
|
|
23
|
+
#
|
|
24
|
+
# Use <tt>case...in</tt> to dispatch on every possible event type. This ensures
|
|
25
|
+
# you handle every case without needing an +else+ clause:
|
|
26
|
+
#
|
|
27
|
+
# case RatatuiRuby.poll_event
|
|
28
|
+
# in { type: :key, code: "q" }
|
|
29
|
+
# break
|
|
30
|
+
# in { type: :key, code: code, modifiers: }
|
|
31
|
+
# handle_key(code, modifiers)
|
|
32
|
+
# in { type: :mouse, kind: "down", x:, y: }
|
|
33
|
+
# handle_click(x, y)
|
|
34
|
+
# in { type: :mouse, kind:, x:, y: }
|
|
35
|
+
# # handle other mouse activities
|
|
36
|
+
# in { type: :resize, width:, height: }
|
|
37
|
+
# handle_resize(width, height)
|
|
38
|
+
# in { type: :paste, content: }
|
|
39
|
+
# handle_paste(content)
|
|
40
|
+
# in { type: :focus_gained }
|
|
41
|
+
# handle_focus_gain
|
|
42
|
+
# in { type: :focus_lost }
|
|
43
|
+
# handle_focus_loss
|
|
44
|
+
# in { type: :none }
|
|
45
|
+
# # Idle
|
|
46
|
+
# end
|
|
47
|
+
#
|
|
48
|
+
# == Predicates
|
|
49
|
+
#
|
|
50
|
+
# Check event types with predicates without pattern matching:
|
|
51
|
+
#
|
|
52
|
+
# event = RatatuiRuby.poll_event
|
|
53
|
+
# if event.key?
|
|
54
|
+
# puts "Key pressed"
|
|
55
|
+
# elsif event.none?
|
|
56
|
+
# # Idle
|
|
57
|
+
# elsif event.mouse?
|
|
58
|
+
# puts "Mouse event"
|
|
59
|
+
# end
|
|
13
60
|
class Event
|
|
61
|
+
# Returns true if this is a None event.
|
|
62
|
+
def none?
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
|
|
14
66
|
# Returns true if this is a Key event.
|
|
15
67
|
def key?
|
|
16
68
|
false
|
|
@@ -67,10 +119,10 @@ module RatatuiRuby
|
|
|
67
119
|
def deconstruct_keys(keys)
|
|
68
120
|
{}
|
|
69
121
|
end
|
|
70
|
-
|
|
71
122
|
end
|
|
72
123
|
end
|
|
73
124
|
|
|
125
|
+
require_relative "event/none"
|
|
74
126
|
require_relative "event/key"
|
|
75
127
|
require_relative "event/mouse"
|
|
76
128
|
require_relative "event/resize"
|