ratatui_ruby 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (441) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +98 -176
  7. data/CHANGELOG.md +80 -6
  8. data/README.md +19 -7
  9. data/REUSE.toml +15 -0
  10. data/doc/application_architecture.md +179 -45
  11. data/doc/application_testing.md +80 -32
  12. data/doc/contributors/design/ruby_frontend.md +48 -8
  13. data/doc/contributors/design/rust_backend.md +1 -0
  14. data/doc/contributors/developing_examples.md +191 -48
  15. data/doc/contributors/documentation_style.md +7 -0
  16. data/doc/contributors/examples_audit/p1_high.md +21 -0
  17. data/doc/contributors/examples_audit/p2_moderate.md +81 -0
  18. data/doc/contributors/examples_audit.md +41 -0
  19. data/doc/contributors/index.md +2 -0
  20. data/doc/event_handling.md +21 -7
  21. data/doc/images/app_all_events.png +0 -0
  22. data/doc/images/app_color_picker.png +0 -0
  23. data/doc/images/app_login_form.png +0 -0
  24. data/doc/images/app_stateful_interaction.png +0 -0
  25. data/doc/images/verify_quickstart_dsl.png +0 -0
  26. data/doc/images/verify_quickstart_layout.png +0 -0
  27. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  28. data/doc/images/verify_readme_usage.png +0 -0
  29. data/doc/images/widget_barchart_demo.png +0 -0
  30. data/doc/images/widget_block_demo.png +0 -0
  31. data/doc/images/widget_box_demo.png +0 -0
  32. data/doc/images/widget_calendar_demo.png +0 -0
  33. data/doc/images/widget_canvas_demo.png +0 -0
  34. data/doc/images/widget_cell_demo.png +0 -0
  35. data/doc/images/widget_center_demo.png +0 -0
  36. data/doc/images/widget_chart_demo.png +0 -0
  37. data/doc/images/widget_gauge_demo.png +0 -0
  38. data/doc/images/widget_layout_split.png +0 -0
  39. data/doc/images/widget_line_gauge_demo.png +0 -0
  40. data/doc/images/widget_list_demo.png +0 -0
  41. data/doc/images/widget_overlay_demo.png +0 -0
  42. data/doc/images/widget_ratatui_logo_demo.png +0 -0
  43. data/doc/images/widget_ratatui_mascot_demo.png +0 -0
  44. data/doc/images/widget_render.png +0 -0
  45. data/doc/images/widget_rich_text.png +0 -0
  46. data/doc/images/widget_scroll_text.png +0 -0
  47. data/doc/images/widget_scrollbar_demo.png +0 -0
  48. data/doc/images/widget_sparkline_demo.png +0 -0
  49. data/doc/images/widget_style_colors.png +0 -0
  50. data/doc/images/widget_table_demo.png +0 -0
  51. data/doc/images/widget_table_flex.png +0 -0
  52. data/doc/images/widget_tabs_demo.png +0 -0
  53. data/doc/images/widget_text_width.png +0 -0
  54. data/doc/interactive_design.md +25 -30
  55. data/doc/quickstart.md +150 -130
  56. data/doc/terminal_limitations.md +92 -0
  57. data/examples/app_all_events/README.md +99 -0
  58. data/examples/app_all_events/app.rb +96 -0
  59. data/examples/app_all_events/model/app_model.rb +157 -0
  60. data/examples/app_all_events/model/event_color_cycle.rb +41 -0
  61. data/examples/app_all_events/model/event_entry.rb +92 -0
  62. data/examples/app_all_events/model/msg.rb +37 -0
  63. data/examples/app_all_events/model/timestamp.rb +54 -0
  64. data/examples/app_all_events/update.rb +73 -0
  65. data/examples/app_all_events/view/app_view.rb +78 -0
  66. data/examples/app_all_events/view/controls_view.rb +52 -0
  67. data/examples/app_all_events/view/counts_view.rb +59 -0
  68. data/examples/app_all_events/view/live_view.rb +70 -0
  69. data/examples/app_all_events/view/log_view.rb +55 -0
  70. data/examples/app_all_events/view.rb +7 -0
  71. data/examples/app_color_picker/README.md +134 -0
  72. data/examples/app_color_picker/app.rb +74 -0
  73. data/examples/app_color_picker/clipboard.rb +84 -0
  74. data/examples/app_color_picker/color.rb +191 -0
  75. data/examples/app_color_picker/controls.rb +90 -0
  76. data/examples/app_color_picker/copy_dialog.rb +166 -0
  77. data/examples/app_color_picker/export_pane.rb +126 -0
  78. data/examples/app_color_picker/harmony.rb +56 -0
  79. data/examples/app_color_picker/input.rb +174 -0
  80. data/examples/app_color_picker/main_container.rb +178 -0
  81. data/examples/app_color_picker/palette.rb +109 -0
  82. data/examples/app_login_form/README.md +47 -0
  83. data/examples/{login_form → app_login_form}/app.rb +38 -42
  84. data/examples/app_stateful_interaction/README.md +31 -0
  85. data/examples/app_stateful_interaction/app.rb +272 -0
  86. data/examples/timeout_demo.rb +43 -0
  87. data/examples/verify_quickstart_dsl/README.md +48 -0
  88. data/examples/{quickstart_dsl → verify_quickstart_dsl}/app.rb +17 -6
  89. data/examples/verify_quickstart_layout/README.md +71 -0
  90. data/examples/verify_quickstart_layout/app.rb +71 -0
  91. data/examples/verify_quickstart_lifecycle/README.md +56 -0
  92. data/examples/verify_quickstart_lifecycle/app.rb +54 -0
  93. data/examples/verify_readme_usage/README.md +43 -0
  94. data/examples/verify_readme_usage/app.rb +40 -0
  95. data/examples/widget_barchart_demo/README.md +49 -0
  96. data/examples/widget_barchart_demo/app.rb +238 -0
  97. data/examples/widget_block_demo/README.md +34 -0
  98. data/examples/widget_block_demo/app.rb +256 -0
  99. data/examples/widget_box_demo/README.md +45 -0
  100. data/examples/{box_demo → widget_box_demo}/app.rb +99 -65
  101. data/examples/widget_calendar_demo/README.md +39 -0
  102. data/examples/widget_calendar_demo/app.rb +109 -0
  103. data/examples/widget_canvas_demo/README.md +27 -0
  104. data/examples/widget_canvas_demo/app.rb +123 -0
  105. data/examples/widget_cell_demo/README.md +36 -0
  106. data/examples/widget_cell_demo/app.rb +111 -0
  107. data/examples/widget_center_demo/README.md +29 -0
  108. data/examples/widget_center_demo/app.rb +116 -0
  109. data/examples/widget_chart_demo/README.md +41 -0
  110. data/examples/widget_chart_demo/app.rb +218 -0
  111. data/examples/widget_gauge_demo/README.md +41 -0
  112. data/examples/widget_gauge_demo/app.rb +212 -0
  113. data/examples/widget_layout_split/README.md +44 -0
  114. data/examples/widget_layout_split/app.rb +246 -0
  115. data/examples/widget_line_gauge_demo/README.md +41 -0
  116. data/examples/widget_line_gauge_demo/app.rb +217 -0
  117. data/examples/widget_list_demo/README.md +49 -0
  118. data/examples/widget_list_demo/app.rb +366 -0
  119. data/examples/widget_map_demo/README.md +39 -0
  120. data/examples/{map_demo → widget_map_demo}/app.rb +24 -21
  121. data/examples/widget_overlay_demo/app.rb +248 -0
  122. data/examples/widget_popup_demo/README.md +36 -0
  123. data/examples/widget_popup_demo/app.rb +104 -0
  124. data/examples/widget_ratatui_logo_demo/README.md +34 -0
  125. data/examples/widget_ratatui_logo_demo/app.rb +103 -0
  126. data/examples/widget_ratatui_mascot_demo/README.md +34 -0
  127. data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
  128. data/examples/widget_rect/README.md +38 -0
  129. data/examples/widget_rect/app.rb +205 -0
  130. data/examples/widget_render/README.md +37 -0
  131. data/examples/widget_render/app.rb +184 -0
  132. data/examples/widget_rich_text/README.md +35 -0
  133. data/examples/widget_rich_text/app.rb +166 -0
  134. data/examples/widget_scroll_text/README.md +37 -0
  135. data/examples/widget_scroll_text/app.rb +107 -0
  136. data/examples/widget_scrollbar_demo/README.md +37 -0
  137. data/examples/widget_scrollbar_demo/app.rb +153 -0
  138. data/examples/widget_sparkline_demo/README.md +42 -0
  139. data/examples/widget_sparkline_demo/app.rb +275 -0
  140. data/examples/widget_style_colors/README.md +34 -0
  141. data/examples/widget_style_colors/app.rb +19 -21
  142. data/examples/widget_table_demo/README.md +48 -0
  143. data/examples/widget_table_demo/app.rb +239 -0
  144. data/examples/widget_tabs_demo/README.md +41 -0
  145. data/examples/widget_tabs_demo/app.rb +181 -0
  146. data/examples/widget_text_width/README.md +35 -0
  147. data/examples/widget_text_width/app.rb +106 -0
  148. data/ext/ratatui_ruby/Cargo.lock +11 -4
  149. data/ext/ratatui_ruby/Cargo.toml +2 -1
  150. data/ext/ratatui_ruby/src/events.rs +359 -62
  151. data/ext/ratatui_ruby/src/frame.rs +227 -0
  152. data/ext/ratatui_ruby/src/lib.rs +110 -27
  153. data/ext/ratatui_ruby/src/rendering.rs +8 -4
  154. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  155. data/ext/ratatui_ruby/src/style.rs +138 -57
  156. data/ext/ratatui_ruby/src/terminal.rs +42 -22
  157. data/ext/ratatui_ruby/src/text.rs +14 -7
  158. data/ext/ratatui_ruby/src/widgets/barchart.rs +74 -54
  159. data/ext/ratatui_ruby/src/widgets/block.rs +7 -6
  160. data/ext/ratatui_ruby/src/widgets/canvas.rs +21 -3
  161. data/ext/ratatui_ruby/src/widgets/chart.rs +20 -10
  162. data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
  163. data/ext/ratatui_ruby/src/widgets/layout.rs +9 -4
  164. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
  165. data/ext/ratatui_ruby/src/widgets/list.rs +211 -12
  166. data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
  167. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  168. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  169. data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
  170. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +19 -8
  171. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +17 -10
  172. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +97 -3
  173. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  174. data/ext/ratatui_ruby/src/widgets/sparkline.rs +14 -11
  175. data/ext/ratatui_ruby/src/widgets/table.rs +121 -5
  176. data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
  177. data/ext/ratatui_ruby/src/widgets/tabs.rs +11 -11
  178. data/lib/ratatui_ruby/cell.rb +7 -7
  179. data/lib/ratatui_ruby/event/key/character.rb +35 -0
  180. data/lib/ratatui_ruby/event/key/media.rb +44 -0
  181. data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
  182. data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
  183. data/lib/ratatui_ruby/event/key/system.rb +45 -0
  184. data/lib/ratatui_ruby/event/key.rb +112 -52
  185. data/lib/ratatui_ruby/event/mouse.rb +3 -3
  186. data/lib/ratatui_ruby/event/none.rb +43 -0
  187. data/lib/ratatui_ruby/event/paste.rb +1 -1
  188. data/lib/ratatui_ruby/event.rb +56 -4
  189. data/lib/ratatui_ruby/frame.rb +183 -0
  190. data/lib/ratatui_ruby/list_state.rb +88 -0
  191. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +13 -13
  192. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +1 -5
  193. data/lib/ratatui_ruby/schema/bar_chart.rb +217 -217
  194. data/lib/ratatui_ruby/schema/block.rb +163 -168
  195. data/lib/ratatui_ruby/schema/calendar.rb +66 -67
  196. data/lib/ratatui_ruby/schema/canvas.rb +63 -63
  197. data/lib/ratatui_ruby/schema/center.rb +46 -46
  198. data/lib/ratatui_ruby/schema/chart.rb +135 -143
  199. data/lib/ratatui_ruby/schema/clear.rb +42 -42
  200. data/lib/ratatui_ruby/schema/constraint.rb +76 -76
  201. data/lib/ratatui_ruby/schema/cursor.rb +30 -25
  202. data/lib/ratatui_ruby/schema/gauge.rb +54 -52
  203. data/lib/ratatui_ruby/schema/layout.rb +87 -87
  204. data/lib/ratatui_ruby/schema/line_gauge.rb +62 -62
  205. data/lib/ratatui_ruby/schema/list.rb +103 -80
  206. data/lib/ratatui_ruby/schema/list_item.rb +41 -0
  207. data/lib/ratatui_ruby/schema/overlay.rb +31 -31
  208. data/lib/ratatui_ruby/schema/paragraph.rb +80 -80
  209. data/lib/ratatui_ruby/schema/ratatui_logo.rb +10 -6
  210. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +10 -5
  211. data/lib/ratatui_ruby/schema/rect.rb +99 -56
  212. data/lib/ratatui_ruby/schema/scrollbar.rb +119 -119
  213. data/lib/ratatui_ruby/schema/shape/label.rb +1 -1
  214. data/lib/ratatui_ruby/schema/sparkline.rb +111 -110
  215. data/lib/ratatui_ruby/schema/style.rb +66 -46
  216. data/lib/ratatui_ruby/schema/table.rb +126 -115
  217. data/lib/ratatui_ruby/schema/tabs.rb +66 -67
  218. data/lib/ratatui_ruby/schema/text.rb +69 -1
  219. data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
  220. data/lib/ratatui_ruby/session/autodoc.rb +482 -0
  221. data/lib/ratatui_ruby/session.rb +55 -23
  222. data/lib/ratatui_ruby/table_state.rb +90 -0
  223. data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
  224. data/lib/ratatui_ruby/test_helper/snapshot.rb +390 -0
  225. data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
  226. data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
  227. data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
  228. data/lib/ratatui_ruby/test_helper.rb +66 -193
  229. data/lib/ratatui_ruby/version.rb +1 -1
  230. data/lib/ratatui_ruby.rb +100 -51
  231. data/{examples/sparkline_demo → sig/examples/app_all_events}/app.rbs +3 -2
  232. data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
  233. data/sig/examples/app_all_events/model/events.rbs +15 -0
  234. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  235. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  236. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  237. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  238. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  239. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  240. data/sig/examples/app_all_events/view.rbs +8 -0
  241. data/sig/examples/app_all_events/view_state.rbs +15 -0
  242. data/{examples/list_demo → sig/examples/app_color_picker}/app.rbs +2 -2
  243. data/sig/examples/app_login_form/app.rbs +11 -0
  244. data/sig/examples/app_stateful_interaction/app.rbs +33 -0
  245. data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
  246. data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
  247. data/sig/examples/verify_readme_usage/app.rbs +11 -0
  248. data/sig/examples/widget_block_demo/app.rbs +32 -0
  249. data/sig/examples/widget_box_demo/app.rbs +11 -0
  250. data/sig/examples/widget_calendar_demo/app.rbs +11 -0
  251. data/sig/examples/widget_cell_demo/app.rbs +11 -0
  252. data/sig/examples/widget_chart_demo/app.rbs +11 -0
  253. data/{examples/gauge_demo → sig/examples/widget_gauge_demo}/app.rbs +4 -0
  254. data/sig/examples/widget_layout_split/app.rbs +10 -0
  255. data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
  256. data/sig/examples/widget_list_demo/app.rbs +12 -0
  257. data/sig/examples/widget_map_demo/app.rbs +11 -0
  258. data/sig/examples/widget_popup_demo/app.rbs +11 -0
  259. data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
  260. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
  261. data/sig/examples/widget_rect/app.rbs +12 -0
  262. data/sig/examples/widget_render/app.rbs +10 -0
  263. data/sig/examples/widget_rich_text/app.rbs +11 -0
  264. data/sig/examples/widget_scroll_text/app.rbs +11 -0
  265. data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
  266. data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
  267. data/{examples → sig/examples}/widget_style_colors/app.rbs +1 -1
  268. data/sig/examples/widget_table_demo/app.rbs +11 -0
  269. data/sig/examples/widget_text_width/app.rbs +10 -0
  270. data/sig/ratatui_ruby/event.rbs +11 -1
  271. data/sig/ratatui_ruby/frame.rbs +11 -0
  272. data/sig/ratatui_ruby/list_state.rbs +13 -0
  273. data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -4
  274. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
  275. data/sig/ratatui_ruby/schema/draw.rbs +4 -0
  276. data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
  277. data/sig/ratatui_ruby/schema/layout.rbs +1 -1
  278. data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
  279. data/sig/ratatui_ruby/schema/list.rbs +4 -2
  280. data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
  281. data/sig/ratatui_ruby/schema/rect.rbs +3 -0
  282. data/sig/ratatui_ruby/schema/style.rbs +3 -3
  283. data/sig/ratatui_ruby/schema/table.rbs +3 -1
  284. data/sig/ratatui_ruby/schema/text.rbs +8 -6
  285. data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
  286. data/sig/ratatui_ruby/session.rbs +107 -0
  287. data/sig/ratatui_ruby/table_state.rbs +15 -0
  288. data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
  289. data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
  290. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
  291. data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
  292. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
  293. data/sig/ratatui_ruby/test_helper.rbs +5 -4
  294. data/tasks/autodoc/examples.rb +79 -0
  295. data/tasks/autodoc/inventory.rb +63 -0
  296. data/tasks/autodoc/member.rb +56 -0
  297. data/tasks/autodoc/name.rb +19 -0
  298. data/tasks/autodoc/notice.rb +26 -0
  299. data/tasks/autodoc/rbs.rb +38 -0
  300. data/tasks/autodoc/rdoc.rb +45 -0
  301. data/tasks/autodoc.rake +53 -0
  302. data/tasks/bump/changelog.rb +3 -3
  303. data/tasks/bump/history.rb +2 -2
  304. data/tasks/bump/links.rb +67 -0
  305. data/tasks/doc.rake +600 -6
  306. data/tasks/example_viewer.html.erb +172 -0
  307. data/tasks/lint.rake +8 -4
  308. data/tasks/resources/index.html.erb +6 -0
  309. data/tasks/sourcehut.rake +70 -30
  310. data/tasks/terminal_preview/app_screenshot.rb +14 -6
  311. data/tasks/terminal_preview/crash_report.rb +7 -9
  312. data/tasks/terminal_preview/launcher_script.rb +4 -6
  313. data/tasks/terminal_preview/preview_collection.rb +4 -6
  314. data/tasks/terminal_preview/safety_confirmation.rb +3 -5
  315. data/tasks/terminal_preview/saved_screenshot.rb +10 -11
  316. data/tasks/terminal_preview/terminal_window.rb +7 -9
  317. data/tasks/test.rake +1 -1
  318. data/tasks/website/index_page.rb +3 -3
  319. data/tasks/website/version.rb +10 -10
  320. data/tasks/website/version_menu.rb +10 -12
  321. data/tasks/website/versioned_documentation.rb +49 -17
  322. data/tasks/website/website.rb +6 -8
  323. data/tasks/website.rake +4 -4
  324. metadata +232 -127
  325. data/LICENSES/BSD-2-Clause.txt +0 -9
  326. data/doc/contributors/better_dx.md +0 -543
  327. data/doc/contributors/example_analysis.md +0 -82
  328. data/doc/images/all_events.png +0 -0
  329. data/doc/images/block_padding.png +0 -0
  330. data/doc/images/block_titles.png +0 -0
  331. data/doc/images/box_demo.png +0 -0
  332. data/doc/images/calendar_demo.png +0 -0
  333. data/doc/images/cell_demo.png +0 -0
  334. data/doc/images/chart_demo.png +0 -0
  335. data/doc/images/flex_layout.png +0 -0
  336. data/doc/images/gauge_demo.png +0 -0
  337. data/doc/images/line_gauge_demo.png +0 -0
  338. data/doc/images/list_demo.png +0 -0
  339. data/doc/images/list_styles.png +0 -0
  340. data/doc/images/login_form.png +0 -0
  341. data/doc/images/quickstart_dsl.png +0 -0
  342. data/doc/images/quickstart_lifecycle.png +0 -0
  343. data/doc/images/readme_usage.png +0 -0
  344. data/doc/images/rich_text.png +0 -0
  345. data/doc/images/scroll_text.png +0 -0
  346. data/doc/images/scrollbar_demo.png +0 -0
  347. data/doc/images/sparkline_demo.png +0 -0
  348. data/doc/images/table_flex.png +0 -0
  349. data/doc/images/table_select.png +0 -0
  350. data/examples/all_events/app.rb +0 -169
  351. data/examples/all_events/app.rbs +0 -7
  352. data/examples/all_events/test_app.rb +0 -139
  353. data/examples/analytics/app.rb +0 -258
  354. data/examples/analytics/app.rbs +0 -7
  355. data/examples/analytics/test_app.rb +0 -132
  356. data/examples/block_padding/app.rb +0 -63
  357. data/examples/block_padding/app.rbs +0 -7
  358. data/examples/block_padding/test_app.rb +0 -31
  359. data/examples/block_titles/app.rb +0 -61
  360. data/examples/block_titles/app.rbs +0 -7
  361. data/examples/block_titles/test_app.rb +0 -34
  362. data/examples/box_demo/app.rbs +0 -7
  363. data/examples/box_demo/test_app.rb +0 -88
  364. data/examples/calendar_demo/app.rb +0 -101
  365. data/examples/calendar_demo/app.rbs +0 -7
  366. data/examples/calendar_demo/test_app.rb +0 -108
  367. data/examples/cell_demo/app.rb +0 -108
  368. data/examples/cell_demo/app.rbs +0 -7
  369. data/examples/cell_demo/test_app.rb +0 -36
  370. data/examples/chart_demo/app.rb +0 -203
  371. data/examples/chart_demo/app.rbs +0 -7
  372. data/examples/chart_demo/test_app.rb +0 -102
  373. data/examples/custom_widget/app.rb +0 -51
  374. data/examples/custom_widget/app.rbs +0 -7
  375. data/examples/custom_widget/test_app.rb +0 -30
  376. data/examples/flex_layout/app.rb +0 -156
  377. data/examples/flex_layout/app.rbs +0 -7
  378. data/examples/flex_layout/test_app.rb +0 -65
  379. data/examples/gauge_demo/app.rb +0 -182
  380. data/examples/gauge_demo/test_app.rb +0 -120
  381. data/examples/hit_test/app.rb +0 -175
  382. data/examples/hit_test/app.rbs +0 -7
  383. data/examples/hit_test/test_app.rb +0 -102
  384. data/examples/line_gauge_demo/app.rb +0 -190
  385. data/examples/line_gauge_demo/app.rbs +0 -7
  386. data/examples/line_gauge_demo/test_app.rb +0 -129
  387. data/examples/list_demo/app.rb +0 -253
  388. data/examples/list_demo/test_app.rb +0 -237
  389. data/examples/list_styles/app.rb +0 -140
  390. data/examples/list_styles/app.rbs +0 -7
  391. data/examples/list_styles/test_app.rb +0 -157
  392. data/examples/login_form/app.rbs +0 -7
  393. data/examples/login_form/test_app.rb +0 -51
  394. data/examples/map_demo/app.rbs +0 -7
  395. data/examples/map_demo/test_app.rb +0 -149
  396. data/examples/mouse_events/app.rb +0 -97
  397. data/examples/mouse_events/app.rbs +0 -7
  398. data/examples/mouse_events/test_app.rb +0 -53
  399. data/examples/popup_demo/app.rb +0 -103
  400. data/examples/popup_demo/app.rbs +0 -7
  401. data/examples/popup_demo/test_app.rb +0 -54
  402. data/examples/quickstart_dsl/app.rbs +0 -7
  403. data/examples/quickstart_dsl/test_app.rb +0 -29
  404. data/examples/quickstart_lifecycle/app.rb +0 -39
  405. data/examples/quickstart_lifecycle/app.rbs +0 -7
  406. data/examples/quickstart_lifecycle/test_app.rb +0 -29
  407. data/examples/ratatui_logo_demo/app.rb +0 -79
  408. data/examples/ratatui_logo_demo/app.rbs +0 -7
  409. data/examples/ratatui_logo_demo/test_app.rb +0 -51
  410. data/examples/ratatui_mascot_demo/app.rb +0 -84
  411. data/examples/ratatui_mascot_demo/app.rbs +0 -7
  412. data/examples/ratatui_mascot_demo/test_app.rb +0 -47
  413. data/examples/readme_usage/app.rb +0 -29
  414. data/examples/readme_usage/app.rbs +0 -7
  415. data/examples/readme_usage/test_app.rb +0 -29
  416. data/examples/rich_text/app.rb +0 -141
  417. data/examples/rich_text/app.rbs +0 -7
  418. data/examples/rich_text/test_app.rb +0 -166
  419. data/examples/scroll_text/app.rb +0 -103
  420. data/examples/scroll_text/app.rbs +0 -7
  421. data/examples/scroll_text/test_app.rb +0 -110
  422. data/examples/scrollbar_demo/app.rb +0 -143
  423. data/examples/scrollbar_demo/app.rbs +0 -7
  424. data/examples/scrollbar_demo/test_app.rb +0 -77
  425. data/examples/sparkline_demo/app.rb +0 -240
  426. data/examples/sparkline_demo/test_app.rb +0 -107
  427. data/examples/table_flex/app.rb +0 -65
  428. data/examples/table_flex/app.rbs +0 -7
  429. data/examples/table_flex/test_app.rb +0 -36
  430. data/examples/table_select/app.rb +0 -198
  431. data/examples/table_select/app.rbs +0 -7
  432. data/examples/table_select/test_app.rb +0 -180
  433. data/examples/widget_style_colors/test_app.rb +0 -48
  434. data/tasks/bump/comparison_links.rb +0 -41
  435. /data/doc/images/{analytics.png → app_analytics.png} +0 -0
  436. /data/doc/images/{custom_widget.png → app_custom_widget.png} +0 -0
  437. /data/doc/images/{mouse_events.png → app_mouse_events.png} +0 -0
  438. /data/doc/images/{map_demo.png → widget_map_demo.png} +0 -0
  439. /data/doc/images/{popup_demo.png → widget_popup_demo.png} +0 -0
  440. /data/doc/images/{hit_test.png → widget_rect.png} +0 -0
  441. /data/{doc/images/ratatui_logo_demo.png → exe/.gitkeep} +0 -0
@@ -0,0 +1,272 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
+ require "ratatui_ruby"
8
+ require "faker"
9
+
10
+ # A "Master Class" example demonstrating Stateful Widget Rendering and Interaction.
11
+ #
12
+ # This example shows how to:
13
+ # 1. Use mutable State objects (ListState, TableState) for selection and scrolling
14
+ # 2. Read back the calculated scroll offset from the backend (state.offset)
15
+ # 3. Implement precise mouse-click-to-row interaction using that offset
16
+ class AppStatefulInteraction
17
+ def initialize
18
+ # Data Models
19
+ # Tables are the categories on the left
20
+ @tables = ["Users", "Orders", "Products", "Invoices", "Audit Logs"]
21
+ @headers = {
22
+ "Users" => ["Name", "Email", "Role"],
23
+ "Orders" => ["Order ID", "Status", "Amount"],
24
+ "Products" => ["Product", "SKU", "Status"],
25
+ "Invoices" => ["Invoice #", "Status", "Amount"],
26
+ "Audit Logs" => ["Event", "Action", "IP Address"],
27
+ }
28
+
29
+ # Generate dummy data for each table
30
+ # Use fixed seed for deterministic behavior in CI/Tests
31
+ if ENV["CI"] == "true" || ENV["RATA_SEED"]
32
+ seed = (ENV["RATA_SEED"] || 12345).to_i
33
+ Faker::Config.random = Random.new(seed)
34
+ # Also seed Kernel.rand/Array#sample just in case
35
+ srand(seed)
36
+ end
37
+ rand_price = -> { "$#{Faker::Commerce.price(range: 10..500.0)}" }
38
+
39
+ @data = {
40
+ "Users" => Array.new(50) { [Faker::Name.name, Faker::Internet.email, %w[Admin Editor Viewer].sample] },
41
+ "Orders" => Array.new(50) { [Faker::Commerce.promotion_code(digits: 4), ["Completed", "Pending", "Failed"].sample, rand_price.call] },
42
+ "Products" => Array.new(50) { [Faker::Commerce.product_name, "SKU-#{Faker::Number.number(digits: 4)}", ["In Stock", "Low Stock"].sample] },
43
+ "Invoices" => Array.new(50) { ["INV-#{Faker::Number.number(digits: 6)}", ["Paid", "Unpaid"].sample, rand_price.call] },
44
+ "Audit Logs" => Array.new(50) { ["Log #{Faker::Number.unique.number(digits: 3)}", ["Login Success", "Login Failed", "Logout"].sample, Faker::Internet.ip_v4_address] },
45
+ }
46
+
47
+ # State Objects - These are mutable and persist across frames!
48
+ @list_state = RatatuiRuby::ListState.new(nil)
49
+ @table_state = RatatuiRuby::TableState.new(nil)
50
+
51
+ # Initialize selection
52
+ @list_state.select(0)
53
+ @table_state.select(0)
54
+
55
+ # Active Pane Focus (:list or :table)
56
+ @active_pane = :list
57
+ end
58
+
59
+ def run
60
+ RatatuiRuby.run do |tui|
61
+ @tui = tui
62
+
63
+ # Styles can only be created once TUI is initialized
64
+ @style_active = @tui.style(fg: :yellow, modifiers: [:bold])
65
+ @style_inactive = @tui.style(fg: :dark_gray)
66
+ @style_highlight = @tui.style(bg: :blue, fg: :white, modifiers: [:bold])
67
+
68
+ loop do
69
+ render
70
+ break if handle_input == :quit
71
+ end
72
+ end
73
+ end
74
+
75
+ private def render
76
+ @tui.draw do |frame|
77
+ # 1. Layout
78
+ main_area, help_area = @tui.layout_split(
79
+ frame.area,
80
+ direction: :vertical,
81
+ constraints: [
82
+ @tui.constraint_fill(1),
83
+ @tui.constraint_length(1),
84
+ ]
85
+ )
86
+
87
+ list_area, table_area = @tui.layout_split(
88
+ main_area,
89
+ direction: :horizontal,
90
+ constraints: [
91
+ @tui.constraint_percentage(30),
92
+ @tui.constraint_percentage(70),
93
+ ]
94
+ )
95
+
96
+ # Save areas for hit testing
97
+ @list_area = list_area
98
+ @table_area = table_area
99
+
100
+ # 2. Render List (Left Pane)
101
+ render_list(frame, list_area)
102
+
103
+ # 3. Render Table (Right Pane)
104
+ render_table(frame, table_area)
105
+
106
+ # 4. Render Help
107
+ help_text = "q: Quit | Tab/Arrows: Nav | Mouse: Click rows (Try it!)"
108
+ frame.render_widget(@tui.paragraph(text: help_text), help_area)
109
+ end
110
+ end
111
+
112
+ private def render_list(frame, area)
113
+ is_active = @active_pane == :list
114
+
115
+ # Render main list
116
+ list = @tui.list(
117
+ items: @tables,
118
+ block: @tui.block(
119
+ title: " Tables ",
120
+ borders: [:all],
121
+ border_style: is_active ? @style_active : @style_inactive
122
+ ),
123
+ highlight_style: @style_highlight
124
+ )
125
+ # KEY STEP: Pass the state object!
126
+ frame.render_stateful_widget(list, area, @list_state)
127
+
128
+ # Render Scrollbar
129
+ scrollbar = @tui.scrollbar(
130
+ content_length: 0,
131
+ position: 0,
132
+ orientation: :vertical_right,
133
+ track_symbol: nil,
134
+ thumb_symbol: "▐"
135
+ )
136
+ scrollbar_state = RatatuiRuby::ScrollbarState.new(@tables.size)
137
+ scrollbar_state.position = @list_state.offset
138
+ scrollbar_state.viewport_content_length = area.height - 2
139
+
140
+ frame.render_stateful_widget(scrollbar, area, scrollbar_state)
141
+ end
142
+
143
+ private def render_table(frame, area)
144
+ is_active = @active_pane == :table
145
+
146
+ # Get current data based on list selection
147
+ current_table = @tables[@list_state.selected || 0]
148
+ rows = @data[current_table]
149
+
150
+ # Render table
151
+ table = @tui.table(
152
+ rows:,
153
+ header: @headers[current_table],
154
+ widths: [
155
+ @tui.constraint_percentage(30),
156
+ @tui.constraint_percentage(40),
157
+ @tui.constraint_percentage(30),
158
+ ],
159
+ block: @tui.block(
160
+ title: " #{current_table} Data ",
161
+ borders: [:all],
162
+ border_style: is_active ? @style_active : @style_inactive
163
+ ),
164
+ highlight_style: @style_highlight
165
+ )
166
+
167
+ frame.render_stateful_widget(table, area, @table_state)
168
+
169
+ # Render Scrollbar
170
+ scrollbar = @tui.scrollbar(
171
+ content_length: 0,
172
+ position: 0,
173
+ orientation: :vertical_right,
174
+ track_symbol: nil,
175
+ thumb_symbol: "▐"
176
+ )
177
+ scrollbar_state = RatatuiRuby::ScrollbarState.new(rows.size)
178
+ scrollbar_state.position = @table_state.offset
179
+ scrollbar_state.viewport_content_length = area.height - 4 # borders + header + margin
180
+
181
+ frame.render_stateful_widget(scrollbar, area, scrollbar_state)
182
+ end
183
+
184
+ private def handle_input
185
+ case @tui.poll_event
186
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
187
+ :quit
188
+
189
+ # Navigation
190
+ in { type: :key, code: "tab" } | { type: :key, code: "right" } | { type: :key, code: "left" }
191
+ @active_pane = (@active_pane == :list) ? :table : :list
192
+
193
+ in { type: :key, code: "down" }
194
+ scroll_active(1)
195
+
196
+ in { type: :key, code: "up" }
197
+ scroll_active(-1)
198
+
199
+ # Mouse Interaction
200
+ in { type: :mouse, kind: "down", x:, y: }
201
+ handle_click(x, y)
202
+
203
+ else
204
+ # no-op
205
+ end
206
+ end
207
+
208
+ private def scroll_active(delta)
209
+ if @active_pane == :list
210
+ i = @list_state.selected || 0
211
+ new_i = (i + delta).clamp(0, @tables.size - 1)
212
+ @list_state.select(new_i)
213
+ # Reset table selection when switching categories
214
+ if i != new_i
215
+ @table_state.select(0)
216
+ @table_state.select_column(nil) # Ensure clean slate
217
+ end
218
+ else
219
+ current_rows = @data[@tables[@list_state.selected || 0]].size
220
+ i = @table_state.selected || 0
221
+ new_i = (i + delta).clamp(0, current_rows - 1)
222
+ @table_state.select(new_i)
223
+ end
224
+ end
225
+
226
+ private def handle_click(x, y)
227
+ if @list_area.contains?(x, y)
228
+ handle_list_click(y)
229
+ elsif @table_area.contains?(x, y)
230
+ handle_table_click(y)
231
+ end
232
+ end
233
+
234
+ private def handle_list_click(mouse_y)
235
+ @active_pane = :list
236
+
237
+ # CRITICAL: Read back the offset!
238
+ # Formula: clicked_index = (mouse_y - list_top - border_width) + offset
239
+ offset = @list_state.offset
240
+ list_top = @list_area.y
241
+ border_width = 1 # Top border
242
+
243
+ clicked_row = (mouse_y - list_top - border_width) + offset
244
+
245
+ if clicked_row >= 0 && clicked_row < @tables.size
246
+ @list_state.select(clicked_row)
247
+ @table_state.select(0) # Reset table when category changes
248
+ end
249
+ end
250
+
251
+ private def handle_table_click(mouse_y)
252
+ @active_pane = :table
253
+
254
+ # CRITICAL: Read back the offset!
255
+ # Formula: clicked_index = (mouse_y - table_top - border - header_height - margin) + offset
256
+ offset = @table_state.offset
257
+ table_top = @table_area.y
258
+ border_width = 1
259
+ header_height = 1
260
+ # No header_margin without Row margin
261
+ effective_top = table_top + border_width + header_height
262
+
263
+ clicked_row = (mouse_y - effective_top) + offset
264
+
265
+ current_table_data = @data[@tables[@list_state.selected || 0]]
266
+ if clicked_row >= 0 && clicked_row < current_table_data.size
267
+ @table_state.select(clicked_row)
268
+ end
269
+ end
270
+ end
271
+
272
+ AppStatefulInteraction.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ # Timeout Demo: Non-Blocking Event Polling
7
+ #
8
+ # This demo shows how to use poll_event with a timeout for game loops
9
+ # and animation systems that need to update at a fixed frame rate
10
+ # regardless of user input.
11
+ #
12
+ # Run: bundle exec ruby examples/timeout_demo.rb
13
+ #
14
+ # Expected behavior:
15
+ # - "Tick..." prints every 100ms continuously
16
+ # - Pressing a key prints "Key Pressed: [key]" immediately
17
+ # - Press 'q' to quit
18
+
19
+ require "bundler/setup"
20
+ require "ratatui_ruby"
21
+
22
+ puts "Timeout Demo - Press 'q' to quit"
23
+ puts "Watch: continuous ticks with responsive key handling"
24
+ puts
25
+
26
+ tick_count = 0
27
+ running = true
28
+
29
+ while running
30
+ # Poll with 100ms timeout (~10 FPS tick rate)
31
+ event = RatatuiRuby.poll_event(timeout: 0.1)
32
+
33
+ if event.none?
34
+ # No input, just tick
35
+ tick_count += 1
36
+ puts "Tick #{tick_count}..."
37
+ elsif event.key?
38
+ puts "Key Pressed: #{event.code}"
39
+ running = false if event.code == "q"
40
+ end
41
+ end
42
+
43
+ puts "\nGoodbye!"
@@ -0,0 +1,48 @@
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 "Idiomatic Session" tutorial in the [Quickstart](../../doc/quickstart.md#idiomatic-session).
9
+
10
+ This example exists as a documentation regression test. It ensures the recommended DSL and session-based workflow remains functional.
11
+
12
+ ## Usage
13
+
14
+ <!-- SYNC:START:app.rb:main -->
15
+ ```ruby
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_color: "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
+ ```
46
+ <!-- SYNC:END -->
47
+
48
+ ![verify_quickstart_dsl](../../doc/images/verify_quickstart_dsl.png)
@@ -7,9 +7,10 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
7
 
8
8
  require "ratatui_ruby"
9
9
 
10
- class QuickstartDslApp
10
+ class VerifyQuickstartDsl
11
11
  def run
12
12
  # 1. Initialize the terminal, start the run loop, and ensure the terminal is restored.
13
+ # [SYNC:START:main]
13
14
  RatatuiRuby.run do |tui|
14
15
  loop do
15
16
  # 2. Create your UI with methods instead of classes.
@@ -18,19 +19,29 @@ class QuickstartDslApp
18
19
  alignment: :center,
19
20
  block: tui.block(
20
21
  title: "My Ruby TUI App",
22
+ title_alignment: :center,
21
23
  borders: [:all],
22
- border_color: "cyan"
24
+ border_color: "cyan",
25
+ style: { fg: "white" }
23
26
  )
24
27
  )
25
28
 
26
29
  # 3. Use RatatuiRuby methods, too.
27
- tui.draw(view)
28
- event = tui.poll_event
30
+ tui.draw do |frame|
31
+ frame.render_widget(view, frame.area)
32
+ end
29
33
 
30
- break if event == "q" || event == :ctrl_c
34
+ # 4. Poll for events with pattern matching
35
+ case tui.poll_event
36
+ in { type: :key, code: "q" }
37
+ break
38
+ else
39
+ # Ignore other events
40
+ end
31
41
  end
32
42
  end
43
+ # [SYNC:END:main]
33
44
  end
34
45
  end
35
46
 
36
- QuickstartDslApp.new.run if __FILE__ == $PROGRAM_NAME
47
+ VerifyQuickstartDsl.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,71 @@
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/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
+ <!-- SYNC:START:app.rb:main -->
15
+ ```ruby
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_color: "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
+ ```
69
+ <!-- SYNC:END -->
70
+
71
+ ![verify_quickstart_layout](../../doc/images/verify_quickstart_layout.png)
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
+
8
+ require "ratatui_ruby"
9
+
10
+ class VerifyQuickstartLayout
11
+ def run
12
+ RatatuiRuby.run do |tui|
13
+ # [SYNC:START:main]
14
+ loop do
15
+ tui.draw do |frame|
16
+ # 1. Split the screen
17
+ top, bottom = tui.layout_split(
18
+ frame.area,
19
+ direction: :vertical,
20
+ constraints: [
21
+ tui.constraint_percentage(75),
22
+ tui.constraint_percentage(25),
23
+ ]
24
+ )
25
+
26
+ # 2. Render Top Widget
27
+ frame.render_widget(
28
+ tui.paragraph(
29
+ text: "Hello, Ratatui!",
30
+ alignment: :center,
31
+ block: tui.block(title: "Content", borders: [:all], border_color: "cyan")
32
+ ),
33
+ top
34
+ )
35
+
36
+ # 3. Render Bottom Widget with Styled Text
37
+ # We use a Line of Spans to style specific characters
38
+ text_line = tui.text_line(
39
+ spans: [
40
+ tui.text_span(content: "Press '"),
41
+ tui.text_span(
42
+ content: "q",
43
+ style: tui.style(modifiers: [:bold, :underlined])
44
+ ),
45
+ tui.text_span(content: "' to quit."),
46
+ ],
47
+ alignment: :center
48
+ )
49
+
50
+ frame.render_widget(
51
+ tui.paragraph(
52
+ text: text_line,
53
+ block: tui.block(title: "Controls", borders: [:all])
54
+ ),
55
+ bottom
56
+ )
57
+ end
58
+
59
+ case tui.poll_event
60
+ in { type: :key, code: "q" }
61
+ break
62
+ else
63
+ # Ignore other events
64
+ end
65
+ end
66
+ # [SYNC:END:main]
67
+ end
68
+ end
69
+ end
70
+
71
+ VerifyQuickstartLayout.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,56 @@
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/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
+ <!-- SYNC:START:app.rb:main -->
15
+ ```ruby
16
+ # 1. Initialize the terminal
17
+ RatatuiRuby.init_terminal
18
+
19
+ begin
20
+ # The Main Loop
21
+ loop do
22
+ # 2. Create your UI (Immediate Mode)
23
+ # We define a Paragraph widget inside a Block with a title and borders.
24
+ view = RatatuiRuby::Paragraph.new(
25
+ text: "Hello, Ratatui! Press 'q' to quit.",
26
+ alignment: :center,
27
+ block: RatatuiRuby::Block.new(
28
+ title: "My Ruby TUI App",
29
+ title_alignment: :center,
30
+ borders: [:all],
31
+ border_color: "cyan",
32
+ style: { fg: "white" }
33
+ )
34
+ )
35
+
36
+ # 3. Draw the UI
37
+ RatatuiRuby.draw do |frame|
38
+ frame.render_widget(view, frame.area)
39
+ end
40
+
41
+ # 4. Poll for events
42
+ case RatatuiRuby.poll_event
43
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
44
+ break
45
+ else
46
+ nil
47
+ end
48
+ end
49
+ ensure
50
+ # 5. Restore the terminal to its original state
51
+ RatatuiRuby.restore_terminal
52
+ end
53
+ ```
54
+ <!-- SYNC:END -->
55
+
56
+ ![verify_quickstart_lifecycle](../../doc/images/verify_quickstart_lifecycle.png)
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
+
8
+ require "ratatui_ruby"
9
+
10
+ class VerifyQuickstartLifecycle
11
+ def run
12
+ # [SYNC:START:main]
13
+ # 1. Initialize the terminal
14
+ RatatuiRuby.init_terminal
15
+
16
+ begin
17
+ # The Main Loop
18
+ loop do
19
+ # 2. Create your UI (Immediate Mode)
20
+ # We define a Paragraph widget inside a Block with a title and borders.
21
+ view = RatatuiRuby::Paragraph.new(
22
+ text: "Hello, Ratatui! Press 'q' to quit.",
23
+ alignment: :center,
24
+ block: RatatuiRuby::Block.new(
25
+ title: "My Ruby TUI App",
26
+ title_alignment: :center,
27
+ borders: [:all],
28
+ border_color: "cyan",
29
+ style: { fg: "white" }
30
+ )
31
+ )
32
+
33
+ # 3. Draw the UI
34
+ RatatuiRuby.draw do |frame|
35
+ frame.render_widget(view, frame.area)
36
+ end
37
+
38
+ # 4. Poll for events
39
+ case RatatuiRuby.poll_event
40
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
41
+ break
42
+ else
43
+ nil
44
+ end
45
+ end
46
+ ensure
47
+ # 5. Restore the terminal to its original state
48
+ RatatuiRuby.restore_terminal
49
+ end
50
+ # [SYNC:END:main]
51
+ end
52
+ end
53
+
54
+ VerifyQuickstartLifecycle.new.run if __FILE__ == $PROGRAM_NAME