ratatui_ruby 0.8.0 → 0.9.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 +2 -2
- data/.builds/ruby-3.3.yml +2 -2
- data/.builds/ruby-3.4.yml +2 -2
- data/.builds/ruby-4.0.0.yml +2 -2
- data/.pre-commit-config.yaml +1 -1
- data/AGENTS.md +3 -3
- data/CHANGELOG.md +53 -1
- data/LICENSES/LGPL-3.0-or-later.txt +304 -0
- data/LICENSES/MIT-0.txt +16 -0
- data/README.md +33 -5
- data/Rakefile +1 -1
- data/doc/concepts/application_architecture.md +44 -3
- data/doc/concepts/application_testing.md +43 -1
- data/doc/concepts/async.md +32 -2
- data/doc/concepts/custom_widgets.md +247 -0
- data/doc/concepts/event_handling.md +32 -3
- data/doc/concepts/interactive_design.md +32 -2
- data/doc/contributors/auditing/parity.md +7 -1
- data/doc/contributors/design/ruby_frontend.md +85 -1
- data/doc/contributors/design/rust_backend.md +67 -1
- data/doc/contributors/developing_examples.md +56 -2
- data/doc/contributors/documentation_style.md +20 -3
- data/doc/contributors/future_work.md +169 -0
- data/doc/contributors/index.md +1 -1
- data/doc/contributors/v1.0.0_blockers.md +15 -175
- data/doc/getting_started/quickstart.md +22 -4
- data/doc/getting_started/why.md +1 -1
- data/doc/index.md +2 -1
- data/doc/troubleshooting/debugging.md +32 -2
- data/doc/troubleshooting/terminal_limitations.md +8 -2
- data/doc/troubleshooting/tui_output.md +42 -0
- data/examples/app_all_events/README.md +14 -2
- data/examples/app_all_events/app.rb +1 -1
- data/examples/app_all_events/model/app_model.rb +1 -1
- data/examples/app_all_events/model/event_color_cycle.rb +1 -1
- data/examples/app_all_events/model/event_entry.rb +1 -1
- data/examples/app_all_events/model/msg.rb +1 -1
- data/examples/app_all_events/model/timestamp.rb +1 -1
- data/examples/app_all_events/update.rb +1 -1
- data/examples/app_all_events/view/app_view.rb +1 -1
- data/examples/app_all_events/view/controls_view.rb +1 -1
- data/examples/app_all_events/view/counts_view.rb +1 -1
- data/examples/app_all_events/view/live_view.rb +1 -1
- data/examples/app_all_events/view/log_view.rb +1 -1
- data/examples/app_all_events/view.rb +1 -1
- data/examples/app_color_picker/README.md +20 -2
- data/examples/app_color_picker/app.rb +1 -1
- data/examples/app_color_picker/clipboard.rb +1 -1
- data/examples/app_color_picker/color.rb +1 -1
- data/examples/app_color_picker/controls.rb +1 -1
- data/examples/app_color_picker/copy_dialog.rb +1 -1
- data/examples/app_color_picker/export_pane.rb +1 -1
- data/examples/app_color_picker/harmony.rb +1 -1
- data/examples/app_color_picker/input.rb +1 -1
- data/examples/app_color_picker/main_container.rb +1 -1
- data/examples/app_color_picker/palette.rb +1 -1
- data/examples/app_login_form/README.md +8 -2
- data/examples/app_login_form/app.rb +1 -1
- data/examples/app_stateful_interaction/README.md +2 -2
- data/examples/app_stateful_interaction/app.rb +71 -17
- data/examples/timeout_demo.rb +1 -1
- data/examples/verify_quickstart_dsl/README.md +6 -0
- data/examples/verify_quickstart_dsl/app.rb +3 -3
- data/examples/verify_quickstart_layout/README.md +6 -0
- data/examples/verify_quickstart_layout/app.rb +3 -3
- data/examples/verify_quickstart_lifecycle/README.md +6 -0
- data/examples/verify_quickstart_lifecycle/app.rb +3 -3
- data/examples/verify_readme_usage/README.md +6 -0
- data/examples/verify_readme_usage/app.rb +3 -3
- data/examples/widget_barchart/README.md +6 -0
- data/examples/widget_barchart/app.rb +2 -2
- data/examples/widget_block/README.md +7 -1
- data/examples/widget_block/app.rb +2 -2
- data/examples/widget_box/README.md +6 -0
- data/examples/widget_box/app.rb +9 -6
- data/examples/widget_calendar/README.md +6 -0
- data/examples/widget_calendar/app.rb +2 -2
- data/examples/widget_canvas/README.md +4 -0
- data/examples/widget_canvas/app.rb +2 -2
- data/examples/widget_cell/README.md +6 -0
- data/examples/widget_cell/app.rb +2 -3
- data/examples/widget_center/README.md +4 -0
- data/examples/widget_center/app.rb +2 -2
- data/examples/widget_chart/README.md +6 -0
- data/examples/widget_chart/app.rb +2 -2
- data/examples/widget_gauge/README.md +6 -0
- data/examples/widget_gauge/app.rb +2 -2
- data/examples/widget_layout_split/README.md +6 -0
- data/examples/widget_layout_split/app.rb +3 -3
- data/examples/widget_line_gauge/README.md +6 -0
- data/examples/widget_line_gauge/app.rb +2 -2
- data/examples/widget_list/README.md +6 -0
- data/examples/widget_list/app.rb +2 -2
- data/examples/widget_map/README.md +8 -2
- data/examples/widget_map/app.rb +2 -2
- data/examples/widget_overlay/README.md +7 -1
- data/examples/widget_overlay/app.rb +2 -2
- data/examples/widget_popup/README.md +6 -0
- data/examples/widget_popup/app.rb +2 -2
- data/examples/widget_ratatui_logo/README.md +6 -0
- data/examples/widget_ratatui_logo/app.rb +2 -3
- data/examples/widget_ratatui_mascot/README.md +6 -0
- data/examples/widget_ratatui_mascot/app.rb +2 -2
- data/examples/widget_rect/README.md +12 -0
- data/examples/widget_rect/app.rb +40 -26
- data/examples/widget_render/README.md +6 -0
- data/examples/widget_render/app.rb +2 -2
- data/examples/widget_render/app.rbs +41 -0
- data/examples/widget_rich_text/README.md +6 -0
- data/examples/widget_rich_text/app.rb +2 -2
- data/examples/widget_scroll_text/README.md +6 -0
- data/examples/widget_scroll_text/app.rb +2 -2
- data/examples/widget_scrollbar/README.md +6 -0
- data/examples/widget_scrollbar/app.rb +2 -2
- data/examples/widget_sparkline/README.md +6 -0
- data/examples/widget_sparkline/app.rb +2 -2
- data/examples/widget_style_colors/README.md +6 -0
- data/examples/widget_style_colors/app.rb +2 -2
- data/examples/widget_table/README.md +8 -2
- data/examples/widget_table/app.rb +2 -2
- data/examples/widget_tabs/README.md +6 -0
- data/examples/widget_tabs/app.rb +2 -2
- data/examples/widget_text_width/README.md +6 -0
- data/examples/widget_text_width/app.rb +4 -4
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/ext/ratatui_ruby/extconf.rb +2 -2
- data/ext/ratatui_ruby/src/rendering.rs +1 -1
- data/ext/ratatui_ruby/src/style.rs +0 -8
- data/ext/ratatui_ruby/src/widgets/chart.rs +0 -118
- data/ext/ratatui_ruby/src/widgets/list_state.rs +36 -0
- data/lib/ratatui_ruby/buffer/cell.rb +34 -2
- data/lib/ratatui_ruby/buffer.rb +2 -2
- data/lib/ratatui_ruby/cell.rb +34 -2
- data/lib/ratatui_ruby/event/focus_gained.rb +26 -2
- data/lib/ratatui_ruby/event/focus_lost.rb +26 -2
- data/lib/ratatui_ruby/event/key/character.rb +18 -2
- data/lib/ratatui_ruby/event/key/media.rb +2 -2
- data/lib/ratatui_ruby/event/key/modifier.rb +10 -2
- data/lib/ratatui_ruby/event/key/navigation.rb +2 -2
- data/lib/ratatui_ruby/event/key/system.rb +2 -2
- data/lib/ratatui_ruby/event/key.rb +114 -2
- data/lib/ratatui_ruby/event/mouse.rb +42 -2
- data/lib/ratatui_ruby/event/none.rb +10 -2
- data/lib/ratatui_ruby/event/paste.rb +34 -2
- data/lib/ratatui_ruby/event/resize.rb +34 -2
- data/lib/ratatui_ruby/event.rb +26 -2
- data/lib/ratatui_ruby/frame.rb +74 -2
- data/lib/ratatui_ruby/layout/constraint.rb +58 -2
- data/lib/ratatui_ruby/layout/layout.rb +47 -2
- data/lib/ratatui_ruby/layout/rect.rb +403 -2
- data/lib/ratatui_ruby/layout.rb +2 -2
- data/lib/ratatui_ruby/list_state.rb +113 -2
- data/lib/ratatui_ruby/output_guard.rb +26 -3
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +2 -2
- data/lib/ratatui_ruby/schema/bar_chart.rb +50 -2
- data/lib/ratatui_ruby/schema/block.rb +21 -15
- data/lib/ratatui_ruby/schema/calendar.rb +2 -2
- data/lib/ratatui_ruby/schema/canvas.rb +10 -2
- data/lib/ratatui_ruby/schema/center.rb +10 -2
- data/lib/ratatui_ruby/schema/chart.rb +2 -28
- data/lib/ratatui_ruby/schema/clear.rb +10 -2
- data/lib/ratatui_ruby/schema/constraint.rb +58 -2
- data/lib/ratatui_ruby/schema/cursor.rb +10 -2
- data/lib/ratatui_ruby/schema/draw.rb +10 -2
- data/lib/ratatui_ruby/schema/gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/layout.rb +18 -2
- data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/list.rb +10 -2
- data/lib/ratatui_ruby/schema/list_item.rb +10 -2
- data/lib/ratatui_ruby/schema/overlay.rb +10 -2
- data/lib/ratatui_ruby/schema/paragraph.rb +10 -2
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +2 -2
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +2 -2
- data/lib/ratatui_ruby/schema/rect.rb +58 -2
- data/lib/ratatui_ruby/schema/row.rb +10 -2
- data/lib/ratatui_ruby/schema/scrollbar.rb +2 -2
- data/lib/ratatui_ruby/schema/shape/label.rb +10 -2
- data/lib/ratatui_ruby/schema/sparkline.rb +10 -2
- data/lib/ratatui_ruby/schema/style.rb +18 -2
- data/lib/ratatui_ruby/schema/table.rb +2 -2
- data/lib/ratatui_ruby/schema/tabs.rb +2 -2
- data/lib/ratatui_ruby/schema/text.rb +34 -2
- data/lib/ratatui_ruby/scrollbar_state.rb +10 -2
- data/lib/ratatui_ruby/style/style.rb +18 -2
- data/lib/ratatui_ruby/style.rb +2 -2
- data/lib/ratatui_ruby/table_state.rb +10 -2
- data/lib/ratatui_ruby/terminal_lifecycle.rb +18 -3
- data/lib/ratatui_ruby/test_helper/event_injection.rb +34 -2
- data/lib/ratatui_ruby/test_helper/snapshot.rb +74 -9
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +98 -2
- data/lib/ratatui_ruby/test_helper/terminal.rb +50 -2
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +18 -2
- data/lib/ratatui_ruby/test_helper.rb +10 -2
- data/lib/ratatui_ruby/tui/buffer_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/canvas_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/core.rb +2 -2
- data/lib/ratatui_ruby/tui/layout_factories.rb +32 -2
- data/lib/ratatui_ruby/tui/state_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/style_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/text_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/widget_factories.rb +2 -2
- data/lib/ratatui_ruby/tui.rb +11 -3
- data/lib/ratatui_ruby/version.rb +3 -3
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -2
- data/lib/ratatui_ruby/widgets/bar_chart.rb +58 -2
- data/lib/ratatui_ruby/widgets/block.rb +37 -15
- data/lib/ratatui_ruby/widgets/calendar.rb +2 -2
- data/lib/ratatui_ruby/widgets/canvas.rb +10 -2
- data/lib/ratatui_ruby/widgets/cell.rb +10 -2
- data/lib/ratatui_ruby/widgets/center.rb +10 -2
- data/lib/ratatui_ruby/widgets/chart.rb +2 -28
- data/lib/ratatui_ruby/widgets/clear.rb +10 -2
- data/lib/ratatui_ruby/widgets/cursor.rb +10 -2
- data/lib/ratatui_ruby/widgets/gauge.rb +16 -2
- data/lib/ratatui_ruby/widgets/line_gauge.rb +16 -2
- data/lib/ratatui_ruby/widgets/list.rb +41 -2
- data/lib/ratatui_ruby/widgets/list_item.rb +10 -2
- data/lib/ratatui_ruby/widgets/overlay.rb +10 -2
- data/lib/ratatui_ruby/widgets/paragraph.rb +10 -2
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -2
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -2
- data/lib/ratatui_ruby/widgets/row.rb +10 -2
- data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -2
- data/lib/ratatui_ruby/widgets/shape/label.rb +10 -2
- data/lib/ratatui_ruby/widgets/sparkline.rb +10 -2
- data/lib/ratatui_ruby/widgets/table.rb +62 -2
- data/lib/ratatui_ruby/widgets/tabs.rb +2 -2
- data/lib/ratatui_ruby/widgets.rb +2 -2
- data/lib/ratatui_ruby.rb +90 -2
- data/sig/examples/app_all_events/view.rbs +7 -1
- data/sig/examples/app_all_events/view_state.rbs +7 -1
- data/sig/examples/app_color_picker/app.rbs +5 -0
- data/sig/examples/app_stateful_interaction/app.rbs +7 -1
- data/sig/examples/verify_quickstart_dsl/app.rbs +7 -1
- data/sig/examples/verify_quickstart_lifecycle/app.rbs +7 -1
- data/sig/examples/verify_readme_usage/app.rbs +7 -1
- data/sig/examples/widget_block_demo/app.rbs +6 -0
- data/sig/examples/widget_box_demo/app.rbs +7 -1
- data/sig/examples/widget_calendar_demo/app.rbs +7 -1
- data/sig/examples/widget_cell_demo/app.rbs +7 -1
- data/sig/examples/widget_chart_demo/app.rbs +7 -1
- data/sig/examples/widget_gauge_demo/app.rbs +7 -1
- data/sig/examples/widget_layout_split/app.rbs +7 -1
- data/sig/examples/widget_line_gauge_demo/app.rbs +7 -1
- data/sig/examples/widget_list_demo/app.rbs +5 -0
- data/sig/examples/widget_map_demo/app.rbs +7 -1
- data/sig/examples/widget_popup_demo/app.rbs +7 -1
- data/sig/examples/widget_ratatui_logo_demo/app.rbs +7 -1
- data/sig/examples/widget_ratatui_mascot_demo/app.rbs +7 -1
- data/sig/examples/widget_rect/app.rbs +7 -1
- data/sig/examples/widget_render/app.rbs +7 -1
- data/sig/examples/widget_rich_text/app.rbs +7 -1
- data/sig/examples/widget_scroll_text/app.rbs +7 -1
- data/sig/examples/widget_scrollbar_demo/app.rbs +7 -1
- data/sig/examples/widget_sparkline_demo/app.rbs +7 -1
- data/sig/examples/widget_style_colors/app.rbs +7 -1
- data/sig/examples/widget_table_demo/app.rbs +7 -1
- data/sig/examples/widget_text_width/app.rbs +7 -1
- data/sig/ratatui_ruby/event.rbs +7 -1
- data/sig/ratatui_ruby/frame.rbs +15 -3
- data/sig/ratatui_ruby/list_state.rbs +11 -1
- data/sig/ratatui_ruby/ratatui_ruby.rbs +8 -2
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +7 -1
- data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +6 -0
- data/sig/ratatui_ruby/schema/bar_chart.rbs +6 -0
- data/sig/ratatui_ruby/schema/block.rbs +7 -1
- data/sig/ratatui_ruby/schema/calendar.rbs +6 -0
- data/sig/ratatui_ruby/schema/canvas.rbs +6 -0
- data/sig/ratatui_ruby/schema/center.rbs +6 -0
- data/sig/ratatui_ruby/schema/chart.rbs +6 -9
- data/sig/ratatui_ruby/schema/constraint.rbs +6 -0
- data/sig/ratatui_ruby/schema/cursor.rbs +6 -0
- data/sig/ratatui_ruby/schema/draw.rbs +6 -0
- data/sig/ratatui_ruby/schema/gauge.rbs +9 -1
- data/sig/ratatui_ruby/schema/layout.rbs +6 -0
- data/sig/ratatui_ruby/schema/line_gauge.rbs +9 -1
- data/sig/ratatui_ruby/schema/list.rbs +9 -1
- data/sig/ratatui_ruby/schema/list_item.rbs +7 -1
- data/sig/ratatui_ruby/schema/overlay.rbs +6 -0
- data/sig/ratatui_ruby/schema/paragraph.rbs +6 -0
- data/sig/ratatui_ruby/schema/ratatui_logo.rbs +6 -0
- data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +5 -0
- data/sig/ratatui_ruby/schema/rect.rbs +30 -0
- data/sig/ratatui_ruby/schema/row.rbs +7 -1
- data/sig/ratatui_ruby/schema/scrollbar.rbs +6 -0
- data/sig/ratatui_ruby/schema/sparkline.rbs +6 -0
- data/sig/ratatui_ruby/schema/style.rbs +7 -1
- data/sig/ratatui_ruby/schema/table.rbs +11 -1
- data/sig/ratatui_ruby/schema/tabs.rbs +6 -0
- data/sig/ratatui_ruby/schema/text.rbs +7 -1
- data/sig/ratatui_ruby/scrollbar_state.rbs +7 -1
- data/sig/ratatui_ruby/session.rbs +7 -1
- data/sig/ratatui_ruby/table_state.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/terminal.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -1
- data/sig/ratatui_ruby/test_helper.rbs +7 -1
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/core.rbs +7 -1
- data/sig/ratatui_ruby/tui/layout_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/state_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/style_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/text_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/widget_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui.rbs +7 -1
- data/sig/ratatui_ruby/version.rbs +6 -0
- data/tasks/autodoc/examples.rb +1 -1
- data/tasks/autodoc/member.rb +1 -1
- data/tasks/autodoc/name.rb +1 -1
- data/tasks/bump/cargo_lockfile.rb +1 -1
- data/tasks/bump/changelog.rb +1 -1
- data/tasks/bump/header.rb +1 -1
- data/tasks/bump/history.rb +1 -1
- data/tasks/bump/links.rb +1 -1
- data/tasks/bump/manifest.rb +1 -1
- data/tasks/bump/ruby_gem.rb +1 -1
- data/tasks/bump/sem_ver.rb +1 -1
- data/tasks/bump/unreleased_section.rb +1 -1
- data/tasks/license/headers_md.rb +223 -0
- data/tasks/license/headers_rb.rb +210 -0
- data/tasks/license/license_utils.rb +130 -0
- data/tasks/license/snippets_md.rb +315 -0
- data/tasks/license/snippets_rdoc.rb +150 -0
- data/tasks/license.rake +91 -0
- data/tasks/rdoc_config.rb +1 -1
- data/tasks/resources/build.yml.erb +13 -7
- data/tasks/sourcehut.rake +3 -1
- data/tasks/terminal_preview/app_screenshot.rb +1 -1
- data/tasks/terminal_preview/crash_report.rb +1 -1
- data/tasks/terminal_preview/example_app.rb +1 -1
- data/tasks/terminal_preview/launcher_script.rb +1 -1
- data/tasks/terminal_preview/preview_collection.rb +1 -1
- data/tasks/terminal_preview/preview_timing.rb +1 -1
- data/tasks/terminal_preview/safety_confirmation.rb +1 -1
- data/tasks/terminal_preview/saved_screenshot.rb +1 -1
- data/tasks/terminal_preview/system_appearance.rb +1 -1
- data/tasks/terminal_preview/terminal_window.rb +1 -1
- data/tasks/terminal_preview/window_id.rb +1 -1
- data/tasks/website/index_page.rb +1 -1
- data/tasks/website/version.rb +1 -1
- data/tasks/website/version_menu.rb +1 -1
- data/tasks/website/versioned_documentation.rb +1 -1
- data/tasks/website/website.rb +1 -1
- metadata +13 -3
- data/doc/migration/v0_7_0.md +0 -236
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
SPDX-FileCopyrightText:
|
|
3
|
-
|
|
4
|
-
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
5
4
|
-->
|
|
6
5
|
|
|
7
6
|
# Event Handling
|
|
@@ -30,6 +29,11 @@ For simple key events, `RatatuiRuby::Event::Key` objects can be compared directl
|
|
|
30
29
|
|
|
31
30
|
For a complete list of supported keys, modifiers, and event types, please refer to the [API Documentation for RatatuiRuby::Event](file:///Users/kerrick/Developer/ratatui_ruby/lib/ratatui_ruby/event.rb).
|
|
32
31
|
|
|
32
|
+
<!-- SPDX-SnippetBegin -->
|
|
33
|
+
<!--
|
|
34
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
35
|
+
SPDX-License-Identifier: MIT-0
|
|
36
|
+
-->
|
|
33
37
|
```ruby
|
|
34
38
|
event = RatatuiRuby.poll_event
|
|
35
39
|
|
|
@@ -43,6 +47,7 @@ if event == :enter
|
|
|
43
47
|
submit_form
|
|
44
48
|
end
|
|
45
49
|
```
|
|
50
|
+
<!-- SPDX-SnippetEnd -->
|
|
46
51
|
|
|
47
52
|
## 3. Predicate Methods (Intermediate)
|
|
48
53
|
|
|
@@ -54,6 +59,11 @@ Safe to call on *any* event object. They return `true` only for the matching eve
|
|
|
54
59
|
|
|
55
60
|
Available: `key?`, `mouse?`, `resize?`, `paste?`, `focus_gained?`, `focus_lost?`.
|
|
56
61
|
|
|
62
|
+
<!-- SPDX-SnippetBegin -->
|
|
63
|
+
<!--
|
|
64
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
65
|
+
SPDX-License-Identifier: MIT-0
|
|
66
|
+
-->
|
|
57
67
|
```ruby
|
|
58
68
|
event = RatatuiRuby.poll_event
|
|
59
69
|
|
|
@@ -65,6 +75,7 @@ elsif event.resize?
|
|
|
65
75
|
resize_layout(event.width, event.height)
|
|
66
76
|
end
|
|
67
77
|
```
|
|
78
|
+
<!-- SPDX-SnippetEnd -->
|
|
68
79
|
|
|
69
80
|
### Helper Predicates
|
|
70
81
|
|
|
@@ -74,26 +85,43 @@ Specific to certain event classes to simplify checks.
|
|
|
74
85
|
* `ctrl?`, `alt?`, `shift?`: Check if modifier is held.
|
|
75
86
|
* `text?`: Returns `true` if the event is a printable character (length == 1).
|
|
76
87
|
|
|
88
|
+
<!-- SPDX-SnippetBegin -->
|
|
89
|
+
<!--
|
|
90
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
91
|
+
SPDX-License-Identifier: MIT-0
|
|
92
|
+
-->
|
|
77
93
|
```ruby
|
|
78
94
|
if event.key? && event.ctrl? && event.code == "s"
|
|
79
95
|
save_file
|
|
80
96
|
end
|
|
81
97
|
```
|
|
98
|
+
<!-- SPDX-SnippetEnd -->
|
|
82
99
|
|
|
83
100
|
#### `RatatuiRuby::Event::Mouse`
|
|
84
101
|
* `down?`, `up?`, `drag?`: Check mouse action.
|
|
85
102
|
* `scroll_up?`, `scroll_down?`: Check scroll direction.
|
|
86
103
|
|
|
104
|
+
<!-- SPDX-SnippetBegin -->
|
|
105
|
+
<!--
|
|
106
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
107
|
+
SPDX-License-Identifier: MIT-0
|
|
108
|
+
-->
|
|
87
109
|
```ruby
|
|
88
110
|
if event.mouse? && event.scroll_up?
|
|
89
111
|
scroll_view(-1)
|
|
90
112
|
end
|
|
91
113
|
```
|
|
114
|
+
<!-- SPDX-SnippetEnd -->
|
|
92
115
|
|
|
93
116
|
## 4. Pattern Matching (Powerful)
|
|
94
117
|
|
|
95
118
|
For complex applications, Ruby 3.0+ Pattern Matching with the `type:` discriminator is the most idiomatic and concise approach.
|
|
96
119
|
|
|
120
|
+
<!-- SPDX-SnippetBegin -->
|
|
121
|
+
<!--
|
|
122
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
123
|
+
SPDX-License-Identifier: MIT-0
|
|
124
|
+
-->
|
|
97
125
|
```ruby
|
|
98
126
|
loop do
|
|
99
127
|
case RatatuiRuby.poll_event
|
|
@@ -119,6 +147,7 @@ loop do
|
|
|
119
147
|
end
|
|
120
148
|
end
|
|
121
149
|
```
|
|
150
|
+
<!-- SPDX-SnippetEnd -->
|
|
122
151
|
|
|
123
152
|
## Summary of Event Classes
|
|
124
153
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
SPDX-FileCopyrightText:
|
|
3
|
-
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
4
|
-->
|
|
5
5
|
|
|
6
6
|
# Interactive TUI Design Patterns
|
|
@@ -19,6 +19,11 @@ Canonical patterns for building responsive, interactive terminal user interfaces
|
|
|
19
19
|
|
|
20
20
|
Structure your event loop into three clear phases:
|
|
21
21
|
|
|
22
|
+
<!-- SPDX-SnippetBegin -->
|
|
23
|
+
<!--
|
|
24
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
25
|
+
SPDX-License-Identifier: MIT-0
|
|
26
|
+
-->
|
|
22
27
|
```ruby
|
|
23
28
|
def run
|
|
24
29
|
RatatuiRuby.run do |tui|
|
|
@@ -33,11 +38,17 @@ def run
|
|
|
33
38
|
end
|
|
34
39
|
end
|
|
35
40
|
```
|
|
41
|
+
<!-- SPDX-SnippetEnd -->
|
|
36
42
|
|
|
37
43
|
**Phase 1: Layout Calculation**
|
|
38
44
|
|
|
39
45
|
Call this inside your `draw` block. It uses the current terminal area provided by the frame:
|
|
40
46
|
|
|
47
|
+
<!-- SPDX-SnippetBegin -->
|
|
48
|
+
<!--
|
|
49
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
50
|
+
SPDX-License-Identifier: MIT-0
|
|
51
|
+
-->
|
|
41
52
|
```ruby
|
|
42
53
|
def calculate_layout(area)
|
|
43
54
|
# Main area vs sidebar (70% / 30%)
|
|
@@ -61,22 +72,34 @@ def calculate_layout(area)
|
|
|
61
72
|
)
|
|
62
73
|
end
|
|
63
74
|
```
|
|
75
|
+
<!-- SPDX-SnippetEnd -->
|
|
64
76
|
|
|
65
77
|
**Phase 2: Rendering**
|
|
66
78
|
|
|
67
79
|
Reuse the cached rects. Build and draw:
|
|
68
80
|
|
|
81
|
+
<!-- SPDX-SnippetBegin -->
|
|
82
|
+
<!--
|
|
83
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
84
|
+
SPDX-License-Identifier: MIT-0
|
|
85
|
+
-->
|
|
69
86
|
```ruby
|
|
70
87
|
def render(frame)
|
|
71
88
|
frame.render_widget(build_widget(@left_rect), @left_rect)
|
|
72
89
|
frame.render_widget(build_widget(@right_rect), @right_rect)
|
|
73
90
|
end
|
|
74
91
|
```
|
|
92
|
+
<!-- SPDX-SnippetEnd -->
|
|
75
93
|
|
|
76
94
|
**Phase 3: Event Handling**
|
|
77
95
|
|
|
78
96
|
Reuse the cached rects. Test clicks:
|
|
79
97
|
|
|
98
|
+
<!-- SPDX-SnippetBegin -->
|
|
99
|
+
<!--
|
|
100
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long
|
|
101
|
+
SPDX-License-Identifier: MIT-0
|
|
102
|
+
-->
|
|
80
103
|
```ruby
|
|
81
104
|
def handle_input
|
|
82
105
|
event = RatatuiRuby.poll_event
|
|
@@ -93,6 +116,7 @@ def handle_input
|
|
|
93
116
|
end
|
|
94
117
|
end
|
|
95
118
|
```
|
|
119
|
+
<!-- SPDX-SnippetEnd -->
|
|
96
120
|
|
|
97
121
|
### Why This Matters
|
|
98
122
|
|
|
@@ -105,6 +129,11 @@ end
|
|
|
105
129
|
|
|
106
130
|
`Layout.split` computes layout geometry without rendering. It returns an array of `Rect` objects. While you can call `RatatuiRuby::Layout.split` directly, we recommend using the `TUI` helper (`tui.layout_split`) for cleaner application code.
|
|
107
131
|
|
|
132
|
+
<!-- SPDX-SnippetBegin -->
|
|
133
|
+
<!--
|
|
134
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
135
|
+
SPDX-License-Identifier: MIT-0
|
|
136
|
+
-->
|
|
108
137
|
```ruby
|
|
109
138
|
# Preferred (TUI API)
|
|
110
139
|
left, right = tui.layout_split(area, constraints: [...])
|
|
@@ -112,5 +141,6 @@ left, right = tui.layout_split(area, constraints: [...])
|
|
|
112
141
|
# Manual (Core API)
|
|
113
142
|
left, right = RatatuiRuby::Layout.split(area, constraints: [...])
|
|
114
143
|
```
|
|
144
|
+
<!-- SPDX-SnippetEnd -->
|
|
115
145
|
|
|
116
146
|
Use it to establish the single source of truth inside your `draw` block. Store the results in instance variables and reuse them in both `render` and `handle_input`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
SPDX-FileCopyrightText:
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
3
|
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
4
|
-->
|
|
5
5
|
|
|
@@ -32,6 +32,11 @@ Every audit begins with a question:
|
|
|
32
32
|
|
|
33
33
|
Every RatatuiRuby feature has three layers. Gaps can occur at any layer:
|
|
34
34
|
|
|
35
|
+
<!-- SPDX-SnippetBegin -->
|
|
36
|
+
<!--
|
|
37
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
38
|
+
SPDX-License-Identifier: MIT-0
|
|
39
|
+
-->
|
|
35
40
|
```
|
|
36
41
|
┌─────────────────────────────┐
|
|
37
42
|
│ Ruby API (lib/**/*.rb) │ ← What users see
|
|
@@ -41,6 +46,7 @@ Every RatatuiRuby feature has three layers. Gaps can occur at any layer:
|
|
|
41
46
|
│ Upstream Ratatui │ ← Source of truth
|
|
42
47
|
└─────────────────────────────┘
|
|
43
48
|
```
|
|
49
|
+
<!-- SPDX-SnippetEnd -->
|
|
44
50
|
|
|
45
51
|
### Layer 1: Ruby API Gaps
|
|
46
52
|
Ruby doesn't expose a parameter that upstream supports.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
SPDX-FileCopyrightText:
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
3
|
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
4
|
-->
|
|
5
5
|
|
|
@@ -39,11 +39,17 @@ Located in `lib/ratatui_ruby/widgets/`, `lib/ratatui_ruby/layout/`, etc.
|
|
|
39
39
|
|
|
40
40
|
These are the actual `Data.define` classes that the Rust backend expects. They have deep, explicit namespaces that match Ratatui:
|
|
41
41
|
|
|
42
|
+
<!-- SPDX-SnippetBegin -->
|
|
43
|
+
<!--
|
|
44
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
45
|
+
SPDX-License-Identifier: MIT-0
|
|
46
|
+
-->
|
|
42
47
|
```ruby
|
|
43
48
|
RatatuiRuby::Widgets::Paragraph.new(text: "Hello")
|
|
44
49
|
RatatuiRuby::Layout::Constraint.length(20)
|
|
45
50
|
RatatuiRuby::Style::Style.new(fg: :red)
|
|
46
51
|
```
|
|
52
|
+
<!-- SPDX-SnippetEnd -->
|
|
47
53
|
|
|
48
54
|
**Layer 2: TUI Facade (The DSL)**
|
|
49
55
|
|
|
@@ -51,6 +57,11 @@ Located in `lib/ratatui_ruby/tui.rb` and `lib/ratatui_ruby/tui/*.rb` mixins.
|
|
|
51
57
|
|
|
52
58
|
The `TUI` class provides shorthand factory methods that hide namespace verbosity:
|
|
53
59
|
|
|
60
|
+
<!-- SPDX-SnippetBegin -->
|
|
61
|
+
<!--
|
|
62
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
63
|
+
SPDX-License-Identifier: MIT-0
|
|
64
|
+
-->
|
|
54
65
|
```ruby
|
|
55
66
|
RatatuiRuby.run do |tui|
|
|
56
67
|
tui.paragraph(text: "Hello")
|
|
@@ -58,6 +69,7 @@ RatatuiRuby.run do |tui|
|
|
|
58
69
|
tui.style(fg: :red)
|
|
59
70
|
end
|
|
60
71
|
```
|
|
72
|
+
<!-- SPDX-SnippetEnd -->
|
|
61
73
|
|
|
62
74
|
**Why This Matters:**
|
|
63
75
|
|
|
@@ -69,6 +81,11 @@ The TUI facade uses explicit factory method definitions, not runtime metaprogram
|
|
|
69
81
|
|
|
70
82
|
**What We Do:**
|
|
71
83
|
|
|
84
|
+
<!-- SPDX-SnippetBegin -->
|
|
85
|
+
<!--
|
|
86
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
87
|
+
SPDX-License-Identifier: MIT-0
|
|
88
|
+
-->
|
|
72
89
|
```ruby
|
|
73
90
|
# lib/ratatui_ruby/tui/widget_factories.rb
|
|
74
91
|
module RatatuiRuby
|
|
@@ -85,15 +102,22 @@ module RatatuiRuby
|
|
|
85
102
|
end
|
|
86
103
|
end
|
|
87
104
|
```
|
|
105
|
+
<!-- SPDX-SnippetEnd -->
|
|
88
106
|
|
|
89
107
|
**What We Don't Do:**
|
|
90
108
|
|
|
109
|
+
<!-- SPDX-SnippetBegin -->
|
|
110
|
+
<!--
|
|
111
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
112
|
+
SPDX-License-Identifier: MIT-0
|
|
113
|
+
-->
|
|
91
114
|
```ruby
|
|
92
115
|
# NO: Dynamic method generation
|
|
93
116
|
RatatuiRuby.constants.each do |const|
|
|
94
117
|
define_method(const.underscore) { |**kw| RatatuiRuby.const_get(const).new(**kw) }
|
|
95
118
|
end
|
|
96
119
|
```
|
|
120
|
+
<!-- SPDX-SnippetEnd -->
|
|
97
121
|
|
|
98
122
|
**Benefits of Explicit Definitions:**
|
|
99
123
|
|
|
@@ -109,6 +133,11 @@ All UI components are pure, immutable `Data.define` value objects. They describe
|
|
|
109
133
|
|
|
110
134
|
**Widgets Are Inputs:**
|
|
111
135
|
|
|
136
|
+
<!-- SPDX-SnippetBegin -->
|
|
137
|
+
<!--
|
|
138
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
139
|
+
SPDX-License-Identifier: MIT-0
|
|
140
|
+
-->
|
|
112
141
|
```ruby
|
|
113
142
|
# This is just data. It has no behavior, no side effects.
|
|
114
143
|
paragraph = RatatuiRuby::Widgets::Paragraph.new(
|
|
@@ -119,11 +148,17 @@ paragraph = RatatuiRuby::Widgets::Paragraph.new(
|
|
|
119
148
|
# Pass to renderer as input
|
|
120
149
|
frame.render_widget(paragraph, area)
|
|
121
150
|
```
|
|
151
|
+
<!-- SPDX-SnippetEnd -->
|
|
122
152
|
|
|
123
153
|
**Immediate Mode Loop:**
|
|
124
154
|
|
|
125
155
|
Every frame, the application constructs a fresh view tree and passes it to `draw`. No widget state persists between frames. This is Ratatui's core paradigm.
|
|
126
156
|
|
|
157
|
+
<!-- SPDX-SnippetBegin -->
|
|
158
|
+
<!--
|
|
159
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
160
|
+
SPDX-License-Identifier: MIT-0
|
|
161
|
+
-->
|
|
127
162
|
```ruby
|
|
128
163
|
loop do
|
|
129
164
|
tui.draw do |frame|
|
|
@@ -133,6 +168,7 @@ loop do
|
|
|
133
168
|
break if tui.poll_event.key? && tui.poll_event.code == "q"
|
|
134
169
|
end
|
|
135
170
|
```
|
|
171
|
+
<!-- SPDX-SnippetEnd -->
|
|
136
172
|
|
|
137
173
|
### 5. Separation of Configuration and Status
|
|
138
174
|
|
|
@@ -142,14 +178,25 @@ Widgets (Configuration) and State (Status) are strictly separated.
|
|
|
142
178
|
|
|
143
179
|
Widgets define *what* to render. They are created, rendered, and discarded.
|
|
144
180
|
|
|
181
|
+
<!-- SPDX-SnippetBegin -->
|
|
182
|
+
<!--
|
|
183
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
184
|
+
SPDX-License-Identifier: MIT-0
|
|
185
|
+
-->
|
|
145
186
|
```ruby
|
|
146
187
|
list = tui.list(items: ["A", "B", "C", "D", "E"])
|
|
147
188
|
```
|
|
189
|
+
<!-- SPDX-SnippetEnd -->
|
|
148
190
|
|
|
149
191
|
**Status (Output):**
|
|
150
192
|
|
|
151
193
|
State objects track *runtime metrics* computed by the Rust backend: scroll offsets, selection positions, etc. They persist across frames.
|
|
152
194
|
|
|
195
|
+
<!-- SPDX-SnippetBegin -->
|
|
196
|
+
<!--
|
|
197
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
198
|
+
SPDX-License-Identifier: MIT-0
|
|
199
|
+
-->
|
|
153
200
|
```ruby
|
|
154
201
|
# Created once
|
|
155
202
|
@list_state = RatatuiRuby::ListState.new
|
|
@@ -160,6 +207,7 @@ frame.render_stateful_widget(list, area, @list_state)
|
|
|
160
207
|
# Read back computed values
|
|
161
208
|
puts "Scroll offset: #{@list_state.offset}"
|
|
162
209
|
```
|
|
210
|
+
<!-- SPDX-SnippetEnd -->
|
|
163
211
|
|
|
164
212
|
**Precedence Rule:**
|
|
165
213
|
|
|
@@ -188,6 +236,11 @@ This separation ensures rendering performance remains in Rust while Ruby handles
|
|
|
188
236
|
|
|
189
237
|
## Directory Structure
|
|
190
238
|
|
|
239
|
+
<!-- SPDX-SnippetBegin -->
|
|
240
|
+
<!--
|
|
241
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
242
|
+
SPDX-License-Identifier: MIT-0
|
|
243
|
+
-->
|
|
191
244
|
```
|
|
192
245
|
lib/ratatui_ruby/
|
|
193
246
|
├── tui.rb # TUI class, includes all mixins
|
|
@@ -221,6 +274,7 @@ lib/ratatui_ruby/
|
|
|
221
274
|
│ └── cell.rb # For get_cell_at inspection
|
|
222
275
|
└── schema/ # Legacy location (being migrated)
|
|
223
276
|
```
|
|
277
|
+
<!-- SPDX-SnippetEnd -->
|
|
224
278
|
|
|
225
279
|
---
|
|
226
280
|
|
|
@@ -230,6 +284,11 @@ lib/ratatui_ruby/
|
|
|
230
284
|
|
|
231
285
|
Define the Data class in the appropriate namespace directory:
|
|
232
286
|
|
|
287
|
+
<!-- SPDX-SnippetBegin -->
|
|
288
|
+
<!--
|
|
289
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
290
|
+
SPDX-License-Identifier: MIT-0
|
|
291
|
+
-->
|
|
233
292
|
```ruby
|
|
234
293
|
# lib/ratatui_ruby/widgets/my_widget.rb
|
|
235
294
|
module RatatuiRuby
|
|
@@ -247,9 +306,15 @@ module RatatuiRuby
|
|
|
247
306
|
end
|
|
248
307
|
end
|
|
249
308
|
```
|
|
309
|
+
<!-- SPDX-SnippetEnd -->
|
|
250
310
|
|
|
251
311
|
### Step 2: Add the RBS Type
|
|
252
312
|
|
|
313
|
+
<!-- SPDX-SnippetBegin -->
|
|
314
|
+
<!--
|
|
315
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
316
|
+
SPDX-License-Identifier: MIT-0
|
|
317
|
+
-->
|
|
253
318
|
```rbs
|
|
254
319
|
# sig/ratatui_ruby/widgets/my_widget.rbs
|
|
255
320
|
module RatatuiRuby
|
|
@@ -264,15 +329,22 @@ module RatatuiRuby
|
|
|
264
329
|
end
|
|
265
330
|
end
|
|
266
331
|
```
|
|
332
|
+
<!-- SPDX-SnippetEnd -->
|
|
267
333
|
|
|
268
334
|
### Step 3: Add the TUI Factory Method
|
|
269
335
|
|
|
336
|
+
<!-- SPDX-SnippetBegin -->
|
|
337
|
+
<!--
|
|
338
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
339
|
+
SPDX-License-Identifier: MIT-0
|
|
340
|
+
-->
|
|
270
341
|
```ruby
|
|
271
342
|
# lib/ratatui_ruby/tui/widget_factories.rb
|
|
272
343
|
def my_widget(**kwargs)
|
|
273
344
|
Widgets::MyWidget.new(**kwargs)
|
|
274
345
|
end
|
|
275
346
|
```
|
|
347
|
+
<!-- SPDX-SnippetEnd -->
|
|
276
348
|
|
|
277
349
|
### Step 4: Implement Rust Rendering
|
|
278
350
|
|
|
@@ -282,9 +354,15 @@ See `rust_backend.md` for the Rust implementation steps.
|
|
|
282
354
|
|
|
283
355
|
Add to `lib/ratatui_ruby.rb`:
|
|
284
356
|
|
|
357
|
+
<!-- SPDX-SnippetBegin -->
|
|
358
|
+
<!--
|
|
359
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
360
|
+
SPDX-License-Identifier: MIT-0
|
|
361
|
+
-->
|
|
285
362
|
```ruby
|
|
286
363
|
require_relative "ratatui_ruby/widgets/my_widget"
|
|
287
364
|
```
|
|
365
|
+
<!-- SPDX-SnippetEnd -->
|
|
288
366
|
|
|
289
367
|
---
|
|
290
368
|
|
|
@@ -324,6 +402,11 @@ These have side effects and are intentionally not Ractor-safe:
|
|
|
324
402
|
- `TUI` — Has terminal I/O methods
|
|
325
403
|
- `Frame` — Valid only during the `draw` block; invalid after
|
|
326
404
|
|
|
405
|
+
<!-- SPDX-SnippetBegin -->
|
|
406
|
+
<!--
|
|
407
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
408
|
+
SPDX-License-Identifier: MIT-0
|
|
409
|
+
-->
|
|
327
410
|
```ruby
|
|
328
411
|
# OK: Cache TUI during run loop
|
|
329
412
|
RatatuiRuby.run do |tui|
|
|
@@ -334,3 +417,4 @@ end
|
|
|
334
417
|
# NOT OK: Include in immutable Model
|
|
335
418
|
Model = Data.define(:tui, :count) # Don't do this
|
|
336
419
|
```
|
|
420
|
+
<!-- SPDX-SnippetEnd -->
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
SPDX-FileCopyrightText:
|
|
2
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
3
3
|
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
4
|
-->
|
|
5
5
|
|
|
@@ -41,6 +41,11 @@ The Rust backend is a pure rendering engine. It receives Ruby objects representi
|
|
|
41
41
|
|
|
42
42
|
The backend implements one generic rendering function that accepts any Ruby `Value` and dispatches based on class name. There is no compile-time knowledge of Ruby types—everything is runtime reflection.
|
|
43
43
|
|
|
44
|
+
<!-- SPDX-SnippetBegin -->
|
|
45
|
+
<!--
|
|
46
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
47
|
+
SPDX-License-Identifier: MIT-0
|
|
48
|
+
-->
|
|
44
49
|
```rust
|
|
45
50
|
// rendering.rs
|
|
46
51
|
pub fn render_widget(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
@@ -58,20 +63,32 @@ pub fn render_widget(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
58
63
|
}
|
|
59
64
|
}
|
|
60
65
|
```
|
|
66
|
+
<!-- SPDX-SnippetEnd -->
|
|
61
67
|
|
|
62
68
|
### 3. No Custom Rust Structs for UI
|
|
63
69
|
|
|
64
70
|
Do not define Rust structs that mirror Ruby UI components. This would create synchronization problems when Ruby classes change.
|
|
65
71
|
|
|
66
72
|
**What We Do:**
|
|
73
|
+
<!-- SPDX-SnippetBegin -->
|
|
74
|
+
<!--
|
|
75
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
76
|
+
SPDX-License-Identifier: MIT-0
|
|
77
|
+
-->
|
|
67
78
|
```rust
|
|
68
79
|
// Extract directly from Ruby object
|
|
69
80
|
let text: String = node.funcall("text", ())?;
|
|
70
81
|
let style_val: Value = node.funcall("style", ())?;
|
|
71
82
|
let style = parse_style(style_val)?;
|
|
72
83
|
```
|
|
84
|
+
<!-- SPDX-SnippetEnd -->
|
|
73
85
|
|
|
74
86
|
**What We Don't Do:**
|
|
87
|
+
<!-- SPDX-SnippetBegin -->
|
|
88
|
+
<!--
|
|
89
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
90
|
+
SPDX-License-Identifier: MIT-0
|
|
91
|
+
-->
|
|
75
92
|
```rust
|
|
76
93
|
// NO: Rust struct mirroring Ruby
|
|
77
94
|
struct Paragraph {
|
|
@@ -80,6 +97,7 @@ struct Paragraph {
|
|
|
80
97
|
block: Option<Block>,
|
|
81
98
|
}
|
|
82
99
|
```
|
|
100
|
+
<!-- SPDX-SnippetEnd -->
|
|
83
101
|
|
|
84
102
|
### 4. Immediate Mode Rendering
|
|
85
103
|
|
|
@@ -91,6 +109,11 @@ This mirrors Ratatui's own immediate mode paradigm. The Rust backend is stateles
|
|
|
91
109
|
|
|
92
110
|
Ruby's GC can move or collect objects at any time. All data extracted from Ruby must be owned (copied) before use, never borrowed.
|
|
93
111
|
|
|
112
|
+
<!-- SPDX-SnippetBegin -->
|
|
113
|
+
<!--
|
|
114
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
115
|
+
SPDX-License-Identifier: MIT-0
|
|
116
|
+
-->
|
|
94
117
|
```rust
|
|
95
118
|
// SAFE: Convert to owned String immediately
|
|
96
119
|
let text: String = node.funcall::<_, String>("text", ())?.into_owned();
|
|
@@ -100,11 +123,17 @@ let text_ref: &str = node.funcall("text", ())?; // DON'T
|
|
|
100
123
|
do_something_that_might_gc();
|
|
101
124
|
use(text_ref); // CRASH: text_ref may be invalid
|
|
102
125
|
```
|
|
126
|
+
<!-- SPDX-SnippetEnd -->
|
|
103
127
|
|
|
104
128
|
---
|
|
105
129
|
|
|
106
130
|
## Directory Structure
|
|
107
131
|
|
|
132
|
+
<!-- SPDX-SnippetBegin -->
|
|
133
|
+
<!--
|
|
134
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
135
|
+
SPDX-License-Identifier: MIT-0
|
|
136
|
+
-->
|
|
108
137
|
```
|
|
109
138
|
ext/ratatui_ruby/src/
|
|
110
139
|
├── lib.rs # Entry point, Ruby module registration
|
|
@@ -123,6 +152,7 @@ ext/ratatui_ruby/src/
|
|
|
123
152
|
├── canvas.rs
|
|
124
153
|
└── ...
|
|
125
154
|
```
|
|
155
|
+
<!-- SPDX-SnippetEnd -->
|
|
126
156
|
|
|
127
157
|
---
|
|
128
158
|
|
|
@@ -159,6 +189,11 @@ Pure functions for extracting style information from Ruby values. Handles `parse
|
|
|
159
189
|
|
|
160
190
|
The routing layer that maps Ruby class names to widget renderers:
|
|
161
191
|
|
|
192
|
+
<!-- SPDX-SnippetBegin -->
|
|
193
|
+
<!--
|
|
194
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
195
|
+
SPDX-License-Identifier: MIT-0
|
|
196
|
+
-->
|
|
162
197
|
```rust
|
|
163
198
|
pub fn render_widget(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
164
199
|
let class_name: String = node.class().name()?.into_owned();
|
|
@@ -191,6 +226,7 @@ pub fn render_widget(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
191
226
|
}
|
|
192
227
|
}
|
|
193
228
|
```
|
|
229
|
+
<!-- SPDX-SnippetEnd -->
|
|
194
230
|
|
|
195
231
|
**Namespace Pattern:** All built-in widgets use the `RatatuiRuby::Widgets::*` namespace. The dispatcher matches on full class names, not prefixes.
|
|
196
232
|
|
|
@@ -198,6 +234,11 @@ pub fn render_widget(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
198
234
|
|
|
199
235
|
Each widget has its own module with a standard interface:
|
|
200
236
|
|
|
237
|
+
<!-- SPDX-SnippetBegin -->
|
|
238
|
+
<!--
|
|
239
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
240
|
+
SPDX-License-Identifier: MIT-0
|
|
241
|
+
-->
|
|
201
242
|
```rust
|
|
202
243
|
// widgets/paragraph.rs
|
|
203
244
|
pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
@@ -222,6 +263,7 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
222
263
|
Ok(())
|
|
223
264
|
}
|
|
224
265
|
```
|
|
266
|
+
<!-- SPDX-SnippetEnd -->
|
|
225
267
|
|
|
226
268
|
---
|
|
227
269
|
|
|
@@ -229,6 +271,11 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
229
271
|
|
|
230
272
|
### Step 1: Create the Widget Module
|
|
231
273
|
|
|
274
|
+
<!-- SPDX-SnippetBegin -->
|
|
275
|
+
<!--
|
|
276
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
277
|
+
SPDX-License-Identifier: MIT-0
|
|
278
|
+
-->
|
|
232
279
|
```rust
|
|
233
280
|
// src/widgets/my_widget.rs
|
|
234
281
|
|
|
@@ -249,18 +296,31 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
249
296
|
Ok(())
|
|
250
297
|
}
|
|
251
298
|
```
|
|
299
|
+
<!-- SPDX-SnippetEnd -->
|
|
252
300
|
|
|
253
301
|
### Step 2: Register in `widgets/mod.rs`
|
|
254
302
|
|
|
303
|
+
<!-- SPDX-SnippetBegin -->
|
|
304
|
+
<!--
|
|
305
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
306
|
+
SPDX-License-Identifier: MIT-0
|
|
307
|
+
-->
|
|
255
308
|
```rust
|
|
256
309
|
pub mod my_widget;
|
|
257
310
|
```
|
|
311
|
+
<!-- SPDX-SnippetEnd -->
|
|
258
312
|
|
|
259
313
|
### Step 3: Add Dispatch Arm in `rendering.rs`
|
|
260
314
|
|
|
315
|
+
<!-- SPDX-SnippetBegin -->
|
|
316
|
+
<!--
|
|
317
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
318
|
+
SPDX-License-Identifier: MIT-0
|
|
319
|
+
-->
|
|
261
320
|
```rust
|
|
262
321
|
"RatatuiRuby::Widgets::MyWidget" => widgets::my_widget::render(frame, area, node),
|
|
263
322
|
```
|
|
323
|
+
<!-- SPDX-SnippetEnd -->
|
|
264
324
|
|
|
265
325
|
### Step 4: Test
|
|
266
326
|
|
|
@@ -274,6 +334,11 @@ Some widgets (List, Table, Scrollbar) support stateful rendering where a mutable
|
|
|
274
334
|
|
|
275
335
|
### The Pattern
|
|
276
336
|
|
|
337
|
+
<!-- SPDX-SnippetBegin -->
|
|
338
|
+
<!--
|
|
339
|
+
SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
340
|
+
SPDX-License-Identifier: MIT-0
|
|
341
|
+
-->
|
|
277
342
|
```rust
|
|
278
343
|
pub fn render_stateful_widget(
|
|
279
344
|
frame: &mut Frame,
|
|
@@ -299,6 +364,7 @@ pub fn render_stateful_widget(
|
|
|
299
364
|
Ok(())
|
|
300
365
|
}
|
|
301
366
|
```
|
|
367
|
+
<!-- SPDX-SnippetEnd -->
|
|
302
368
|
|
|
303
369
|
**State Precedence:** When using stateful rendering, the State object's values take precedence over Widget properties. This is documented in Ruby.
|
|
304
370
|
|