ratatui_ruby 0.5.0 → 0.6.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 +6 -0
- data/CHANGELOG.md +44 -7
- data/README.md +11 -4
- data/REUSE.toml +2 -7
- data/doc/application_architecture.md +84 -10
- data/doc/application_testing.md +75 -29
- data/doc/contributors/design/ruby_frontend.md +39 -3
- data/doc/contributors/design/rust_backend.md +1 -0
- data/doc/contributors/developing_examples.md +129 -44
- data/doc/contributors/examples_audit/p1_high.md +21 -0
- data/doc/contributors/examples_audit/p2_moderate.md +81 -0
- data/doc/contributors/examples_audit.md +41 -0
- data/doc/event_handling.md +11 -3
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_stateful_interaction.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_demo.png +0 -0
- data/doc/images/widget_canvas_demo.png +0 -0
- data/doc/images/widget_cell_demo.png +0 -0
- data/doc/images/widget_center_demo.png +0 -0
- data/doc/images/widget_chart_demo.png +0 -0
- data/doc/images/widget_list_demo.png +0 -0
- data/doc/images/widget_overlay_demo.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_sparkline_demo.png +0 -0
- data/doc/images/widget_table_demo.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/quickstart.md +69 -76
- data/doc/terminal_limitations.md +92 -0
- data/examples/app_all_events/README.md +45 -27
- data/examples/app_all_events/app.rb +38 -35
- data/examples/app_all_events/model/app_model.rb +157 -0
- data/examples/app_all_events/model/event_entry.rb +17 -0
- data/examples/app_all_events/model/msg.rb +37 -0
- data/examples/app_all_events/update.rb +73 -0
- data/examples/app_all_events/view/app_view.rb +8 -8
- data/examples/app_all_events/view/controls_view.rb +8 -6
- data/examples/app_all_events/view/counts_view.rb +12 -8
- data/examples/app_all_events/view/live_view.rb +8 -7
- data/examples/app_all_events/view/log_view.rb +10 -15
- data/examples/app_color_picker/README.md +84 -44
- data/examples/app_color_picker/app.rb +24 -62
- data/examples/app_color_picker/controls.rb +90 -0
- data/examples/app_color_picker/copy_dialog.rb +45 -49
- data/examples/app_color_picker/export_pane.rb +126 -0
- data/examples/app_color_picker/input.rb +99 -67
- data/examples/app_color_picker/main_container.rb +178 -0
- data/examples/app_color_picker/palette.rb +55 -26
- data/examples/app_login_form/README.md +47 -0
- data/examples/app_login_form/app.rb +2 -3
- data/examples/app_stateful_interaction/README.md +31 -0
- data/examples/app_stateful_interaction/app.rb +272 -0
- data/examples/timeout_demo.rb +43 -0
- data/examples/verify_quickstart_dsl/README.md +48 -0
- data/examples/verify_quickstart_dsl/app.rb +2 -0
- data/examples/verify_quickstart_layout/README.md +71 -0
- data/examples/verify_quickstart_layout/app.rb +2 -0
- data/examples/verify_quickstart_lifecycle/README.md +56 -0
- data/examples/verify_quickstart_lifecycle/app.rb +8 -2
- data/examples/verify_readme_usage/README.md +43 -0
- data/examples/verify_readme_usage/app.rb +8 -2
- data/examples/widget_barchart_demo/README.md +49 -0
- data/examples/widget_barchart_demo/app.rb +5 -5
- data/examples/widget_block_demo/README.md +34 -0
- data/examples/widget_block_demo/app.rb +256 -0
- data/examples/widget_box_demo/README.md +45 -0
- data/examples/widget_calendar_demo/README.md +39 -0
- data/examples/widget_canvas_demo/README.md +27 -0
- data/examples/widget_canvas_demo/app.rb +123 -0
- data/examples/widget_cell_demo/README.md +36 -0
- data/examples/widget_cell_demo/app.rb +31 -24
- data/examples/widget_center_demo/README.md +29 -0
- data/examples/widget_center_demo/app.rb +116 -0
- data/examples/widget_chart_demo/README.md +41 -0
- data/examples/widget_chart_demo/app.rb +7 -2
- data/examples/widget_gauge_demo/README.md +41 -0
- data/examples/widget_layout_split/README.md +44 -0
- data/examples/widget_line_gauge_demo/README.md +41 -0
- data/examples/widget_list_demo/README.md +49 -0
- data/examples/widget_list_demo/app.rb +91 -107
- data/examples/widget_map_demo/README.md +39 -0
- data/examples/{app_map_demo → widget_map_demo}/app.rb +2 -2
- data/examples/widget_overlay_demo/app.rb +248 -0
- data/examples/widget_popup_demo/README.md +36 -0
- data/examples/widget_ratatui_logo_demo/README.md +34 -0
- data/examples/widget_ratatui_mascot_demo/README.md +34 -0
- data/examples/widget_rect/README.md +38 -0
- data/examples/widget_render/README.md +37 -0
- data/examples/widget_rich_text/README.md +35 -0
- data/examples/widget_rich_text/app.rb +62 -33
- data/examples/widget_scroll_text/README.md +37 -0
- data/examples/widget_scroll_text/app.rb +0 -1
- data/examples/widget_scrollbar_demo/README.md +37 -0
- data/examples/widget_sparkline_demo/README.md +42 -0
- data/examples/widget_sparkline_demo/app.rb +4 -3
- data/examples/widget_style_colors/README.md +34 -0
- data/examples/widget_table_demo/README.md +48 -0
- data/examples/{app_table_select → widget_table_demo}/app.rb +46 -8
- data/examples/widget_tabs_demo/README.md +41 -0
- data/examples/widget_tabs_demo/app.rb +15 -1
- data/examples/widget_text_width/README.md +35 -0
- data/examples/widget_text_width/app.rb +106 -0
- data/exe/.gitkeep +0 -0
- data/ext/ratatui_ruby/Cargo.lock +11 -4
- data/ext/ratatui_ruby/Cargo.toml +2 -1
- data/ext/ratatui_ruby/src/events.rs +238 -26
- data/ext/ratatui_ruby/src/frame.rs +113 -1
- data/ext/ratatui_ruby/src/lib.rs +34 -4
- data/ext/ratatui_ruby/src/string_width.rs +101 -0
- data/ext/ratatui_ruby/src/terminal.rs +39 -15
- data/ext/ratatui_ruby/src/text.rs +1 -1
- data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
- data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
- data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
- data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/table.rs +113 -1
- data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
- data/lib/ratatui_ruby/cell.rb +4 -4
- data/lib/ratatui_ruby/event/key/character.rb +35 -0
- data/lib/ratatui_ruby/event/key/media.rb +44 -0
- data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
- data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
- data/lib/ratatui_ruby/event/key/system.rb +45 -0
- data/lib/ratatui_ruby/event/key.rb +111 -51
- data/lib/ratatui_ruby/event/mouse.rb +3 -3
- data/lib/ratatui_ruby/event/paste.rb +1 -1
- data/lib/ratatui_ruby/frame.rb +96 -0
- data/lib/ratatui_ruby/list_state.rb +88 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/schema/cursor.rb +5 -0
- data/lib/ratatui_ruby/schema/gauge.rb +3 -1
- data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/list.rb +25 -4
- data/lib/ratatui_ruby/schema/list_item.rb +41 -0
- data/lib/ratatui_ruby/schema/rect.rb +43 -0
- data/lib/ratatui_ruby/schema/style.rb +24 -4
- data/lib/ratatui_ruby/schema/table.rb +21 -3
- data/lib/ratatui_ruby/schema/text.rb +69 -1
- data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
- data/lib/ratatui_ruby/session/autodoc.rb +65 -0
- data/lib/ratatui_ruby/session.rb +22 -7
- data/lib/ratatui_ruby/table_state.rb +90 -0
- data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
- data/lib/ratatui_ruby/test_helper/snapshot.rb +390 -0
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
- data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
- data/lib/ratatui_ruby/test_helper.rb +65 -358
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +42 -19
- data/sig/examples/app_stateful_interaction/app.rbs +33 -0
- data/sig/examples/widget_block_demo/app.rbs +32 -0
- data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
- data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
- data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
- data/sig/ratatui_ruby/event.rbs +11 -1
- data/sig/ratatui_ruby/frame.rbs +2 -0
- data/sig/ratatui_ruby/list_state.rbs +13 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
- data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/list.rbs +4 -2
- data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
- data/sig/ratatui_ruby/schema/rect.rbs +3 -0
- data/sig/ratatui_ruby/schema/style.rbs +3 -3
- data/sig/ratatui_ruby/schema/table.rbs +3 -1
- data/sig/ratatui_ruby/schema/text.rbs +8 -6
- data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
- data/sig/ratatui_ruby/session.rbs +13 -0
- data/sig/ratatui_ruby/table_state.rbs +15 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
- data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
- data/sig/ratatui_ruby/test_helper.rbs +5 -4
- data/tasks/autodoc/examples.rb +79 -0
- data/tasks/autodoc/inventory.rb +9 -7
- data/tasks/autodoc.rake +11 -5
- data/tasks/bump/changelog.rb +3 -3
- data/tasks/bump/links.rb +67 -0
- data/tasks/sourcehut.rake +61 -21
- data/tasks/terminal_preview/app_screenshot.rb +13 -3
- data/tasks/terminal_preview/saved_screenshot.rb +4 -3
- metadata +111 -37
- data/doc/images/app_table_select.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_list_styles.png +0 -0
- data/examples/app_all_events/model/events.rb +0 -180
- data/examples/app_all_events/model/highlight.rb +0 -57
- data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
- data/examples/app_all_events/view_state.rb +0 -42
- data/examples/app_color_picker/scene.rb +0 -201
- data/examples/widget_block_padding/app.rb +0 -67
- data/examples/widget_block_titles/app.rb +0 -69
- data/examples/widget_list_styles/app.rb +0 -141
- data/examples/widget_table_flex/app.rb +0 -95
- data/sig/examples/widget_block_padding/app.rbs +0 -11
- data/sig/examples/widget_block_titles/app.rbs +0 -11
- data/sig/examples/widget_list_styles/app.rbs +0 -11
- data/tasks/bump/comparison_links.rb +0 -41
- /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
data/doc/quickstart.md
CHANGED
|
@@ -35,12 +35,11 @@ gem install ratatui_ruby
|
|
|
35
35
|
|
|
36
36
|
Here is a "Hello World" application that demonstrates the core lifecycle of a **ratatui_ruby** app.
|
|
37
37
|
|
|
38
|
+
<!-- SYNC:START:../examples/verify_quickstart_lifecycle/app.rb:main -->
|
|
38
39
|
```ruby
|
|
39
|
-
require "ratatui_ruby"
|
|
40
|
-
|
|
41
40
|
# 1. Initialize the terminal
|
|
42
41
|
RatatuiRuby.init_terminal
|
|
43
|
-
|
|
42
|
+
|
|
44
43
|
begin
|
|
45
44
|
# The Main Loop
|
|
46
45
|
loop do
|
|
@@ -57,21 +56,26 @@ begin
|
|
|
57
56
|
style: { fg: "white" }
|
|
58
57
|
)
|
|
59
58
|
)
|
|
60
|
-
|
|
59
|
+
|
|
61
60
|
# 3. Draw the UI
|
|
62
61
|
RatatuiRuby.draw do |frame|
|
|
63
62
|
frame.render_widget(view, frame.area)
|
|
64
63
|
end
|
|
65
|
-
|
|
64
|
+
|
|
66
65
|
# 4. Poll for events
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
case RatatuiRuby.poll_event
|
|
67
|
+
in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
|
|
68
|
+
break
|
|
69
|
+
else
|
|
70
|
+
nil
|
|
71
|
+
end
|
|
69
72
|
end
|
|
70
73
|
ensure
|
|
71
74
|
# 5. Restore the terminal to its original state
|
|
72
75
|
RatatuiRuby.restore_terminal
|
|
73
76
|
end
|
|
74
77
|
```
|
|
78
|
+
<!-- SYNC:END -->
|
|
75
79
|
|
|
76
80
|

|
|
77
81
|
|
|
@@ -87,10 +91,8 @@ end
|
|
|
87
91
|
|
|
88
92
|
You can simplify your code by using `RatatuiRuby.run`. This method handles the terminal lifecycle for you, yielding a `Session` object with factory methods for widgets.
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
# 1. Initialize the terminal and ensure it is restored.
|
|
94
|
+
<!-- SYNC:START:../examples/verify_quickstart_dsl/app.rb:main -->
|
|
95
|
+
```ruby
|
|
94
96
|
RatatuiRuby.run do |tui|
|
|
95
97
|
loop do
|
|
96
98
|
# 2. Create your UI with methods instead of classes.
|
|
@@ -121,6 +123,7 @@ RatatuiRuby.run do |tui|
|
|
|
121
123
|
end
|
|
122
124
|
end
|
|
123
125
|
```
|
|
126
|
+
<!-- SYNC:END -->
|
|
124
127
|
|
|
125
128
|
#### How it works
|
|
126
129
|
|
|
@@ -135,64 +138,62 @@ For a deeper dive into the available application architectures (Manual vs Manage
|
|
|
135
138
|
|
|
136
139
|
Real-world applications often need to split the screen into multiple areas. `RatatuiRuby::Layout` lets you do this easily.
|
|
137
140
|
|
|
141
|
+
<!-- SYNC:START:../examples/verify_quickstart_layout/app.rb:main -->
|
|
138
142
|
```ruby
|
|
139
|
-
|
|
143
|
+
loop do
|
|
144
|
+
tui.draw do |frame|
|
|
145
|
+
# 1. Split the screen
|
|
146
|
+
top, bottom = tui.layout_split(
|
|
147
|
+
frame.area,
|
|
148
|
+
direction: :vertical,
|
|
149
|
+
constraints: [
|
|
150
|
+
tui.constraint_percentage(75),
|
|
151
|
+
tui.constraint_percentage(25),
|
|
152
|
+
]
|
|
153
|
+
)
|
|
140
154
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
tui.constraint_percentage(25),
|
|
151
|
-
]
|
|
152
|
-
)
|
|
155
|
+
# 2. Render Top Widget
|
|
156
|
+
frame.render_widget(
|
|
157
|
+
tui.paragraph(
|
|
158
|
+
text: "Hello, Ratatui!",
|
|
159
|
+
alignment: :center,
|
|
160
|
+
block: tui.block(title: "Content", borders: [:all], border_color: "cyan")
|
|
161
|
+
),
|
|
162
|
+
top
|
|
163
|
+
)
|
|
153
164
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
165
|
+
# 3. Render Bottom Widget with Styled Text
|
|
166
|
+
# We use a Line of Spans to style specific characters
|
|
167
|
+
text_line = tui.text_line(
|
|
168
|
+
spans: [
|
|
169
|
+
tui.text_span(content: "Press '"),
|
|
170
|
+
tui.text_span(
|
|
171
|
+
content: "q",
|
|
172
|
+
style: tui.style(modifiers: [:bold, :underlined])
|
|
160
173
|
),
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
# We use a Line of Spans to style specific characters
|
|
166
|
-
text_line = tui.text_line(
|
|
167
|
-
spans: [
|
|
168
|
-
tui.text_span(content: "Press '"),
|
|
169
|
-
tui.text_span(
|
|
170
|
-
content: "q",
|
|
171
|
-
style: tui.style(modifiers: [:bold, :underlined])
|
|
172
|
-
),
|
|
173
|
-
tui.text_span(content: "' to quit."),
|
|
174
|
-
],
|
|
175
|
-
alignment: :center
|
|
176
|
-
)
|
|
174
|
+
tui.text_span(content: "' to quit."),
|
|
175
|
+
],
|
|
176
|
+
alignment: :center
|
|
177
|
+
)
|
|
177
178
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
179
|
+
frame.render_widget(
|
|
180
|
+
tui.paragraph(
|
|
181
|
+
text: text_line,
|
|
182
|
+
block: tui.block(title: "Controls", borders: [:all])
|
|
183
|
+
),
|
|
184
|
+
bottom
|
|
185
|
+
)
|
|
186
|
+
end
|
|
186
187
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
end
|
|
188
|
+
case tui.poll_event
|
|
189
|
+
in { type: :key, code: "q" }
|
|
190
|
+
break
|
|
191
|
+
else
|
|
192
|
+
# Ignore other events
|
|
193
193
|
end
|
|
194
194
|
end
|
|
195
195
|
```
|
|
196
|
+
<!-- SYNC:END -->
|
|
196
197
|
|
|
197
198
|
#### How it works
|
|
198
199
|
|
|
@@ -221,7 +222,7 @@ Use it to debug your input handling or verify terminal behavior.
|
|
|
221
222
|
|
|
222
223
|
**What you'll learn:**
|
|
223
224
|
|
|
224
|
-
* **
|
|
225
|
+
* **Proto-TEA Architecture**: Implements unidirectional data flow (Model-View-Update) with immutable state and pure functions.
|
|
225
226
|
* **Event Handling**: Captures and distinguishes all input types, including modifiers (`Ctrl+C`) and focus changes.
|
|
226
227
|
* **Scalable Structure**: Organizes a non-trivial application into small, focused classes instead of a monolithic script.
|
|
227
228
|
|
|
@@ -231,15 +232,15 @@ Use it to debug your input handling or verify terminal behavior.
|
|
|
231
232
|
|
|
232
233
|
Interactive tools require complex state. Mapping mouse clicks to widgets and handling modal dialogs creates messy code if handled in the main loop.
|
|
233
234
|
|
|
234
|
-
This app implements a full Color Picker using a "
|
|
235
|
+
This app implements a full Color Picker using a "Proto-Kit (Component-Based)" pattern. Each component encapsulates its own rendering, state, and event handling.
|
|
235
236
|
|
|
236
237
|
Use it to build forms, editors, and mouse-driven tools.
|
|
237
238
|
|
|
238
239
|
**What you'll learn:**
|
|
239
240
|
|
|
240
|
-
* **
|
|
241
|
-
* **Hit Testing**:
|
|
242
|
-
* **Modal Dialogs**: Implements overlay patterns that intercept input.
|
|
241
|
+
* **Proto-Kit Architecture**: Self-contained components with `render(tui, frame, area)` and `handle_event(event)`.
|
|
242
|
+
* **Encapsulated Hit Testing**: Components cache their render area and check `contains?` internally.
|
|
243
|
+
* **Modal Dialogs**: Implements overlay patterns that intercept input via Chain of Responsibility.
|
|
243
244
|
|
|
244
245
|
#### [Custom Widget (Escape Hatch)](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/app_custom_widget/app.rb)
|
|
245
246
|
|
|
@@ -259,17 +260,9 @@ Shows how to use `Overlay`, `Center`, and `Cursor` to build a modal login form w
|
|
|
259
260
|
|
|
260
261
|

|
|
261
262
|
|
|
262
|
-
#### [Map Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/app_map_demo/app.rb)
|
|
263
|
-
|
|
264
|
-
Exhibits the `Canvas` widget's power, rendering a world map with city labels, animated circles, and lines.
|
|
265
|
-
|
|
266
|
-

|
|
267
263
|
|
|
268
|
-
#### [Table Select](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/app_table_select/app.rb)
|
|
269
264
|
|
|
270
|
-
Demonstrates interactive row selection in the `Table` widget with keyboard navigation, highlighting selected rows with custom styles and symbols, applying a base style, and dynamically adjusting `column_spacing`. Also demonstrates `column_highlight_style` and the new `cell_highlight_style` for precise selection visualization.
|
|
271
265
|
|
|
272
|
-

|
|
273
266
|
|
|
274
267
|
|
|
275
268
|
### Widget Demos
|
|
@@ -277,14 +270,14 @@ Demonstrates interactive row selection in the `Table` widget with keyboard navig
|
|
|
277
270
|
These smaller, focused examples demonstrate specific widgets and their configuration options.
|
|
278
271
|
|
|
279
272
|
* [Bar Chart](../examples/widget_barchart_demo/app.rb)
|
|
280
|
-
* [Block
|
|
281
|
-
* [Block Titles](../examples/widget_block_titles/app.rb)
|
|
273
|
+
* [Block (Interactive Demo)](../examples/widget_block_demo/app.rb)
|
|
282
274
|
* [Box (Block/Paragraph)](../examples/widget_box_demo/app.rb)
|
|
283
275
|
* [Calendar](../examples/widget_calendar_demo/app.rb)
|
|
284
276
|
* [Chart](../examples/widget_chart_demo/app.rb)
|
|
285
277
|
* [Gauge](../examples/widget_gauge_demo/app.rb)
|
|
286
278
|
* [Line Gauge](../examples/widget_line_gauge_demo/app.rb)
|
|
287
279
|
* [List](../examples/widget_list_demo/app.rb)
|
|
280
|
+
* [Map (Canvas)](../examples/widget_map_demo/app.rb)
|
|
288
281
|
* [Popup (Clear)](../examples/widget_popup_demo/app.rb)
|
|
289
282
|
* [Rect](../examples/widget_rect/app.rb)
|
|
290
283
|
* [Ratatui Logo](../examples/widget_ratatui_logo_demo/app.rb)
|
|
@@ -293,6 +286,6 @@ These smaller, focused examples demonstrate specific widgets and their configura
|
|
|
293
286
|
* [Scrollbar](../examples/widget_scrollbar_demo/app.rb)
|
|
294
287
|
* [Scroll Text](../examples/widget_scroll_text/app.rb)
|
|
295
288
|
* [Sparkline](../examples/widget_sparkline_demo/app.rb)
|
|
296
|
-
* [Table
|
|
289
|
+
* [Table (Selection)](../examples/widget_table_demo/app.rb)
|
|
297
290
|
* [Tabs](../examples/widget_tabs_demo/app.rb)
|
|
298
291
|
* [Widget Style Colors](../examples/widget_style_colors/app.rb)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
3
|
+
SPDX-License-Identifier: CC-BY-SA-4.0
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Terminal Limitations
|
|
7
|
+
|
|
8
|
+
Some behaviors are outside the control of `ratatui_ruby`. This document explains common pitfalls that affect your application or your users, but cannot be fixed in the library.
|
|
9
|
+
|
|
10
|
+
## Keyboard Event Interception
|
|
11
|
+
|
|
12
|
+
### The Problem
|
|
13
|
+
|
|
14
|
+
Your application receives a key event, but the modifier flags are missing. You pressed Ctrl+PageUp, but the event shows `code="page_up"` with `modifiers=[]`.
|
|
15
|
+
|
|
16
|
+
### The Cause
|
|
17
|
+
|
|
18
|
+
Terminal emulators intercept certain key combinations for their own features. The key press never reaches your application—the terminal consumes it first.
|
|
19
|
+
|
|
20
|
+
Common culprits on macOS:
|
|
21
|
+
|
|
22
|
+
| Key Combination | Terminal Behavior |
|
|
23
|
+
|---------------------|--------------------------------------|
|
|
24
|
+
| Ctrl+PageUp/Down | Switch tabs (Terminal.app, iTerm2) |
|
|
25
|
+
| Ctrl+Tab | Switch tabs |
|
|
26
|
+
| Cmd+T / Cmd+N | New tab / New window |
|
|
27
|
+
| Cmd+C / Cmd+V | Copy / Paste (not Ctrl) |
|
|
28
|
+
|
|
29
|
+
Linux terminals vary widely. Windows Terminal and ConEmu have their own defaults.
|
|
30
|
+
|
|
31
|
+
### The Solution
|
|
32
|
+
|
|
33
|
+
1. **Test with different terminals.** Kitty, WezTerm, and Alacritty pass more key combinations through to applications by default. If a key works in Kitty but not Terminal.app, the terminal is the issue.
|
|
34
|
+
|
|
35
|
+
2. **Reconfigure your terminal.** Most terminal emulators let you unbind or remap default shortcuts in their settings.
|
|
36
|
+
|
|
37
|
+
3. **Use alternative key bindings.** If your users will run your application in various terminals, design your keybindings to avoid commonly intercepted combinations:
|
|
38
|
+
- Use Alt+PageUp instead of Ctrl+PageUp
|
|
39
|
+
- Use Ctrl+J/K instead of Ctrl+Up/Down
|
|
40
|
+
- Avoid Ctrl+Tab entirely
|
|
41
|
+
|
|
42
|
+
4. **Document requirements.** If your application depends on specific key combinations, document the terminal requirements for your users.
|
|
43
|
+
|
|
44
|
+
### Enhanced Keyboard Protocol
|
|
45
|
+
|
|
46
|
+
Some terminals support the [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/), which provides unambiguous key event reporting including:
|
|
47
|
+
|
|
48
|
+
- Individual modifier key events (LeftShift vs RightShift)
|
|
49
|
+
- Media keys (Play, Pause, Volume controls)
|
|
50
|
+
- Repeat and release events
|
|
51
|
+
|
|
52
|
+
Terminals with full protocol support:
|
|
53
|
+
- Kitty
|
|
54
|
+
- WezTerm
|
|
55
|
+
- Foot
|
|
56
|
+
- Alacritty (partial)
|
|
57
|
+
|
|
58
|
+
Standard terminals (Terminal.app, iTerm2, GNOME Terminal) do not support the enhanced protocol.
|
|
59
|
+
|
|
60
|
+
**RatatuiRuby Status:** The underlying library (crossterm) supports this protocol, but RatatuiRuby does not yet expose a way to enable it. The key code mappings for media keys and individual modifier keys exist, but they will only be received from terminals that enable the protocol by default. This is planned for a future release.
|
|
61
|
+
|
|
62
|
+
## Mouse Event Limitations
|
|
63
|
+
|
|
64
|
+
### The Problem
|
|
65
|
+
|
|
66
|
+
Mouse events work in some terminals but not others. Or they work, but only up to certain coordinates.
|
|
67
|
+
|
|
68
|
+
### The Cause
|
|
69
|
+
|
|
70
|
+
Mouse reporting requires terminal escape sequence support. Older terminals may not support:
|
|
71
|
+
|
|
72
|
+
- SGR mouse mode (coordinates > 223)
|
|
73
|
+
- Mouse motion tracking
|
|
74
|
+
- Button-event tracking
|
|
75
|
+
|
|
76
|
+
### The Solution
|
|
77
|
+
|
|
78
|
+
Ensure your terminal supports modern mouse modes. Most actively maintained terminals do. If running in a legacy environment, test mouse functionality and provide keyboard alternatives.
|
|
79
|
+
|
|
80
|
+
## Focus Events
|
|
81
|
+
|
|
82
|
+
### The Problem
|
|
83
|
+
|
|
84
|
+
`Event::FocusGained` and `Event::FocusLost` are never received.
|
|
85
|
+
|
|
86
|
+
### The Cause
|
|
87
|
+
|
|
88
|
+
Focus event reporting requires explicit terminal support and configuration. Some terminals don't support it at all.
|
|
89
|
+
|
|
90
|
+
### The Solution
|
|
91
|
+
|
|
92
|
+
Don't rely on focus events for critical functionality. Treat them as nice-to-have enhancements. If your application shows stale data when the user returns, periodically refresh instead of waiting for focus events.
|
|
@@ -5,37 +5,54 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# App All Events Example
|
|
7
7
|
|
|
8
|
-
This example application captures and visualizes every event supported by `ratatui_ruby`. It serves as a comprehensive reference for event handling and a demonstration of
|
|
8
|
+
This example application captures and visualizes every event supported by `ratatui_ruby`. It serves as a comprehensive reference for event handling and a demonstration of the Proto-TEA architectural pattern.
|
|
9
9
|
|
|
10
|
-
## Architecture:
|
|
10
|
+
## Architecture: Proto-TEA (Model-View-Update)
|
|
11
11
|
|
|
12
|
-
This application demonstrates
|
|
12
|
+
This application demonstrates **unidirectional data flow** inspired by The Elm Architecture. This separation ensures that state management is predictable and easy to test.
|
|
13
13
|
|
|
14
|
-
### 1. Model (`model
|
|
15
|
-
|
|
14
|
+
### 1. Model (`model/app_model.rb`)
|
|
15
|
+
A single immutable `Data.define` object holding **all** application state:
|
|
16
|
+
* Event log entries
|
|
17
|
+
* Focus state
|
|
18
|
+
* Window size
|
|
19
|
+
* Highlight timestamps
|
|
20
|
+
* Color cycle index
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
* **`EventEntry` (`model/event_entry.rb`)**: A value object representing a single recorded event.
|
|
22
|
+
State changes use `.with(...)` to return a new Model instance.
|
|
19
23
|
|
|
20
|
-
### 2.
|
|
21
|
-
|
|
24
|
+
### 2. Msg (`model/msg.rb`)
|
|
25
|
+
Semantic value objects that decouple raw terminal events from business logic:
|
|
26
|
+
* `Msg::Input` — keyboard, mouse, or paste events
|
|
27
|
+
* `Msg::Resize` — terminal size changes
|
|
28
|
+
* `Msg::Focus` — focus gained/lost
|
|
29
|
+
* `Msg::Quit` — exit signal
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
### 3. Update (`update.rb`)
|
|
32
|
+
A **pure function** that computes the next state:
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
```ruby
|
|
35
|
+
Update.call(msg, model) -> Model
|
|
36
|
+
```
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
* **Sub-views**: `Counts`, `Live`, `Log`, `Controls`. Each is a small, focused component that renders a specific part of the screen based on the data in `ViewState`.
|
|
38
|
+
All logic previously in `Events.record` now lives here. The function never mutates, never draws, never performs IO.
|
|
31
39
|
|
|
32
|
-
### 4.
|
|
33
|
-
|
|
40
|
+
### 4. View (`view/`)
|
|
41
|
+
Pure rendering logic. Views accept the immutable `AppModel` and draw to the screen.
|
|
42
|
+
* **`View::App`**: Root view handling high-level layout
|
|
43
|
+
* **Sub-views**: `Counts`, `Live`, `Log`, `Controls`
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
### 5. Runtime (`app.rb`)
|
|
46
|
+
The MVU loop:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
loop do
|
|
50
|
+
tui.draw { |f| view.call(model, tui, f, f.area) }
|
|
51
|
+
msg = map_event_to_msg(tui.poll_event, model)
|
|
52
|
+
break if msg.is_a?(Msg::Quit)
|
|
53
|
+
model = Update.call(msg, model)
|
|
54
|
+
end
|
|
55
|
+
```
|
|
39
56
|
|
|
40
57
|
## Library Features Showcased
|
|
41
58
|
|
|
@@ -57,10 +74,10 @@ Reading this code will teach you how to:
|
|
|
57
74
|
If you are building an app and your logic isn't catching `Ctrl+Left`, run this app and press the keys. You will see exactly how `ratatui_ruby` parses that input (e.g., is it a `Key` event? What are the modifiers?).
|
|
58
75
|
|
|
59
76
|
### "How do I structure a real app?"
|
|
60
|
-
Hello World examples are great, but they don't scale. This example shows how to structure an application that can grow. By
|
|
77
|
+
Hello World examples are great, but they don't scale. This example shows how to structure an application that can grow. By using immutable state and pure functions, it solves the problem of "where does my state live and how does it change?"
|
|
61
78
|
|
|
62
|
-
### "How do I
|
|
63
|
-
|
|
79
|
+
### "How do I test my business logic?"
|
|
80
|
+
The `Update` function is pure. You can test it by constructing a `Msg`, calling `Update.call(msg, model)`, and asserting on the returned `Model`. No mocking required.
|
|
64
81
|
|
|
65
82
|
## Comparison: Choosing an Architecture
|
|
66
83
|
|
|
@@ -68,14 +85,15 @@ Complex applications require structured state habits. `AppAllEvents` and the [Co
|
|
|
68
85
|
|
|
69
86
|
### The Dashboard Approach (AppAllEvents)
|
|
70
87
|
|
|
71
|
-
Dashboards display data. They rarely require complex mouse interaction.
|
|
88
|
+
Dashboards display data. They rarely require complex mouse interaction. Proto-TEA works best here. State is immutable. Logic is pure. Updates are predictable. This simplifies testing.
|
|
72
89
|
|
|
73
90
|
Use this pattern for logs, monitors, and data viewers.
|
|
74
91
|
|
|
75
92
|
### The Tool Approach (Color Picker)
|
|
76
93
|
|
|
77
|
-
Tools require interaction. Users click buttons and drag sliders.
|
|
94
|
+
Tools require interaction. Users click buttons and drag sliders. Each UI component needs to know where it exists on screen for hit testing.
|
|
78
95
|
|
|
79
|
-
The Color Picker uses a "
|
|
96
|
+
The Color Picker uses a "Proto-Kit (Component-Based)" pattern. Each component encapsulates its own rendering, state, and event handling. The Container routes events and coordinates cross-component effects.
|
|
80
97
|
|
|
81
98
|
Use this pattern for forms, editors, and mouse-driven tools.
|
|
99
|
+
|
|
@@ -7,8 +7,9 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
|
7
7
|
$LOAD_PATH.unshift File.expand_path(__dir__)
|
|
8
8
|
|
|
9
9
|
require "ratatui_ruby"
|
|
10
|
-
require_relative "model/
|
|
11
|
-
require_relative "
|
|
10
|
+
require_relative "model/app_model"
|
|
11
|
+
require_relative "model/msg"
|
|
12
|
+
require_relative "update"
|
|
12
13
|
require_relative "view/app_view"
|
|
13
14
|
|
|
14
15
|
# Demonstrates the full range of terminal events supported by RatatuiRuby.
|
|
@@ -20,6 +21,14 @@ require_relative "view/app_view"
|
|
|
20
21
|
#
|
|
21
22
|
# Use it to verify your terminal's capabilities or as a reference for complex event handling.
|
|
22
23
|
#
|
|
24
|
+
# === Architecture
|
|
25
|
+
#
|
|
26
|
+
# This example uses the Proto-TEA (Model-View-Update) pattern:
|
|
27
|
+
# - **Model**: Immutable AppModel holds all state
|
|
28
|
+
# - **Msg**: Semantic message types decouple events from logic
|
|
29
|
+
# - **Update**: Pure function computes next state
|
|
30
|
+
# - **View**: Renders Model to screen
|
|
31
|
+
#
|
|
23
32
|
# === Examples
|
|
24
33
|
#
|
|
25
34
|
# # Run from the command line:
|
|
@@ -31,62 +40,56 @@ class AppAllEvents
|
|
|
31
40
|
# List of all event types tracked by this application.
|
|
32
41
|
EVENT_TYPES = %i[key mouse resize paste focus none].freeze
|
|
33
42
|
|
|
34
|
-
# Creates a new AppAllEvents instance and initializes its
|
|
43
|
+
# Creates a new AppAllEvents instance and initializes its view.
|
|
35
44
|
def initialize
|
|
36
45
|
@view = View::App.new
|
|
37
|
-
@events = Events.new
|
|
38
|
-
@focused = true
|
|
39
|
-
@last_dimensions = [80, 24]
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
# Starts the application event loop.
|
|
43
49
|
#
|
|
50
|
+
# Implements the MVU (Model-View-Update) runtime:
|
|
51
|
+
# 1. **View**: Render current model
|
|
52
|
+
# 2. **Poll**: Get next event
|
|
53
|
+
# 3. **Map**: Convert raw event to semantic Msg
|
|
54
|
+
# 4. **Update**: Compute next model
|
|
55
|
+
#
|
|
44
56
|
# === Example
|
|
45
57
|
#
|
|
46
58
|
# app.run
|
|
47
59
|
def run
|
|
48
60
|
RatatuiRuby.run do |tui|
|
|
49
|
-
|
|
61
|
+
model = AppModel.initial
|
|
62
|
+
|
|
50
63
|
loop do
|
|
51
|
-
|
|
52
|
-
break if handle_input == :quit
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
64
|
+
tui.draw { |frame| @view.call(model, tui, frame, frame.area) }
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@focused,
|
|
61
|
-
@tui,
|
|
62
|
-
nil
|
|
63
|
-
)
|
|
66
|
+
event = tui.poll_event
|
|
67
|
+
msg = map_event_to_msg(event, model)
|
|
68
|
+
break if msg.is_a?(Msg::Quit)
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
model = Update.call(msg, model)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
66
73
|
end
|
|
67
74
|
|
|
68
|
-
private def
|
|
69
|
-
event = @tui.poll_event
|
|
70
|
-
|
|
75
|
+
private def map_event_to_msg(event, model)
|
|
71
76
|
case event
|
|
72
77
|
when RatatuiRuby::Event::Key
|
|
73
|
-
return
|
|
74
|
-
return
|
|
75
|
-
|
|
78
|
+
return Msg::Quit.new if event.code == "q"
|
|
79
|
+
return Msg::Quit.new if event.code == "c" && event.modifiers.include?("ctrl")
|
|
80
|
+
|
|
81
|
+
Msg::Input.new(event:)
|
|
76
82
|
when RatatuiRuby::Event::Resize
|
|
77
|
-
|
|
78
|
-
@last_dimensions = [event.width, event.height]
|
|
83
|
+
Msg::Resize.new(width: event.width, height: event.height, previous_size: model.window_size)
|
|
79
84
|
when RatatuiRuby::Event::FocusGained
|
|
80
|
-
|
|
81
|
-
@events.record(event)
|
|
85
|
+
Msg::Focus.new(gained: true)
|
|
82
86
|
when RatatuiRuby::Event::FocusLost
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
Msg::Focus.new(gained: false)
|
|
88
|
+
when RatatuiRuby::Event::None
|
|
89
|
+
Msg::NoneEvent.new
|
|
85
90
|
else
|
|
86
|
-
|
|
91
|
+
Msg::Input.new(event:)
|
|
87
92
|
end
|
|
88
|
-
|
|
89
|
-
nil
|
|
90
93
|
end
|
|
91
94
|
end
|
|
92
95
|
|