ratatui_ruby 1.3.0 → 1.4.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.
Files changed (263) hide show
  1. checksums.yaml +4 -4
  2. data/ext/ratatui_ruby/Cargo.lock +2 -1
  3. data/ext/ratatui_ruby/Cargo.toml +2 -1
  4. data/ext/ratatui_ruby/src/events.rs +157 -18
  5. data/lib/ratatui_ruby/test_helper/snapshot.rb +58 -10
  6. data/lib/ratatui_ruby/test_helper/subprocess_timeout.rb +35 -0
  7. data/lib/ratatui_ruby/test_helper.rb +2 -0
  8. data/lib/ratatui_ruby/version.rb +1 -1
  9. metadata +17 -270
  10. data/.builds/ruby-3.2.yml +0 -54
  11. data/.builds/ruby-3.3.yml +0 -54
  12. data/.builds/ruby-3.4.yml +0 -54
  13. data/.builds/ruby-4.0.0.yml +0 -54
  14. data/.pre-commit-config.yaml +0 -16
  15. data/.rubocop.yml +0 -10
  16. data/AGENTS.md +0 -147
  17. data/CHANGELOG.md +0 -771
  18. data/README.md +0 -187
  19. data/README.rdoc +0 -302
  20. data/Rakefile +0 -11
  21. data/Steepfile +0 -50
  22. data/doc/concepts/application_architecture.md +0 -321
  23. data/doc/concepts/application_testing.md +0 -193
  24. data/doc/concepts/async.md +0 -190
  25. data/doc/concepts/custom_widgets.md +0 -247
  26. data/doc/concepts/debugging.md +0 -401
  27. data/doc/concepts/event_handling.md +0 -162
  28. data/doc/concepts/interactive_design.md +0 -146
  29. data/doc/contributors/auditing/parity.md +0 -239
  30. data/doc/contributors/design/ruby_frontend.md +0 -448
  31. data/doc/contributors/design/rust_backend.md +0 -434
  32. data/doc/contributors/design.md +0 -11
  33. data/doc/contributors/developing_examples.md +0 -400
  34. data/doc/contributors/documentation_style.md +0 -121
  35. data/doc/contributors/index.md +0 -21
  36. data/doc/contributors/releasing.md +0 -215
  37. data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -381
  38. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -200
  39. data/doc/contributors/todo/align/term.md +0 -351
  40. data/doc/contributors/todo/align/terminal.md +0 -647
  41. data/doc/contributors/todo/future_work.md +0 -169
  42. data/doc/contributors/upstream_requests/paragraph_span_rects.md +0 -259
  43. data/doc/contributors/upstream_requests/tab_rects.md +0 -173
  44. data/doc/contributors/upstream_requests/title_rects.md +0 -132
  45. data/doc/custom.css +0 -22
  46. data/doc/getting_started/quickstart.md +0 -291
  47. data/doc/getting_started/why.md +0 -93
  48. data/doc/images/app_all_events.png +0 -0
  49. data/doc/images/app_cli_rich_moments.gif +0 -0
  50. data/doc/images/app_color_picker.png +0 -0
  51. data/doc/images/app_debugging_showcase.gif +0 -0
  52. data/doc/images/app_debugging_showcase.png +0 -0
  53. data/doc/images/app_external_editor.gif +0 -0
  54. data/doc/images/app_login_form.png +0 -0
  55. data/doc/images/app_stateful_interaction.png +0 -0
  56. data/doc/images/verify_quickstart_dsl.png +0 -0
  57. data/doc/images/verify_quickstart_layout.png +0 -0
  58. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  59. data/doc/images/verify_readme_usage.png +0 -0
  60. data/doc/images/widget_barchart.png +0 -0
  61. data/doc/images/widget_block.png +0 -0
  62. data/doc/images/widget_box.png +0 -0
  63. data/doc/images/widget_calendar.png +0 -0
  64. data/doc/images/widget_canvas.png +0 -0
  65. data/doc/images/widget_cell.png +0 -0
  66. data/doc/images/widget_center.png +0 -0
  67. data/doc/images/widget_chart.png +0 -0
  68. data/doc/images/widget_gauge.png +0 -0
  69. data/doc/images/widget_layout_split.png +0 -0
  70. data/doc/images/widget_line_gauge.png +0 -0
  71. data/doc/images/widget_list.png +0 -0
  72. data/doc/images/widget_map.png +0 -0
  73. data/doc/images/widget_overlay.png +0 -0
  74. data/doc/images/widget_popup.png +0 -0
  75. data/doc/images/widget_ratatui_logo.png +0 -0
  76. data/doc/images/widget_ratatui_mascot.png +0 -0
  77. data/doc/images/widget_rect.png +0 -0
  78. data/doc/images/widget_render.png +0 -0
  79. data/doc/images/widget_rich_text.png +0 -0
  80. data/doc/images/widget_scroll_text.png +0 -0
  81. data/doc/images/widget_scrollbar.png +0 -0
  82. data/doc/images/widget_sparkline.png +0 -0
  83. data/doc/images/widget_style_colors.png +0 -0
  84. data/doc/images/widget_table.png +0 -0
  85. data/doc/images/widget_tabs.png +0 -0
  86. data/doc/images/widget_text_width.png +0 -0
  87. data/doc/index.md +0 -34
  88. data/doc/troubleshooting/async.md +0 -4
  89. data/doc/troubleshooting/terminal_limitations.md +0 -131
  90. data/doc/troubleshooting/tui_output.md +0 -197
  91. data/examples/app_all_events/README.md +0 -114
  92. data/examples/app_all_events/app.rb +0 -98
  93. data/examples/app_all_events/model/app_model.rb +0 -159
  94. data/examples/app_all_events/model/event_color_cycle.rb +0 -43
  95. data/examples/app_all_events/model/event_entry.rb +0 -94
  96. data/examples/app_all_events/model/msg.rb +0 -39
  97. data/examples/app_all_events/model/timestamp.rb +0 -56
  98. data/examples/app_all_events/update.rb +0 -75
  99. data/examples/app_all_events/view/app_view.rb +0 -80
  100. data/examples/app_all_events/view/controls_view.rb +0 -54
  101. data/examples/app_all_events/view/counts_view.rb +0 -61
  102. data/examples/app_all_events/view/live_view.rb +0 -72
  103. data/examples/app_all_events/view/log_view.rb +0 -57
  104. data/examples/app_all_events/view.rb +0 -9
  105. data/examples/app_cli_rich_moments/README.md +0 -81
  106. data/examples/app_cli_rich_moments/app.rb +0 -189
  107. data/examples/app_color_picker/README.md +0 -156
  108. data/examples/app_color_picker/app.rb +0 -76
  109. data/examples/app_color_picker/clipboard.rb +0 -86
  110. data/examples/app_color_picker/color.rb +0 -193
  111. data/examples/app_color_picker/controls.rb +0 -92
  112. data/examples/app_color_picker/copy_dialog.rb +0 -168
  113. data/examples/app_color_picker/export_pane.rb +0 -128
  114. data/examples/app_color_picker/harmony.rb +0 -58
  115. data/examples/app_color_picker/input.rb +0 -176
  116. data/examples/app_color_picker/main_container.rb +0 -180
  117. data/examples/app_color_picker/palette.rb +0 -111
  118. data/examples/app_debugging_showcase/README.md +0 -119
  119. data/examples/app_debugging_showcase/app.rb +0 -318
  120. data/examples/app_external_editor/README.md +0 -62
  121. data/examples/app_external_editor/app.rb +0 -344
  122. data/examples/app_login_form/README.md +0 -58
  123. data/examples/app_login_form/app.rb +0 -109
  124. data/examples/app_stateful_interaction/README.md +0 -35
  125. data/examples/app_stateful_interaction/app.rb +0 -328
  126. data/examples/timeout_demo.rb +0 -45
  127. data/examples/verify_quickstart_dsl/README.md +0 -55
  128. data/examples/verify_quickstart_dsl/app.rb +0 -49
  129. data/examples/verify_quickstart_layout/README.md +0 -77
  130. data/examples/verify_quickstart_layout/app.rb +0 -73
  131. data/examples/verify_quickstart_lifecycle/README.md +0 -68
  132. data/examples/verify_quickstart_lifecycle/app.rb +0 -62
  133. data/examples/verify_readme_usage/README.md +0 -49
  134. data/examples/verify_readme_usage/app.rb +0 -42
  135. data/examples/verify_website_managed/README.md +0 -48
  136. data/examples/verify_website_managed/app.rb +0 -36
  137. data/examples/verify_website_menu/README.md +0 -60
  138. data/examples/verify_website_menu/app.rb +0 -84
  139. data/examples/verify_website_spinner/README.md +0 -44
  140. data/examples/verify_website_spinner/app.rb +0 -34
  141. data/examples/widget_barchart/README.md +0 -58
  142. data/examples/widget_barchart/app.rb +0 -240
  143. data/examples/widget_block/README.md +0 -44
  144. data/examples/widget_block/app.rb +0 -258
  145. data/examples/widget_box/README.md +0 -54
  146. data/examples/widget_box/app.rb +0 -255
  147. data/examples/widget_calendar/README.md +0 -48
  148. data/examples/widget_calendar/app.rb +0 -115
  149. data/examples/widget_canvas/README.md +0 -31
  150. data/examples/widget_canvas/app.rb +0 -130
  151. data/examples/widget_cell/README.md +0 -45
  152. data/examples/widget_cell/app.rb +0 -112
  153. data/examples/widget_center/README.md +0 -33
  154. data/examples/widget_center/app.rb +0 -118
  155. data/examples/widget_chart/README.md +0 -50
  156. data/examples/widget_chart/app.rb +0 -220
  157. data/examples/widget_gauge/README.md +0 -50
  158. data/examples/widget_gauge/app.rb +0 -229
  159. data/examples/widget_layout_split/README.md +0 -53
  160. data/examples/widget_layout_split/app.rb +0 -260
  161. data/examples/widget_line_gauge/README.md +0 -50
  162. data/examples/widget_line_gauge/app.rb +0 -219
  163. data/examples/widget_list/README.md +0 -58
  164. data/examples/widget_list/app.rb +0 -382
  165. data/examples/widget_map/README.md +0 -48
  166. data/examples/widget_map/app.rb +0 -95
  167. data/examples/widget_overlay/README.md +0 -45
  168. data/examples/widget_overlay/app.rb +0 -250
  169. data/examples/widget_popup/README.md +0 -45
  170. data/examples/widget_popup/app.rb +0 -106
  171. data/examples/widget_ratatui_logo/README.md +0 -43
  172. data/examples/widget_ratatui_logo/app.rb +0 -104
  173. data/examples/widget_ratatui_mascot/README.md +0 -43
  174. data/examples/widget_ratatui_mascot/app.rb +0 -95
  175. data/examples/widget_rect/README.md +0 -53
  176. data/examples/widget_rect/app.rb +0 -222
  177. data/examples/widget_render/README.md +0 -46
  178. data/examples/widget_render/app.rb +0 -186
  179. data/examples/widget_render/app.rbs +0 -41
  180. data/examples/widget_rich_text/README.md +0 -44
  181. data/examples/widget_rich_text/app.rb +0 -193
  182. data/examples/widget_scroll_text/README.md +0 -46
  183. data/examples/widget_scroll_text/app.rb +0 -109
  184. data/examples/widget_scrollbar/README.md +0 -46
  185. data/examples/widget_scrollbar/app.rb +0 -155
  186. data/examples/widget_sparkline/README.md +0 -51
  187. data/examples/widget_sparkline/app.rb +0 -277
  188. data/examples/widget_style_colors/README.md +0 -43
  189. data/examples/widget_style_colors/app.rb +0 -83
  190. data/examples/widget_table/README.md +0 -57
  191. data/examples/widget_table/app.rb +0 -285
  192. data/examples/widget_tabs/README.md +0 -50
  193. data/examples/widget_tabs/app.rb +0 -183
  194. data/examples/widget_text_width/README.md +0 -44
  195. data/examples/widget_text_width/app.rb +0 -117
  196. data/migrate_to_buffer.rb +0 -145
  197. data/mise.toml +0 -8
  198. data/tasks/autodoc/examples.rb +0 -87
  199. data/tasks/autodoc/member.rb +0 -58
  200. data/tasks/autodoc/name.rb +0 -21
  201. data/tasks/autodoc.rake +0 -21
  202. data/tasks/bump/bump_workflow.rb +0 -49
  203. data/tasks/bump/cargo_lockfile.rb +0 -21
  204. data/tasks/bump/changelog.rb +0 -104
  205. data/tasks/bump/header.rb +0 -32
  206. data/tasks/bump/history.rb +0 -32
  207. data/tasks/bump/links.rb +0 -69
  208. data/tasks/bump/manifest.rb +0 -33
  209. data/tasks/bump/patch_release.rb +0 -19
  210. data/tasks/bump/release_branch.rb +0 -17
  211. data/tasks/bump/release_from_trunk.rb +0 -49
  212. data/tasks/bump/repository.rb +0 -54
  213. data/tasks/bump/ruby_gem.rb +0 -29
  214. data/tasks/bump/sem_ver.rb +0 -44
  215. data/tasks/bump/unreleased_section.rb +0 -73
  216. data/tasks/bump.rake +0 -61
  217. data/tasks/doc/documentation.rb +0 -59
  218. data/tasks/doc/link/file_url.rb +0 -30
  219. data/tasks/doc/link/relative_path.rb +0 -61
  220. data/tasks/doc/link/web_url.rb +0 -55
  221. data/tasks/doc/link.rb +0 -52
  222. data/tasks/doc/link_audit.rb +0 -116
  223. data/tasks/doc/problem.rb +0 -40
  224. data/tasks/doc/source_file.rb +0 -93
  225. data/tasks/doc.rake +0 -905
  226. data/tasks/example_viewer.html.erb +0 -172
  227. data/tasks/extension.rake +0 -14
  228. data/tasks/license/headers_md.rb +0 -223
  229. data/tasks/license/headers_rb.rb +0 -210
  230. data/tasks/license/license_utils.rb +0 -130
  231. data/tasks/license/snippets_md.rb +0 -315
  232. data/tasks/license/snippets_rdoc.rb +0 -150
  233. data/tasks/license.rake +0 -91
  234. data/tasks/lint.rake +0 -170
  235. data/tasks/rbs_predicates/predicate_catalog.rb +0 -52
  236. data/tasks/rbs_predicates/predicate_tests.rb +0 -124
  237. data/tasks/rbs_predicates/rbs_signature.rb +0 -63
  238. data/tasks/rbs_predicates.rake +0 -31
  239. data/tasks/rdoc_config.rb +0 -29
  240. data/tasks/resources/build.yml.erb +0 -60
  241. data/tasks/resources/index.html.erb +0 -141
  242. data/tasks/resources/rubies.yml +0 -7
  243. data/tasks/sourcehut.rake +0 -122
  244. data/tasks/steep.rake +0 -11
  245. data/tasks/terminal_preview/app_screenshot.rb +0 -45
  246. data/tasks/terminal_preview/crash_report.rb +0 -54
  247. data/tasks/terminal_preview/example_app.rb +0 -27
  248. data/tasks/terminal_preview/launcher_script.rb +0 -48
  249. data/tasks/terminal_preview/preview_collection.rb +0 -60
  250. data/tasks/terminal_preview/preview_timing.rb +0 -24
  251. data/tasks/terminal_preview/safety_confirmation.rb +0 -58
  252. data/tasks/terminal_preview/saved_screenshot.rb +0 -56
  253. data/tasks/terminal_preview/system_appearance.rb +0 -13
  254. data/tasks/terminal_preview/terminal_window.rb +0 -138
  255. data/tasks/terminal_preview/window_id.rb +0 -16
  256. data/tasks/terminal_preview.rake +0 -30
  257. data/tasks/test.rake +0 -36
  258. data/tasks/website/index_page.rb +0 -30
  259. data/tasks/website/version.rb +0 -122
  260. data/tasks/website/version_menu.rb +0 -68
  261. data/tasks/website/versioned_documentation.rb +0 -83
  262. data/tasks/website/website.rb +0 -53
  263. data/tasks/website.rake +0 -28
@@ -1,328 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
6
- #++
7
-
8
- $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
9
- require "ratatui_ruby"
10
- require "faker"
11
-
12
- # A "Master Class" example demonstrating Stateful Widget Rendering and Interaction.
13
- #
14
- # This example shows how to:
15
- # 1. Use mutable State objects (ListState, TableState) for selection and scrolling
16
- # 2. Read back the calculated scroll offset from the backend (state.offset)
17
- # 3. Implement precise mouse-click-to-row interaction using that offset
18
- class AppStatefulInteraction
19
- def initialize
20
- # Data Models
21
- # Tables are the categories on the left
22
- @tables = ["Users", "Orders", "Products", "Invoices", "Audit Logs"]
23
- @headers = {
24
- "Users" => ["Name", "Email", "Role"],
25
- "Orders" => ["Order ID", "Status", "Amount"],
26
- "Products" => ["Product", "SKU", "Status"],
27
- "Invoices" => ["Invoice #", "Status", "Amount"],
28
- "Audit Logs" => ["Event", "Action", "IP Address"],
29
- }
30
-
31
- # Generate dummy data for each table
32
- # Use fixed seed for deterministic behavior in CI/Tests
33
- if ENV["CI"] == "true" || ENV["RATA_SEED"]
34
- seed = (ENV["RATA_SEED"] || 12345).to_i
35
- Faker::Config.random = Random.new(seed)
36
- # Also seed Kernel.rand/Array#sample just in case
37
- srand(seed)
38
- end
39
- rand_price = -> { "$#{Faker::Commerce.price(range: 10..500.0)}" }
40
-
41
- @data = {
42
- "Users" => Array.new(50) { [Faker::Name.name, Faker::Internet.email, %w[Admin Editor Viewer].sample] },
43
- "Orders" => Array.new(50) { [Faker::Commerce.promotion_code(digits: 4), ["Completed", "Pending", "Failed"].sample, rand_price.call] },
44
- "Products" => Array.new(50) { [Faker::Commerce.product_name, "SKU-#{Faker::Number.number(digits: 4)}", ["In Stock", "Low Stock"].sample] },
45
- "Invoices" => Array.new(50) { ["INV-#{Faker::Number.number(digits: 6)}", ["Paid", "Unpaid"].sample, rand_price.call] },
46
- "Audit Logs" => Array.new(50) { ["Log #{Faker::Number.unique.number(digits: 3)}", ["Login Success", "Login Failed", "Logout"].sample, Faker::Internet.ip_v4_address] },
47
- }
48
-
49
- # State Objects - These are mutable and persist across frames!
50
- @list_state = RatatuiRuby::ListState.new(nil)
51
- @table_state = RatatuiRuby::TableState.new(nil)
52
-
53
- # Initialize selection
54
- @list_state.select(0)
55
- @table_state.select(0)
56
-
57
- # Active Pane Focus (:list or :table)
58
- @active_pane = :list
59
- end
60
-
61
- def run
62
- RatatuiRuby.run do |tui|
63
- @tui = tui
64
-
65
- # Styles can only be created once TUI is initialized
66
- @style_active = @tui.style(fg: :yellow, modifiers: [:bold])
67
- @style_inactive = @tui.style(fg: :dark_gray)
68
- @style_highlight = @tui.style(bg: :blue, fg: :white, modifiers: [:bold])
69
-
70
- loop do
71
- render
72
- break if handle_input == :quit
73
- end
74
- end
75
- end
76
-
77
- private def render
78
- @tui.draw do |frame|
79
- # 1. Layout
80
- main_area, help_area = @tui.layout_split(
81
- frame.area,
82
- direction: :vertical,
83
- constraints: [
84
- @tui.constraint_fill(1),
85
- @tui.constraint_length(1),
86
- ]
87
- )
88
-
89
- list_area, table_area = @tui.layout_split(
90
- main_area,
91
- direction: :horizontal,
92
- constraints: [
93
- @tui.constraint_percentage(30),
94
- @tui.constraint_percentage(70),
95
- ]
96
- )
97
-
98
- # Save areas for hit testing
99
- @list_area = list_area
100
- @table_area = table_area
101
-
102
- # 2. Render List (Left Pane)
103
- render_list(frame, list_area)
104
-
105
- # 3. Render Table (Right Pane)
106
- render_table(frame, table_area)
107
-
108
- # 4. Render Help
109
- help_text = "q: Quit | Tab/Arrows: Nav | Home/End: Jump | Mouse: Click rows"
110
- frame.render_widget(@tui.paragraph(text: help_text), help_area)
111
- end
112
- end
113
-
114
- private def render_list(frame, area)
115
- is_active = @active_pane == :list
116
-
117
- # Render main list
118
- list = @tui.list(
119
- items: @tables,
120
- block: @tui.block(
121
- title: " Tables ",
122
- borders: [:all],
123
- border_style: is_active ? @style_active : @style_inactive
124
- ),
125
- highlight_style: @style_highlight
126
- )
127
- # KEY STEP: Pass the state object!
128
- frame.render_stateful_widget(list, area, @list_state)
129
-
130
- # Render Scrollbar
131
- scrollbar = @tui.scrollbar(
132
- content_length: 0,
133
- position: 0,
134
- orientation: :vertical_right,
135
- track_symbol: nil,
136
- thumb_symbol: "▐"
137
- )
138
- scrollbar_state = RatatuiRuby::ScrollbarState.new(@tables.size)
139
- scrollbar_state.position = @list_state.offset
140
- scrollbar_state.viewport_content_length = area.height - 2
141
-
142
- frame.render_stateful_widget(scrollbar, area, scrollbar_state)
143
- end
144
-
145
- private def render_table(frame, area)
146
- is_active = @active_pane == :table
147
-
148
- # Get current data based on list selection
149
- current_table = @tables[@list_state.selected || 0]
150
- rows = @data[current_table]
151
-
152
- # Render table
153
- table = @tui.table(
154
- rows:,
155
- header: @headers[current_table],
156
- widths: [
157
- @tui.constraint_percentage(30),
158
- @tui.constraint_percentage(40),
159
- @tui.constraint_percentage(30),
160
- ],
161
- block: @tui.block(
162
- title: " #{current_table} Data ",
163
- borders: [:all],
164
- border_style: is_active ? @style_active : @style_inactive
165
- ),
166
- row_highlight_style: @style_highlight
167
- )
168
-
169
- frame.render_stateful_widget(table, area, @table_state)
170
-
171
- # Render Scrollbar
172
- scrollbar = @tui.scrollbar(
173
- content_length: 0,
174
- position: 0,
175
- orientation: :vertical_right,
176
- track_symbol: nil,
177
- thumb_symbol: "▐"
178
- )
179
- scrollbar_state = RatatuiRuby::ScrollbarState.new(rows.size)
180
- scrollbar_state.position = @table_state.offset
181
- scrollbar_state.viewport_content_length = area.height - 4 # borders + header + margin
182
-
183
- frame.render_stateful_widget(scrollbar, area, scrollbar_state)
184
- end
185
-
186
- private def handle_input
187
- case @tui.poll_event
188
- in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
189
- :quit
190
-
191
- # Navigation
192
- in { type: :key, code: "tab" } | { type: :key, code: "right" } | { type: :key, code: "left" }
193
- @active_pane = (@active_pane == :list) ? :table : :list
194
-
195
- in { type: :key, code: "down" }
196
- move_selection_next
197
-
198
- in { type: :key, code: "up" }
199
- move_selection_previous
200
-
201
- in { type: :key, code: "home" }
202
- move_selection_first
203
-
204
- in { type: :key, code: "end" }
205
- move_selection_last
206
-
207
- # Mouse Interaction
208
- in { type: :mouse, kind: "down", x:, y: }
209
- handle_click(x, y)
210
-
211
- else
212
- # no-op
213
- end
214
- end
215
-
216
- private def move_selection_next
217
- if @active_pane == :list
218
- current = @list_state.selected || 0
219
- max_index = @tables.size - 1
220
- return if current >= max_index # Already at end
221
-
222
- @list_state.select_next
223
- reset_table_selection
224
- else
225
- current = @table_state.selected || 0
226
- max_index = current_table_rows.size - 1
227
- return if current >= max_index
228
-
229
- @table_state.select_next
230
- end
231
- end
232
-
233
- private def move_selection_previous
234
- if @active_pane == :list
235
- current = @list_state.selected || 0
236
- return if current <= 0 # Already at start
237
-
238
- @list_state.select_previous
239
- reset_table_selection
240
- else
241
- current = @table_state.selected || 0
242
- return if current <= 0
243
-
244
- @table_state.select_previous
245
- end
246
- end
247
-
248
- private def move_selection_first
249
- if @active_pane == :list
250
- current = @list_state.selected || 0
251
- return if current == 0 # Already at first
252
-
253
- @list_state.select_first
254
- reset_table_selection
255
- else
256
- @table_state.select_first
257
- end
258
- end
259
-
260
- private def move_selection_last
261
- if @active_pane == :list
262
- current = @list_state.selected || 0
263
- max_index = @tables.size - 1
264
- return if current == max_index # Already at last
265
-
266
- @list_state.select(max_index)
267
- reset_table_selection
268
- else
269
- @table_state.select(current_table_rows.size - 1)
270
- end
271
- end
272
-
273
- private def reset_table_selection
274
- @table_state.select(0)
275
- @table_state.select_column(nil)
276
- end
277
-
278
- private def current_table_rows
279
- @data[@tables[@list_state.selected || 0]]
280
- end
281
-
282
- private def handle_click(x, y)
283
- if @list_area.contains?(x, y)
284
- handle_list_click(y)
285
- elsif @table_area.contains?(x, y)
286
- handle_table_click(y)
287
- end
288
- end
289
-
290
- private def handle_list_click(mouse_y)
291
- @active_pane = :list
292
-
293
- # CRITICAL: Read back the offset!
294
- # Formula: clicked_index = (mouse_y - list_top - border_width) + offset
295
- offset = @list_state.offset
296
- list_top = @list_area.y
297
- border_width = 1 # Top border
298
-
299
- clicked_row = (mouse_y - list_top - border_width) + offset
300
-
301
- if clicked_row >= 0 && clicked_row < @tables.size
302
- @list_state.select(clicked_row)
303
- @table_state.select(0) # Reset table when category changes
304
- end
305
- end
306
-
307
- private def handle_table_click(mouse_y)
308
- @active_pane = :table
309
-
310
- # CRITICAL: Read back the offset!
311
- # Formula: clicked_index = (mouse_y - table_top - border - header_height - margin) + offset
312
- offset = @table_state.offset
313
- table_top = @table_area.y
314
- border_width = 1
315
- header_height = 1
316
- # No header_margin without Row margin
317
- effective_top = table_top + border_width + header_height
318
-
319
- clicked_row = (mouse_y - effective_top) + offset
320
-
321
- current_table_data = @data[@tables[@list_state.selected || 0]]
322
- if clicked_row >= 0 && clicked_row < current_table_data.size
323
- @table_state.select(clicked_row)
324
- end
325
- end
326
- end
327
-
328
- AppStatefulInteraction.new.run if __FILE__ == $PROGRAM_NAME
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
6
- #++
7
-
8
- # Timeout Demo: Non-Blocking Event Polling
9
- #
10
- # This demo shows how to use poll_event with a timeout for game loops
11
- # and animation systems that need to update at a fixed frame rate
12
- # regardless of user input.
13
- #
14
- # Run: bundle exec ruby examples/timeout_demo.rb
15
- #
16
- # Expected behavior:
17
- # - "Tick..." prints every 100ms continuously
18
- # - Pressing a key prints "Key Pressed: [key]" immediately
19
- # - Press 'q' to quit
20
-
21
- require "bundler/setup"
22
- require "ratatui_ruby"
23
-
24
- puts "Timeout Demo - Press 'q' to quit"
25
- puts "Watch: continuous ticks with responsive key handling"
26
- puts
27
-
28
- tick_count = 0
29
- running = true
30
-
31
- while running
32
- # Poll with 100ms timeout (~10 FPS tick rate)
33
- event = RatatuiRuby.poll_event(timeout: 0.1)
34
-
35
- if event.none?
36
- # No input, just tick
37
- tick_count += 1
38
- puts "Tick #{tick_count}..."
39
- elsif event.key?
40
- puts "Key Pressed: #{event.code}"
41
- running = false if event.code == "q"
42
- end
43
- end
44
-
45
- puts "\nGoodbye!"
@@ -1,55 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Quickstart DSL Verification
7
-
8
- Verifies the "Simplified API" tutorial in the [Quickstart](../../doc/getting_started/quickstart.md#simplified-api).
9
-
10
- This example exists as a documentation regression test. It ensures the recommended TUI facade and managed lifecycle workflow remains functional.
11
-
12
- ## Usage
13
-
14
- <!-- SPDX-SnippetBegin -->
15
- <!--
16
- SPDX-FileCopyrightText: 2026 Kerrick Long
17
- SPDX-License-Identifier: MIT-0
18
- -->
19
- <!-- SYNC:START:app.rb:main -->
20
- ```ruby
21
- # 1. Initialize the terminal, start the run loop, and ensure the terminal is restored.
22
- RatatuiRuby.run do |tui|
23
- loop do
24
- # 2. Create your UI with methods instead of classes.
25
- view = tui.paragraph(
26
- text: "Hello, Ratatui! Press 'q' to quit.",
27
- alignment: :center,
28
- block: tui.block(
29
- title: "My Ruby TUI App",
30
- title_alignment: :center,
31
- borders: [:all],
32
- border_color: "cyan",
33
- style: { fg: "white" }
34
- )
35
- )
36
-
37
- # 3. Use RatatuiRuby methods, too.
38
- tui.draw do |frame|
39
- frame.render_widget(view, frame.area)
40
- end
41
-
42
- # 4. Poll for events with pattern matching
43
- case tui.poll_event
44
- in { type: :key, code: "q" }
45
- break
46
- else
47
- # Ignore other events
48
- end
49
- end
50
- end
51
- ```
52
- <!-- SYNC:END -->
53
- <!-- SPDX-SnippetEnd -->
54
-
55
- [![verify_quickstart_dsl](../../doc/images/verify_quickstart_dsl.png)](../../doc/getting_started/quickstart.md#simplified-api)
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
9
-
10
- require "ratatui_ruby"
11
-
12
- class VerifyQuickstartDsl
13
- def run
14
- # [SYNC:START:main]
15
- # 1. Initialize the terminal, start the run loop, and ensure the terminal is restored.
16
- RatatuiRuby.run do |tui|
17
- loop do
18
- # 2. Create your UI with methods instead of classes.
19
- view = tui.paragraph(
20
- text: "Hello, Ratatui! Press 'q' to quit.",
21
- alignment: :center,
22
- block: tui.block(
23
- title: "My Ruby TUI App",
24
- title_alignment: :center,
25
- borders: [:all],
26
- border_style: { fg: "cyan" },
27
- style: { fg: "white" }
28
- )
29
- )
30
-
31
- # 3. Use RatatuiRuby methods, too.
32
- tui.draw do |frame|
33
- frame.render_widget(view, frame.area)
34
- end
35
-
36
- # 4. Poll for events with pattern matching
37
- case tui.poll_event
38
- in { type: :key, code: "q" }
39
- break
40
- else
41
- # Ignore other events
42
- end
43
- end
44
- end
45
- # [SYNC:END:main]
46
- end
47
- end
48
-
49
- VerifyQuickstartDsl.new.run if __FILE__ == $PROGRAM_NAME
@@ -1,77 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Quickstart Layout Verification
7
-
8
- Verifies the "Adding Layouts" tutorial in the [Quickstart](../../doc/getting_started/quickstart.md#adding-layouts).
9
-
10
- This example exists as a documentation regression test. It ensures the layout and constraints examples remain functional.
11
-
12
- ## Usage
13
-
14
- <!-- SPDX-SnippetBegin -->
15
- <!--
16
- SPDX-FileCopyrightText: 2026 Kerrick Long
17
- SPDX-License-Identifier: MIT-0
18
- -->
19
- <!-- SYNC:START:app.rb:main -->
20
- ```ruby
21
- loop do
22
- tui.draw do |frame|
23
- # 1. Split the screen
24
- top, bottom = tui.layout_split(
25
- frame.area,
26
- direction: :vertical,
27
- constraints: [
28
- tui.constraint_percentage(75),
29
- tui.constraint_percentage(25),
30
- ]
31
- )
32
-
33
- # 2. Render Top Widget
34
- frame.render_widget(
35
- tui.paragraph(
36
- text: "Hello, Ratatui!",
37
- alignment: :center,
38
- block: tui.block(title: "Content", borders: [:all], border_color: "cyan")
39
- ),
40
- top
41
- )
42
-
43
- # 3. Render Bottom Widget with Styled Text
44
- # We use a Line of Spans to style specific characters
45
- text_line = tui.text_line(
46
- spans: [
47
- tui.text_span(content: "Press '"),
48
- tui.text_span(
49
- content: "q",
50
- style: tui.style(modifiers: [:bold, :underlined])
51
- ),
52
- tui.text_span(content: "' to quit."),
53
- ],
54
- alignment: :center
55
- )
56
-
57
- frame.render_widget(
58
- tui.paragraph(
59
- text: text_line,
60
- block: tui.block(title: "Controls", borders: [:all])
61
- ),
62
- bottom
63
- )
64
- end
65
-
66
- case tui.poll_event
67
- in { type: :key, code: "q" }
68
- break
69
- else
70
- # Ignore other events
71
- end
72
- end
73
- ```
74
- <!-- SYNC:END -->
75
- <!-- SPDX-SnippetEnd -->
76
-
77
- [![verify_quickstart_layout](../../doc/images/verify_quickstart_layout.png)](../../doc/getting_started/quickstart.md#adding-layouts)
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: MIT-0
6
- #++
7
-
8
- $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
9
-
10
- require "ratatui_ruby"
11
-
12
- class VerifyQuickstartLayout
13
- def run
14
- RatatuiRuby.run do |tui|
15
- # [SYNC:START:main]
16
- loop do
17
- tui.draw do |frame|
18
- # 1. Split the screen
19
- top, bottom = tui.layout_split(
20
- frame.area,
21
- direction: :vertical,
22
- constraints: [
23
- tui.constraint_percentage(75),
24
- tui.constraint_percentage(25),
25
- ]
26
- )
27
-
28
- # 2. Render Top Widget
29
- frame.render_widget(
30
- tui.paragraph(
31
- text: "Hello, Ratatui!",
32
- alignment: :center,
33
- block: tui.block(title: "Content", borders: [:all], border_style: { fg: "cyan" })
34
- ),
35
- top
36
- )
37
-
38
- # 3. Render Bottom Widget with Styled Text
39
- # We use a Line of Spans to style specific characters
40
- text_line = tui.text_line(
41
- spans: [
42
- tui.text_span(content: "Press '"),
43
- tui.text_span(
44
- content: "q",
45
- style: tui.style(modifiers: [:bold, :underlined])
46
- ),
47
- tui.text_span(content: "' to quit."),
48
- ],
49
- alignment: :center
50
- )
51
-
52
- frame.render_widget(
53
- tui.paragraph(
54
- text: text_line,
55
- block: tui.block(title: "Controls", borders: [:all])
56
- ),
57
- bottom
58
- )
59
- end
60
-
61
- case tui.poll_event
62
- in { type: :key, code: "q" }
63
- break
64
- else
65
- # Ignore other events
66
- end
67
- end
68
- # [SYNC:END:main]
69
- end
70
- end
71
- end
72
-
73
- VerifyQuickstartLayout.new.run if __FILE__ == $PROGRAM_NAME
@@ -1,68 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Quickstart Lifecycle Verification
7
-
8
- Verifies the "Basic Application" tutorial in the [Quickstart](../../doc/getting_started/quickstart.md#basic-application).
9
-
10
- This example exists as a documentation regression test. It ensures the core lifecycle example presented to new users remains functional.
11
-
12
- ## Usage
13
-
14
- <!-- SPDX-SnippetBegin -->
15
- <!--
16
- SPDX-FileCopyrightText: 2026 Kerrick Long
17
- SPDX-License-Identifier: MIT-0
18
- -->
19
- <!-- SYNC:START:app.rb:main -->
20
- ```ruby
21
- # 1. Initialize the terminal
22
- RatatuiRuby.init_terminal
23
-
24
- begin
25
- # The Main Loop
26
- loop do
27
- # 2. Create your UI (Immediate Mode)
28
- # We define a Paragraph widget inside a Block with a title and borders.
29
- view = RatatuiRuby::Widgets::Paragraph.new(
30
- text: "Hello, Ratatui! Press 'q' to quit.",
31
- alignment: :center,
32
- block: RatatuiRuby::Widgets::Block.new(
33
- title: "My Ruby TUI App",
34
- title_alignment: :center,
35
- borders: [:all],
36
- border_color: "cyan",
37
- style: { fg: "white" }
38
- )
39
- )
40
-
41
- # 3. Draw the UI
42
- RatatuiRuby.draw do |frame|
43
- frame.render_widget(view, frame.area)
44
- end
45
-
46
- # 4. Poll for events
47
- case RatatuiRuby.poll_event
48
- in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
49
- break
50
- else
51
- nil
52
- end
53
-
54
- # 5. Guard against accidental output (optional but recommended)
55
- # Wrap any code that might puts/warn to prevent screen corruption.
56
- RatatuiRuby.guard_io do
57
- # SomeChattyGem.do_something
58
- end
59
- end
60
- ensure
61
- # 6. Restore the terminal to its original state
62
- RatatuiRuby.restore_terminal
63
- end
64
- ```
65
- <!-- SYNC:END -->
66
- <!-- SPDX-SnippetEnd -->
67
-
68
- [![verify_quickstart_lifecycle](../../doc/images/verify_quickstart_lifecycle.png)](../../doc/getting_started/quickstart.md#basic-application)