ratatui_ruby 1.1.0 → 1.1.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 (259) 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 -255
  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 -147
  13. data/CHANGELOG.md +0 -736
  14. data/README.md +0 -187
  15. data/README.rdoc +0 -302
  16. data/Rakefile +0 -11
  17. data/Steepfile +0 -50
  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 -448
  27. data/doc/contributors/design/rust_backend.md +0 -434
  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/releasing.md +0 -215
  33. data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -381
  34. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -200
  35. data/doc/contributors/todo/align/term.md +0 -351
  36. data/doc/contributors/todo/align/terminal.md +0 -647
  37. data/doc/contributors/todo/future_work.md +0 -169
  38. data/doc/contributors/upstream_requests/paragraph_span_rects.md +0 -259
  39. data/doc/contributors/upstream_requests/tab_rects.md +0 -173
  40. data/doc/contributors/upstream_requests/title_rects.md +0 -132
  41. data/doc/custom.css +0 -22
  42. data/doc/getting_started/quickstart.md +0 -291
  43. data/doc/getting_started/why.md +0 -93
  44. data/doc/images/app_all_events.png +0 -0
  45. data/doc/images/app_cli_rich_moments.gif +0 -0
  46. data/doc/images/app_color_picker.png +0 -0
  47. data/doc/images/app_debugging_showcase.gif +0 -0
  48. data/doc/images/app_debugging_showcase.png +0 -0
  49. data/doc/images/app_external_editor.gif +0 -0
  50. data/doc/images/app_login_form.png +0 -0
  51. data/doc/images/app_stateful_interaction.png +0 -0
  52. data/doc/images/verify_quickstart_dsl.png +0 -0
  53. data/doc/images/verify_quickstart_layout.png +0 -0
  54. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  55. data/doc/images/verify_readme_usage.png +0 -0
  56. data/doc/images/widget_barchart.png +0 -0
  57. data/doc/images/widget_block.png +0 -0
  58. data/doc/images/widget_box.png +0 -0
  59. data/doc/images/widget_calendar.png +0 -0
  60. data/doc/images/widget_canvas.png +0 -0
  61. data/doc/images/widget_cell.png +0 -0
  62. data/doc/images/widget_center.png +0 -0
  63. data/doc/images/widget_chart.png +0 -0
  64. data/doc/images/widget_gauge.png +0 -0
  65. data/doc/images/widget_layout_split.png +0 -0
  66. data/doc/images/widget_line_gauge.png +0 -0
  67. data/doc/images/widget_list.png +0 -0
  68. data/doc/images/widget_map.png +0 -0
  69. data/doc/images/widget_overlay.png +0 -0
  70. data/doc/images/widget_popup.png +0 -0
  71. data/doc/images/widget_ratatui_logo.png +0 -0
  72. data/doc/images/widget_ratatui_mascot.png +0 -0
  73. data/doc/images/widget_rect.png +0 -0
  74. data/doc/images/widget_render.png +0 -0
  75. data/doc/images/widget_rich_text.png +0 -0
  76. data/doc/images/widget_scroll_text.png +0 -0
  77. data/doc/images/widget_scrollbar.png +0 -0
  78. data/doc/images/widget_sparkline.png +0 -0
  79. data/doc/images/widget_style_colors.png +0 -0
  80. data/doc/images/widget_table.png +0 -0
  81. data/doc/images/widget_tabs.png +0 -0
  82. data/doc/images/widget_text_width.png +0 -0
  83. data/doc/index.md +0 -34
  84. data/doc/troubleshooting/async.md +0 -4
  85. data/doc/troubleshooting/terminal_limitations.md +0 -131
  86. data/doc/troubleshooting/tui_output.md +0 -197
  87. data/examples/app_all_events/README.md +0 -114
  88. data/examples/app_all_events/app.rb +0 -98
  89. data/examples/app_all_events/model/app_model.rb +0 -159
  90. data/examples/app_all_events/model/event_color_cycle.rb +0 -43
  91. data/examples/app_all_events/model/event_entry.rb +0 -94
  92. data/examples/app_all_events/model/msg.rb +0 -39
  93. data/examples/app_all_events/model/timestamp.rb +0 -56
  94. data/examples/app_all_events/update.rb +0 -75
  95. data/examples/app_all_events/view/app_view.rb +0 -80
  96. data/examples/app_all_events/view/controls_view.rb +0 -54
  97. data/examples/app_all_events/view/counts_view.rb +0 -61
  98. data/examples/app_all_events/view/live_view.rb +0 -72
  99. data/examples/app_all_events/view/log_view.rb +0 -57
  100. data/examples/app_all_events/view.rb +0 -9
  101. data/examples/app_cli_rich_moments/README.md +0 -81
  102. data/examples/app_cli_rich_moments/app.rb +0 -189
  103. data/examples/app_color_picker/README.md +0 -156
  104. data/examples/app_color_picker/app.rb +0 -76
  105. data/examples/app_color_picker/clipboard.rb +0 -86
  106. data/examples/app_color_picker/color.rb +0 -193
  107. data/examples/app_color_picker/controls.rb +0 -92
  108. data/examples/app_color_picker/copy_dialog.rb +0 -168
  109. data/examples/app_color_picker/export_pane.rb +0 -128
  110. data/examples/app_color_picker/harmony.rb +0 -58
  111. data/examples/app_color_picker/input.rb +0 -176
  112. data/examples/app_color_picker/main_container.rb +0 -180
  113. data/examples/app_color_picker/palette.rb +0 -111
  114. data/examples/app_debugging_showcase/README.md +0 -119
  115. data/examples/app_debugging_showcase/app.rb +0 -318
  116. data/examples/app_external_editor/README.md +0 -62
  117. data/examples/app_external_editor/app.rb +0 -344
  118. data/examples/app_login_form/README.md +0 -58
  119. data/examples/app_login_form/app.rb +0 -109
  120. data/examples/app_stateful_interaction/README.md +0 -35
  121. data/examples/app_stateful_interaction/app.rb +0 -328
  122. data/examples/timeout_demo.rb +0 -45
  123. data/examples/verify_quickstart_dsl/README.md +0 -55
  124. data/examples/verify_quickstart_dsl/app.rb +0 -49
  125. data/examples/verify_quickstart_layout/README.md +0 -77
  126. data/examples/verify_quickstart_layout/app.rb +0 -73
  127. data/examples/verify_quickstart_lifecycle/README.md +0 -68
  128. data/examples/verify_quickstart_lifecycle/app.rb +0 -62
  129. data/examples/verify_readme_usage/README.md +0 -49
  130. data/examples/verify_readme_usage/app.rb +0 -42
  131. data/examples/verify_website_managed/README.md +0 -48
  132. data/examples/verify_website_managed/app.rb +0 -36
  133. data/examples/verify_website_menu/README.md +0 -60
  134. data/examples/verify_website_menu/app.rb +0 -84
  135. data/examples/verify_website_spinner/README.md +0 -44
  136. data/examples/verify_website_spinner/app.rb +0 -34
  137. data/examples/widget_barchart/README.md +0 -58
  138. data/examples/widget_barchart/app.rb +0 -240
  139. data/examples/widget_block/README.md +0 -44
  140. data/examples/widget_block/app.rb +0 -258
  141. data/examples/widget_box/README.md +0 -54
  142. data/examples/widget_box/app.rb +0 -255
  143. data/examples/widget_calendar/README.md +0 -48
  144. data/examples/widget_calendar/app.rb +0 -115
  145. data/examples/widget_canvas/README.md +0 -31
  146. data/examples/widget_canvas/app.rb +0 -130
  147. data/examples/widget_cell/README.md +0 -45
  148. data/examples/widget_cell/app.rb +0 -112
  149. data/examples/widget_center/README.md +0 -33
  150. data/examples/widget_center/app.rb +0 -118
  151. data/examples/widget_chart/README.md +0 -50
  152. data/examples/widget_chart/app.rb +0 -220
  153. data/examples/widget_gauge/README.md +0 -50
  154. data/examples/widget_gauge/app.rb +0 -229
  155. data/examples/widget_layout_split/README.md +0 -53
  156. data/examples/widget_layout_split/app.rb +0 -260
  157. data/examples/widget_line_gauge/README.md +0 -50
  158. data/examples/widget_line_gauge/app.rb +0 -219
  159. data/examples/widget_list/README.md +0 -58
  160. data/examples/widget_list/app.rb +0 -382
  161. data/examples/widget_map/README.md +0 -48
  162. data/examples/widget_map/app.rb +0 -95
  163. data/examples/widget_overlay/README.md +0 -45
  164. data/examples/widget_overlay/app.rb +0 -250
  165. data/examples/widget_popup/README.md +0 -45
  166. data/examples/widget_popup/app.rb +0 -106
  167. data/examples/widget_ratatui_logo/README.md +0 -43
  168. data/examples/widget_ratatui_logo/app.rb +0 -104
  169. data/examples/widget_ratatui_mascot/README.md +0 -43
  170. data/examples/widget_ratatui_mascot/app.rb +0 -95
  171. data/examples/widget_rect/README.md +0 -53
  172. data/examples/widget_rect/app.rb +0 -222
  173. data/examples/widget_render/README.md +0 -46
  174. data/examples/widget_render/app.rb +0 -186
  175. data/examples/widget_render/app.rbs +0 -41
  176. data/examples/widget_rich_text/README.md +0 -44
  177. data/examples/widget_rich_text/app.rb +0 -193
  178. data/examples/widget_scroll_text/README.md +0 -46
  179. data/examples/widget_scroll_text/app.rb +0 -109
  180. data/examples/widget_scrollbar/README.md +0 -46
  181. data/examples/widget_scrollbar/app.rb +0 -155
  182. data/examples/widget_sparkline/README.md +0 -51
  183. data/examples/widget_sparkline/app.rb +0 -277
  184. data/examples/widget_style_colors/README.md +0 -43
  185. data/examples/widget_style_colors/app.rb +0 -83
  186. data/examples/widget_table/README.md +0 -57
  187. data/examples/widget_table/app.rb +0 -285
  188. data/examples/widget_tabs/README.md +0 -50
  189. data/examples/widget_tabs/app.rb +0 -183
  190. data/examples/widget_text_width/README.md +0 -44
  191. data/examples/widget_text_width/app.rb +0 -117
  192. data/migrate_to_buffer.rb +0 -145
  193. data/mise.toml +0 -8
  194. data/tasks/autodoc/examples.rb +0 -87
  195. data/tasks/autodoc/member.rb +0 -58
  196. data/tasks/autodoc/name.rb +0 -21
  197. data/tasks/autodoc.rake +0 -21
  198. data/tasks/bump/bump_workflow.rb +0 -49
  199. data/tasks/bump/cargo_lockfile.rb +0 -21
  200. data/tasks/bump/changelog.rb +0 -104
  201. data/tasks/bump/header.rb +0 -32
  202. data/tasks/bump/history.rb +0 -32
  203. data/tasks/bump/links.rb +0 -69
  204. data/tasks/bump/manifest.rb +0 -33
  205. data/tasks/bump/patch_release.rb +0 -19
  206. data/tasks/bump/release_branch.rb +0 -17
  207. data/tasks/bump/release_from_trunk.rb +0 -49
  208. data/tasks/bump/repository.rb +0 -54
  209. data/tasks/bump/ruby_gem.rb +0 -29
  210. data/tasks/bump/sem_ver.rb +0 -44
  211. data/tasks/bump/unreleased_section.rb +0 -73
  212. data/tasks/bump.rake +0 -61
  213. data/tasks/doc/documentation.rb +0 -59
  214. data/tasks/doc/link/file_url.rb +0 -30
  215. data/tasks/doc/link/relative_path.rb +0 -61
  216. data/tasks/doc/link/web_url.rb +0 -55
  217. data/tasks/doc/link.rb +0 -52
  218. data/tasks/doc/link_audit.rb +0 -116
  219. data/tasks/doc/problem.rb +0 -40
  220. data/tasks/doc/source_file.rb +0 -93
  221. data/tasks/doc.rake +0 -905
  222. data/tasks/example_viewer.html.erb +0 -172
  223. data/tasks/extension.rake +0 -14
  224. data/tasks/license/headers_md.rb +0 -223
  225. data/tasks/license/headers_rb.rb +0 -210
  226. data/tasks/license/license_utils.rb +0 -130
  227. data/tasks/license/snippets_md.rb +0 -315
  228. data/tasks/license/snippets_rdoc.rb +0 -150
  229. data/tasks/license.rake +0 -91
  230. data/tasks/lint.rake +0 -170
  231. data/tasks/rbs_predicates/predicate_catalog.rb +0 -52
  232. data/tasks/rbs_predicates/predicate_tests.rb +0 -124
  233. data/tasks/rbs_predicates/rbs_signature.rb +0 -63
  234. data/tasks/rbs_predicates.rake +0 -31
  235. data/tasks/rdoc_config.rb +0 -29
  236. data/tasks/resources/build.yml.erb +0 -60
  237. data/tasks/resources/index.html.erb +0 -141
  238. data/tasks/resources/rubies.yml +0 -7
  239. data/tasks/sourcehut.rake +0 -110
  240. data/tasks/steep.rake +0 -11
  241. data/tasks/terminal_preview/app_screenshot.rb +0 -45
  242. data/tasks/terminal_preview/crash_report.rb +0 -54
  243. data/tasks/terminal_preview/example_app.rb +0 -27
  244. data/tasks/terminal_preview/launcher_script.rb +0 -48
  245. data/tasks/terminal_preview/preview_collection.rb +0 -60
  246. data/tasks/terminal_preview/preview_timing.rb +0 -24
  247. data/tasks/terminal_preview/safety_confirmation.rb +0 -58
  248. data/tasks/terminal_preview/saved_screenshot.rb +0 -56
  249. data/tasks/terminal_preview/system_appearance.rb +0 -13
  250. data/tasks/terminal_preview/terminal_window.rb +0 -138
  251. data/tasks/terminal_preview/window_id.rb +0 -16
  252. data/tasks/terminal_preview.rake +0 -30
  253. data/tasks/test.rake +0 -36
  254. data/tasks/website/index_page.rb +0 -30
  255. data/tasks/website/version.rb +0 -122
  256. data/tasks/website/version_menu.rb +0 -68
  257. data/tasks/website/versioned_documentation.rb +0 -83
  258. data/tasks/website/website.rb +0 -53
  259. 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)