ratatui_ruby 1.2.0 → 1.2.2

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