ratatui_ruby 0.3.1 → 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 +14 -12
- data/.builds/ruby-3.3.yml +14 -12
- data/.builds/ruby-3.4.yml +14 -12
- data/.builds/ruby-4.0.0.yml +14 -12
- data/AGENTS.md +89 -132
- data/CHANGELOG.md +223 -1
- data/README.md +23 -16
- data/REUSE.toml +20 -0
- data/doc/application_architecture.md +176 -0
- data/doc/application_testing.md +17 -10
- data/doc/contributors/design/ruby_frontend.md +11 -7
- data/doc/contributors/developing_examples.md +261 -0
- data/doc/contributors/documentation_style.md +104 -0
- data/doc/contributors/dwim_dx.md +366 -0
- data/doc/contributors/index.md +2 -0
- data/doc/custom.css +14 -0
- data/doc/event_handling.md +125 -0
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_analytics.png +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_custom_widget.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_map_demo.png +0 -0
- data/doc/images/app_mouse_events.png +0 -0
- data/doc/images/app_table_select.png +0 -0
- data/doc/images/verify_quickstart_dsl.png +0 -0
- data/doc/images/verify_quickstart_layout.png +0 -0
- data/doc/images/verify_quickstart_lifecycle.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_list_styles.png +0 -0
- data/doc/images/widget_popup_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_rect.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_rich_text.png +0 -0
- data/doc/images/widget_scroll_text.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/index.md +1 -0
- data/doc/interactive_design.md +116 -0
- data/doc/quickstart.md +186 -84
- 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/{lib/ratatui_ruby/output.rb → examples/app_all_events/view.rb} +1 -1
- 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/app_login_form/app.rb +108 -0
- data/examples/app_map_demo/app.rb +93 -0
- data/examples/app_table_select/app.rb +201 -0
- data/examples/verify_quickstart_dsl/app.rb +45 -0
- data/examples/verify_quickstart_layout/app.rb +69 -0
- data/examples/verify_quickstart_lifecycle/app.rb +48 -0
- data/examples/verify_readme_usage/app.rb +34 -0
- data/examples/widget_barchart_demo/app.rb +238 -0
- data/examples/widget_block_padding/app.rb +67 -0
- data/examples/widget_block_titles/app.rb +69 -0
- data/examples/widget_box_demo/app.rb +250 -0
- 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 +102 -0
- data/examples/widget_table_flex/app.rb +95 -0
- data/examples/widget_tabs_demo/app.rb +167 -0
- data/ext/ratatui_ruby/Cargo.lock +889 -115
- data/ext/ratatui_ruby/Cargo.toml +4 -3
- data/ext/ratatui_ruby/clippy.toml +7 -0
- data/ext/ratatui_ruby/extconf.rb +7 -0
- data/ext/ratatui_ruby/src/events.rs +293 -219
- data/ext/ratatui_ruby/src/frame.rs +115 -0
- data/ext/ratatui_ruby/src/lib.rs +105 -24
- data/ext/ratatui_ruby/src/rendering.rs +94 -10
- data/ext/ratatui_ruby/src/style.rs +357 -93
- data/ext/ratatui_ruby/src/terminal.rs +121 -31
- data/ext/ratatui_ruby/src/text.rs +178 -0
- data/ext/ratatui_ruby/src/widgets/barchart.rs +99 -24
- data/ext/ratatui_ruby/src/widgets/block.rs +32 -3
- data/ext/ratatui_ruby/src/widgets/calendar.rs +45 -44
- data/ext/ratatui_ruby/src/widgets/canvas.rs +44 -9
- data/ext/ratatui_ruby/src/widgets/chart.rs +79 -27
- data/ext/ratatui_ruby/src/widgets/clear.rs +3 -1
- data/ext/ratatui_ruby/src/widgets/gauge.rs +11 -4
- data/ext/ratatui_ruby/src/widgets/layout.rs +223 -15
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +92 -0
- data/ext/ratatui_ruby/src/widgets/list.rs +114 -11
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
- data/ext/ratatui_ruby/src/widgets/overlay.rs +4 -2
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +35 -13
- data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
- data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +51 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +61 -7
- data/ext/ratatui_ruby/src/widgets/sparkline.rs +73 -6
- data/ext/ratatui_ruby/src/widgets/table.rs +177 -64
- data/ext/ratatui_ruby/src/widgets/tabs.rs +105 -5
- data/lib/ratatui_ruby/cell.rb +166 -0
- data/lib/ratatui_ruby/event/focus_gained.rb +49 -0
- data/lib/ratatui_ruby/event/focus_lost.rb +50 -0
- data/lib/ratatui_ruby/event/key.rb +211 -0
- data/lib/ratatui_ruby/event/mouse.rb +124 -0
- data/lib/ratatui_ruby/event/none.rb +43 -0
- data/lib/ratatui_ruby/event/paste.rb +71 -0
- data/lib/ratatui_ruby/event/resize.rb +80 -0
- data/lib/ratatui_ruby/event.rb +131 -0
- data/lib/ratatui_ruby/frame.rb +87 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +45 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +23 -0
- data/lib/ratatui_ruby/schema/bar_chart.rb +226 -17
- data/lib/ratatui_ruby/schema/block.rb +178 -11
- data/lib/ratatui_ruby/schema/calendar.rb +70 -14
- data/lib/ratatui_ruby/schema/canvas.rb +213 -46
- data/lib/ratatui_ruby/schema/center.rb +46 -8
- data/lib/ratatui_ruby/schema/chart.rb +134 -32
- data/lib/ratatui_ruby/schema/clear.rb +22 -53
- data/lib/ratatui_ruby/schema/constraint.rb +72 -12
- data/lib/ratatui_ruby/schema/cursor.rb +23 -5
- data/lib/ratatui_ruby/schema/draw.rb +53 -0
- data/lib/ratatui_ruby/schema/gauge.rb +56 -12
- data/lib/ratatui_ruby/schema/layout.rb +91 -9
- data/lib/ratatui_ruby/schema/line_gauge.rb +78 -0
- data/lib/ratatui_ruby/schema/list.rb +92 -16
- data/lib/ratatui_ruby/schema/overlay.rb +29 -3
- data/lib/ratatui_ruby/schema/paragraph.rb +82 -25
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +29 -0
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +34 -0
- data/lib/ratatui_ruby/schema/rect.rb +59 -10
- data/lib/ratatui_ruby/schema/scrollbar.rb +127 -19
- data/lib/ratatui_ruby/schema/shape/label.rb +66 -0
- data/lib/ratatui_ruby/schema/sparkline.rb +120 -12
- data/lib/ratatui_ruby/schema/style.rb +39 -11
- data/lib/ratatui_ruby/schema/table.rb +109 -18
- data/lib/ratatui_ruby/schema/tabs.rb +71 -10
- data/lib/ratatui_ruby/schema/text.rb +90 -0
- data/lib/ratatui_ruby/session/autodoc.rb +417 -0
- data/lib/ratatui_ruby/session.rb +163 -0
- data/lib/ratatui_ruby/test_helper.rb +322 -13
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +184 -38
- data/sig/examples/app_all_events/app.rbs +11 -0
- 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/sig/examples/app_color_picker/app.rbs +12 -0
- 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/sig/examples/widget_gauge_demo/app.rbs +11 -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/sig/examples/widget_style_colors/app.rbs +14 -0
- data/sig/examples/widget_table_flex/app.rbs +11 -0
- data/sig/ratatui_ruby/event.rbs +69 -0
- data/sig/ratatui_ruby/frame.rbs +9 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -3
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +16 -0
- data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +13 -0
- data/sig/ratatui_ruby/schema/bar_chart.rbs +20 -2
- data/sig/ratatui_ruby/schema/block.rbs +5 -4
- data/sig/ratatui_ruby/schema/calendar.rbs +6 -2
- data/sig/ratatui_ruby/schema/canvas.rbs +52 -39
- data/sig/ratatui_ruby/schema/center.rbs +3 -3
- data/sig/ratatui_ruby/schema/chart.rbs +8 -5
- data/sig/ratatui_ruby/schema/constraint.rbs +8 -5
- data/sig/ratatui_ruby/schema/cursor.rbs +1 -1
- data/sig/ratatui_ruby/schema/draw.rbs +27 -0
- data/sig/ratatui_ruby/schema/gauge.rbs +4 -2
- data/sig/ratatui_ruby/schema/layout.rbs +11 -1
- data/sig/ratatui_ruby/schema/line_gauge.rbs +16 -0
- data/sig/ratatui_ruby/schema/list.rbs +5 -1
- data/sig/ratatui_ruby/schema/paragraph.rbs +4 -1
- data/sig/ratatui_ruby/schema/ratatui_logo.rbs +8 -0
- data/sig/ratatui_ruby/{buffer.rbs → schema/ratatui_mascot.rbs} +4 -3
- data/sig/ratatui_ruby/schema/rect.rbs +2 -1
- data/sig/ratatui_ruby/schema/scrollbar.rbs +18 -2
- data/sig/ratatui_ruby/schema/sparkline.rbs +6 -2
- data/sig/ratatui_ruby/schema/table.rbs +8 -1
- data/sig/ratatui_ruby/schema/tabs.rbs +5 -1
- data/sig/ratatui_ruby/schema/text.rbs +22 -0
- 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/build.yml.erb +13 -11
- data/tasks/resources/index.html.erb +6 -0
- data/tasks/sourcehut.rake +4 -4
- data/tasks/terminal_preview/app_screenshot.rb +33 -0
- data/tasks/terminal_preview/crash_report.rb +52 -0
- data/tasks/terminal_preview/example_app.rb +25 -0
- data/tasks/terminal_preview/launcher_script.rb +46 -0
- data/tasks/terminal_preview/preview_collection.rb +58 -0
- data/tasks/terminal_preview/preview_timing.rb +22 -0
- data/tasks/terminal_preview/safety_confirmation.rb +56 -0
- data/tasks/terminal_preview/saved_screenshot.rb +53 -0
- data/tasks/terminal_preview/system_appearance.rb +11 -0
- data/tasks/terminal_preview/terminal_window.rb +136 -0
- data/tasks/terminal_preview/window_id.rb +14 -0
- data/tasks/terminal_preview.rake +28 -0
- data/tasks/test.rake +2 -2
- 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 +206 -54
- data/LICENSES/BSD-2-Clause.txt +0 -9
- data/doc/images/examples-analytics.rb.png +0 -0
- data/doc/images/examples-box_demo.rb.png +0 -0
- data/doc/images/examples-calendar_demo.rb.png +0 -0
- data/doc/images/examples-chart_demo.rb.png +0 -0
- data/doc/images/examples-custom_widget.rb.png +0 -0
- data/doc/images/examples-dashboard.rb.png +0 -0
- data/doc/images/examples-list_styles.rb.png +0 -0
- data/doc/images/examples-login_form.rb.png +0 -0
- data/doc/images/examples-map_demo.rb.png +0 -0
- data/doc/images/examples-mouse_events.rb.png +0 -0
- data/doc/images/examples-popup_demo.rb.gif +0 -0
- data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
- data/doc/images/examples-scroll_text.rb.png +0 -0
- data/doc/images/examples-scrollbar_demo.rb.png +0 -0
- data/doc/images/examples-stock_ticker.rb.png +0 -0
- data/doc/images/examples-system_monitor.rb.png +0 -0
- data/doc/images/examples-table_select.rb.png +0 -0
- data/examples/analytics.rb +0 -88
- data/examples/box_demo.rb +0 -71
- data/examples/calendar_demo.rb +0 -55
- data/examples/chart_demo.rb +0 -84
- data/examples/custom_widget.rb +0 -43
- data/examples/dashboard.rb +0 -72
- data/examples/list_styles.rb +0 -66
- data/examples/login_form.rb +0 -115
- data/examples/map_demo.rb +0 -58
- data/examples/mouse_events.rb +0 -95
- data/examples/popup_demo.rb +0 -105
- data/examples/quickstart_dsl.rb +0 -30
- data/examples/quickstart_lifecycle.rb +0 -40
- data/examples/readme_usage.rb +0 -21
- data/examples/scroll_text.rb +0 -74
- data/examples/scrollbar_demo.rb +0 -75
- data/examples/stock_ticker.rb +0 -93
- data/examples/system_monitor.rb +0 -94
- data/examples/table_select.rb +0 -70
- data/examples/test_analytics.rb +0 -65
- data/examples/test_box_demo.rb +0 -38
- data/examples/test_calendar_demo.rb +0 -66
- data/examples/test_dashboard.rb +0 -38
- data/examples/test_list_styles.rb +0 -61
- data/examples/test_login_form.rb +0 -63
- data/examples/test_map_demo.rb +0 -100
- data/examples/test_popup_demo.rb +0 -62
- data/examples/test_scroll_text.rb +0 -130
- data/examples/test_stock_ticker.rb +0 -39
- data/examples/test_system_monitor.rb +0 -40
- data/examples/test_table_select.rb +0 -37
- data/ext/ratatui_ruby/src/buffer.rs +0 -54
- data/lib/ratatui_ruby/dsl.rb +0 -64
data/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,224 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
18
18
|
|
|
19
19
|
### Removed
|
|
20
20
|
|
|
21
|
+
## [0.5.0] - 2026-01-01
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
#### Frame API
|
|
26
|
+
|
|
27
|
+
- **`RatatuiRuby.draw { |frame| ... }`**: New block-based drawing API that yields a `Frame` object for explicit widget placement. Enables hit testing without duplicating layout calculations.
|
|
28
|
+
- **`Frame#area`**: Returns the terminal area as a `Rect`.
|
|
29
|
+
- **`Frame#render_widget(widget, rect)`**: Renders a widget at a specific position. Works with all existing widgets and `Rect` objects.
|
|
30
|
+
|
|
31
|
+
#### Testing
|
|
32
|
+
|
|
33
|
+
- **`RatatuiRuby::TestHelper#inject_mouse`**: comprehensive mouse event injection helper supporting coordinates, buttons, and modifiers.
|
|
34
|
+
- **`RatatuiRuby::TestHelper#inject_click`**: Helper for left-click events.
|
|
35
|
+
- **`RatatuiRuby::TestHelper#inject_right_click`**: Helper for right-click events.
|
|
36
|
+
- **`RatatuiRuby::TestHelper#inject_drag`**: Helper for mouse drag events.
|
|
37
|
+
- **`RatatuiRuby::TestHelper#assert_screen_matches`**: Assert that the current terminal content matches a stored golden snapshot.
|
|
38
|
+
|
|
39
|
+
#### Session API
|
|
40
|
+
|
|
41
|
+
- **Convenience Methods**: `Session` now wraps class methods from `Layout`, `Constraint`, and other schema classes as instance methods (e.g., `layout_split` delegates to `Layout.split`, `constraint_percentage` to `Constraint.percentage`). This enables a more fluent API in `RatatuiRuby.run` blocks.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
#### Event System
|
|
46
|
+
|
|
47
|
+
- **`Event::None` (Breaking)**: `RatatuiRuby.poll_event` now returns `Event::None` instead of `nil` when no event is available. This null-object responds safely to all event predicates with `false`. Use `event.none?` or pattern-match on `type: :none`. Code using `while (event = poll_event)` must change to `while (event = poll_event) && !event.none?`.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
#### Session API
|
|
52
|
+
|
|
53
|
+
- **Missing Convenience Methods**: Fixed `Session` convenience methods (e.g., `bar_chart`) being missed by replacing the manual list with automatic runtime introspection of the `RatatuiRuby` module.
|
|
54
|
+
|
|
55
|
+
### Removed
|
|
56
|
+
|
|
57
|
+
## [0.4.0] - 2025-12-30
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
#### Hex Color Support
|
|
62
|
+
|
|
63
|
+
- **Style**: `fg` and `bg` parameters now accept hex color strings (e.g., `"#ff0000"` for red). Requires a 24-bit true color capable terminal (Kitty, iTerm2, modern Terminal.app). Terminals without true color support will gracefully fall back to the closest ANSI color.
|
|
64
|
+
|
|
65
|
+
#### RatatuiMascot Widget
|
|
66
|
+
|
|
67
|
+
- **RatatuiMascot**: New widget to display the Ratatui mascot (Ferris).
|
|
68
|
+
|
|
69
|
+
#### RatatuiLogo Widget
|
|
70
|
+
|
|
71
|
+
- **RatatuiLogo**: New widget to display the Ratatui logo.
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
#### Duck-Typed Numeric Coercion
|
|
75
|
+
|
|
76
|
+
- All numeric parameters now accept any object that responds to `to_f` (for floats) or `to_int`/`to_i` (for integers). This provides idiomatic Ruby interoperability with `BigDecimal`, `Rational`, and custom numeric types. Uses Ruby's built-in `Float()` and `Integer()` Kernel methods for proper duck-type handling.
|
|
77
|
+
|
|
78
|
+
#### Paragraph Widget
|
|
79
|
+
|
|
80
|
+
- `alignment`: **Breaking Change**: Renamed `align` to `alignment` to match Ratatui 0.30 API.
|
|
81
|
+
|
|
82
|
+
#### Custom Widgets
|
|
83
|
+
|
|
84
|
+
- **Draw Command API**: Custom widgets now return an array of `Draw` commands instead of writing to a buffer. Use `RatatuiRuby::Draw.string(x, y, string, style)` and `RatatuiRuby::Draw.cell(x, y, cell)` to create draw commands. This eliminates use-after-free bugs by keeping all pointers inside Rust while Ruby works with pure data objects. **Breaking:** The `render` method signature changed from `render(area, buffer)` to `render(area)`.
|
|
85
|
+
|
|
86
|
+
#### BarChart Widget
|
|
87
|
+
|
|
88
|
+
- `bar_set`: Customize bar characters (digits, symbols, blocks).
|
|
89
|
+
- `group_gap`: Control spacing between groups in grouped bar charts.
|
|
90
|
+
- `data`: Now accepts an Array of `BarGroup` objects, enabling grouped bar charts.
|
|
91
|
+
- `Bar` and `BarGroup`: New schema classes for defining grouped bar data.
|
|
92
|
+
|
|
93
|
+
#### Block Widget
|
|
94
|
+
|
|
95
|
+
- `border_type`: Customize border style (`:plain`, `:rounded`, `:double`, `:thick`, `:quadrant_inside`, `:quadrant_outside`).
|
|
96
|
+
- `border_set`: Customize border characters (e.g., digits, symbols).
|
|
97
|
+
- `border_style`: Apply full style support (colors and modifiers) to borders. Takes precedence over `border_color`.
|
|
98
|
+
- `children`: Declare child widgets within the block's area for composable UI structures.
|
|
99
|
+
- Multiple `titles`: Display multiple titles with individual alignment (`:left`, `:center`, `:right`) and vertical positioning (`:top`, `:bottom`). Each title supports its own `style`.
|
|
100
|
+
- `title_style`: Base style applied to all titles.
|
|
101
|
+
- `style`: Base style applied to the entire block.
|
|
102
|
+
- `padding`: Directional padding via a single integer (uniform) or array of 4 integers (`[left, right, top, bottom]`).
|
|
103
|
+
- `line_count(width)`: **(Experimental)** Calculate rendered lines (including borders/padding) for a given width. Delegates to Ratatui's underlying unstable `line_count`.
|
|
104
|
+
- `line_width`: **(Experimental)** Calculate minimum width to avoid wrapping (including borders/padding). Delegates to Ratatui's underlying unstable `line_width`.
|
|
105
|
+
|
|
106
|
+
#### Calendar Widget
|
|
107
|
+
|
|
108
|
+
- `events`: Hash mapping `Date` objects to `Style` objects for highlighting specific dates.
|
|
109
|
+
- `show_month_header`: Toggle month header visibility (defaults to `false`). **Breaking:** Previously always shown.
|
|
110
|
+
- `show_weekdays_header`: Toggle weekday names (Mon, Tue, etc.) visibility (defaults to `true`).
|
|
111
|
+
- `show_surrounding`: Optional `Style` to display dates from adjacent months, or `nil` to hide them.
|
|
112
|
+
|
|
113
|
+
#### Chart Widget
|
|
114
|
+
|
|
115
|
+
- `legend_position`: Position legend at `:top_left`, `:top_right`, `:bottom_left`, or `:bottom_right` (defaults to `:top_right`).
|
|
116
|
+
- `hidden_legend_constraints`: Array of two `Constraint` objects to hide the legend when chart area is too small.
|
|
117
|
+
- **Axis**: `labels_alignment` to control horizontal alignment (`:left`, `:center`, `:right`) of axis labels.
|
|
118
|
+
- `Dataset`: `style` parameter replaces `color`, enabling full styling (fg, bg, modifiers) for chart datasets. **Breaking**: `color` parameter removed.
|
|
119
|
+
|
|
120
|
+
#### Gauge Widget
|
|
121
|
+
|
|
122
|
+
- `style`: Base style applied to the entire gauge background.
|
|
123
|
+
- `gauge_style`: Style applied specifically to the filled bar. **Breaking:** Use this instead of `style` if you want bar coloring; `style` no longer defaults to `Style.default`.
|
|
124
|
+
- `percent`: Convenience parameter alternative to `ratio` for initialization.
|
|
125
|
+
- `use_unicode`: Explicitly toggle between unicode blocks and ASCII rendering (defaults to `true`).
|
|
126
|
+
|
|
127
|
+
#### LineGauge Widget
|
|
128
|
+
|
|
129
|
+
- `style`: Base style applied to the entire gauge area.
|
|
130
|
+
|
|
131
|
+
#### List Widget
|
|
132
|
+
|
|
133
|
+
- `scroll_padding`: Number of items to keep visible above and below the selected item during scrolling.
|
|
134
|
+
- `repeat_highlight_symbol`: When `true`, repeat highlight symbol on each line of multi-line selections.
|
|
135
|
+
- `highlight_spacing`: Control selection column reservation (`:always`, `:when_selected`, `:never`).
|
|
136
|
+
- `direction`: List orientation (`:top_to_bottom` or `:bottom_to_top`).
|
|
137
|
+
|
|
138
|
+
#### Sparkline Widget
|
|
139
|
+
|
|
140
|
+
- `absent_value_symbol` and `absent_value_style`: Customize rendering of `nil` values (distinct from `0`).
|
|
141
|
+
- `direction`: Rendering direction (`:left_to_right` or `:right_to_left`).
|
|
142
|
+
- `bar_set`: Customize bar characters.
|
|
143
|
+
|
|
144
|
+
#### Table Widget
|
|
145
|
+
|
|
146
|
+
- `style`: Base style applied to the entire table.
|
|
147
|
+
- `column_spacing`: Horizontal spacing between columns.
|
|
148
|
+
- `footer`: Summary rows at the bottom of the table.
|
|
149
|
+
- `flex`: Layout distribution mode (`:legacy`, `:start`, `:center`, `:end`, `:space_between`, `:space_around`, `:space_evenly`).
|
|
150
|
+
- `highlight_spacing`: Control selection column reservation (`:always`, `:when_selected`, `:never`).
|
|
151
|
+
- `column_highlight_style`: Style applied to the selected column.
|
|
152
|
+
- `cell_highlight_style`: Style applied to the selected cell (intersection of row and column).
|
|
153
|
+
- `selected_column`: Index of the selected column (Integer or nil).
|
|
154
|
+
- `widths`: Now support all constraint types (`:max`, `:fill`, `:ratio`) with full flexibility.
|
|
155
|
+
|
|
156
|
+
#### Tabs Widget
|
|
157
|
+
|
|
158
|
+
- `style`: Base style applied to the entire tabs area.
|
|
159
|
+
- `padding_left` and `padding_right`: Horizontal padding around tab titles.
|
|
160
|
+
- `width`: Calculate total width of the tabs (including dividers/padding).
|
|
161
|
+
|
|
162
|
+
#### Canvas Widget
|
|
163
|
+
|
|
164
|
+
- `background_color`: Set canvas background color.
|
|
165
|
+
- `:half_block` marker: Block-based rendering using half-height blocks.
|
|
166
|
+
- `:quadrant`, `:sextant`, `:octant` markers: High-resolution pseudo-pixel rendering.
|
|
167
|
+
- `Shape::Label`: Text labels at canvas coordinates with optional styling.
|
|
168
|
+
|
|
169
|
+
#### Scrollbar Widget
|
|
170
|
+
|
|
171
|
+
- Full styling support: `thumb_style`, `track_symbol`, `track_style`, `begin_symbol`, `begin_style`, `end_symbol`, `end_style`, `style`.
|
|
172
|
+
- All orientation variants: `:vertical_left`, `:vertical_right`, `:horizontal_top`, `:horizontal_bottom` (`:vertical` and `:horizontal` remain as aliases).
|
|
173
|
+
|
|
174
|
+
#### Layout & Constraints
|
|
175
|
+
|
|
176
|
+
- `Constraint.ratio(numerator, denominator)`: Proportional constraints with explicit ratio.
|
|
177
|
+
- `Constraint.fill(weight)`: Distribute remaining space proportionally. Use multiple `Fill` to split space (e.g., `Fill(1)` and `Fill(3)` split 1:3).
|
|
178
|
+
- `Constraint.max(value)`: Cap maximum size of a section.
|
|
179
|
+
- `Layout.split(area, direction:, constraints:, flex:)`: Compute layout rectangles without rendering, enabling hit testing.
|
|
180
|
+
- `Flex::SpaceEvenly`: New layout mode for `Layout` widget.
|
|
181
|
+
- `flex` parameter: All layout options (`:legacy`, `:start`, `:center`, `:end`, `:space_between`, `:space_around`, `:space_evenly`).
|
|
182
|
+
|
|
183
|
+
#### Rich Text & Text Components
|
|
184
|
+
|
|
185
|
+
- `Text::Span` and `Text::Line`: Styled text with inline formatting. Combine spans into lines with optional alignment.
|
|
186
|
+
- `Shape` module: Canvas shape primitives (`Shape::Line`, `Shape::Circle`, `Shape::Rectangle`, `Shape::Point`, `Shape::Map`) to avoid naming conflicts with `Text::Line`.
|
|
187
|
+
|
|
188
|
+
#### Event System
|
|
189
|
+
|
|
190
|
+
- Typed `Event` API: `RatatuiRuby.poll_event` returns typed objects (`Event::Key`, `Event::Mouse`, `Event::Resize`, `Event::Paste`, `Event::FocusGained`, `Event::FocusLost`).
|
|
191
|
+
- Predicate methods: `key?`, `mouse?`, `ctrl?`, etc. for cleaner event handling.
|
|
192
|
+
- Pattern matching support and discriminator pattern via `type:` key in `#deconstruct_keys`.
|
|
193
|
+
- `Event::Resize`: Terminal resize events with `width` and `height` attributes.
|
|
194
|
+
- `Event::Paste`: Bracketed paste as atomic event with `content:`.
|
|
195
|
+
- `Event::FocusGained` and `Event::FocusLost`: Terminal focus changes.
|
|
196
|
+
- `Event::Mouse.new` accepts `nil` for `button` parameter (treated as `"none"`).
|
|
197
|
+
|
|
198
|
+
#### Geometry & Hit Testing
|
|
199
|
+
|
|
200
|
+
- `Rect#contains?(x, y)`: Test whether a point is inside a rectangle. Essential for mouse click handlers.
|
|
201
|
+
- `Layout.split`: Enables calculating widget positions before rendering.
|
|
202
|
+
|
|
203
|
+
#### Testing
|
|
204
|
+
|
|
205
|
+
- `RatatuiRuby::TestHelper#inject_keys`: Concise event injection helper.
|
|
206
|
+
- `RatatuiRuby::TestHelper#get_cell` and `#assert_cell_style`: Inspect terminal cell attributes (colors, characters).
|
|
207
|
+
- `with_test_terminal`: Default timeout of 2 seconds to prevent hanging tests. **Breaking:** Default size is now 80×24 (VT100 standard) instead of 20×10.
|
|
208
|
+
- Error on `inject_event`/`inject_keys` outside `with_test_terminal`: Prevents test hangs from race conditions.
|
|
209
|
+
- Value equality (`==`) for `Event` objects: Simplify assertions.
|
|
210
|
+
|
|
211
|
+
#### Lifecycle & Application Structure
|
|
212
|
+
|
|
213
|
+
- `RatatuiRuby.run`: New context manager that initializes terminal, yields session, and restores on exit. Allows custom event loop control.
|
|
214
|
+
- **Session** class: Renamed from `DSL` to better reflect its purpose as a managed terminal session with convenience methods.
|
|
215
|
+
- Focus and Bracketed Paste events: Enabled by default in `RatatuiRuby.run` and `RatatuiRuby.init_terminal` (disable with `focus_events: false` or `bracketed_paste: false`).
|
|
216
|
+
|
|
217
|
+
#### Documentation & Examples
|
|
218
|
+
|
|
219
|
+
- **Cached Layout Pattern**: Documented in `doc/interactive_design.md`. Three-phase lifecycle pattern (`calculate_layout`, `render`, `handle_input`) solves layout duplication in immediate-mode UI. Foundation for Component architecture in Gem 1.5.
|
|
220
|
+
|
|
221
|
+
### Changed
|
|
222
|
+
|
|
223
|
+
- **Calendar:** Renamed `day_style` to `default_style` to match Ratatui 0.30 API. This is a breaking change. [Kerrick Long]
|
|
224
|
+
|
|
225
|
+
- **Custom Widget `render` Method (Breaking)**: Changed signature from `render(area, buffer)` to `render(area)`, with render methods now returning an array of `Draw` commands instead of writing directly to a buffer. This change improves memory safety by eliminating use-after-free risks.
|
|
226
|
+
- **Ratatui Upgraded to 0.30.0**: Underlying `ratatui` library upgraded from 0.29, bringing modularized crates, `no_std` support for embedded targets, and major widget/layout enhancements. Layout cache explicitly enabled for performance.
|
|
227
|
+
- **Event API (Breaking)**: `RatatuiRuby.poll_event` returns typed `Event` objects instead of raw Hashes. Code using `event[:type]` must change to `event.key?`, `event.code`, etc.
|
|
228
|
+
- **RatatuiRuby.main_loop Removed (Breaking)**: Removed in favor of `RatatuiRuby.run` for more explicit lifecycle control.
|
|
229
|
+
- **TestHelper Terminal Size (Breaking)**: `with_test_terminal` defaults to 80×24 instead of 20×10.
|
|
230
|
+
- **Calendar Month Header Default (Breaking)**: `show_month_header` defaults to `false` (previously always shown). Set `show_month_header: true` if relying on the old behavior.
|
|
231
|
+
- **Gauge `style` Default (Breaking)**: No longer defaults to `Style.default`. Use `gauge_style` for bar coloring instead.
|
|
232
|
+
|
|
233
|
+
### Fixed
|
|
234
|
+
|
|
235
|
+
- **Alpine Linux Support**: Fixed gem installation failures on Alpine Linux (musl targets) by properly configuring `crate-type` to support static linking where dynamic linking is unsupported.
|
|
236
|
+
- **Rust Safety**: Convert `class.name()` results to owned strings for proper GC safety with Magnus 0.8.
|
|
237
|
+
- **Terminal Preview Detection**: Detect staged changes correctly in preview generation.
|
|
238
|
+
|
|
21
239
|
## [0.3.1] - 2025-12-28
|
|
22
240
|
|
|
23
241
|
### Added
|
|
@@ -28,10 +246,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
28
246
|
|
|
29
247
|
### Added
|
|
30
248
|
|
|
249
|
+
- **Custom Widget API (Breaking)**: Custom widgets that define a `render` method now receive only `area` (not `buffer`) and must return an array of `Draw` commands. Use `RatatuiRuby::Draw.string(x, y, string, style)` and `RatatuiRuby::Draw.cell(x, y, cell)` instead of `buffer.set_string` and `buffer.set_cell`. This eliminates a class of use-after-free bugs by ensuring Ruby never holds pointers to Rust-owned memory. Widgets can now be unit tested by asserting on the returned array.
|
|
31
250
|
- **The Escape Hatch (Ruby Render Callback)**: Added the ability to define custom widgets in pure Ruby by implementing a `render(area, buffer)` method. The `Buffer` object provides low-level drawing primitives like `set_string`, allowing developers to create custom TUI components without writing Rust code.
|
|
32
251
|
- **Clear Widget**: Added the `Clear` widget, which resets the terminal buffer in the area it is rendered. This is essential for creating opaque popups and modals that prevent background styles from "bleeding" through transparent widgets.
|
|
33
252
|
- **Interactive Table Selection**: The `Table` widget now supports row selection with `selected_row`, `highlight_style`, and `highlight_symbol` parameters. This enables building interactive data grids and file explorers where users can navigate through rows using keyboard input.
|
|
34
253
|
- **Scrollable Paragraphs**: The `Paragraph` widget now supports a `scroll` parameter that accepts a `(y, x)` array to scroll content vertically and horizontally. This enables viewing long text content that exceeds the visible area, such as logs or documents. The parameter order matches ratatui's convention.
|
|
254
|
+
- **Enhanced Tabs Customization**: The `Tabs` widget now supports `highlight_style` for the selected tab and a customizable `divider` string (defaulting to the standard pipe `|`). This allows for richer visual feedback in tabbed interfaces.
|
|
35
255
|
|
|
36
256
|
### Changed
|
|
37
257
|
|
|
@@ -65,7 +285,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
65
285
|
- **Input Handling**: Robust handling for both Keyboard and Mouse events.
|
|
66
286
|
- **Testing Support**: Included `RatatuiRuby::TestHelper` and RSpec integration to make testing your TUI applications possible.
|
|
67
287
|
|
|
68
|
-
[Unreleased]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.
|
|
288
|
+
[Unreleased]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.5.0...HEAD
|
|
289
|
+
[0.5.0]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.4.0...v0.5.0
|
|
290
|
+
[0.4.0]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.3.1...v0.4.0
|
|
69
291
|
[0.3.1]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.3.0...v0.3.1
|
|
70
292
|
[0.3.0]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.2.0...v0.3.0
|
|
71
293
|
[0.2.0]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.1.0...v0.2.0
|
data/README.md
CHANGED
|
@@ -9,7 +9,7 @@ builds.sr.ht status](https://builds.sr.ht/~kerrick/ratatui_ruby.svg)](https://bu
|
|
|
9
9
|
License](https://img.shields.io/badge/dynamic/regex?url=https%3A%2F%2Fgit.sr.ht%2F~kerrick%2Fratatui_ruby%2Fblob%2Fmain%2Fratatui_ruby.gemspec&search=spec%5C.license%20%3D%20%22(.*)%22&replace=%241&label=License&color=a2c93e)](https://spdx.org/licenses/AGPL-3.0-or-later.html) [](https://rubygems.org/gems/ratatui_ruby) [](https://rubygems.org/gems/ratatui_ruby) [](https://crates.io/crates/ratatui/0.
|
|
12
|
+
Ratatui Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fgit.sr.ht%2F~kerrick%2Fratatui_ruby%2Fblob%2Fmain%2Fext%2Fratatui_ruby%2FCargo.toml&query=%24.dependencies.ratatui.version&prefix=v&logo=ratatui&label=Ratatui)](https://crates.io/crates/ratatui/0.30) [](https://lists.sr.ht/~kerrick/ratatui_ruby-discuss) [](https://lists.sr.ht/~kerrick/ratatui_ruby-devel) [](https://lists.sr.ht/~kerrick/ratatui_ruby-announce)
|
|
@@ -20,8 +20,8 @@ Mailing List: Announcements](https://img.shields.io/badge/mailing_list-announcem
|
|
|
20
20
|
**ratatui_ruby** is a Ruby wrapper for [Ratatui](https://ratatui.rs). It allows you to cook up Terminal User Interfaces in Ruby.
|
|
21
21
|
**ratatui_ruby** is a community wrapper that is not affiliated with [the Ratatui team](https://github.com/orgs/ratatui/people).
|
|
22
22
|
|
|
23
|
-
> [!WARNING]
|
|
24
|
-
> **ratatui_ruby** is currently in
|
|
23
|
+
> [!WARNING]
|
|
24
|
+
> **ratatui_ruby** is currently in **BETA**. The API may change between minor versions.
|
|
25
25
|
|
|
26
26
|
Please join the **announce** mailing list at https://lists.sr.ht/~kerrick/ratatui_ruby-announce to stay up-to-date on new releases and announcements.
|
|
27
27
|
|
|
@@ -65,22 +65,29 @@ gem install ratatui_ruby
|
|
|
65
65
|
|
|
66
66
|
```ruby
|
|
67
67
|
require "ratatui_ruby"
|
|
68
|
-
RatatuiRuby.
|
|
69
|
-
|
|
70
|
-
tui.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
RatatuiRuby.run do |tui|
|
|
69
|
+
loop do
|
|
70
|
+
tui.draw do |frame|
|
|
71
|
+
frame.render_widget(
|
|
72
|
+
tui.paragraph(
|
|
73
|
+
text: "Hello, Ratatui! Press 'q' to quit.",
|
|
74
|
+
alignment: :center,
|
|
75
|
+
block: tui.block(
|
|
76
|
+
title: "My Ruby TUI App",
|
|
77
|
+
borders: [:all],
|
|
78
|
+
border_color: "cyan"
|
|
79
|
+
)
|
|
80
|
+
),
|
|
81
|
+
frame.area
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
event = tui.poll_event
|
|
85
|
+
break if event == "q" || event == :ctrl_c
|
|
86
|
+
end
|
|
80
87
|
end
|
|
81
88
|
```
|
|
82
89
|
|
|
83
|
-
For a full tutorial, see [the Quickstart](./doc/quickstart.md).
|
|
90
|
+
For a full tutorial, see [the Quickstart](./doc/quickstart.md). For an explanation of the application architecture, see [Application Architecture](./doc/application_architecture.md).
|
|
84
91
|
|
|
85
92
|
|
|
86
93
|
## Documentation
|
data/REUSE.toml
CHANGED
|
@@ -15,3 +15,23 @@ SPDX-License-Identifier = "CC0-1.0"
|
|
|
15
15
|
path = 'doc/images/*.png'
|
|
16
16
|
SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
|
17
17
|
SPDX-License-Identifier = "CC-BY-SA-4.0"
|
|
18
|
+
|
|
19
|
+
[[annotations]]
|
|
20
|
+
path = 'test/fixtures/*.txt'
|
|
21
|
+
SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
|
22
|
+
SPDX-License-Identifier = "AGPL-3.0-or-later"
|
|
23
|
+
|
|
24
|
+
[[annotations]]
|
|
25
|
+
path = 'test/snapshots/*.txt'
|
|
26
|
+
SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
|
27
|
+
SPDX-License-Identifier = "AGPL-3.0-or-later"
|
|
28
|
+
|
|
29
|
+
[[annotations]]
|
|
30
|
+
path = 'examples/app_all_events/test/snapshots/*.txt'
|
|
31
|
+
SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
|
32
|
+
SPDX-License-Identifier = "AGPL-3.0-or-later"
|
|
33
|
+
|
|
34
|
+
[[annotations]]
|
|
35
|
+
path = 'test/examples/app_all_events/snapshots/*.txt'
|
|
36
|
+
SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
|
|
37
|
+
SPDX-License-Identifier = "AGPL-3.0-or-later"
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# Application Architecture
|
|
8
|
+
|
|
9
|
+
Architect robust TUI applications using core lifecycle patterns and API best practices.
|
|
10
|
+
|
|
11
|
+
## Core Concepts
|
|
12
|
+
|
|
13
|
+
Your app lives inside a terminal. You need to respect its rules.
|
|
14
|
+
|
|
15
|
+
### Lifecycle Management
|
|
16
|
+
|
|
17
|
+
Terminals have state. They remember cursor positions, input modes, and screen buffers.
|
|
18
|
+
|
|
19
|
+
**The Problem:** If your app crashes or exits without cleaning up, it "breaks" the user's terminal. The cursor vanishes. Input echoes constantly. The alternate screen doesn't clear.
|
|
20
|
+
|
|
21
|
+
**The Solution:** The library's lifecycle manager handles this for you. It enters "raw mode" on startup and guarantees restoration on exit.
|
|
22
|
+
|
|
23
|
+
#### Use `RatatuiRuby.run`
|
|
24
|
+
|
|
25
|
+
This method acts as a safety net. It initializes the terminal, yields control to your block, and restores the terminal afterwards—even if your code raises an exception.
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
RatatuiRuby.run do |tui|
|
|
29
|
+
loop do
|
|
30
|
+
tui.draw do |frame|
|
|
31
|
+
frame.render_widget(tui.paragraph(text: "Hello"), frame.area)
|
|
32
|
+
end
|
|
33
|
+
break if tui.poll_event == "q"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
# Terminal is restored here
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Manual Management
|
|
40
|
+
|
|
41
|
+
Need granular control? You can initialize and restore the terminal yourself. Use `ensure` blocks to guarantee cleanup.
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
RatatuiRuby.init_terminal
|
|
45
|
+
begin
|
|
46
|
+
RatatuiRuby.draw do |frame|
|
|
47
|
+
frame.render_widget(RatatuiRuby::Paragraph.new(text: "Hello"), frame.area)
|
|
48
|
+
end
|
|
49
|
+
ensure
|
|
50
|
+
RatatuiRuby.restore_terminal
|
|
51
|
+
# Terminal is restored here
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### API Convenience
|
|
56
|
+
|
|
57
|
+
Writing UI trees involves nesting many widgets.
|
|
58
|
+
|
|
59
|
+
**The Problem:** Explicitly namespacing `RatatuiRuby::` for every widget (e.g., `RatatuiRuby::Paragraph.new`) is tedious. It creates visual noise that hides your layout structure.
|
|
60
|
+
|
|
61
|
+
**The Solution:** The Session API (`tui`) provides shorthand factories for every widget. It yields a session object to your block.
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
RatatuiRuby.run do |tui|
|
|
65
|
+
loop do
|
|
66
|
+
tui.draw do |frame|
|
|
67
|
+
# Split layout using Session helpes
|
|
68
|
+
sidebar_area, content_area = tui.layout_split(
|
|
69
|
+
frame.area,
|
|
70
|
+
direction: :horizontal,
|
|
71
|
+
constraints: [
|
|
72
|
+
tui.constraint_length(20),
|
|
73
|
+
tui.constraint_min(0)
|
|
74
|
+
]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Render sidebar
|
|
78
|
+
frame.render_widget(
|
|
79
|
+
tui.paragraph(
|
|
80
|
+
text: tui.text_line(spans: [
|
|
81
|
+
tui.text_span(content: "Side", style: tui.style(fg: :blue)),
|
|
82
|
+
tui.text_span(content: "bar")
|
|
83
|
+
]),
|
|
84
|
+
block: tui.block(borders: [:all], title: "Nav")
|
|
85
|
+
),
|
|
86
|
+
sidebar_area
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Render main content
|
|
90
|
+
frame.render_widget(
|
|
91
|
+
tui.paragraph(
|
|
92
|
+
text: "Main Content",
|
|
93
|
+
style: tui.style(fg: :green),
|
|
94
|
+
block: tui.block(borders: [:all], title: "Content")
|
|
95
|
+
),
|
|
96
|
+
content_area
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
event = tui.poll_event
|
|
101
|
+
break if event == "q" || event == :ctrl_c
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### Raw API
|
|
107
|
+
|
|
108
|
+
Building your own abstractions? You might prefer explicit class instantiation. The raw constants are always available.
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
RatatuiRuby.run do
|
|
112
|
+
loop do
|
|
113
|
+
RatatuiRuby.draw do |frame|
|
|
114
|
+
# Manual split
|
|
115
|
+
rects = RatatuiRuby::Layout.split(
|
|
116
|
+
frame.area,
|
|
117
|
+
direction: :horizontal,
|
|
118
|
+
constraints: [
|
|
119
|
+
RatatuiRuby::Constraint.length(20),
|
|
120
|
+
RatatuiRuby::Constraint.min(0)
|
|
121
|
+
]
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
frame.render_widget(
|
|
125
|
+
RatatuiRuby::Paragraph.new(
|
|
126
|
+
text: RatatuiRuby::Text::Line.new(spans: [
|
|
127
|
+
RatatuiRuby::Text::Span.new(content: "Side", style: RatatuiRuby::Style.new(fg: :blue)),
|
|
128
|
+
RatatuiRuby::Text::Span.new(content: "bar")
|
|
129
|
+
]),
|
|
130
|
+
block: RatatuiRuby::Block.new(borders: [:all], title: "Nav")
|
|
131
|
+
),
|
|
132
|
+
rects[0]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
frame.render_widget(
|
|
136
|
+
RatatuiRuby::Paragraph.new(
|
|
137
|
+
text: "Main Content",
|
|
138
|
+
style: RatatuiRuby::Style.new(fg: :green),
|
|
139
|
+
block: RatatuiRuby::Block.new(borders: [:all], title: "Content")
|
|
140
|
+
),
|
|
141
|
+
rects[1]
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
event = RatatuiRuby.poll_event
|
|
146
|
+
break if event == "q" || event == :ctrl_c
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Reference Architectures
|
|
152
|
+
|
|
153
|
+
Simple scripts work well with valid linear code. Complex apps need structure.
|
|
154
|
+
|
|
155
|
+
We provide these reference architectures to inspire you:
|
|
156
|
+
|
|
157
|
+
### MVVM (Model-View-ViewModel)
|
|
158
|
+
|
|
159
|
+
**Source:** [examples/app_all_events](../examples/app_all_events/README.md)
|
|
160
|
+
|
|
161
|
+
This pattern strictly separates concerns:
|
|
162
|
+
* **Model:** Pure business logic. It handles data and events.
|
|
163
|
+
* **View State (ViewModel):** An immutable data structure built fresh every frame. It calculates logic-dependent properties (like `border_color`) so the View doesn't have to.
|
|
164
|
+
* **View:** Pure rendering logic. It takes the View State and draws it.
|
|
165
|
+
|
|
166
|
+
Use this when your app has complex state rules that clutter your rendering code.
|
|
167
|
+
|
|
168
|
+
### Scene-Orchestrated MVC
|
|
169
|
+
|
|
170
|
+
**Source:** [examples/app_color_picker](../examples/app_color_picker/README.md)
|
|
171
|
+
|
|
172
|
+
This pattern addresses the difficulty of mouse interaction and layout management:
|
|
173
|
+
* **Scene:** A specialized View that owns the layout *and* hit testing. It caches the screen coordinates of widgets during the draw phase.
|
|
174
|
+
* **App (Controller):** Handles events by querying the Scene (e.g., `scene.rect_at(x, y)`).
|
|
175
|
+
|
|
176
|
+
Use this when you need rich interactivity (mouse clicks, drag-and-drop) or complex dynamic layouts.
|
data/doc/application_testing.md
CHANGED
|
@@ -44,13 +44,15 @@ Wrap your test assertions in `with_test_terminal`. This sets up a temporary, in-
|
|
|
44
44
|
|
|
45
45
|
```ruby
|
|
46
46
|
def test_rendering
|
|
47
|
-
#
|
|
48
|
-
with_test_terminal
|
|
49
|
-
# 1. Instantiate your
|
|
47
|
+
# Uses default 80x24 terminal
|
|
48
|
+
with_test_terminal do
|
|
49
|
+
# 1. Instantiate your widget
|
|
50
50
|
widget = RatatuiRuby::Paragraph.new(text: "Hello World")
|
|
51
51
|
|
|
52
|
-
# 2. Render it
|
|
53
|
-
RatatuiRuby.draw
|
|
52
|
+
# 2. Render it using the Frame API
|
|
53
|
+
RatatuiRuby.draw do |frame|
|
|
54
|
+
frame.render_widget(widget, frame.area)
|
|
55
|
+
end
|
|
54
56
|
|
|
55
57
|
# 3. Assert on the output
|
|
56
58
|
assert_includes buffer_content[0], "Hello World"
|
|
@@ -82,13 +84,18 @@ assert_equal 2, pos[:y]
|
|
|
82
84
|
|
|
83
85
|
Injects a mock event into the event queue. This is the preferred way to simulate user input instead of stubbing `poll_event`.
|
|
84
86
|
|
|
87
|
+
> [!IMPORTANT]
|
|
88
|
+
> You must call `inject_event` inside a `with_test_terminal` block. Calling it outside leads to race conditions where events are flushed before the application starts.
|
|
89
|
+
|
|
85
90
|
```ruby
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
with_test_terminal do
|
|
92
|
+
# Simulate 'q' key press
|
|
93
|
+
inject_event("key", { code: "q" })
|
|
88
94
|
|
|
89
|
-
# Now poll_event will return the 'q' key event
|
|
90
|
-
event = RatatuiRuby.poll_event
|
|
91
|
-
assert_equal "q", event
|
|
95
|
+
# Now poll_event will return the 'q' key event
|
|
96
|
+
event = RatatuiRuby.poll_event
|
|
97
|
+
assert_equal "q", event.code
|
|
98
|
+
end
|
|
92
99
|
```
|
|
93
100
|
|
|
94
101
|
## Example
|
|
@@ -9,6 +9,8 @@ This document describes the design philosophy and structure of the Ruby layer in
|
|
|
9
9
|
|
|
10
10
|
## Core Philosophy: Data-Driven UI
|
|
11
11
|
|
|
12
|
+
|
|
13
|
+
|
|
12
14
|
The Ruby frontend is designed as a **thin, declarative layer** over the Rust backend. It uses an **Immediate Mode** paradigm where the user constructs a tree of pure data objects every frame to represent the desired UI state.
|
|
13
15
|
|
|
14
16
|
### 1. View Tree as Data
|
|
@@ -41,13 +43,15 @@ The application loop typically looks like this:
|
|
|
41
43
|
loop do
|
|
42
44
|
# 1. & 2. Handle events and update state
|
|
43
45
|
event = RatatuiRuby.poll_event
|
|
44
|
-
break if event
|
|
45
|
-
|
|
46
|
-
# 3. Construct View Tree
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
break if event == :esc
|
|
47
|
+
|
|
48
|
+
# 3. Construct View Tree & Draw
|
|
49
|
+
RatatuiRuby.draw do |frame|
|
|
50
|
+
frame.render_widget(
|
|
51
|
+
RatatuiRuby::Paragraph.new(text: "Time: #{Time.now}"),
|
|
52
|
+
frame.area
|
|
53
|
+
)
|
|
54
|
+
end
|
|
51
55
|
end
|
|
52
56
|
```
|
|
53
57
|
|