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,318 +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
- ##
13
- # Interactive demonstration of RatatuiRuby debugging features.
14
- #
15
- # This example lets you trigger each debugging feature with a hotkey to verify
16
- # your setup works before encountering a real bug.
17
- #
18
- # == Hotkeys
19
- #
20
- # [d] Enable debug_mode! — Shows the debug socket path for remote attachment
21
- # [p] Trigger test_panic! — Deliberately crashes to verify Rust backtrace visibility
22
- # [t] Cause TypeError — Passes wrong type to widget factory to show Rust stack frames
23
- # [b] Show backtrace status — Displays current debug configuration
24
- # [q] Quit
25
- #
26
- # == Usage
27
- #
28
- # # Normal mode (no backtraces):
29
- # ruby examples/verify_debugging_usage/app.rb
30
- #
31
- # # With Rust backtraces only:
32
- # RUST_BACKTRACE=1 ruby examples/verify_debugging_usage/app.rb
33
- #
34
- # # Full debug mode (stops at startup for debugger attachment):
35
- # RR_DEBUG=1 ruby examples/verify_debugging_usage/app.rb
36
- #
37
- # == Remote Debugging
38
- #
39
- # When you press [d] to enable debug_mode!, the app continues running but
40
- # prints a socket path. From another terminal:
41
- #
42
- # rdbg --attach
43
- #
44
- # This gives you a full debugger REPL while the TUI keeps running.
45
- class VerifyDebuggingUsage
46
- def initialize
47
- @status_message = "Press a key to test debugging features"
48
- @show_debug_info = false
49
- @quit = false
50
-
51
- # If debug mode was enabled via RR_DEBUG=1 at startup, capture the socket path
52
- if RatatuiRuby::Debug.enabled?
53
- @socket_path = begin
54
- ::DEBUGGER__.create_unix_domain_socket_name
55
- rescue NameError
56
- nil
57
- end
58
- @show_debug_info = true
59
- @status_message = "RR_DEBUG=1 detected — debug mode active"
60
- end
61
- end
62
-
63
- def run
64
- RatatuiRuby.run do |tui|
65
- @tui = tui
66
- @loop_count = 0
67
-
68
- loop do
69
- @loop_count += 1
70
-
71
- # 🎯 Breakpoint every 250 loops. Try: p @status_message
72
- if RatatuiRuby::Debug.enabled? && (@loop_count % 250).zero?
73
- you_found_me = "🎉 You found me! Loop ##{@loop_count}"
74
- # rubocop:disable Lint/Debugger
75
- debugger
76
- # rubocop:enable Lint/Debugger
77
- _ = you_found_me # Suppress unused variable warning
78
- end
79
-
80
- render
81
- break if @quit || handle_input == :quit
82
- end
83
- end
84
- end
85
-
86
- private def render
87
- @tui.draw do |frame|
88
- constraints = [
89
- @tui.constraint_length(3), # Status
90
- @tui.constraint_length(5), # Config
91
- @tui.constraint_length(6), # Actions
92
- ]
93
-
94
- if @show_debug_info
95
- constraints << @tui.constraint_length(6) # Debug info
96
- end
97
-
98
- constraints << @tui.constraint_fill(1) # Spacer
99
- constraints << @tui.constraint_length(3) # Help
100
-
101
- chunks = @tui.layout_split(frame.area, direction: :vertical, constraints:)
102
-
103
- idx = 0
104
- render_status(frame, chunks[idx])
105
- idx += 1
106
- render_config(frame, chunks[idx])
107
- idx += 1
108
- render_actions(frame, chunks[idx])
109
- idx += 1
110
-
111
- if @show_debug_info
112
- render_debug_info(frame, chunks[idx])
113
- idx += 1
114
- end
115
-
116
- # Skip spacer
117
- idx += 1
118
- render_help(frame, chunks[idx])
119
- end
120
- end
121
-
122
- private def render_status(frame, area)
123
- frame.render_widget(
124
- @tui.paragraph(
125
- text: @status_message,
126
- alignment: :center,
127
- block: @tui.block(
128
- title: " Status ",
129
- title_alignment: :center,
130
- borders: [:all],
131
- border_style: { fg: :yellow }
132
- )
133
- ),
134
- area
135
- )
136
- end
137
-
138
- private def render_config(frame, area)
139
- config_lines = [
140
- "Rust Backtraces: #{flag(RatatuiRuby::Debug.rust_backtrace_enabled?)}",
141
- "Full Debug Mode: #{flag(RatatuiRuby::Debug.enabled?)}",
142
- "Remote Debugging: #{remote_mode_description}",
143
- ].join("\n")
144
-
145
- frame.render_widget(
146
- @tui.paragraph(
147
- text: config_lines,
148
- block: @tui.block(
149
- title: " Current Debug Configuration ",
150
- borders: [:all],
151
- border_style: { fg: :cyan }
152
- )
153
- ),
154
- area
155
- )
156
- end
157
-
158
- private def render_actions(frame, area)
159
- actions_lines = [
160
- "[d] Enable debug_mode! and show socket info",
161
- "[p] Trigger test_panic! to verify backtrace visibility",
162
- "[t] Cause TypeError (pass wrong type to widget)",
163
- "[b] Refresh debug status",
164
- ].join("\n")
165
-
166
- frame.render_widget(
167
- @tui.paragraph(
168
- text: actions_lines,
169
- block: @tui.block(
170
- title: " Available Actions ",
171
- borders: [:all],
172
- border_style: { fg: :green }
173
- )
174
- ),
175
- area
176
- )
177
- end
178
-
179
- private def render_debug_info(frame, area)
180
- socket_display = @socket_path || "(socket not available)"
181
- info_lines = [
182
- "Socket: #{socket_display}",
183
- "Attach: rdbg --attach",
184
- "Hint: type 'continue' if you see SIGURG",
185
- ]
186
-
187
- frame.render_widget(
188
- @tui.paragraph(
189
- text: info_lines.join("\n"),
190
- block: @tui.block(
191
- title: " Remote Debugging ",
192
- borders: [:all],
193
- border_style: { fg: :magenta }
194
- )
195
- ),
196
- area
197
- )
198
- end
199
-
200
- private def render_help(frame, area)
201
- frame.render_widget(
202
- @tui.paragraph(
203
- text: "[d] debug_mode! [p] test_panic! [t] TypeError [b] status [q] quit",
204
- alignment: :center,
205
- block: @tui.block(
206
- borders: [:all],
207
- border_style: { fg: :dark_gray }
208
- )
209
- ),
210
- area
211
- )
212
- end
213
-
214
- private def flag(value)
215
- value ? "✓ enabled" : "✗ disabled"
216
- end
217
-
218
- private def remote_mode_description
219
- case RatatuiRuby::Debug.remote_debugging_mode
220
- when :open
221
- attached = debugger_attached? ? " — ATTACHED" : " — waiting"
222
- "✓ open#{attached}"
223
- when :open_nonstop
224
- attached = debugger_attached? ? " — ATTACHED" : ""
225
- "✓ open_nonstop#{attached}"
226
- else
227
- "✗ not configured"
228
- end
229
- end
230
-
231
- # ☣️ FRAGILE: This pokes at debug gem internals.
232
- #
233
- # Private instance variables can change between gem versions. This code
234
- # may silently break. We accept that risk here because this showcase
235
- # exists specifically to demonstrate debugger attachment status.
236
- #
237
- # For production apps, checking Debug.enabled? is sufficient — knowing
238
- # whether a client has attached rarely matters.
239
- private def debugger_attached?
240
- return false unless defined?(::DEBUGGER__::SESSION)
241
-
242
- ui = ::DEBUGGER__::SESSION.instance_variable_get(:@ui)
243
- return false unless ui
244
-
245
- # The @sock instance variable is set when a client connects
246
- sock = ui.instance_variable_get(:@sock)
247
- !sock.nil?
248
- rescue
249
- false
250
- end
251
-
252
- private def handle_input
253
- case @tui.poll_event
254
- in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
255
- :quit
256
-
257
- in { type: :key, code: "d" }
258
- enable_debug_mode!
259
-
260
- in { type: :key, code: "p" }
261
- trigger_test_panic!
262
-
263
- in { type: :key, code: "t" }
264
- trigger_type_error!
265
-
266
- in { type: :key, code: "b" }
267
- @status_message = "Debug status refreshed at #{Time.now.strftime('%H:%M:%S')}"
268
-
269
- else
270
- nil
271
- end
272
- end
273
-
274
- private def enable_debug_mode!
275
- if RatatuiRuby::Debug.enabled?
276
- @status_message = "Debug mode already enabled!"
277
- else
278
- # debug_mode! returns the socket path and suppresses the debug gem's output
279
- @socket_path = RatatuiRuby.debug_mode!
280
- @status_message = "debug_mode! enabled"
281
- @show_debug_info = true
282
- end
283
- end
284
-
285
- private def trigger_test_panic!
286
- if RatatuiRuby::Debug.rust_backtrace_enabled?
287
- @status_message = "Triggering test_panic! — check stderr for backtrace..."
288
- else
289
- @status_message = "Triggering test_panic! — backtrace hidden (set RUST_BACKTRACE=1)"
290
- end
291
- render # Show the message before crashing
292
-
293
- # Give a moment for the render to complete
294
- sleep 0.1
295
-
296
- # This will crash the app with a Rust panic. If RUST_BACKTRACE=1 or
297
- # debug mode is enabled, you'll see the full Rust stack trace after
298
- # the terminal is restored.
299
- RatatuiRuby::Debug.test_panic!
300
- end
301
-
302
- private def trigger_type_error!
303
- if RatatuiRuby::Debug.rust_backtrace_enabled?
304
- @status_message = "Triggering TypeError — check stderr for error message..."
305
- else
306
- @status_message = "Triggering TypeError — set RUST_BACKTRACE=1 for stack trace"
307
- end
308
- render # Show the message before crashing
309
- sleep 0.1
310
-
311
- # Bypass the factory's DWIM coercion to trigger a real Rust TypeError.
312
- # Uses Widgets::Table.new directly with invalid rows type.
313
- bad_table = RatatuiRuby::Widgets::Table.new(rows: 42, widths: [])
314
- @tui.draw { |f| f.render_widget(bad_table, f.area) }
315
- end
316
- end
317
-
318
- VerifyDebuggingUsage.new.run if __FILE__ == $PROGRAM_NAME
@@ -1,58 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Login Form Example
7
-
8
- [![Login Form](../../doc/images/app_login_form.png)](app.rb)
9
-
10
- Demonstrates how to create a modal overlay for user input.
11
-
12
- Many applications need to block interaction with the main UI while collecting specific information, like a login prompt or confirmation dialog. Managing the z-index and input focus for these overlays can be tricky.
13
-
14
- This example solves this by using the `Overlay` widget to stack a centered popup on top of a base layer, conditionally rendering the popup based on state.
15
-
16
- ## Features Demonstrated
17
-
18
- - **Overlays:** Stacking widgets on top of each other using `tui.overlay`.
19
- - **Centering:** Positioning a widget in the center of the screen using `tui.center`.
20
- - **State Management:** Switching between "Base" and "Popup" views.
21
- - **Input Handling:** Capturing text input and handling specific keys (Enter, Esc) to trigger state changes.
22
- - **Cursor Positioning:** Manually calculating cursor position within a `Paragraph`.
23
-
24
- ## Hotkeys
25
-
26
- ### Form Mode
27
- - **Text Input**: Type to enter username (supports all characters including 'q').
28
- - **Backspace**: Deletes the last character.
29
- - **Enter**: Submits the form and opens the success popup.
30
- - **Esc**: Quits the application.
31
- - **Ctrl+C**: Quits the application.
32
-
33
- ### Popup Mode
34
- - **q**: Closes the popup and quits the application.
35
- - **Ctrl+C**: Quits the application.
36
-
37
- ## Usage
38
-
39
- <!-- SPDX-SnippetBegin -->
40
- <!--
41
- SPDX-FileCopyrightText: 2026 Kerrick Long
42
- SPDX-License-Identifier: MIT-0
43
- -->
44
- ```bash
45
- ruby examples/app_login_form/app.rb
46
- ```
47
- <!-- SPDX-SnippetEnd -->
48
-
49
- ## Learning Outcomes
50
-
51
- Use this example if you need to...
52
-
53
- - Create a modal dialog or popup.
54
- - Center a widget on the screen (vertically and horizontally).
55
- - Implement a simple text input field with cursor management.
56
- - layer widgets using the `Overlay` widget.
57
-
58
- [Read the source code →](app.rb)
@@ -1,109 +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
-
11
- class AppLoginForm
12
- PREFIX = "Enter Username: [ "
13
- SUFFIX = " ]"
14
-
15
- def initialize
16
- @username = ""
17
- @show_popup = false
18
- end
19
-
20
- def run
21
- RatatuiRuby.run do |tui|
22
- @tui = tui
23
- loop do
24
- render
25
- break if handle_input == :quit
26
- end
27
- end
28
- end
29
-
30
- private def render
31
- # 1. Base Layer Construction
32
- # We want a cursor relative to the paragraph.
33
- # So we wrap Paragraph and Cursor in an Overlay, and put that Overlay in a Center.
34
-
35
- # Calculate cursor position
36
- # Border takes 1 cell.
37
- # Cursor X = 1 (border) + PREFIX.length + username.length
38
- # Cursor Y = 1 (border + line 0)
39
- cursor_x = 1 + PREFIX.length + @username.length
40
- cursor_y = 1
41
-
42
- # The content of the base form
43
- form_content = @tui.overlay(layers: [
44
- @tui.paragraph(
45
- text: "#{PREFIX}#{@username}#{SUFFIX}",
46
- block: @tui.block(borders: :all, title: "Login Form"),
47
- alignment: :left
48
- ),
49
- @tui.cursor(x: cursor_x, y: cursor_y),
50
- ])
51
-
52
- # Center the form on screen
53
- base_layer = @tui.center(
54
- child: form_content,
55
- width_percent: 50,
56
- height_percent: 20
57
- )
58
-
59
- # 2. Popup Layer Construction
60
- final_view = if @show_popup
61
- popup_message = @tui.center(
62
- child: @tui.paragraph(
63
- text: "Login Successful!\nPress 'q' to quit.",
64
- style: @tui.style(fg: :green, bg: :black),
65
- block: @tui.block(borders: :all),
66
- alignment: :center,
67
- wrap: true
68
- ),
69
- width_percent: 30,
70
- height_percent: 20
71
- )
72
-
73
- # Render Base Layer (background) THEN Popup Layer
74
- @tui.overlay(layers: [base_layer, popup_message])
75
- else
76
- base_layer
77
- end
78
-
79
- # 3. Draw
80
- @tui.draw do |frame|
81
- frame.render_widget(final_view, frame.area)
82
- end
83
- end
84
-
85
- private def handle_input
86
- case @tui.poll_event
87
- in { type: :key, code: "c", modifiers: ["ctrl"] }
88
- :quit
89
- in { type: :key, code: "q" } if @show_popup
90
- :quit
91
- in { type: :key, code: "enter" }
92
- @show_popup ||= true
93
- nil
94
- in { type: :key, code: "backspace" }
95
- @username.chop! unless @show_popup
96
- nil
97
- in { type: :key, code: "esc" }
98
- :quit unless @show_popup
99
- in { type: :key, code:, modifiers: [] }
100
- # Simple text input (single character, no modifiers)
101
- @username += code if !@show_popup && code.length == 1
102
- nil
103
- else
104
- nil
105
- end
106
- end
107
- end
108
-
109
- AppLoginForm.new.run if __FILE__ == $0
@@ -1,35 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Stateful Interaction Example
7
-
8
- [![Stateful Interaction](../../doc/images/app_stateful_interaction.png)](app.rb)
9
-
10
- This example demonstrates High-Fidelity Interaction using **Stateful Widget Rendering**.
11
-
12
- It showcases a "Database Viewer" layout where:
13
- 1. **Selection Persistence:** `ListState` and `TableState` objects persist across frames, maintaining selection without manual index tracking variables.
14
- 2. **Offset Read-back:** The application reads `state.offset` *after* rendering to know exactly which items were visible on screen.
15
- 3. **Mouse Interaction:** Using the read-back offset, we can calculate exactly which row was clicked, even when the specific item wasn't drawn at that absolute Y position due to scrolling.
16
-
17
- ## Key Concept: The "Read-back" Loop
18
-
19
- Standard immediate-mode interaction often requires you to re-calculate layout logic to determine what was clicked.
20
-
21
- In `ratatui_ruby`'s Stateful Rendering:
22
- 1. **Update**: You modify `state` (e.g., `state.select(1)`).
23
- 2. **Render**: You pass `state` to `render_stateful_widget`. Ratatui's Rust backend calculates layout and **updates** `state.offset` in-place if scrolling happened.
24
- 3. **Interact**: On the next event loop, you use `state.offset` to correctly map mouse coordinates to data indices.
25
-
26
- ## Hotkeys
27
-
28
- | Key | Action |
29
- | --- | --- |
30
- | `↑` / `↓` | Scroll the active pane |
31
- | `Tab` / `←` / `→` | Switch active pane (List vs Table) |
32
- | `Mouse Click` | Select the clicked row (Works with scrolling!) |
33
- | `q` | Quit |
34
-
35
- [Read the source code →](app.rb)