ratatui_ruby 1.0.0 → 1.0.1

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 (236) hide show
  1. checksums.yaml +4 -4
  2. data/ext/ratatui_ruby/Cargo.lock +1 -1
  3. data/ext/ratatui_ruby/Cargo.toml +1 -1
  4. data/lib/ratatui_ruby/version.rb +1 -1
  5. metadata +1 -232
  6. data/.builds/ruby-3.2.yml +0 -54
  7. data/.builds/ruby-3.3.yml +0 -54
  8. data/.builds/ruby-3.4.yml +0 -54
  9. data/.builds/ruby-4.0.0.yml +0 -54
  10. data/.pre-commit-config.yaml +0 -16
  11. data/.rubocop.yml +0 -10
  12. data/AGENTS.md +0 -146
  13. data/CHANGELOG.md +0 -710
  14. data/README.md +0 -187
  15. data/README.rdoc +0 -302
  16. data/Rakefile +0 -11
  17. data/Steepfile +0 -49
  18. data/doc/concepts/application_architecture.md +0 -321
  19. data/doc/concepts/application_testing.md +0 -193
  20. data/doc/concepts/async.md +0 -190
  21. data/doc/concepts/custom_widgets.md +0 -247
  22. data/doc/concepts/debugging.md +0 -401
  23. data/doc/concepts/event_handling.md +0 -162
  24. data/doc/concepts/interactive_design.md +0 -146
  25. data/doc/contributors/auditing/parity.md +0 -239
  26. data/doc/contributors/design/ruby_frontend.md +0 -420
  27. data/doc/contributors/design/rust_backend.md +0 -422
  28. data/doc/contributors/design.md +0 -11
  29. data/doc/contributors/developing_examples.md +0 -400
  30. data/doc/contributors/documentation_style.md +0 -121
  31. data/doc/contributors/index.md +0 -21
  32. data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -375
  33. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -206
  34. data/doc/contributors/todo/align/terminal.md +0 -647
  35. data/doc/contributors/todo/future_work.md +0 -169
  36. data/doc/contributors/upstream_requests/tab_rects.md +0 -173
  37. data/doc/contributors/upstream_requests/title_rects.md +0 -132
  38. data/doc/custom.css +0 -22
  39. data/doc/getting_started/quickstart.md +0 -291
  40. data/doc/getting_started/why.md +0 -93
  41. data/doc/images/app_all_events.png +0 -0
  42. data/doc/images/app_cli_rich_moments.gif +0 -0
  43. data/doc/images/app_color_picker.png +0 -0
  44. data/doc/images/app_debugging_showcase.gif +0 -0
  45. data/doc/images/app_debugging_showcase.png +0 -0
  46. data/doc/images/app_login_form.png +0 -0
  47. data/doc/images/app_stateful_interaction.png +0 -0
  48. data/doc/images/verify_quickstart_dsl.png +0 -0
  49. data/doc/images/verify_quickstart_layout.png +0 -0
  50. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  51. data/doc/images/verify_readme_usage.png +0 -0
  52. data/doc/images/widget_barchart.png +0 -0
  53. data/doc/images/widget_block.png +0 -0
  54. data/doc/images/widget_box.png +0 -0
  55. data/doc/images/widget_calendar.png +0 -0
  56. data/doc/images/widget_canvas.png +0 -0
  57. data/doc/images/widget_cell.png +0 -0
  58. data/doc/images/widget_center.png +0 -0
  59. data/doc/images/widget_chart.png +0 -0
  60. data/doc/images/widget_gauge.png +0 -0
  61. data/doc/images/widget_layout_split.png +0 -0
  62. data/doc/images/widget_line_gauge.png +0 -0
  63. data/doc/images/widget_list.png +0 -0
  64. data/doc/images/widget_map.png +0 -0
  65. data/doc/images/widget_overlay.png +0 -0
  66. data/doc/images/widget_popup.png +0 -0
  67. data/doc/images/widget_ratatui_logo.png +0 -0
  68. data/doc/images/widget_ratatui_mascot.png +0 -0
  69. data/doc/images/widget_rect.png +0 -0
  70. data/doc/images/widget_render.png +0 -0
  71. data/doc/images/widget_rich_text.png +0 -0
  72. data/doc/images/widget_scroll_text.png +0 -0
  73. data/doc/images/widget_scrollbar.png +0 -0
  74. data/doc/images/widget_sparkline.png +0 -0
  75. data/doc/images/widget_style_colors.png +0 -0
  76. data/doc/images/widget_table.png +0 -0
  77. data/doc/images/widget_tabs.png +0 -0
  78. data/doc/images/widget_text_width.png +0 -0
  79. data/doc/index.md +0 -39
  80. data/doc/troubleshooting/async.md +0 -4
  81. data/doc/troubleshooting/terminal_limitations.md +0 -131
  82. data/doc/troubleshooting/tui_output.md +0 -197
  83. data/examples/app_all_events/README.md +0 -114
  84. data/examples/app_all_events/app.rb +0 -98
  85. data/examples/app_all_events/model/app_model.rb +0 -159
  86. data/examples/app_all_events/model/event_color_cycle.rb +0 -43
  87. data/examples/app_all_events/model/event_entry.rb +0 -94
  88. data/examples/app_all_events/model/msg.rb +0 -39
  89. data/examples/app_all_events/model/timestamp.rb +0 -56
  90. data/examples/app_all_events/update.rb +0 -75
  91. data/examples/app_all_events/view/app_view.rb +0 -80
  92. data/examples/app_all_events/view/controls_view.rb +0 -54
  93. data/examples/app_all_events/view/counts_view.rb +0 -61
  94. data/examples/app_all_events/view/live_view.rb +0 -72
  95. data/examples/app_all_events/view/log_view.rb +0 -57
  96. data/examples/app_all_events/view.rb +0 -9
  97. data/examples/app_cli_rich_moments/README.md +0 -81
  98. data/examples/app_cli_rich_moments/app.rb +0 -189
  99. data/examples/app_color_picker/README.md +0 -156
  100. data/examples/app_color_picker/app.rb +0 -76
  101. data/examples/app_color_picker/clipboard.rb +0 -86
  102. data/examples/app_color_picker/color.rb +0 -193
  103. data/examples/app_color_picker/controls.rb +0 -92
  104. data/examples/app_color_picker/copy_dialog.rb +0 -168
  105. data/examples/app_color_picker/export_pane.rb +0 -128
  106. data/examples/app_color_picker/harmony.rb +0 -58
  107. data/examples/app_color_picker/input.rb +0 -176
  108. data/examples/app_color_picker/main_container.rb +0 -180
  109. data/examples/app_color_picker/palette.rb +0 -111
  110. data/examples/app_debugging_showcase/README.md +0 -119
  111. data/examples/app_debugging_showcase/app.rb +0 -318
  112. data/examples/app_login_form/README.md +0 -58
  113. data/examples/app_login_form/app.rb +0 -109
  114. data/examples/app_stateful_interaction/README.md +0 -35
  115. data/examples/app_stateful_interaction/app.rb +0 -328
  116. data/examples/timeout_demo.rb +0 -45
  117. data/examples/verify_quickstart_dsl/README.md +0 -55
  118. data/examples/verify_quickstart_dsl/app.rb +0 -49
  119. data/examples/verify_quickstart_layout/README.md +0 -77
  120. data/examples/verify_quickstart_layout/app.rb +0 -73
  121. data/examples/verify_quickstart_lifecycle/README.md +0 -68
  122. data/examples/verify_quickstart_lifecycle/app.rb +0 -62
  123. data/examples/verify_readme_usage/README.md +0 -49
  124. data/examples/verify_readme_usage/app.rb +0 -42
  125. data/examples/verify_website_managed/README.md +0 -48
  126. data/examples/verify_website_managed/app.rb +0 -36
  127. data/examples/verify_website_menu/README.md +0 -60
  128. data/examples/verify_website_menu/app.rb +0 -84
  129. data/examples/verify_website_spinner/README.md +0 -44
  130. data/examples/verify_website_spinner/app.rb +0 -34
  131. data/examples/widget_barchart/README.md +0 -58
  132. data/examples/widget_barchart/app.rb +0 -240
  133. data/examples/widget_block/README.md +0 -44
  134. data/examples/widget_block/app.rb +0 -258
  135. data/examples/widget_box/README.md +0 -54
  136. data/examples/widget_box/app.rb +0 -255
  137. data/examples/widget_calendar/README.md +0 -48
  138. data/examples/widget_calendar/app.rb +0 -115
  139. data/examples/widget_canvas/README.md +0 -31
  140. data/examples/widget_canvas/app.rb +0 -130
  141. data/examples/widget_cell/README.md +0 -45
  142. data/examples/widget_cell/app.rb +0 -112
  143. data/examples/widget_center/README.md +0 -33
  144. data/examples/widget_center/app.rb +0 -118
  145. data/examples/widget_chart/README.md +0 -50
  146. data/examples/widget_chart/app.rb +0 -220
  147. data/examples/widget_gauge/README.md +0 -50
  148. data/examples/widget_gauge/app.rb +0 -229
  149. data/examples/widget_layout_split/README.md +0 -53
  150. data/examples/widget_layout_split/app.rb +0 -260
  151. data/examples/widget_line_gauge/README.md +0 -50
  152. data/examples/widget_line_gauge/app.rb +0 -219
  153. data/examples/widget_list/README.md +0 -58
  154. data/examples/widget_list/app.rb +0 -384
  155. data/examples/widget_map/README.md +0 -48
  156. data/examples/widget_map/app.rb +0 -95
  157. data/examples/widget_overlay/README.md +0 -45
  158. data/examples/widget_overlay/app.rb +0 -250
  159. data/examples/widget_popup/README.md +0 -45
  160. data/examples/widget_popup/app.rb +0 -106
  161. data/examples/widget_ratatui_logo/README.md +0 -43
  162. data/examples/widget_ratatui_logo/app.rb +0 -104
  163. data/examples/widget_ratatui_mascot/README.md +0 -43
  164. data/examples/widget_ratatui_mascot/app.rb +0 -95
  165. data/examples/widget_rect/README.md +0 -53
  166. data/examples/widget_rect/app.rb +0 -222
  167. data/examples/widget_render/README.md +0 -46
  168. data/examples/widget_render/app.rb +0 -186
  169. data/examples/widget_render/app.rbs +0 -41
  170. data/examples/widget_rich_text/README.md +0 -44
  171. data/examples/widget_rich_text/app.rb +0 -193
  172. data/examples/widget_scroll_text/README.md +0 -46
  173. data/examples/widget_scroll_text/app.rb +0 -109
  174. data/examples/widget_scrollbar/README.md +0 -46
  175. data/examples/widget_scrollbar/app.rb +0 -155
  176. data/examples/widget_sparkline/README.md +0 -51
  177. data/examples/widget_sparkline/app.rb +0 -277
  178. data/examples/widget_style_colors/README.md +0 -43
  179. data/examples/widget_style_colors/app.rb +0 -83
  180. data/examples/widget_table/README.md +0 -57
  181. data/examples/widget_table/app.rb +0 -279
  182. data/examples/widget_tabs/README.md +0 -50
  183. data/examples/widget_tabs/app.rb +0 -183
  184. data/examples/widget_text_width/README.md +0 -44
  185. data/examples/widget_text_width/app.rb +0 -117
  186. data/migrate_to_buffer.rb +0 -145
  187. data/mise.toml +0 -8
  188. data/tasks/autodoc/examples.rb +0 -87
  189. data/tasks/autodoc/member.rb +0 -58
  190. data/tasks/autodoc/name.rb +0 -21
  191. data/tasks/autodoc.rake +0 -21
  192. data/tasks/bump/cargo_lockfile.rb +0 -21
  193. data/tasks/bump/changelog.rb +0 -47
  194. data/tasks/bump/header.rb +0 -32
  195. data/tasks/bump/history.rb +0 -32
  196. data/tasks/bump/links.rb +0 -69
  197. data/tasks/bump/manifest.rb +0 -33
  198. data/tasks/bump/ruby_gem.rb +0 -49
  199. data/tasks/bump/sem_ver.rb +0 -40
  200. data/tasks/bump/unreleased_section.rb +0 -56
  201. data/tasks/bump.rake +0 -51
  202. data/tasks/doc.rake +0 -887
  203. data/tasks/example_viewer.html.erb +0 -172
  204. data/tasks/extension.rake +0 -14
  205. data/tasks/license/headers_md.rb +0 -223
  206. data/tasks/license/headers_rb.rb +0 -210
  207. data/tasks/license/license_utils.rb +0 -130
  208. data/tasks/license/snippets_md.rb +0 -315
  209. data/tasks/license/snippets_rdoc.rb +0 -150
  210. data/tasks/license.rake +0 -91
  211. data/tasks/lint.rake +0 -170
  212. data/tasks/rdoc_config.rb +0 -29
  213. data/tasks/resources/build.yml.erb +0 -60
  214. data/tasks/resources/index.html.erb +0 -141
  215. data/tasks/resources/rubies.yml +0 -7
  216. data/tasks/sourcehut.rake +0 -110
  217. data/tasks/steep.rake +0 -11
  218. data/tasks/terminal_preview/app_screenshot.rb +0 -45
  219. data/tasks/terminal_preview/crash_report.rb +0 -54
  220. data/tasks/terminal_preview/example_app.rb +0 -27
  221. data/tasks/terminal_preview/launcher_script.rb +0 -48
  222. data/tasks/terminal_preview/preview_collection.rb +0 -60
  223. data/tasks/terminal_preview/preview_timing.rb +0 -24
  224. data/tasks/terminal_preview/safety_confirmation.rb +0 -58
  225. data/tasks/terminal_preview/saved_screenshot.rb +0 -56
  226. data/tasks/terminal_preview/system_appearance.rb +0 -13
  227. data/tasks/terminal_preview/terminal_window.rb +0 -138
  228. data/tasks/terminal_preview/window_id.rb +0 -16
  229. data/tasks/terminal_preview.rake +0 -30
  230. data/tasks/test.rake +0 -33
  231. data/tasks/website/index_page.rb +0 -30
  232. data/tasks/website/version.rb +0 -127
  233. data/tasks/website/version_menu.rb +0 -68
  234. data/tasks/website/versioned_documentation.rb +0 -83
  235. data/tasks/website/website.rb +0 -53
  236. 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)