ratatui_ruby 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.builds/ruby-3.2.yml +1 -1
- data/.builds/ruby-3.3.yml +1 -1
- data/.builds/ruby-3.4.yml +1 -1
- data/.builds/ruby-4.0.0.yml +1 -1
- data/AGENTS.md +87 -171
- data/CHANGELOG.md +38 -1
- data/README.md +8 -3
- data/REUSE.toml +20 -0
- data/doc/application_architecture.md +105 -45
- data/doc/application_testing.md +5 -3
- data/doc/contributors/design/ruby_frontend.md +9 -5
- data/doc/contributors/developing_examples.md +76 -18
- data/doc/contributors/documentation_style.md +7 -0
- data/doc/contributors/index.md +2 -0
- data/doc/event_handling.md +10 -4
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_barchart_demo.png +0 -0
- data/doc/images/widget_block_padding.png +0 -0
- data/doc/images/widget_block_titles.png +0 -0
- data/doc/images/widget_box_demo.png +0 -0
- data/doc/images/widget_calendar_demo.png +0 -0
- data/doc/images/widget_cell_demo.png +0 -0
- data/doc/images/widget_chart_demo.png +0 -0
- data/doc/images/widget_gauge_demo.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge_demo.png +0 -0
- data/doc/images/widget_list_demo.png +0 -0
- data/doc/images/widget_ratatui_logo_demo.png +0 -0
- data/doc/images/widget_ratatui_mascot_demo.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_scrollbar_demo.png +0 -0
- data/doc/images/widget_sparkline_demo.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table_flex.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/interactive_design.md +25 -30
- data/doc/quickstart.md +147 -120
- data/examples/app_all_events/README.md +81 -0
- data/examples/app_all_events/app.rb +93 -0
- data/examples/app_all_events/model/event_color_cycle.rb +41 -0
- data/examples/app_all_events/model/event_entry.rb +75 -0
- data/examples/app_all_events/model/events.rb +180 -0
- data/examples/app_all_events/model/highlight.rb +57 -0
- data/examples/app_all_events/model/timestamp.rb +54 -0
- data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
- data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
- data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
- data/examples/app_all_events/view/app_view.rb +78 -0
- data/examples/app_all_events/view/controls_view.rb +50 -0
- data/examples/app_all_events/view/counts_view.rb +55 -0
- data/examples/app_all_events/view/live_view.rb +69 -0
- data/examples/app_all_events/view/log_view.rb +60 -0
- data/examples/app_all_events/view.rb +7 -0
- data/examples/app_all_events/view_state.rb +42 -0
- data/examples/app_color_picker/README.md +94 -0
- data/examples/app_color_picker/app.rb +112 -0
- data/examples/app_color_picker/clipboard.rb +84 -0
- data/examples/app_color_picker/color.rb +191 -0
- data/examples/app_color_picker/copy_dialog.rb +170 -0
- data/examples/app_color_picker/harmony.rb +56 -0
- data/examples/app_color_picker/input.rb +142 -0
- data/examples/app_color_picker/palette.rb +80 -0
- data/examples/app_color_picker/scene.rb +201 -0
- data/examples/{login_form → app_login_form}/app.rb +39 -42
- data/examples/{map_demo → app_map_demo}/app.rb +24 -21
- data/examples/{table_select → app_table_select}/app.rb +68 -65
- data/examples/{quickstart_dsl → verify_quickstart_dsl}/app.rb +15 -6
- data/examples/verify_quickstart_layout/app.rb +69 -0
- data/examples/{quickstart_lifecycle → verify_quickstart_lifecycle}/app.rb +19 -10
- data/examples/verify_readme_usage/app.rb +34 -0
- data/examples/widget_barchart_demo/app.rb +238 -0
- data/examples/{block_padding → widget_block_padding}/app.rb +17 -13
- data/examples/{block_titles → widget_block_titles}/app.rb +25 -17
- data/examples/{box_demo → widget_box_demo}/app.rb +99 -65
- data/examples/widget_calendar_demo/app.rb +109 -0
- data/examples/widget_cell_demo/app.rb +104 -0
- data/examples/widget_chart_demo/app.rb +213 -0
- data/examples/widget_gauge_demo/app.rb +212 -0
- data/examples/widget_layout_split/app.rb +246 -0
- data/examples/widget_line_gauge_demo/app.rb +217 -0
- data/examples/widget_list_demo/app.rb +382 -0
- data/examples/widget_list_styles/app.rb +141 -0
- data/examples/widget_popup_demo/app.rb +104 -0
- data/examples/widget_ratatui_logo_demo/app.rb +103 -0
- data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
- data/examples/widget_rect/app.rb +205 -0
- data/examples/widget_render/app.rb +184 -0
- data/examples/widget_rich_text/app.rb +137 -0
- data/examples/widget_scroll_text/app.rb +108 -0
- data/examples/widget_scrollbar_demo/app.rb +153 -0
- data/examples/widget_sparkline_demo/app.rb +274 -0
- data/examples/widget_style_colors/app.rb +19 -21
- data/examples/widget_table_flex/app.rb +95 -0
- data/examples/widget_tabs_demo/app.rb +167 -0
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/ext/ratatui_ruby/src/events.rs +121 -36
- data/ext/ratatui_ruby/src/frame.rs +115 -0
- data/ext/ratatui_ruby/src/lib.rs +79 -26
- data/ext/ratatui_ruby/src/rendering.rs +8 -4
- data/ext/ratatui_ruby/src/style.rs +138 -57
- data/ext/ratatui_ruby/src/terminal.rs +5 -9
- data/ext/ratatui_ruby/src/text.rs +13 -6
- data/ext/ratatui_ruby/src/widgets/barchart.rs +56 -54
- data/ext/ratatui_ruby/src/widgets/block.rs +7 -6
- data/ext/ratatui_ruby/src/widgets/canvas.rs +21 -3
- data/ext/ratatui_ruby/src/widgets/chart.rs +20 -10
- data/ext/ratatui_ruby/src/widgets/layout.rs +9 -4
- data/ext/ratatui_ruby/src/widgets/list.rs +32 -9
- data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
- data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +19 -8
- data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +17 -10
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +4 -2
- data/ext/ratatui_ruby/src/widgets/sparkline.rs +14 -11
- data/ext/ratatui_ruby/src/widgets/table.rs +8 -4
- data/ext/ratatui_ruby/src/widgets/tabs.rs +11 -11
- data/lib/ratatui_ruby/cell.rb +3 -3
- data/lib/ratatui_ruby/event/key.rb +1 -1
- data/lib/ratatui_ruby/event/none.rb +43 -0
- data/lib/ratatui_ruby/event.rb +56 -4
- data/lib/ratatui_ruby/frame.rb +87 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +11 -11
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +1 -5
- data/lib/ratatui_ruby/schema/bar_chart.rb +217 -217
- data/lib/ratatui_ruby/schema/block.rb +163 -168
- data/lib/ratatui_ruby/schema/calendar.rb +66 -67
- data/lib/ratatui_ruby/schema/canvas.rb +63 -63
- data/lib/ratatui_ruby/schema/center.rb +46 -46
- data/lib/ratatui_ruby/schema/chart.rb +135 -143
- data/lib/ratatui_ruby/schema/clear.rb +42 -42
- data/lib/ratatui_ruby/schema/constraint.rb +76 -76
- data/lib/ratatui_ruby/schema/cursor.rb +25 -25
- data/lib/ratatui_ruby/schema/gauge.rb +53 -53
- data/lib/ratatui_ruby/schema/layout.rb +87 -87
- data/lib/ratatui_ruby/schema/line_gauge.rb +62 -62
- data/lib/ratatui_ruby/schema/list.rb +86 -84
- data/lib/ratatui_ruby/schema/overlay.rb +31 -31
- data/lib/ratatui_ruby/schema/paragraph.rb +80 -80
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +10 -6
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +10 -5
- data/lib/ratatui_ruby/schema/rect.rb +60 -60
- data/lib/ratatui_ruby/schema/scrollbar.rb +119 -119
- data/lib/ratatui_ruby/schema/shape/label.rb +1 -1
- data/lib/ratatui_ruby/schema/sparkline.rb +111 -110
- data/lib/ratatui_ruby/schema/style.rb +46 -46
- data/lib/ratatui_ruby/schema/table.rb +112 -119
- data/lib/ratatui_ruby/schema/tabs.rb +66 -67
- data/lib/ratatui_ruby/session/autodoc.rb +417 -0
- data/lib/ratatui_ruby/session.rb +40 -23
- data/lib/ratatui_ruby/test_helper.rb +185 -19
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +65 -39
- data/{examples/sparkline_demo → sig/examples/app_all_events}/app.rbs +3 -2
- data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
- data/sig/examples/app_all_events/model/events.rbs +15 -0
- data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
- data/sig/examples/app_all_events/view/app_view.rbs +8 -0
- data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
- data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
- data/sig/examples/app_all_events/view/live_view.rbs +6 -0
- data/sig/examples/app_all_events/view/log_view.rbs +6 -0
- data/sig/examples/app_all_events/view.rbs +8 -0
- data/sig/examples/app_all_events/view_state.rbs +15 -0
- data/{examples/list_demo → sig/examples/app_color_picker}/app.rbs +2 -2
- data/sig/examples/app_login_form/app.rbs +11 -0
- data/sig/examples/app_map_demo/app.rbs +11 -0
- data/sig/examples/app_table_select/app.rbs +11 -0
- data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
- data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
- data/sig/examples/verify_readme_usage/app.rbs +11 -0
- data/sig/examples/widget_block_padding/app.rbs +11 -0
- data/sig/examples/widget_block_titles/app.rbs +11 -0
- data/sig/examples/widget_box_demo/app.rbs +11 -0
- data/sig/examples/widget_calendar_demo/app.rbs +11 -0
- data/sig/examples/widget_cell_demo/app.rbs +11 -0
- data/sig/examples/widget_chart_demo/app.rbs +11 -0
- data/{examples/gauge_demo → sig/examples/widget_gauge_demo}/app.rbs +4 -0
- data/sig/examples/widget_layout_split/app.rbs +10 -0
- data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
- data/sig/examples/widget_list_demo/app.rbs +12 -0
- data/sig/examples/widget_list_styles/app.rbs +11 -0
- data/sig/examples/widget_popup_demo/app.rbs +11 -0
- data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
- data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
- data/sig/examples/widget_rect/app.rbs +12 -0
- data/sig/examples/widget_render/app.rbs +10 -0
- data/sig/examples/widget_rich_text/app.rbs +11 -0
- data/sig/examples/widget_scroll_text/app.rbs +11 -0
- data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
- data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
- data/{examples → sig/examples}/widget_style_colors/app.rbs +1 -1
- data/sig/examples/widget_table_flex/app.rbs +11 -0
- data/sig/ratatui_ruby/frame.rbs +9 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +3 -2
- data/sig/ratatui_ruby/schema/draw.rbs +4 -0
- data/sig/ratatui_ruby/schema/layout.rbs +1 -1
- data/sig/ratatui_ruby/session.rbs +94 -0
- data/tasks/autodoc/inventory.rb +61 -0
- data/tasks/autodoc/member.rb +56 -0
- data/tasks/autodoc/name.rb +19 -0
- data/tasks/autodoc/notice.rb +26 -0
- data/tasks/autodoc/rbs.rb +38 -0
- data/tasks/autodoc/rdoc.rb +45 -0
- data/tasks/autodoc.rake +47 -0
- data/tasks/bump/history.rb +2 -2
- data/tasks/doc.rake +600 -6
- data/tasks/example_viewer.html.erb +172 -0
- data/tasks/lint.rake +8 -4
- data/tasks/resources/index.html.erb +6 -0
- data/tasks/sourcehut.rake +4 -4
- data/tasks/terminal_preview/app_screenshot.rb +1 -3
- data/tasks/terminal_preview/crash_report.rb +7 -9
- data/tasks/terminal_preview/launcher_script.rb +4 -6
- data/tasks/terminal_preview/preview_collection.rb +4 -6
- data/tasks/terminal_preview/safety_confirmation.rb +3 -5
- data/tasks/terminal_preview/saved_screenshot.rb +7 -9
- data/tasks/terminal_preview/terminal_window.rb +7 -9
- data/tasks/test.rake +1 -1
- data/tasks/website/index_page.rb +3 -3
- data/tasks/website/version.rb +10 -10
- data/tasks/website/version_menu.rb +10 -12
- data/tasks/website/versioned_documentation.rb +49 -17
- data/tasks/website/website.rb +6 -8
- data/tasks/website.rake +4 -4
- metadata +156 -125
- data/LICENSES/BSD-2-Clause.txt +0 -9
- data/doc/contributors/better_dx.md +0 -543
- data/doc/contributors/example_analysis.md +0 -82
- data/doc/images/all_events.png +0 -0
- data/doc/images/block_padding.png +0 -0
- data/doc/images/block_titles.png +0 -0
- data/doc/images/box_demo.png +0 -0
- data/doc/images/calendar_demo.png +0 -0
- data/doc/images/cell_demo.png +0 -0
- data/doc/images/chart_demo.png +0 -0
- data/doc/images/flex_layout.png +0 -0
- data/doc/images/gauge_demo.png +0 -0
- data/doc/images/line_gauge_demo.png +0 -0
- data/doc/images/list_demo.png +0 -0
- data/doc/images/readme_usage.png +0 -0
- data/doc/images/scrollbar_demo.png +0 -0
- data/doc/images/sparkline_demo.png +0 -0
- data/doc/images/table_flex.png +0 -0
- data/examples/all_events/app.rb +0 -169
- data/examples/all_events/app.rbs +0 -7
- data/examples/all_events/test_app.rb +0 -139
- data/examples/analytics/app.rb +0 -258
- data/examples/analytics/app.rbs +0 -7
- data/examples/analytics/test_app.rb +0 -132
- data/examples/block_padding/app.rbs +0 -7
- data/examples/block_padding/test_app.rb +0 -31
- data/examples/block_titles/app.rbs +0 -7
- data/examples/block_titles/test_app.rb +0 -34
- data/examples/box_demo/app.rbs +0 -7
- data/examples/box_demo/test_app.rb +0 -88
- data/examples/calendar_demo/app.rb +0 -101
- data/examples/calendar_demo/app.rbs +0 -7
- data/examples/calendar_demo/test_app.rb +0 -108
- data/examples/cell_demo/app.rb +0 -108
- data/examples/cell_demo/app.rbs +0 -7
- data/examples/cell_demo/test_app.rb +0 -36
- data/examples/chart_demo/app.rb +0 -203
- data/examples/chart_demo/app.rbs +0 -7
- data/examples/chart_demo/test_app.rb +0 -102
- data/examples/custom_widget/app.rb +0 -51
- data/examples/custom_widget/app.rbs +0 -7
- data/examples/custom_widget/test_app.rb +0 -30
- data/examples/flex_layout/app.rb +0 -156
- data/examples/flex_layout/app.rbs +0 -7
- data/examples/flex_layout/test_app.rb +0 -65
- data/examples/gauge_demo/app.rb +0 -182
- data/examples/gauge_demo/test_app.rb +0 -120
- data/examples/hit_test/app.rb +0 -175
- data/examples/hit_test/app.rbs +0 -7
- data/examples/hit_test/test_app.rb +0 -102
- data/examples/line_gauge_demo/app.rb +0 -190
- data/examples/line_gauge_demo/app.rbs +0 -7
- data/examples/line_gauge_demo/test_app.rb +0 -129
- data/examples/list_demo/app.rb +0 -253
- data/examples/list_demo/test_app.rb +0 -237
- data/examples/list_styles/app.rb +0 -140
- data/examples/list_styles/app.rbs +0 -7
- data/examples/list_styles/test_app.rb +0 -157
- data/examples/login_form/app.rbs +0 -7
- data/examples/login_form/test_app.rb +0 -51
- data/examples/map_demo/app.rbs +0 -7
- data/examples/map_demo/test_app.rb +0 -149
- data/examples/mouse_events/app.rb +0 -97
- data/examples/mouse_events/app.rbs +0 -7
- data/examples/mouse_events/test_app.rb +0 -53
- data/examples/popup_demo/app.rb +0 -103
- data/examples/popup_demo/app.rbs +0 -7
- data/examples/popup_demo/test_app.rb +0 -54
- data/examples/quickstart_dsl/app.rbs +0 -7
- data/examples/quickstart_dsl/test_app.rb +0 -29
- data/examples/quickstart_lifecycle/app.rbs +0 -7
- data/examples/quickstart_lifecycle/test_app.rb +0 -29
- data/examples/ratatui_logo_demo/app.rb +0 -79
- data/examples/ratatui_logo_demo/app.rbs +0 -7
- data/examples/ratatui_logo_demo/test_app.rb +0 -51
- data/examples/ratatui_mascot_demo/app.rb +0 -84
- data/examples/ratatui_mascot_demo/app.rbs +0 -7
- data/examples/ratatui_mascot_demo/test_app.rb +0 -47
- data/examples/readme_usage/app.rb +0 -29
- data/examples/readme_usage/app.rbs +0 -7
- data/examples/readme_usage/test_app.rb +0 -29
- data/examples/rich_text/app.rb +0 -141
- data/examples/rich_text/app.rbs +0 -7
- data/examples/rich_text/test_app.rb +0 -166
- data/examples/scroll_text/app.rb +0 -103
- data/examples/scroll_text/app.rbs +0 -7
- data/examples/scroll_text/test_app.rb +0 -110
- data/examples/scrollbar_demo/app.rb +0 -143
- data/examples/scrollbar_demo/app.rbs +0 -7
- data/examples/scrollbar_demo/test_app.rb +0 -77
- data/examples/sparkline_demo/app.rb +0 -240
- data/examples/sparkline_demo/test_app.rb +0 -107
- data/examples/table_flex/app.rb +0 -65
- data/examples/table_flex/app.rbs +0 -7
- data/examples/table_flex/test_app.rb +0 -36
- data/examples/table_select/app.rbs +0 -7
- data/examples/table_select/test_app.rb +0 -180
- data/examples/widget_style_colors/test_app.rb +0 -48
- /data/doc/images/{analytics.png → app_analytics.png} +0 -0
- /data/doc/images/{custom_widget.png → app_custom_widget.png} +0 -0
- /data/doc/images/{login_form.png → app_login_form.png} +0 -0
- /data/doc/images/{map_demo.png → app_map_demo.png} +0 -0
- /data/doc/images/{mouse_events.png → app_mouse_events.png} +0 -0
- /data/doc/images/{table_select.png → app_table_select.png} +0 -0
- /data/doc/images/{quickstart_dsl.png → verify_quickstart_dsl.png} +0 -0
- /data/doc/images/{ratatui_logo_demo.png → verify_quickstart_layout.png} +0 -0
- /data/doc/images/{quickstart_lifecycle.png → verify_quickstart_lifecycle.png} +0 -0
- /data/doc/images/{list_styles.png → widget_list_styles.png} +0 -0
- /data/doc/images/{popup_demo.png → widget_popup_demo.png} +0 -0
- /data/doc/images/{hit_test.png → widget_rect.png} +0 -0
- /data/doc/images/{rich_text.png → widget_rich_text.png} +0 -0
- /data/doc/images/{scroll_text.png → widget_scroll_text.png} +0 -0
|
@@ -1,58 +1,81 @@
|
|
|
1
|
-
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
-->
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
# Application Architecture
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
Architect robust TUI applications using core lifecycle patterns and API best practices.
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## Core Concepts
|
|
10
12
|
|
|
11
|
-
|
|
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.
|
|
12
26
|
|
|
13
27
|
```ruby
|
|
14
28
|
RatatuiRuby.run do |tui|
|
|
15
29
|
loop do
|
|
16
|
-
|
|
17
|
-
|
|
30
|
+
tui.draw do |frame|
|
|
31
|
+
frame.render_widget(tui.paragraph(text: "Hello"), frame.area)
|
|
32
|
+
end
|
|
33
|
+
break if tui.poll_event == "q"
|
|
18
34
|
end
|
|
19
35
|
end
|
|
20
36
|
# Terminal is restored here
|
|
21
37
|
```
|
|
22
38
|
|
|
23
|
-
|
|
39
|
+
#### Manual Management
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
Need granular control? You can initialize and restore the terminal yourself. Use `ensure` blocks to guarantee cleanup.
|
|
26
42
|
|
|
27
43
|
```ruby
|
|
28
44
|
RatatuiRuby.init_terminal
|
|
29
45
|
begin
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
RatatuiRuby.draw do |frame|
|
|
47
|
+
frame.render_widget(RatatuiRuby::Paragraph.new(text: "Hello"), frame.area)
|
|
48
|
+
end
|
|
32
49
|
ensure
|
|
33
50
|
RatatuiRuby.restore_terminal
|
|
51
|
+
# Terminal is restored here
|
|
34
52
|
end
|
|
35
53
|
```
|
|
36
54
|
|
|
37
|
-
|
|
55
|
+
### API Convenience
|
|
38
56
|
|
|
39
|
-
|
|
57
|
+
Writing UI trees involves nesting many widgets.
|
|
40
58
|
|
|
41
|
-
The
|
|
42
|
-
It provides factory methods for every widget class (converting snake_case to CamelCase) and aliases for module functions.
|
|
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.
|
|
43
60
|
|
|
44
|
-
**
|
|
61
|
+
**The Solution:** The Session API (`tui`) provides shorthand factories for every widget. It yields a session object to your block.
|
|
45
62
|
|
|
46
63
|
```ruby
|
|
47
64
|
RatatuiRuby.run do |tui|
|
|
48
65
|
loop do
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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(
|
|
56
79
|
tui.paragraph(
|
|
57
80
|
text: tui.text_line(spans: [
|
|
58
81
|
tui.text_span(content: "Side", style: tui.style(fg: :blue)),
|
|
@@ -60,15 +83,19 @@ RatatuiRuby.run do |tui|
|
|
|
60
83
|
]),
|
|
61
84
|
block: tui.block(borders: [:all], title: "Nav")
|
|
62
85
|
),
|
|
86
|
+
sidebar_area
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Render main content
|
|
90
|
+
frame.render_widget(
|
|
63
91
|
tui.paragraph(
|
|
64
92
|
text: "Main Content",
|
|
65
93
|
style: tui.style(fg: :green),
|
|
66
94
|
block: tui.block(borders: [:all], title: "Content")
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
tui.draw(layout)
|
|
95
|
+
),
|
|
96
|
+
content_area
|
|
97
|
+
)
|
|
98
|
+
end
|
|
72
99
|
|
|
73
100
|
event = tui.poll_event
|
|
74
101
|
break if event == "q" || event == :ctrl_c
|
|
@@ -76,22 +103,25 @@ RatatuiRuby.run do |tui|
|
|
|
76
103
|
end
|
|
77
104
|
```
|
|
78
105
|
|
|
79
|
-
|
|
106
|
+
#### Raw API
|
|
80
107
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
**Comparison:** Notice how much more verbose the same UI definition is.
|
|
108
|
+
Building your own abstractions? You might prefer explicit class instantiation. The raw constants are always available.
|
|
84
109
|
|
|
85
110
|
```ruby
|
|
86
111
|
RatatuiRuby.run do
|
|
87
112
|
loop do
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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(
|
|
95
125
|
RatatuiRuby::Paragraph.new(
|
|
96
126
|
text: RatatuiRuby::Text::Line.new(spans: [
|
|
97
127
|
RatatuiRuby::Text::Span.new(content: "Side", style: RatatuiRuby::Style.new(fg: :blue)),
|
|
@@ -99,18 +129,48 @@ RatatuiRuby.run do
|
|
|
99
129
|
]),
|
|
100
130
|
block: RatatuiRuby::Block.new(borders: [:all], title: "Nav")
|
|
101
131
|
),
|
|
132
|
+
rects[0]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
frame.render_widget(
|
|
102
136
|
RatatuiRuby::Paragraph.new(
|
|
103
137
|
text: "Main Content",
|
|
104
138
|
style: RatatuiRuby::Style.new(fg: :green),
|
|
105
139
|
block: RatatuiRuby::Block.new(borders: [:all], title: "Content")
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
RatatuiRuby.draw(layout)
|
|
140
|
+
),
|
|
141
|
+
rects[1]
|
|
142
|
+
)
|
|
143
|
+
end
|
|
111
144
|
|
|
112
145
|
event = RatatuiRuby.poll_event
|
|
113
146
|
break if event == "q" || event == :ctrl_c
|
|
114
147
|
end
|
|
115
148
|
end
|
|
116
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
|
@@ -46,11 +46,13 @@ Wrap your test assertions in `with_test_terminal`. This sets up a temporary, in-
|
|
|
46
46
|
def test_rendering
|
|
47
47
|
# Uses default 80x24 terminal
|
|
48
48
|
with_test_terminal do
|
|
49
|
-
# 1. Instantiate your
|
|
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"
|
|
@@ -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
|
|
@@ -43,11 +45,13 @@ loop do
|
|
|
43
45
|
event = RatatuiRuby.poll_event
|
|
44
46
|
break if event == :esc
|
|
45
47
|
|
|
46
|
-
# 3. Construct View Tree
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
|
@@ -18,11 +18,12 @@ require "ratatui_ruby"
|
|
|
18
18
|
|
|
19
19
|
class MyExampleApp
|
|
20
20
|
def initialize
|
|
21
|
-
# Initialize state
|
|
21
|
+
# Initialize state (styles must be initialized in run when @tui is available)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def run
|
|
25
|
-
RatatuiRuby.run do
|
|
25
|
+
RatatuiRuby.run do |tui|
|
|
26
|
+
@tui = tui # Store for use in private methods
|
|
26
27
|
loop do
|
|
27
28
|
render
|
|
28
29
|
break if handle_input == :quit
|
|
@@ -33,13 +34,24 @@ class MyExampleApp
|
|
|
33
34
|
private
|
|
34
35
|
|
|
35
36
|
def render
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
@tui.draw do |frame|
|
|
38
|
+
# 1. Split layout using Session helpers
|
|
39
|
+
areas = @tui.layout_split(frame.area, constraints: [@tui.constraint_fill(1)])
|
|
40
|
+
# 2. Create and render widgets
|
|
41
|
+
widget = @tui.paragraph(text: "Hello", block: @tui.block(borders: [:all]))
|
|
42
|
+
frame.render_widget(widget, areas[0])
|
|
43
|
+
end
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
def handle_input
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
case @tui.poll_event
|
|
48
|
+
in { type: :key, code: "q" }
|
|
49
|
+
:quit
|
|
50
|
+
in { type: :key, code: code }
|
|
51
|
+
# Handle other keys
|
|
52
|
+
else
|
|
53
|
+
# Ignore unhandled events
|
|
54
|
+
end
|
|
43
55
|
end
|
|
44
56
|
end
|
|
45
57
|
|
|
@@ -48,11 +60,12 @@ MyExampleApp.new.run if __FILE__ == $PROGRAM_NAME
|
|
|
48
60
|
|
|
49
61
|
### Naming Convention (Required)
|
|
50
62
|
|
|
51
|
-
Example
|
|
52
|
-
-
|
|
53
|
-
-
|
|
63
|
+
Example directories **must** follow a prefixing convention to categorize them alphabetically:
|
|
64
|
+
- `app_`: Application showcases (e.g., `app_analytics`). Class name: `AppAnalytics`.
|
|
65
|
+
- `widget_`: Widget-focused demonstrations (e.g., `widget_gauge_demo`). Class name: `WidgetGaugeDemo`.
|
|
66
|
+
- `verify_`: Documentation verification examples (e.g., `verify_readme_usage`). Class name: `VerifyReadmeUsage`.
|
|
54
67
|
|
|
55
|
-
The class
|
|
68
|
+
The directory and class names must match (snake_case directory maps to PascalCase class).
|
|
56
69
|
|
|
57
70
|
This convention enables the `terminal_preview:update` rake task to automatically capture terminal output for all examples without maintaining a manual registry.
|
|
58
71
|
|
|
@@ -90,13 +103,39 @@ end
|
|
|
90
103
|
|
|
91
104
|
2. **Use `RatatuiRuby.run` for terminal management.** Never call `init_terminal` or `restore_terminal` directly. The `run` block handles terminal setup/teardown automatically and safely, even if an exception occurs.
|
|
92
105
|
|
|
93
|
-
3. **Use
|
|
106
|
+
3. **Use the Session API (`tui`) for cleaner code.** Accept the `tui` block parameter from `RatatuiRuby.run` and use it throughout your app:
|
|
107
|
+
- `@tui.draw { |frame| ... }` instead of `RatatuiRuby.draw`
|
|
108
|
+
- `@tui.poll_event` instead of `RatatuiRuby.poll_event`
|
|
109
|
+
- `@tui.style(...)` instead of `RatatuiRuby::Style.new(...)`
|
|
110
|
+
- `@tui.paragraph(...)` instead of `RatatuiRuby::Paragraph.new(...)`
|
|
111
|
+
- `@tui.block(...)` instead of `RatatuiRuby::Block.new(...)`
|
|
112
|
+
- `@tui.layout_split(...)` instead of `RatatuiRuby::Layout.split(...)`
|
|
113
|
+
- `@tui.constraint_fill(...)` instead of `RatatuiRuby::Constraint.fill(...)`
|
|
114
|
+
- `@tui.text_line(...)` instead of `RatatuiRuby::Text::Line.new(...)`
|
|
115
|
+
- `@tui.text_span(...)` instead of `RatatuiRuby::Text::Span.new(...)`
|
|
116
|
+
|
|
117
|
+
4. **Event handling must include a catch-all pattern.** When using pattern matching in `handle_input`, always include an `else` clause at the end to catch unmatched events (mouse events, resize events, focus events, etc.). Without it, unmatched events will raise `NoMatchingPatternError`:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
def handle_input
|
|
121
|
+
case @tui.poll_event
|
|
122
|
+
in { type: :key, code: "q" }
|
|
123
|
+
:quit
|
|
124
|
+
in { type: :mouse, kind: "down", x:, y: }
|
|
125
|
+
handle_click(x, y)
|
|
126
|
+
else
|
|
127
|
+
# Ignore other events
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
5. **Use keyboard keys to cycle through widget attributes.** Users should be able to interactively explore all widget options. Common patterns:
|
|
94
133
|
- Arrow keys: Navigate or adjust values
|
|
95
134
|
- Letter keys: Cycle through styles, modes, or variants. Prefer all lowercase keys to avoid confusion and simplify the UI description.
|
|
96
135
|
- Space: Toggle or select
|
|
97
136
|
- `q` or Ctrl+C: Quit
|
|
98
137
|
|
|
99
|
-
|
|
138
|
+
5. **Naming Conventions for Controls**
|
|
100
139
|
|
|
101
140
|
When documenting hotkeys and cycling options in the UI, use consistent naming:
|
|
102
141
|
|
|
@@ -105,11 +144,12 @@ When documenting hotkeys and cycling options in the UI, use consistent naming:
|
|
|
105
144
|
- Use "Highlight Style" (not "Highlight") for the `highlight_style:` parameter
|
|
106
145
|
- Use "Repeat Symbol" (not "Repeat") for the `repeat_highlight_symbol:` parameter
|
|
107
146
|
|
|
108
|
-
- **Display names for cycled values:** Create a `name` field in your options hash to keep display names paired with values:
|
|
147
|
+
- **Display names for cycled values:** Create a `name` field in your options hash to keep display names paired with values. Initialize style arrays inside `run` when `@tui` is available:
|
|
109
148
|
```ruby
|
|
149
|
+
# In run method, after @tui = tui:
|
|
110
150
|
@styles = [
|
|
111
|
-
{ name: "Yellow Bold", style:
|
|
112
|
-
{ name: "Blue on White", style:
|
|
151
|
+
{ name: "Yellow Bold", style: @tui.style(fg: :yellow, modifiers: [:bold]) },
|
|
152
|
+
{ name: "Blue on White", style: @tui.style(fg: :blue, bg: :white) }
|
|
113
153
|
]
|
|
114
154
|
|
|
115
155
|
# In controls: "h: Highlight Style (#{@styles[@style_index][:name]})"
|
|
@@ -118,9 +158,15 @@ When documenting hotkeys and cycling options in the UI, use consistent naming:
|
|
|
118
158
|
|
|
119
159
|
This keeps the UI self-documenting and users can see exact parameter names when they read the hotkey help.
|
|
120
160
|
|
|
121
|
-
|
|
161
|
+
7. **Hit Testing**
|
|
122
162
|
|
|
123
|
-
Examples with mouse interaction should
|
|
163
|
+
Examples with mouse interaction should use the **Frame API**. By calling `@tui.layout_split` inside `@tui.draw`, you obtain the exact `Rect`s used for rendering. Store these rects in instance variables (e.g., `@sidebar_rect`) to use them in your `handle_input` method for hit testing:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
if @sidebar_rect&.contains?(event.x, event.y)
|
|
167
|
+
# Handle click
|
|
168
|
+
end
|
|
169
|
+
```
|
|
124
170
|
|
|
125
171
|
## Testing Examples
|
|
126
172
|
|
|
@@ -163,13 +209,25 @@ class TestMyExampleApp < Minitest::Test
|
|
|
163
209
|
assert_includes content, "Changed State"
|
|
164
210
|
end
|
|
165
211
|
end
|
|
212
|
+
|
|
213
|
+
def test_mouse_interaction
|
|
214
|
+
with_test_terminal do
|
|
215
|
+
# Click at (10, 5)
|
|
216
|
+
inject_click(x: 10, y: 5)
|
|
217
|
+
inject_key(:q)
|
|
218
|
+
@app.run
|
|
219
|
+
|
|
220
|
+
content = buffer_content.join("\n")
|
|
221
|
+
assert_includes content, "Clicked at (10, 5)"
|
|
222
|
+
end
|
|
223
|
+
end
|
|
166
224
|
end
|
|
167
225
|
```
|
|
168
226
|
|
|
169
227
|
### Testing Guidelines
|
|
170
228
|
|
|
171
229
|
1. **Inject events, observe buffer.** Tests should only interact through:
|
|
172
|
-
- `inject_key
|
|
230
|
+
- `inject_key`, `inject_click`, `inject_event`, etc. for input
|
|
173
231
|
- `buffer_content` for output verification
|
|
174
232
|
|
|
175
233
|
2. **Never call internal methods.** Don't call `render`, `handle_input`, `__send__`, or access instance variables with `instance_variable_get`. Tests verify behavior through the public `run` method.
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
-->
|
|
6
|
+
|
|
1
7
|
# Documentation Style Guide
|
|
2
8
|
|
|
3
9
|
This project follows a strict and specific documentation style designed to be helpful, readable, and consistent. It combines the structural clarity of Christopher Alexander's Pattern Language with the prose style of William Zinsser's *On Writing Well* and the usability of the U.S. Federal Plain Language Guidelines.
|
|
@@ -9,6 +15,7 @@ This project follows a strict and specific documentation style designed to be he
|
|
|
9
15
|
* **Context, Problem, Solution (Alexandrian Form):** Do not just say *what* a class does. Explain *why* it exists. Start with the context, state the problem (the pain point without this tool), and then present the class as the solution.
|
|
10
16
|
* **Prose Style (Zinsser/Klinkenborg):** Use short, punchy sentences. Use active voice. Cut unnecessary words. Avoid "allow," "enable," "provide," "support," "functionality," and "capability" where possible. Weak verbs hide the action. Strong verbs drive the sentence.
|
|
11
17
|
* **User-Centric (Plain Language):** Speak directly to the user ("You"). Don't abstract them away ("The developer"). Focus on their goals and how this tool helps them achieve those goals.
|
|
18
|
+
* **Tone (Supportive and Authoritative, not Hostile or Prescriptive):** Avoid "must," "requires," "need to," or "mandatory." These words sound bossy. Treat the user as a capable peer. Instead of "You must do X," use imperative "Do X" or cause-and-effect "X ensures Y."
|
|
12
19
|
|
|
13
20
|
## 2. Class Documentation
|
|
14
21
|
|
data/doc/contributors/index.md
CHANGED
data/doc/event_handling.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
|
|
4
|
+
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
-->
|
|
6
|
+
|
|
1
7
|
# Event Handling in RatatuiRuby
|
|
2
8
|
|
|
3
9
|
`ratatui_ruby` provides a rich, object-oriented event system that supports multiple coding styles, from simple boolean predicates to modern Ruby pattern matching.
|
|
4
10
|
|
|
5
|
-
Events are retrieved using `RatatuiRuby.poll_event`. This method returns an instance of a subclass of `RatatuiRuby::Event` (e.g., `RatatuiRuby::Event::Key`, `RatatuiRuby::Event::Mouse`)
|
|
11
|
+
Events are retrieved using `RatatuiRuby.poll_event`. This method returns an instance of a subclass of `RatatuiRuby::Event` (e.g., `RatatuiRuby::Event::Key`, `RatatuiRuby::Event::Mouse`). When no event is available, it returns `RatatuiRuby::Event::None`—a [null object](https://martinfowler.com/eaaCatalog/specialCase.html) that safely responds to all event predicates with `false`.
|
|
6
12
|
|
|
7
13
|
## 1. Symbol and String Comparison (Simplest)
|
|
8
14
|
|
|
@@ -18,7 +24,6 @@ For a complete list of supported keys, modifiers, and event types, please refer
|
|
|
18
24
|
|
|
19
25
|
```ruby
|
|
20
26
|
event = RatatuiRuby.poll_event
|
|
21
|
-
next unless event
|
|
22
27
|
|
|
23
28
|
# 1. Check for quit keys
|
|
24
29
|
if event == "q" || event == :ctrl_c
|
|
@@ -101,8 +106,8 @@ loop do
|
|
|
101
106
|
in type: :mouse, kind: "down", x:, y:
|
|
102
107
|
handle_click(x, y)
|
|
103
108
|
|
|
104
|
-
|
|
105
|
-
#
|
|
109
|
+
in type: :none
|
|
110
|
+
# No event available, continue loop
|
|
106
111
|
end
|
|
107
112
|
end
|
|
108
113
|
```
|
|
@@ -117,3 +122,4 @@ end
|
|
|
117
122
|
| `RatatuiRuby::Event::Paste` | `:paste` | `content` | `paste?` |
|
|
118
123
|
| `RatatuiRuby::Event::FocusGained` | `:focus_gained` | (none) | `focus_gained?` |
|
|
119
124
|
| `RatatuiRuby::Event::FocusLost` | `:focus_lost` | (none) | `focus_lost?` |
|
|
125
|
+
| `RatatuiRuby::Event::None` | `:none` | (none) | `none?` |
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/doc/interactive_design.md
CHANGED
|
@@ -21,10 +21,13 @@ Structure your event loop into three clear phases:
|
|
|
21
21
|
|
|
22
22
|
```ruby
|
|
23
23
|
def run
|
|
24
|
-
RatatuiRuby.run do
|
|
24
|
+
RatatuiRuby.run do |tui|
|
|
25
|
+
@tui = tui
|
|
25
26
|
loop do
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
@tui.draw do |frame|
|
|
28
|
+
calculate_layout(frame.area) # Phase 1: Geometry (once per frame)
|
|
29
|
+
render(frame) # Phase 2: Draw
|
|
30
|
+
end
|
|
28
31
|
break if handle_input == :quit # Phase 3: Input
|
|
29
32
|
end
|
|
30
33
|
end
|
|
@@ -33,29 +36,27 @@ end
|
|
|
33
36
|
|
|
34
37
|
**Phase 1: Layout Calculation**
|
|
35
38
|
|
|
36
|
-
Call this
|
|
39
|
+
Call this inside your `draw` block. It uses the current terminal area provided by the frame:
|
|
37
40
|
|
|
38
41
|
```ruby
|
|
39
|
-
def calculate_layout
|
|
40
|
-
full_area = RatatuiRuby::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
41
|
-
|
|
42
|
+
def calculate_layout(area)
|
|
42
43
|
# Main area vs sidebar (70% / 30%)
|
|
43
|
-
main_area, @sidebar_area =
|
|
44
|
-
|
|
44
|
+
main_area, @sidebar_area = @tui.layout_split(
|
|
45
|
+
area,
|
|
45
46
|
direction: :horizontal,
|
|
46
47
|
constraints: [
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
@tui.constraint_percentage(70),
|
|
49
|
+
@tui.constraint_percentage(30),
|
|
49
50
|
]
|
|
50
51
|
)
|
|
51
52
|
|
|
52
53
|
# Within main area, left vs right panels
|
|
53
|
-
@left_rect, @right_rect =
|
|
54
|
+
@left_rect, @right_rect = @tui.layout_split(
|
|
54
55
|
main_area,
|
|
55
56
|
direction: :horizontal,
|
|
56
57
|
constraints: [
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
@tui.constraint_percentage(@left_ratio),
|
|
59
|
+
@tui.constraint_percentage(100 - @left_ratio)
|
|
59
60
|
]
|
|
60
61
|
)
|
|
61
62
|
end
|
|
@@ -66,10 +67,9 @@ end
|
|
|
66
67
|
Reuse the cached rects. Build and draw:
|
|
67
68
|
|
|
68
69
|
```ruby
|
|
69
|
-
def render
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
# ... draw ...
|
|
70
|
+
def render(frame)
|
|
71
|
+
frame.render_widget(build_widget(@left_rect), @left_rect)
|
|
72
|
+
frame.render_widget(build_widget(@right_rect), @right_rect)
|
|
73
73
|
end
|
|
74
74
|
```
|
|
75
75
|
|
|
@@ -80,7 +80,6 @@ Reuse the cached rects. Test clicks:
|
|
|
80
80
|
```ruby
|
|
81
81
|
def handle_input
|
|
82
82
|
event = RatatuiRuby.poll_event
|
|
83
|
-
return unless event
|
|
84
83
|
|
|
85
84
|
case event
|
|
86
85
|
in type: :mouse, kind: "down", x:, y:
|
|
@@ -104,18 +103,14 @@ end
|
|
|
104
103
|
|
|
105
104
|
## Layout.split
|
|
106
105
|
|
|
107
|
-
`Layout.split` computes layout geometry without rendering. It returns an array of `Rect` objects.
|
|
106
|
+
`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 `Session` helper (`tui.layout_split`) for cleaner application code.
|
|
108
107
|
|
|
109
108
|
```ruby
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
left, right = rects
|
|
117
|
-
# left is a Rect describing the left 70% of the area
|
|
118
|
-
# right is a Rect describing the right 30% of the area
|
|
109
|
+
# Preferred (Session API)
|
|
110
|
+
left, right = tui.layout_split(area, constraints: [...])
|
|
111
|
+
|
|
112
|
+
# Manual (Core API)
|
|
113
|
+
left, right = RatatuiRuby::Layout.split(area, constraints: [...])
|
|
119
114
|
```
|
|
120
115
|
|
|
121
|
-
Use it to establish the single source of truth
|
|
116
|
+
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`.
|