ratatui_ruby 0.3.1 → 0.5.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 (350) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +14 -12
  3. data/.builds/ruby-3.3.yml +14 -12
  4. data/.builds/ruby-3.4.yml +14 -12
  5. data/.builds/ruby-4.0.0.yml +14 -12
  6. data/AGENTS.md +89 -132
  7. data/CHANGELOG.md +223 -1
  8. data/README.md +23 -16
  9. data/REUSE.toml +20 -0
  10. data/doc/application_architecture.md +176 -0
  11. data/doc/application_testing.md +17 -10
  12. data/doc/contributors/design/ruby_frontend.md +11 -7
  13. data/doc/contributors/developing_examples.md +261 -0
  14. data/doc/contributors/documentation_style.md +104 -0
  15. data/doc/contributors/dwim_dx.md +366 -0
  16. data/doc/contributors/index.md +2 -0
  17. data/doc/custom.css +14 -0
  18. data/doc/event_handling.md +125 -0
  19. data/doc/images/app_all_events.png +0 -0
  20. data/doc/images/app_analytics.png +0 -0
  21. data/doc/images/app_color_picker.png +0 -0
  22. data/doc/images/app_custom_widget.png +0 -0
  23. data/doc/images/app_login_form.png +0 -0
  24. data/doc/images/app_map_demo.png +0 -0
  25. data/doc/images/app_mouse_events.png +0 -0
  26. data/doc/images/app_table_select.png +0 -0
  27. data/doc/images/verify_quickstart_dsl.png +0 -0
  28. data/doc/images/verify_quickstart_layout.png +0 -0
  29. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  30. data/doc/images/verify_readme_usage.png +0 -0
  31. data/doc/images/widget_barchart_demo.png +0 -0
  32. data/doc/images/widget_block_padding.png +0 -0
  33. data/doc/images/widget_block_titles.png +0 -0
  34. data/doc/images/widget_box_demo.png +0 -0
  35. data/doc/images/widget_calendar_demo.png +0 -0
  36. data/doc/images/widget_cell_demo.png +0 -0
  37. data/doc/images/widget_chart_demo.png +0 -0
  38. data/doc/images/widget_gauge_demo.png +0 -0
  39. data/doc/images/widget_layout_split.png +0 -0
  40. data/doc/images/widget_line_gauge_demo.png +0 -0
  41. data/doc/images/widget_list_demo.png +0 -0
  42. data/doc/images/widget_list_styles.png +0 -0
  43. data/doc/images/widget_popup_demo.png +0 -0
  44. data/doc/images/widget_ratatui_logo_demo.png +0 -0
  45. data/doc/images/widget_ratatui_mascot_demo.png +0 -0
  46. data/doc/images/widget_rect.png +0 -0
  47. data/doc/images/widget_render.png +0 -0
  48. data/doc/images/widget_rich_text.png +0 -0
  49. data/doc/images/widget_scroll_text.png +0 -0
  50. data/doc/images/widget_scrollbar_demo.png +0 -0
  51. data/doc/images/widget_sparkline_demo.png +0 -0
  52. data/doc/images/widget_style_colors.png +0 -0
  53. data/doc/images/widget_table_flex.png +0 -0
  54. data/doc/images/widget_tabs_demo.png +0 -0
  55. data/doc/index.md +1 -0
  56. data/doc/interactive_design.md +116 -0
  57. data/doc/quickstart.md +186 -84
  58. data/examples/app_all_events/README.md +81 -0
  59. data/examples/app_all_events/app.rb +93 -0
  60. data/examples/app_all_events/model/event_color_cycle.rb +41 -0
  61. data/examples/app_all_events/model/event_entry.rb +75 -0
  62. data/examples/app_all_events/model/events.rb +180 -0
  63. data/examples/app_all_events/model/highlight.rb +57 -0
  64. data/examples/app_all_events/model/timestamp.rb +54 -0
  65. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
  66. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
  67. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
  68. data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
  69. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
  70. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
  71. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
  72. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
  73. data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
  74. data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
  75. data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
  76. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
  77. data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
  78. data/examples/app_all_events/view/app_view.rb +78 -0
  79. data/examples/app_all_events/view/controls_view.rb +50 -0
  80. data/examples/app_all_events/view/counts_view.rb +55 -0
  81. data/examples/app_all_events/view/live_view.rb +69 -0
  82. data/examples/app_all_events/view/log_view.rb +60 -0
  83. data/{lib/ratatui_ruby/output.rb → examples/app_all_events/view.rb} +1 -1
  84. data/examples/app_all_events/view_state.rb +42 -0
  85. data/examples/app_color_picker/README.md +94 -0
  86. data/examples/app_color_picker/app.rb +112 -0
  87. data/examples/app_color_picker/clipboard.rb +84 -0
  88. data/examples/app_color_picker/color.rb +191 -0
  89. data/examples/app_color_picker/copy_dialog.rb +170 -0
  90. data/examples/app_color_picker/harmony.rb +56 -0
  91. data/examples/app_color_picker/input.rb +142 -0
  92. data/examples/app_color_picker/palette.rb +80 -0
  93. data/examples/app_color_picker/scene.rb +201 -0
  94. data/examples/app_login_form/app.rb +108 -0
  95. data/examples/app_map_demo/app.rb +93 -0
  96. data/examples/app_table_select/app.rb +201 -0
  97. data/examples/verify_quickstart_dsl/app.rb +45 -0
  98. data/examples/verify_quickstart_layout/app.rb +69 -0
  99. data/examples/verify_quickstart_lifecycle/app.rb +48 -0
  100. data/examples/verify_readme_usage/app.rb +34 -0
  101. data/examples/widget_barchart_demo/app.rb +238 -0
  102. data/examples/widget_block_padding/app.rb +67 -0
  103. data/examples/widget_block_titles/app.rb +69 -0
  104. data/examples/widget_box_demo/app.rb +250 -0
  105. data/examples/widget_calendar_demo/app.rb +109 -0
  106. data/examples/widget_cell_demo/app.rb +104 -0
  107. data/examples/widget_chart_demo/app.rb +213 -0
  108. data/examples/widget_gauge_demo/app.rb +212 -0
  109. data/examples/widget_layout_split/app.rb +246 -0
  110. data/examples/widget_line_gauge_demo/app.rb +217 -0
  111. data/examples/widget_list_demo/app.rb +382 -0
  112. data/examples/widget_list_styles/app.rb +141 -0
  113. data/examples/widget_popup_demo/app.rb +104 -0
  114. data/examples/widget_ratatui_logo_demo/app.rb +103 -0
  115. data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
  116. data/examples/widget_rect/app.rb +205 -0
  117. data/examples/widget_render/app.rb +184 -0
  118. data/examples/widget_rich_text/app.rb +137 -0
  119. data/examples/widget_scroll_text/app.rb +108 -0
  120. data/examples/widget_scrollbar_demo/app.rb +153 -0
  121. data/examples/widget_sparkline_demo/app.rb +274 -0
  122. data/examples/widget_style_colors/app.rb +102 -0
  123. data/examples/widget_table_flex/app.rb +95 -0
  124. data/examples/widget_tabs_demo/app.rb +167 -0
  125. data/ext/ratatui_ruby/Cargo.lock +889 -115
  126. data/ext/ratatui_ruby/Cargo.toml +4 -3
  127. data/ext/ratatui_ruby/clippy.toml +7 -0
  128. data/ext/ratatui_ruby/extconf.rb +7 -0
  129. data/ext/ratatui_ruby/src/events.rs +293 -219
  130. data/ext/ratatui_ruby/src/frame.rs +115 -0
  131. data/ext/ratatui_ruby/src/lib.rs +105 -24
  132. data/ext/ratatui_ruby/src/rendering.rs +94 -10
  133. data/ext/ratatui_ruby/src/style.rs +357 -93
  134. data/ext/ratatui_ruby/src/terminal.rs +121 -31
  135. data/ext/ratatui_ruby/src/text.rs +178 -0
  136. data/ext/ratatui_ruby/src/widgets/barchart.rs +99 -24
  137. data/ext/ratatui_ruby/src/widgets/block.rs +32 -3
  138. data/ext/ratatui_ruby/src/widgets/calendar.rs +45 -44
  139. data/ext/ratatui_ruby/src/widgets/canvas.rs +44 -9
  140. data/ext/ratatui_ruby/src/widgets/chart.rs +79 -27
  141. data/ext/ratatui_ruby/src/widgets/clear.rs +3 -1
  142. data/ext/ratatui_ruby/src/widgets/gauge.rs +11 -4
  143. data/ext/ratatui_ruby/src/widgets/layout.rs +223 -15
  144. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +92 -0
  145. data/ext/ratatui_ruby/src/widgets/list.rs +114 -11
  146. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  147. data/ext/ratatui_ruby/src/widgets/overlay.rs +4 -2
  148. data/ext/ratatui_ruby/src/widgets/paragraph.rs +35 -13
  149. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
  150. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +51 -0
  151. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +61 -7
  152. data/ext/ratatui_ruby/src/widgets/sparkline.rs +73 -6
  153. data/ext/ratatui_ruby/src/widgets/table.rs +177 -64
  154. data/ext/ratatui_ruby/src/widgets/tabs.rs +105 -5
  155. data/lib/ratatui_ruby/cell.rb +166 -0
  156. data/lib/ratatui_ruby/event/focus_gained.rb +49 -0
  157. data/lib/ratatui_ruby/event/focus_lost.rb +50 -0
  158. data/lib/ratatui_ruby/event/key.rb +211 -0
  159. data/lib/ratatui_ruby/event/mouse.rb +124 -0
  160. data/lib/ratatui_ruby/event/none.rb +43 -0
  161. data/lib/ratatui_ruby/event/paste.rb +71 -0
  162. data/lib/ratatui_ruby/event/resize.rb +80 -0
  163. data/lib/ratatui_ruby/event.rb +131 -0
  164. data/lib/ratatui_ruby/frame.rb +87 -0
  165. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +45 -0
  166. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +23 -0
  167. data/lib/ratatui_ruby/schema/bar_chart.rb +226 -17
  168. data/lib/ratatui_ruby/schema/block.rb +178 -11
  169. data/lib/ratatui_ruby/schema/calendar.rb +70 -14
  170. data/lib/ratatui_ruby/schema/canvas.rb +213 -46
  171. data/lib/ratatui_ruby/schema/center.rb +46 -8
  172. data/lib/ratatui_ruby/schema/chart.rb +134 -32
  173. data/lib/ratatui_ruby/schema/clear.rb +22 -53
  174. data/lib/ratatui_ruby/schema/constraint.rb +72 -12
  175. data/lib/ratatui_ruby/schema/cursor.rb +23 -5
  176. data/lib/ratatui_ruby/schema/draw.rb +53 -0
  177. data/lib/ratatui_ruby/schema/gauge.rb +56 -12
  178. data/lib/ratatui_ruby/schema/layout.rb +91 -9
  179. data/lib/ratatui_ruby/schema/line_gauge.rb +78 -0
  180. data/lib/ratatui_ruby/schema/list.rb +92 -16
  181. data/lib/ratatui_ruby/schema/overlay.rb +29 -3
  182. data/lib/ratatui_ruby/schema/paragraph.rb +82 -25
  183. data/lib/ratatui_ruby/schema/ratatui_logo.rb +29 -0
  184. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +34 -0
  185. data/lib/ratatui_ruby/schema/rect.rb +59 -10
  186. data/lib/ratatui_ruby/schema/scrollbar.rb +127 -19
  187. data/lib/ratatui_ruby/schema/shape/label.rb +66 -0
  188. data/lib/ratatui_ruby/schema/sparkline.rb +120 -12
  189. data/lib/ratatui_ruby/schema/style.rb +39 -11
  190. data/lib/ratatui_ruby/schema/table.rb +109 -18
  191. data/lib/ratatui_ruby/schema/tabs.rb +71 -10
  192. data/lib/ratatui_ruby/schema/text.rb +90 -0
  193. data/lib/ratatui_ruby/session/autodoc.rb +417 -0
  194. data/lib/ratatui_ruby/session.rb +163 -0
  195. data/lib/ratatui_ruby/test_helper.rb +322 -13
  196. data/lib/ratatui_ruby/version.rb +1 -1
  197. data/lib/ratatui_ruby.rb +184 -38
  198. data/sig/examples/app_all_events/app.rbs +11 -0
  199. data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
  200. data/sig/examples/app_all_events/model/events.rbs +15 -0
  201. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  202. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  203. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  204. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  205. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  206. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  207. data/sig/examples/app_all_events/view.rbs +8 -0
  208. data/sig/examples/app_all_events/view_state.rbs +15 -0
  209. data/sig/examples/app_color_picker/app.rbs +12 -0
  210. data/sig/examples/app_login_form/app.rbs +11 -0
  211. data/sig/examples/app_map_demo/app.rbs +11 -0
  212. data/sig/examples/app_table_select/app.rbs +11 -0
  213. data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
  214. data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
  215. data/sig/examples/verify_readme_usage/app.rbs +11 -0
  216. data/sig/examples/widget_block_padding/app.rbs +11 -0
  217. data/sig/examples/widget_block_titles/app.rbs +11 -0
  218. data/sig/examples/widget_box_demo/app.rbs +11 -0
  219. data/sig/examples/widget_calendar_demo/app.rbs +11 -0
  220. data/sig/examples/widget_cell_demo/app.rbs +11 -0
  221. data/sig/examples/widget_chart_demo/app.rbs +11 -0
  222. data/sig/examples/widget_gauge_demo/app.rbs +11 -0
  223. data/sig/examples/widget_layout_split/app.rbs +10 -0
  224. data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
  225. data/sig/examples/widget_list_demo/app.rbs +12 -0
  226. data/sig/examples/widget_list_styles/app.rbs +11 -0
  227. data/sig/examples/widget_popup_demo/app.rbs +11 -0
  228. data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
  229. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
  230. data/sig/examples/widget_rect/app.rbs +12 -0
  231. data/sig/examples/widget_render/app.rbs +10 -0
  232. data/sig/examples/widget_rich_text/app.rbs +11 -0
  233. data/sig/examples/widget_scroll_text/app.rbs +11 -0
  234. data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
  235. data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
  236. data/sig/examples/widget_style_colors/app.rbs +14 -0
  237. data/sig/examples/widget_table_flex/app.rbs +11 -0
  238. data/sig/ratatui_ruby/event.rbs +69 -0
  239. data/sig/ratatui_ruby/frame.rbs +9 -0
  240. data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -3
  241. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +16 -0
  242. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +13 -0
  243. data/sig/ratatui_ruby/schema/bar_chart.rbs +20 -2
  244. data/sig/ratatui_ruby/schema/block.rbs +5 -4
  245. data/sig/ratatui_ruby/schema/calendar.rbs +6 -2
  246. data/sig/ratatui_ruby/schema/canvas.rbs +52 -39
  247. data/sig/ratatui_ruby/schema/center.rbs +3 -3
  248. data/sig/ratatui_ruby/schema/chart.rbs +8 -5
  249. data/sig/ratatui_ruby/schema/constraint.rbs +8 -5
  250. data/sig/ratatui_ruby/schema/cursor.rbs +1 -1
  251. data/sig/ratatui_ruby/schema/draw.rbs +27 -0
  252. data/sig/ratatui_ruby/schema/gauge.rbs +4 -2
  253. data/sig/ratatui_ruby/schema/layout.rbs +11 -1
  254. data/sig/ratatui_ruby/schema/line_gauge.rbs +16 -0
  255. data/sig/ratatui_ruby/schema/list.rbs +5 -1
  256. data/sig/ratatui_ruby/schema/paragraph.rbs +4 -1
  257. data/sig/ratatui_ruby/schema/ratatui_logo.rbs +8 -0
  258. data/sig/ratatui_ruby/{buffer.rbs → schema/ratatui_mascot.rbs} +4 -3
  259. data/sig/ratatui_ruby/schema/rect.rbs +2 -1
  260. data/sig/ratatui_ruby/schema/scrollbar.rbs +18 -2
  261. data/sig/ratatui_ruby/schema/sparkline.rbs +6 -2
  262. data/sig/ratatui_ruby/schema/table.rbs +8 -1
  263. data/sig/ratatui_ruby/schema/tabs.rbs +5 -1
  264. data/sig/ratatui_ruby/schema/text.rbs +22 -0
  265. data/sig/ratatui_ruby/session.rbs +94 -0
  266. data/tasks/autodoc/inventory.rb +61 -0
  267. data/tasks/autodoc/member.rb +56 -0
  268. data/tasks/autodoc/name.rb +19 -0
  269. data/tasks/autodoc/notice.rb +26 -0
  270. data/tasks/autodoc/rbs.rb +38 -0
  271. data/tasks/autodoc/rdoc.rb +45 -0
  272. data/tasks/autodoc.rake +47 -0
  273. data/tasks/bump/history.rb +2 -2
  274. data/tasks/doc.rake +600 -6
  275. data/tasks/example_viewer.html.erb +172 -0
  276. data/tasks/lint.rake +8 -4
  277. data/tasks/resources/build.yml.erb +13 -11
  278. data/tasks/resources/index.html.erb +6 -0
  279. data/tasks/sourcehut.rake +4 -4
  280. data/tasks/terminal_preview/app_screenshot.rb +33 -0
  281. data/tasks/terminal_preview/crash_report.rb +52 -0
  282. data/tasks/terminal_preview/example_app.rb +25 -0
  283. data/tasks/terminal_preview/launcher_script.rb +46 -0
  284. data/tasks/terminal_preview/preview_collection.rb +58 -0
  285. data/tasks/terminal_preview/preview_timing.rb +22 -0
  286. data/tasks/terminal_preview/safety_confirmation.rb +56 -0
  287. data/tasks/terminal_preview/saved_screenshot.rb +53 -0
  288. data/tasks/terminal_preview/system_appearance.rb +11 -0
  289. data/tasks/terminal_preview/terminal_window.rb +136 -0
  290. data/tasks/terminal_preview/window_id.rb +14 -0
  291. data/tasks/terminal_preview.rake +28 -0
  292. data/tasks/test.rake +2 -2
  293. data/tasks/website/index_page.rb +3 -3
  294. data/tasks/website/version.rb +10 -10
  295. data/tasks/website/version_menu.rb +10 -12
  296. data/tasks/website/versioned_documentation.rb +49 -17
  297. data/tasks/website/website.rb +6 -8
  298. data/tasks/website.rake +4 -4
  299. metadata +206 -54
  300. data/LICENSES/BSD-2-Clause.txt +0 -9
  301. data/doc/images/examples-analytics.rb.png +0 -0
  302. data/doc/images/examples-box_demo.rb.png +0 -0
  303. data/doc/images/examples-calendar_demo.rb.png +0 -0
  304. data/doc/images/examples-chart_demo.rb.png +0 -0
  305. data/doc/images/examples-custom_widget.rb.png +0 -0
  306. data/doc/images/examples-dashboard.rb.png +0 -0
  307. data/doc/images/examples-list_styles.rb.png +0 -0
  308. data/doc/images/examples-login_form.rb.png +0 -0
  309. data/doc/images/examples-map_demo.rb.png +0 -0
  310. data/doc/images/examples-mouse_events.rb.png +0 -0
  311. data/doc/images/examples-popup_demo.rb.gif +0 -0
  312. data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
  313. data/doc/images/examples-scroll_text.rb.png +0 -0
  314. data/doc/images/examples-scrollbar_demo.rb.png +0 -0
  315. data/doc/images/examples-stock_ticker.rb.png +0 -0
  316. data/doc/images/examples-system_monitor.rb.png +0 -0
  317. data/doc/images/examples-table_select.rb.png +0 -0
  318. data/examples/analytics.rb +0 -88
  319. data/examples/box_demo.rb +0 -71
  320. data/examples/calendar_demo.rb +0 -55
  321. data/examples/chart_demo.rb +0 -84
  322. data/examples/custom_widget.rb +0 -43
  323. data/examples/dashboard.rb +0 -72
  324. data/examples/list_styles.rb +0 -66
  325. data/examples/login_form.rb +0 -115
  326. data/examples/map_demo.rb +0 -58
  327. data/examples/mouse_events.rb +0 -95
  328. data/examples/popup_demo.rb +0 -105
  329. data/examples/quickstart_dsl.rb +0 -30
  330. data/examples/quickstart_lifecycle.rb +0 -40
  331. data/examples/readme_usage.rb +0 -21
  332. data/examples/scroll_text.rb +0 -74
  333. data/examples/scrollbar_demo.rb +0 -75
  334. data/examples/stock_ticker.rb +0 -93
  335. data/examples/system_monitor.rb +0 -94
  336. data/examples/table_select.rb +0 -70
  337. data/examples/test_analytics.rb +0 -65
  338. data/examples/test_box_demo.rb +0 -38
  339. data/examples/test_calendar_demo.rb +0 -66
  340. data/examples/test_dashboard.rb +0 -38
  341. data/examples/test_list_styles.rb +0 -61
  342. data/examples/test_login_form.rb +0 -63
  343. data/examples/test_map_demo.rb +0 -100
  344. data/examples/test_popup_demo.rb +0 -62
  345. data/examples/test_scroll_text.rb +0 -130
  346. data/examples/test_stock_ticker.rb +0 -39
  347. data/examples/test_system_monitor.rb +0 -40
  348. data/examples/test_table_select.rb +0 -37
  349. data/ext/ratatui_ruby/src/buffer.rs +0 -54
  350. data/lib/ratatui_ruby/dsl.rb +0 -64
@@ -0,0 +1,116 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Interactive TUI Design Patterns
7
+
8
+ Canonical patterns for building responsive, interactive terminal user interfaces with ratatui_ruby.
9
+
10
+ ## The Cached Layout Pattern
11
+
12
+ **Context:** In immediate-mode TUI development, you render once per event loop. The render happens, the user clicks, you respond. This cycle repeats 60 times a second.
13
+
14
+ **Problem:** Your layout has constraints. When you render, you calculate where each widget goes. When the user clicks, you need to know which widget was under the cursor. Two separate calculations means two separate constraint definitions. Change the layout once and forget to update the hit test logic—bugs happen.
15
+
16
+ **Solution:** Calculate layout once. Cache the results. Reuse them everywhere.
17
+
18
+ ### The Three-Phase Lifecycle
19
+
20
+ Structure your event loop into three clear phases:
21
+
22
+ ```ruby
23
+ def run
24
+ RatatuiRuby.run do |tui|
25
+ @tui = tui
26
+ loop do
27
+ @tui.draw do |frame|
28
+ calculate_layout(frame.area) # Phase 1: Geometry (once per frame)
29
+ render(frame) # Phase 2: Draw
30
+ end
31
+ break if handle_input == :quit # Phase 3: Input
32
+ end
33
+ end
34
+ end
35
+ ```
36
+
37
+ **Phase 1: Layout Calculation**
38
+
39
+ Call this inside your `draw` block. It uses the current terminal area provided by the frame:
40
+
41
+ ```ruby
42
+ def calculate_layout(area)
43
+ # Main area vs sidebar (70% / 30%)
44
+ main_area, @sidebar_area = @tui.layout_split(
45
+ area,
46
+ direction: :horizontal,
47
+ constraints: [
48
+ @tui.constraint_percentage(70),
49
+ @tui.constraint_percentage(30),
50
+ ]
51
+ )
52
+
53
+ # Within main area, left vs right panels
54
+ @left_rect, @right_rect = @tui.layout_split(
55
+ main_area,
56
+ direction: :horizontal,
57
+ constraints: [
58
+ @tui.constraint_percentage(@left_ratio),
59
+ @tui.constraint_percentage(100 - @left_ratio)
60
+ ]
61
+ )
62
+ end
63
+ ```
64
+
65
+ **Phase 2: Rendering**
66
+
67
+ Reuse the cached rects. Build and draw:
68
+
69
+ ```ruby
70
+ def render(frame)
71
+ frame.render_widget(build_widget(@left_rect), @left_rect)
72
+ frame.render_widget(build_widget(@right_rect), @right_rect)
73
+ end
74
+ ```
75
+
76
+ **Phase 3: Event Handling**
77
+
78
+ Reuse the cached rects. Test clicks:
79
+
80
+ ```ruby
81
+ def handle_input
82
+ event = RatatuiRuby.poll_event
83
+
84
+ case event
85
+ in type: :mouse, kind: "down", x:, y:
86
+ if @left_rect.contains?(x, y)
87
+ handle_left_click
88
+ elsif @right_rect.contains?(x, y)
89
+ handle_right_click
90
+ end
91
+ else
92
+ nil
93
+ end
94
+ end
95
+ ```
96
+
97
+ ### Why This Matters
98
+
99
+ - **Single source of truth:** Change constraints once. They apply everywhere.
100
+ - **No duplication:** Write `Layout.split(area, constraints:)` once. Use the result in render and input.
101
+ - **Testable:** Layout geometry is explicit. You can verify it.
102
+ - **Foundation for components:** In Gem 1.5, the `Component` class automates this caching. This pattern teaches the mental model.
103
+
104
+ ## Layout.split
105
+
106
+ `Layout.split` computes layout geometry without rendering. It returns an array of `Rect` objects. While you can call `RatatuiRuby::Layout.split` directly, we recommend using the `Session` helper (`tui.layout_split`) for cleaner application code.
107
+
108
+ ```ruby
109
+ # Preferred (Session API)
110
+ left, right = tui.layout_split(area, constraints: [...])
111
+
112
+ # Manual (Core API)
113
+ left, right = RatatuiRuby::Layout.split(area, constraints: [...])
114
+ ```
115
+
116
+ Use it to establish the single source of truth inside your `draw` block. Store the results in instance variables and reuse them in both `render` and `handle_input`.
data/doc/quickstart.md CHANGED
@@ -14,19 +14,24 @@ Add this line to your application's Gemfile:
14
14
  gem "ratatui_ruby"
15
15
  ```
16
16
 
17
+
17
18
  And then execute:
18
19
 
19
20
  ```bash
20
21
  bundle install
21
22
  ```
22
23
 
24
+
23
25
  Or install it yourself as:
24
26
 
25
27
  ```bash
26
28
  gem install ratatui_ruby
27
29
  ```
28
30
 
29
- ## Basic Application
31
+
32
+ ## Tutorials
33
+
34
+ ### Basic Application
30
35
 
31
36
  Here is a "Hello World" application that demonstrates the core lifecycle of a **ratatui_ruby** app.
32
37
 
@@ -43,22 +48,24 @@ begin
43
48
  # We define a Paragraph widget inside a Block with a title and borders.
44
49
  view = RatatuiRuby::Paragraph.new(
45
50
  text: "Hello, Ratatui! Press 'q' to quit.",
46
- align: :center,
51
+ alignment: :center,
47
52
  block: RatatuiRuby::Block.new(
48
53
  title: "My Ruby TUI App",
54
+ title_alignment: :center,
49
55
  borders: [:all],
50
- border_color: "cyan"
56
+ border_color: "cyan",
57
+ style: { fg: "white" }
51
58
  )
52
59
  )
53
60
 
54
61
  # 3. Draw the UI
55
- RatatuiRuby.draw(view)
62
+ RatatuiRuby.draw do |frame|
63
+ frame.render_widget(view, frame.area)
64
+ end
56
65
 
57
66
  # 4. Poll for events
58
67
  event = RatatuiRuby.poll_event
59
- if event && event[:type] == :key && event[:code] == "q"
60
- break
61
- end
68
+ break if event.key? && event.code == "q"
62
69
  end
63
70
  ensure
64
71
  # 5. Restore the terminal to its original state
@@ -66,131 +73,226 @@ ensure
66
73
  end
67
74
  ```
68
75
 
69
- ![Basic Application Screenshot](./images/examples-quickstart_lifecycle.rb.png)
76
+ ![quickstart_lifecycle](./images/verify_quickstart_lifecycle.png)
70
77
 
71
- ### How it works
78
+ #### How it works
72
79
 
73
80
  1. **`RatatuiRuby.init_terminal`**: Enters raw mode and switches to the alternate screen.
74
- 2. **Immediate Mode UI**: On every iteration of the loop, you describe what the UI should look like by creating `Data` objects (like `Paragraph` and `Block`).
75
- 3. **`RatatuiRuby.draw(view)`**: The Ruby UI tree is passed to the Rust backend, which renders it to the terminal.
76
- 4. **`RatatuiRuby.poll_event`**: Checks for keyboard, mouse, or resize events.
77
- 5. **`RatatuiRuby.restore_terminal`**: Crucial for leaving raw mode and returning the user to their shell properly. Always wrap your loop in a `begin...ensure` block to guarantee this runs.
81
+ 2. **Immediate Mode UI**: On every iteration, describe your UI by creating `Data` objects (e.g., `Paragraph`, `Block`).
82
+ 3. **`RatatuiRuby.draw { |frame| ... }`**: The block receives a `Frame` object as a canvas. Render widgets onto specific areas. Nothing is drawn until the block finishes, ensuring flicker-free updates.
83
+ 4. **`RatatuiRuby.poll_event`**: Returns a typed `Event` object with predicates like `key?`, `mouse?`, `resize?`, etc. Returns `RatatuiRuby::Event::None` if no events are pending. Use predicates to check event type without pattern matching.
84
+ 5. **`RatatuiRuby.restore_terminal`**: Essential for leaving raw mode and returning to the shell. Always wrap your loop in `begin...ensure` to guarantee this runs.
78
85
 
79
- ### DSL
86
+ ### Idiomatic Session
80
87
 
81
- A small DSL is provided for convenience when writing scripts.
88
+ You can simplify your code by using `RatatuiRuby.run`. This method handles the terminal lifecycle for you, yielding a `Session` object with factory methods for widgets.
82
89
 
83
90
  ```rb
84
91
  require "ratatui_ruby"
85
92
 
86
- # 1. Initialize the terminal, start the main loop, and ensure the terminal is restored.
87
- RatatuiRuby.main_loop do |tui|
88
- # 2. Create your UI with methods instead of classes.
89
- view = tui.paragraph(
90
- text: "Hello, Ratatui! Press 'q' to quit.",
91
- align: :center,
92
- block: tui.block(
93
- title: "My Ruby TUI App",
94
- borders: [:all],
95
- border_color: "cyan"
93
+ # 1. Initialize the terminal and ensure it is restored.
94
+ RatatuiRuby.run do |tui|
95
+ loop do
96
+ # 2. Create your UI with methods instead of classes.
97
+ view = tui.paragraph(
98
+ text: "Hello, Ratatui! Press 'q' to quit.",
99
+ alignment: :center,
100
+ block: tui.block(
101
+ title: "My Ruby TUI App",
102
+ title_alignment: :center,
103
+ borders: [:all],
104
+ border_color: "cyan",
105
+ style: { fg: "white" }
106
+ )
96
107
  )
97
- )
98
-
99
- # 3. Use RatatuiRuby methods, too.
100
- tui.draw(view)
101
- event = tui.poll_event
102
-
103
- if event && event[:type] == :key && event[:code] == "q"
104
- break
108
+
109
+ # 3. Use RatatuiRuby methods, too.
110
+ tui.draw do |frame|
111
+ frame.render_widget(view, frame.area)
112
+ end
113
+
114
+ # 4. Poll for events with pattern matching
115
+ case tui.poll_event
116
+ in { type: :key, code: "q" }
117
+ break
118
+ else
119
+ # Ignore other events
120
+ end
105
121
  end
106
122
  end
107
123
  ```
108
124
 
109
125
  #### How it works
110
126
 
111
- 1. **`RatatuiRuby.main_loop`**: This helper method manages the entire terminal lifecycle for you. It initializes the terminal before the block starts and ensures `restore_terminal` is called when the block exits (even if an error occurs).
112
- 2. **Widget Shorthand**: The block yields a special DSL object (here named `tui`). This object provides factory methods for every widget, allowing you to write `tui.paragraph(...)` instead of the more verbose `RatatuiRuby::Paragraph.new(...)`.
113
- 3. **Method SHorthand**: The DSL object also provides aliases for module functions of `RatatuiRuby`, allowing you to write `tui.draw(...)` instead of the more verbose `RatatuiRuby::draw(...)`.
127
+ 1. **`RatatuiRuby.run`**: This context manager initializes the terminal before the block starts and ensures `restore_terminal` is called when the block exits (even if an error occurs).
128
+ 2. **Widget Shorthand**: The block yields a `Session` object (here named `tui`). This object provides factory methods for every widget, allowing you to write `tui.paragraph(...)` instead of the more verbose `RatatuiRuby::Paragraph.new(...)`.
129
+ 3. **Method Shorthand**: The session object also provides aliases for module functions of `RatatuiRuby`, allowing you to write `tui.draw(...)` instead of the more verbose `RatatuiRuby.draw(...)`.
130
+ 4. **Pattern Matching for Events**: Use `case...in` with pattern matching for elegant event dispatch. Always include an `else` clause at the end to catch unmatched event types (mouse, resize, paste, focus, etc.), otherwise Ruby raises `NoMatchingPatternError`.
131
+
132
+ For a deeper dive into the available application architectures (Manual vs Managed), see [Application Architecture](./application_architecture.md).
133
+
134
+ ### Adding Layouts
135
+
136
+ Real-world applications often need to split the screen into multiple areas. `RatatuiRuby::Layout` lets you do this easily.
137
+
138
+ ```ruby
139
+ require "ratatui_ruby"
140
+
141
+ RatatuiRuby.run do |tui|
142
+ loop do
143
+ tui.draw do |frame|
144
+ # 1. Split the screen
145
+ top, bottom = tui.layout_split(
146
+ frame.area,
147
+ direction: :vertical,
148
+ constraints: [
149
+ tui.constraint_percentage(75),
150
+ tui.constraint_percentage(25),
151
+ ]
152
+ )
153
+
154
+ # 2. Render Top Widget
155
+ frame.render_widget(
156
+ tui.paragraph(
157
+ text: "Hello, Ratatui!",
158
+ alignment: :center,
159
+ block: tui.block(title: "Content", borders: [:all], border_color: "cyan")
160
+ ),
161
+ top
162
+ )
163
+
164
+ # 3. Render Bottom Widget with Styled Text
165
+ # We use a Line of Spans to style specific characters
166
+ text_line = tui.text_line(
167
+ spans: [
168
+ tui.text_span(content: "Press '"),
169
+ tui.text_span(
170
+ content: "q",
171
+ style: tui.style(modifiers: [:bold, :underlined])
172
+ ),
173
+ tui.text_span(content: "' to quit."),
174
+ ],
175
+ alignment: :center
176
+ )
177
+
178
+ frame.render_widget(
179
+ tui.paragraph(
180
+ text: text_line,
181
+ block: tui.block(title: "Controls", borders: [:all])
182
+ ),
183
+ bottom
184
+ )
185
+ end
186
+
187
+ case tui.poll_event
188
+ in { type: :key, code: "q" }
189
+ break
190
+ else
191
+ # Ignore other events
192
+ end
193
+ end
194
+ end
195
+ ```
196
+
197
+ #### How it works
198
+
199
+ 1. **`tui.layout_split` (`RatatuiRuby::Layout.split`)**: Takes an area (like `frame.area`) and splits it into multiple sub-areas based on constraints.
200
+ 2. **`tui.constraint_*` (`RatatuiRuby::Constraint`)**: Defines how space is distributed (e.g., `percentage`, `length`, `min`, `max`).
201
+ 3. **`Frame#render_widget(widget, rect)`**: You pass the specific area (like `top` or `bottom`) to render the widget into that exact region.
202
+ 4. **`tui.text_span` (`RatatuiRuby::Text::Span`)**: Allows for rich styling within a single line of text.
114
203
 
115
204
  ## Examples
116
205
 
117
- To see more complex layouts and widget usage, check out the `examples/` directory in the repository.
206
+ These examples showcase the full power of **ratatui_ruby**. You can find their source code in the [examples directory](../examples).
118
207
 
119
- ### [Analytics](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/analytics.rb)
120
- Demonstrates the use of `Tabs` and `BarChart` widgets with a simple data-switching mechanism.
208
+ ### Sample Applications
121
209
 
122
- ![Analytics Screenshot](./images/examples-analytics.rb.png)
210
+ Full-featured examples demonstrating complex layouts and real-world TUI patterns.
123
211
 
124
- ### [Box Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/box_demo.rb)
125
- A simple demonstration of `Block` and `Paragraph` widgets, reacting to arrow key presses to change colors.
126
212
 
127
- ![Box Demo Screenshot](./images/examples-box_demo.rb.png)
128
213
 
129
- ### [Calendar Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/calendar_demo.rb)
130
- A simple demo application for the `Calendar` widget.
214
+ #### [All Events](../examples/app_all_events/app.rb)
131
215
 
132
- ![Calendar Screenshot](./images/examples-calendar_demo.rb.png)
216
+ Handling terminal events is unpredictable. Developers need to know exactly what the terminal sends for `Ctrl+C` or a mouse drag.
133
217
 
134
- ### [Chart Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/chart_demo.rb)
135
- Demonstrates the `Chart` widget with both scatter and line datasets, including custom axes.
218
+ This app captures and visualizes every event—keys, mouse, resize, paste, and focus.
136
219
 
137
- ![Chart Screenshot](./images/examples-chart_demo.rb.png)
220
+ Use it to debug your input handling or verify terminal behavior.
138
221
 
139
- ### [Custom Widget (Escape Hatch)](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/custom_widget.rb)
140
- Demonstrates how to define a custom widget in pure Ruby using the `render(area, buffer)` escape hatch for low-level drawing.
222
+ **What you'll learn:**
141
223
 
142
- ![Custom Widget Screenshot](./images/examples-custom_widget.rb.png)
224
+ * **MVVM Architecture**: Separates logic (Model), state (ViewModel), and rendering (View) for clean, testable code.
225
+ * **Event Handling**: Captures and distinguishes all input types, including modifiers (`Ctrl+C`) and focus changes.
226
+ * **Scalable Structure**: Organizes a non-trivial application into small, focused classes instead of a monolithic script.
143
227
 
144
- ### [Dashboard](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/dashboard.rb)
145
- Uses `Layout`, `List`, and `Paragraph` to create a classic sidebar-and-content interface.
228
+ ![all_events](./images/app_all_events.png)
146
229
 
147
- ![Dashboard Screenshot](./images/examples-dashboard.rb.png)
230
+ #### [Color Picker](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/app_color_picker/app.rb)
148
231
 
149
- ### [List Styles](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/list_styles.rb)
150
- Showcases advanced styling options for the `List` widget, including selection highlighting.
232
+ Interactive tools require complex state. Mapping mouse clicks to widgets and handling modal dialogs creates messy code if handled in the main loop.
151
233
 
152
- ### [Login Form](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/login_form.rb)
153
- Shows how to use `Overlay`, `Center`, and `Cursor` to build a modal login form with text input.
234
+ This app implements a full Color Picker using a "Scene-Orchestrated" pattern. The Scene calculates layout and exposes cached rectangles for hit testing.
154
235
 
155
- ![Login Form Screenshot](./images/examples-login_form.rb.png)
236
+ Use it to build forms, editors, and mouse-driven tools.
156
237
 
157
- ### [Map Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/map_demo.rb)
158
- Exhibits the `Canvas` widget's power, rendering a world map along with animated circles and lines.
238
+ **What you'll learn:**
159
239
 
160
- ![Map Demo Screenshot](./images/examples-map_demo.rb.png)
240
+ * **Scene-Orchestrated MVC**: Separates the View (layout/rendering) from the Controller (event loop) and Model (business logic).
241
+ * **Hit Testing**: Caches layout rectangles during the render pass to handle mouse clicks on specific elements.
242
+ * **Modal Dialogs**: Implements overlay patterns that intercept input.
161
243
 
162
- ### [Mouse Events](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/mouse_events.rb)
163
- Detailed plumbing of mouse events, including clicks, drags, and movement tracking.
244
+ #### [Custom Widget (Escape Hatch)](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/app_custom_widget/app.rb)
164
245
 
165
- ![Mouse Events Screenshot](./images/examples-mouse_events.rb.png)
246
+ Demonstrates how to define a custom widget in pure Ruby using the `render(area, buffer)` escape hatch for low-level drawing.
247
+
248
+ ![custom_widget](./images/app_custom_widget.png)
249
+
250
+ #### [Layout Split Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/widget_layout_split/app.rb)
166
251
 
167
- ### [Popup Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/popup_demo.rb)
168
- Demonstrates the `Clear` widget and how to prevent "style bleed" when rendering opaque popups over colored backgrounds.
252
+ Demonstrates `Layout.split` with interactive attribute cycling. Features hotkey controls For direction (vertical/horizontal), all 7 flex modes (legacy, start, center, end, space_between, space_around, space_evenly), and constraint types (fill, length, percentage, min, ratio).
169
253
 
170
- ![Popup Demo Screenshot](./images/examples-popup_demo.rb.gif)
254
+ ![widget_layout_split](./images/widget_layout_split.png)
255
+
256
+ #### [Login Form](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/app_login_form/app.rb)
257
+
258
+ Shows how to use `Overlay`, `Center`, and `Cursor` to build a modal login form with text input.
171
259
 
172
- ### [Scrollbar Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/scrollbar_demo.rb)
173
- A simple example of integrating the `Scrollbar` widget and handling mouse wheel events for scrolling.
260
+ ![login_form](./images/app_login_form.png)
174
261
 
175
- ![Scrollbar Demo Screenshot](./images/examples-scrollbar_demo.rb.png)
262
+ #### [Map Demo](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/app_map_demo/app.rb)
176
263
 
177
- ### [Scroll Text](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/scroll_text.rb)
178
- Demonstrates the `Paragraph` widget's scroll functionality, allowing navigation through long text content using arrow keys for both horizontal and vertical scrolling.
264
+ Exhibits the `Canvas` widget's power, rendering a world map with city labels, animated circles, and lines.
179
265
 
180
- ![Scroll Text Screenshot](./images/examples-scroll_text.rb.png)
266
+ ![map_demo](./images/app_map_demo.png)
181
267
 
182
- ### [Stock Ticker](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/stock_ticker.rb)
183
- Utilizes `Sparkline` and `Chart` widgets to visualize real-time (simulated) data.
268
+ #### [Table Select](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/app_table_select/app.rb)
184
269
 
185
- ![Stock Ticker Screenshot](./images/examples-stock_ticker.rb.png)
270
+ Demonstrates interactive row selection in the `Table` widget with keyboard navigation, highlighting selected rows with custom styles and symbols, applying a base style, and dynamically adjusting `column_spacing`. Also demonstrates `column_highlight_style` and the new `cell_highlight_style` for precise selection visualization.
186
271
 
187
- ### [System Monitor](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/system_monitor.rb)
188
- Combines `Table` and `Gauge` widgets in a vertical layout to create a functional system overview.
272
+ ![table_select](./images/app_table_select.png)
189
273
 
190
- ![System Monitor Screenshot](./images/examples-system_monitor.rb.png)
191
274
 
192
- ### [Table Select](https://git.sr.ht/~kerrick/ratatui_ruby/tree/main/item/examples/table_select.rb)
193
- Demonstrates interactive row selection in the `Table` widget with keyboard navigation, highlighting selected rows with custom styles and symbols.
275
+ ### Widget Demos
194
276
 
195
- ![Table Select Screenshot](./images/examples-table_select.rb.png)
277
+ These smaller, focused examples demonstrate specific widgets and their configuration options.
196
278
 
279
+ * [Bar Chart](../examples/widget_barchart_demo/app.rb)
280
+ * [Block Padding](../examples/widget_block_padding/app.rb)
281
+ * [Block Titles](../examples/widget_block_titles/app.rb)
282
+ * [Box (Block/Paragraph)](../examples/widget_box_demo/app.rb)
283
+ * [Calendar](../examples/widget_calendar_demo/app.rb)
284
+ * [Chart](../examples/widget_chart_demo/app.rb)
285
+ * [Gauge](../examples/widget_gauge_demo/app.rb)
286
+ * [Line Gauge](../examples/widget_line_gauge_demo/app.rb)
287
+ * [List](../examples/widget_list_demo/app.rb)
288
+ * [Popup (Clear)](../examples/widget_popup_demo/app.rb)
289
+ * [Rect](../examples/widget_rect/app.rb)
290
+ * [Ratatui Logo](../examples/widget_ratatui_logo_demo/app.rb)
291
+ * [Ratatui Mascot](../examples/widget_ratatui_mascot_demo/app.rb)
292
+ * [Rich Text](../examples/widget_rich_text/app.rb)
293
+ * [Scrollbar](../examples/widget_scrollbar_demo/app.rb)
294
+ * [Scroll Text](../examples/widget_scroll_text/app.rb)
295
+ * [Sparkline](../examples/widget_sparkline_demo/app.rb)
296
+ * [Table Flex](../examples/widget_table_flex/app.rb)
297
+ * [Tabs](../examples/widget_tabs_demo/app.rb)
298
+ * [Widget Style Colors](../examples/widget_style_colors/app.rb)
@@ -0,0 +1,81 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # App All Events Example
7
+
8
+ This example application captures and visualizes every event supported by `ratatui_ruby`. It serves as a comprehensive reference for event handling and a demonstration of a clean, scalable architectural pattern.
9
+
10
+ ## Architecture: MVVM (Model-View-ViewModel)
11
+
12
+ This application demonstrates the **Model-View-ViewModel (MVVM)** pattern, modified for the immediate-mode nature of terminal UIs. This separation of concerns ensures that the UI logic is completely decoupled from the business logic, making the application easier to test and maintain.
13
+
14
+ ### 1. Model (`model/`)
15
+ The **Model** manages the application's domain data and logic. It knows nothing about the UI.
16
+
17
+ * **`Events` (`model/events.rb`)**: The core store. It records incoming events, maintains statistics (counts), and handles business logic like "highlight this event type for 300ms."
18
+ * **`EventEntry` (`model/event_entry.rb`)**: A value object representing a single recorded event.
19
+
20
+ ### 2. View State (ViewModel) (`view_state.rb`)
21
+ The **View State** (comparable to a ViewModel or Presenter) is an immutable data structure built specifically for the View.
22
+
23
+ * **`ViewState`**: It acts as a bridge. In every render loop, the application builds a fresh `ViewState` object, calculating derived data (like styles, active flags, and formatted strings) from the raw Model data.
24
+ * **Why?**: This prevents the View from having to contain logic. The View doesn't ask "is the app focused so I should use green?"; it just asks `state.border_color`.
25
+
26
+ ### 3. View (`view/`)
27
+ The **View** is responsible **only** for rendering. It receives the `ViewState` and draws to the screen.
28
+
29
+ * **`View::App` (`view/app_view.rb`)**: The root view. It handles the high-level layout (splitting the screen into areas).
30
+ * **Sub-views**: `Counts`, `Live`, `Log`, `Controls`. Each is a small, focused component that renders a specific part of the screen based on the data in `ViewState`.
31
+
32
+ ### 4. Controller/App (`app.rb`)
33
+ The **`AppAllEvents`** class ties it all together. It owns the main loop:
34
+
35
+ 1. **Poll**: Waits for an event from the terminal.
36
+ 2. **Update**: Passes the event to the **Model** (`@events.record`).
37
+ 3. **Build State**: Creates a new **ViewState** from the current Model and global state.
38
+ 4. **Render**: Passes the **ViewState** to the **View** to draw the frame.
39
+
40
+ ## Library Features Showcased
41
+
42
+ Reading this code will teach you how to:
43
+
44
+ * **Handle All Events**:
45
+ * **Keyboard**: Capture normal keys and modifiers (`Ctrl+c`, `q`).
46
+ * **Mouse**: track clicks, drags, and scroll events.
47
+ * **Focus**: React to the terminal window gaining or losing focus (`FocusGained`/`FocusLost`).
48
+ * **Resize**: Dynamically adapt layouts when the terminal size changes.
49
+ * **Paste**: Handle bracketed paste events (if supported by the terminal).
50
+ * **Layouts**: Use `tui.layout_split` with constraints (`Length`, `Fill`) to create complex, responsive dashboards.
51
+ * **Styling**: Apply dynamic styles (bold, colors) based on application state.
52
+ * **Structure**: Organize a non-trivial CLI tool into small, single-purpose classes.
53
+
54
+ ## What Problems Does This Solve?
55
+
56
+ ### "What key code is my terminal sending?"
57
+ If you are building an app and your logic isn't catching `Ctrl+Left`, run this app and press the keys. You will see exactly how `ratatui_ruby` parses that input (e.g., is it a `Key` event? What are the modifiers?).
58
+
59
+ ### "How do I structure a real app?"
60
+ Hello World examples are great, but they don't scale. This example shows how to structure an application that can grow. By simulating a "dashboard" with multiple independent widgets updating in real-time, it solves the problem of "how do I pass data around without global variables?"
61
+
62
+ ### "How do I implement an event loop?"
63
+ It provides a robust reference implementation of the standard `loop { draw; handle_input }` cycle, including the correct way to handle quit signals.
64
+
65
+ ## Comparison: Choosing an Architecture
66
+
67
+ Complex applications require structured state habits. `AppAllEvents` and the [Color Picker](../app_color_picker/README.md) demonstrate two different approaches.
68
+
69
+ ### The Dashboard Approach (AppAllEvents)
70
+
71
+ Dashboards display data. They rarely require complex mouse interaction. Strict MVVM works best here. The View is a pure function. It accepts a `ViewState` and draws it. It ignores input. This simplifies testing.
72
+
73
+ Use this pattern for logs, monitors, and data viewers.
74
+
75
+ ### The Tool Approach (Color Picker)
76
+
77
+ Tools require interaction. Users click buttons and drag sliders. The Controller needs to know where components exist on screen. MVVM hides this layout data.
78
+
79
+ The Color Picker uses a "Scene" pattern. The View exposes layout rectangles. The Controller uses these rectangles to handle mouse clicks.
80
+
81
+ Use this pattern for forms, editors, and mouse-driven tools.
@@ -0,0 +1,93 @@
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
+ $LOAD_PATH.unshift File.expand_path(__dir__)
8
+
9
+ require "ratatui_ruby"
10
+ require_relative "model/events"
11
+ require_relative "view_state"
12
+ require_relative "view/app_view"
13
+
14
+ # Demonstrates the full range of terminal events supported by RatatuiRuby.
15
+ #
16
+ # Developers need a comprehensive example to understand how keys, mouse, resize, and focus events behave.
17
+ # Testing event handling across different terminal emulators and platforms can be unpredictable.
18
+ #
19
+ # This application captures and logs every event received from the backend, providing real-time feedback and history.
20
+ #
21
+ # Use it to verify your terminal's capabilities or as a reference for complex event handling.
22
+ #
23
+ # === Examples
24
+ #
25
+ # # Run from the command line:
26
+ # # ruby examples/app_all_events/app.rb
27
+ #
28
+ # app = AppAllEvents.new
29
+ # app.run
30
+ class AppAllEvents
31
+ # List of all event types tracked by this application.
32
+ EVENT_TYPES = %i[key mouse resize paste focus none].freeze
33
+
34
+ # Creates a new AppAllEvents instance and initializes its state.
35
+ def initialize
36
+ @view = View::App.new
37
+ @events = Events.new
38
+ @focused = true
39
+ @last_dimensions = [80, 24]
40
+ end
41
+
42
+ # Starts the application event loop.
43
+ #
44
+ # === Example
45
+ #
46
+ # app.run
47
+ def run
48
+ RatatuiRuby.run do |tui|
49
+ @tui = tui
50
+ loop do
51
+ render
52
+ break if handle_input == :quit
53
+ end
54
+ end
55
+ end
56
+
57
+ private def render
58
+ view_state = ViewState.build(
59
+ @events,
60
+ @focused,
61
+ @tui,
62
+ nil
63
+ )
64
+
65
+ @tui.draw { |frame| @view.call(view_state, @tui, frame, frame.area) }
66
+ end
67
+
68
+ private def handle_input
69
+ event = @tui.poll_event
70
+
71
+ case event
72
+ when RatatuiRuby::Event::Key
73
+ return :quit if event.code == "q"
74
+ return :quit if event.code == "c" && event.modifiers.include?("ctrl")
75
+ @events.record(event)
76
+ when RatatuiRuby::Event::Resize
77
+ @events.record(event, context: { last_dimensions: @last_dimensions })
78
+ @last_dimensions = [event.width, event.height]
79
+ when RatatuiRuby::Event::FocusGained
80
+ @focused = true
81
+ @events.record(event)
82
+ when RatatuiRuby::Event::FocusLost
83
+ @focused = false
84
+ @events.record(event)
85
+ else
86
+ @events.record(event)
87
+ end
88
+
89
+ nil
90
+ end
91
+ end
92
+
93
+ AppAllEvents.new.run if __FILE__ == $PROGRAM_NAME