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
data/CHANGELOG.md CHANGED
@@ -18,6 +18,224 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
18
18
 
19
19
  ### Removed
20
20
 
21
+ ## [0.5.0] - 2026-01-01
22
+
23
+ ### Added
24
+
25
+ #### Frame API
26
+
27
+ - **`RatatuiRuby.draw { |frame| ... }`**: New block-based drawing API that yields a `Frame` object for explicit widget placement. Enables hit testing without duplicating layout calculations.
28
+ - **`Frame#area`**: Returns the terminal area as a `Rect`.
29
+ - **`Frame#render_widget(widget, rect)`**: Renders a widget at a specific position. Works with all existing widgets and `Rect` objects.
30
+
31
+ #### Testing
32
+
33
+ - **`RatatuiRuby::TestHelper#inject_mouse`**: comprehensive mouse event injection helper supporting coordinates, buttons, and modifiers.
34
+ - **`RatatuiRuby::TestHelper#inject_click`**: Helper for left-click events.
35
+ - **`RatatuiRuby::TestHelper#inject_right_click`**: Helper for right-click events.
36
+ - **`RatatuiRuby::TestHelper#inject_drag`**: Helper for mouse drag events.
37
+ - **`RatatuiRuby::TestHelper#assert_screen_matches`**: Assert that the current terminal content matches a stored golden snapshot.
38
+
39
+ #### Session API
40
+
41
+ - **Convenience Methods**: `Session` now wraps class methods from `Layout`, `Constraint`, and other schema classes as instance methods (e.g., `layout_split` delegates to `Layout.split`, `constraint_percentage` to `Constraint.percentage`). This enables a more fluent API in `RatatuiRuby.run` blocks.
42
+
43
+ ### Changed
44
+
45
+ #### Event System
46
+
47
+ - **`Event::None` (Breaking)**: `RatatuiRuby.poll_event` now returns `Event::None` instead of `nil` when no event is available. This null-object responds safely to all event predicates with `false`. Use `event.none?` or pattern-match on `type: :none`. Code using `while (event = poll_event)` must change to `while (event = poll_event) && !event.none?`.
48
+
49
+ ### Fixed
50
+
51
+ #### Session API
52
+
53
+ - **Missing Convenience Methods**: Fixed `Session` convenience methods (e.g., `bar_chart`) being missed by replacing the manual list with automatic runtime introspection of the `RatatuiRuby` module.
54
+
55
+ ### Removed
56
+
57
+ ## [0.4.0] - 2025-12-30
58
+
59
+ ### Added
60
+
61
+ #### Hex Color Support
62
+
63
+ - **Style**: `fg` and `bg` parameters now accept hex color strings (e.g., `"#ff0000"` for red). Requires a 24-bit true color capable terminal (Kitty, iTerm2, modern Terminal.app). Terminals without true color support will gracefully fall back to the closest ANSI color.
64
+
65
+ #### RatatuiMascot Widget
66
+
67
+ - **RatatuiMascot**: New widget to display the Ratatui mascot (Ferris).
68
+
69
+ #### RatatuiLogo Widget
70
+
71
+ - **RatatuiLogo**: New widget to display the Ratatui logo.
72
+
73
+
74
+ #### Duck-Typed Numeric Coercion
75
+
76
+ - All numeric parameters now accept any object that responds to `to_f` (for floats) or `to_int`/`to_i` (for integers). This provides idiomatic Ruby interoperability with `BigDecimal`, `Rational`, and custom numeric types. Uses Ruby's built-in `Float()` and `Integer()` Kernel methods for proper duck-type handling.
77
+
78
+ #### Paragraph Widget
79
+
80
+ - `alignment`: **Breaking Change**: Renamed `align` to `alignment` to match Ratatui 0.30 API.
81
+
82
+ #### Custom Widgets
83
+
84
+ - **Draw Command API**: Custom widgets now return an array of `Draw` commands instead of writing to a buffer. Use `RatatuiRuby::Draw.string(x, y, string, style)` and `RatatuiRuby::Draw.cell(x, y, cell)` to create draw commands. This eliminates use-after-free bugs by keeping all pointers inside Rust while Ruby works with pure data objects. **Breaking:** The `render` method signature changed from `render(area, buffer)` to `render(area)`.
85
+
86
+ #### BarChart Widget
87
+
88
+ - `bar_set`: Customize bar characters (digits, symbols, blocks).
89
+ - `group_gap`: Control spacing between groups in grouped bar charts.
90
+ - `data`: Now accepts an Array of `BarGroup` objects, enabling grouped bar charts.
91
+ - `Bar` and `BarGroup`: New schema classes for defining grouped bar data.
92
+
93
+ #### Block Widget
94
+
95
+ - `border_type`: Customize border style (`:plain`, `:rounded`, `:double`, `:thick`, `:quadrant_inside`, `:quadrant_outside`).
96
+ - `border_set`: Customize border characters (e.g., digits, symbols).
97
+ - `border_style`: Apply full style support (colors and modifiers) to borders. Takes precedence over `border_color`.
98
+ - `children`: Declare child widgets within the block's area for composable UI structures.
99
+ - Multiple `titles`: Display multiple titles with individual alignment (`:left`, `:center`, `:right`) and vertical positioning (`:top`, `:bottom`). Each title supports its own `style`.
100
+ - `title_style`: Base style applied to all titles.
101
+ - `style`: Base style applied to the entire block.
102
+ - `padding`: Directional padding via a single integer (uniform) or array of 4 integers (`[left, right, top, bottom]`).
103
+ - `line_count(width)`: **(Experimental)** Calculate rendered lines (including borders/padding) for a given width. Delegates to Ratatui's underlying unstable `line_count`.
104
+ - `line_width`: **(Experimental)** Calculate minimum width to avoid wrapping (including borders/padding). Delegates to Ratatui's underlying unstable `line_width`.
105
+
106
+ #### Calendar Widget
107
+
108
+ - `events`: Hash mapping `Date` objects to `Style` objects for highlighting specific dates.
109
+ - `show_month_header`: Toggle month header visibility (defaults to `false`). **Breaking:** Previously always shown.
110
+ - `show_weekdays_header`: Toggle weekday names (Mon, Tue, etc.) visibility (defaults to `true`).
111
+ - `show_surrounding`: Optional `Style` to display dates from adjacent months, or `nil` to hide them.
112
+
113
+ #### Chart Widget
114
+
115
+ - `legend_position`: Position legend at `:top_left`, `:top_right`, `:bottom_left`, or `:bottom_right` (defaults to `:top_right`).
116
+ - `hidden_legend_constraints`: Array of two `Constraint` objects to hide the legend when chart area is too small.
117
+ - **Axis**: `labels_alignment` to control horizontal alignment (`:left`, `:center`, `:right`) of axis labels.
118
+ - `Dataset`: `style` parameter replaces `color`, enabling full styling (fg, bg, modifiers) for chart datasets. **Breaking**: `color` parameter removed.
119
+
120
+ #### Gauge Widget
121
+
122
+ - `style`: Base style applied to the entire gauge background.
123
+ - `gauge_style`: Style applied specifically to the filled bar. **Breaking:** Use this instead of `style` if you want bar coloring; `style` no longer defaults to `Style.default`.
124
+ - `percent`: Convenience parameter alternative to `ratio` for initialization.
125
+ - `use_unicode`: Explicitly toggle between unicode blocks and ASCII rendering (defaults to `true`).
126
+
127
+ #### LineGauge Widget
128
+
129
+ - `style`: Base style applied to the entire gauge area.
130
+
131
+ #### List Widget
132
+
133
+ - `scroll_padding`: Number of items to keep visible above and below the selected item during scrolling.
134
+ - `repeat_highlight_symbol`: When `true`, repeat highlight symbol on each line of multi-line selections.
135
+ - `highlight_spacing`: Control selection column reservation (`:always`, `:when_selected`, `:never`).
136
+ - `direction`: List orientation (`:top_to_bottom` or `:bottom_to_top`).
137
+
138
+ #### Sparkline Widget
139
+
140
+ - `absent_value_symbol` and `absent_value_style`: Customize rendering of `nil` values (distinct from `0`).
141
+ - `direction`: Rendering direction (`:left_to_right` or `:right_to_left`).
142
+ - `bar_set`: Customize bar characters.
143
+
144
+ #### Table Widget
145
+
146
+ - `style`: Base style applied to the entire table.
147
+ - `column_spacing`: Horizontal spacing between columns.
148
+ - `footer`: Summary rows at the bottom of the table.
149
+ - `flex`: Layout distribution mode (`:legacy`, `:start`, `:center`, `:end`, `:space_between`, `:space_around`, `:space_evenly`).
150
+ - `highlight_spacing`: Control selection column reservation (`:always`, `:when_selected`, `:never`).
151
+ - `column_highlight_style`: Style applied to the selected column.
152
+ - `cell_highlight_style`: Style applied to the selected cell (intersection of row and column).
153
+ - `selected_column`: Index of the selected column (Integer or nil).
154
+ - `widths`: Now support all constraint types (`:max`, `:fill`, `:ratio`) with full flexibility.
155
+
156
+ #### Tabs Widget
157
+
158
+ - `style`: Base style applied to the entire tabs area.
159
+ - `padding_left` and `padding_right`: Horizontal padding around tab titles.
160
+ - `width`: Calculate total width of the tabs (including dividers/padding).
161
+
162
+ #### Canvas Widget
163
+
164
+ - `background_color`: Set canvas background color.
165
+ - `:half_block` marker: Block-based rendering using half-height blocks.
166
+ - `:quadrant`, `:sextant`, `:octant` markers: High-resolution pseudo-pixel rendering.
167
+ - `Shape::Label`: Text labels at canvas coordinates with optional styling.
168
+
169
+ #### Scrollbar Widget
170
+
171
+ - Full styling support: `thumb_style`, `track_symbol`, `track_style`, `begin_symbol`, `begin_style`, `end_symbol`, `end_style`, `style`.
172
+ - All orientation variants: `:vertical_left`, `:vertical_right`, `:horizontal_top`, `:horizontal_bottom` (`:vertical` and `:horizontal` remain as aliases).
173
+
174
+ #### Layout & Constraints
175
+
176
+ - `Constraint.ratio(numerator, denominator)`: Proportional constraints with explicit ratio.
177
+ - `Constraint.fill(weight)`: Distribute remaining space proportionally. Use multiple `Fill` to split space (e.g., `Fill(1)` and `Fill(3)` split 1:3).
178
+ - `Constraint.max(value)`: Cap maximum size of a section.
179
+ - `Layout.split(area, direction:, constraints:, flex:)`: Compute layout rectangles without rendering, enabling hit testing.
180
+ - `Flex::SpaceEvenly`: New layout mode for `Layout` widget.
181
+ - `flex` parameter: All layout options (`:legacy`, `:start`, `:center`, `:end`, `:space_between`, `:space_around`, `:space_evenly`).
182
+
183
+ #### Rich Text & Text Components
184
+
185
+ - `Text::Span` and `Text::Line`: Styled text with inline formatting. Combine spans into lines with optional alignment.
186
+ - `Shape` module: Canvas shape primitives (`Shape::Line`, `Shape::Circle`, `Shape::Rectangle`, `Shape::Point`, `Shape::Map`) to avoid naming conflicts with `Text::Line`.
187
+
188
+ #### Event System
189
+
190
+ - Typed `Event` API: `RatatuiRuby.poll_event` returns typed objects (`Event::Key`, `Event::Mouse`, `Event::Resize`, `Event::Paste`, `Event::FocusGained`, `Event::FocusLost`).
191
+ - Predicate methods: `key?`, `mouse?`, `ctrl?`, etc. for cleaner event handling.
192
+ - Pattern matching support and discriminator pattern via `type:` key in `#deconstruct_keys`.
193
+ - `Event::Resize`: Terminal resize events with `width` and `height` attributes.
194
+ - `Event::Paste`: Bracketed paste as atomic event with `content:`.
195
+ - `Event::FocusGained` and `Event::FocusLost`: Terminal focus changes.
196
+ - `Event::Mouse.new` accepts `nil` for `button` parameter (treated as `"none"`).
197
+
198
+ #### Geometry & Hit Testing
199
+
200
+ - `Rect#contains?(x, y)`: Test whether a point is inside a rectangle. Essential for mouse click handlers.
201
+ - `Layout.split`: Enables calculating widget positions before rendering.
202
+
203
+ #### Testing
204
+
205
+ - `RatatuiRuby::TestHelper#inject_keys`: Concise event injection helper.
206
+ - `RatatuiRuby::TestHelper#get_cell` and `#assert_cell_style`: Inspect terminal cell attributes (colors, characters).
207
+ - `with_test_terminal`: Default timeout of 2 seconds to prevent hanging tests. **Breaking:** Default size is now 80×24 (VT100 standard) instead of 20×10.
208
+ - Error on `inject_event`/`inject_keys` outside `with_test_terminal`: Prevents test hangs from race conditions.
209
+ - Value equality (`==`) for `Event` objects: Simplify assertions.
210
+
211
+ #### Lifecycle & Application Structure
212
+
213
+ - `RatatuiRuby.run`: New context manager that initializes terminal, yields session, and restores on exit. Allows custom event loop control.
214
+ - **Session** class: Renamed from `DSL` to better reflect its purpose as a managed terminal session with convenience methods.
215
+ - Focus and Bracketed Paste events: Enabled by default in `RatatuiRuby.run` and `RatatuiRuby.init_terminal` (disable with `focus_events: false` or `bracketed_paste: false`).
216
+
217
+ #### Documentation & Examples
218
+
219
+ - **Cached Layout Pattern**: Documented in `doc/interactive_design.md`. Three-phase lifecycle pattern (`calculate_layout`, `render`, `handle_input`) solves layout duplication in immediate-mode UI. Foundation for Component architecture in Gem 1.5.
220
+
221
+ ### Changed
222
+
223
+ - **Calendar:** Renamed `day_style` to `default_style` to match Ratatui 0.30 API. This is a breaking change. [Kerrick Long]
224
+
225
+ - **Custom Widget `render` Method (Breaking)**: Changed signature from `render(area, buffer)` to `render(area)`, with render methods now returning an array of `Draw` commands instead of writing directly to a buffer. This change improves memory safety by eliminating use-after-free risks.
226
+ - **Ratatui Upgraded to 0.30.0**: Underlying `ratatui` library upgraded from 0.29, bringing modularized crates, `no_std` support for embedded targets, and major widget/layout enhancements. Layout cache explicitly enabled for performance.
227
+ - **Event API (Breaking)**: `RatatuiRuby.poll_event` returns typed `Event` objects instead of raw Hashes. Code using `event[:type]` must change to `event.key?`, `event.code`, etc.
228
+ - **RatatuiRuby.main_loop Removed (Breaking)**: Removed in favor of `RatatuiRuby.run` for more explicit lifecycle control.
229
+ - **TestHelper Terminal Size (Breaking)**: `with_test_terminal` defaults to 80×24 instead of 20×10.
230
+ - **Calendar Month Header Default (Breaking)**: `show_month_header` defaults to `false` (previously always shown). Set `show_month_header: true` if relying on the old behavior.
231
+ - **Gauge `style` Default (Breaking)**: No longer defaults to `Style.default`. Use `gauge_style` for bar coloring instead.
232
+
233
+ ### Fixed
234
+
235
+ - **Alpine Linux Support**: Fixed gem installation failures on Alpine Linux (musl targets) by properly configuring `crate-type` to support static linking where dynamic linking is unsupported.
236
+ - **Rust Safety**: Convert `class.name()` results to owned strings for proper GC safety with Magnus 0.8.
237
+ - **Terminal Preview Detection**: Detect staged changes correctly in preview generation.
238
+
21
239
  ## [0.3.1] - 2025-12-28
22
240
 
23
241
  ### Added
@@ -28,10 +246,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
28
246
 
29
247
  ### Added
30
248
 
249
+ - **Custom Widget API (Breaking)**: Custom widgets that define a `render` method now receive only `area` (not `buffer`) and must return an array of `Draw` commands. Use `RatatuiRuby::Draw.string(x, y, string, style)` and `RatatuiRuby::Draw.cell(x, y, cell)` instead of `buffer.set_string` and `buffer.set_cell`. This eliminates a class of use-after-free bugs by ensuring Ruby never holds pointers to Rust-owned memory. Widgets can now be unit tested by asserting on the returned array.
31
250
  - **The Escape Hatch (Ruby Render Callback)**: Added the ability to define custom widgets in pure Ruby by implementing a `render(area, buffer)` method. The `Buffer` object provides low-level drawing primitives like `set_string`, allowing developers to create custom TUI components without writing Rust code.
32
251
  - **Clear Widget**: Added the `Clear` widget, which resets the terminal buffer in the area it is rendered. This is essential for creating opaque popups and modals that prevent background styles from "bleeding" through transparent widgets.
33
252
  - **Interactive Table Selection**: The `Table` widget now supports row selection with `selected_row`, `highlight_style`, and `highlight_symbol` parameters. This enables building interactive data grids and file explorers where users can navigate through rows using keyboard input.
34
253
  - **Scrollable Paragraphs**: The `Paragraph` widget now supports a `scroll` parameter that accepts a `(y, x)` array to scroll content vertically and horizontally. This enables viewing long text content that exceeds the visible area, such as logs or documents. The parameter order matches ratatui's convention.
254
+ - **Enhanced Tabs Customization**: The `Tabs` widget now supports `highlight_style` for the selected tab and a customizable `divider` string (defaulting to the standard pipe `|`). This allows for richer visual feedback in tabbed interfaces.
35
255
 
36
256
  ### Changed
37
257
 
@@ -65,7 +285,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
65
285
  - **Input Handling**: Robust handling for both Keyboard and Mouse events.
66
286
  - **Testing Support**: Included `RatatuiRuby::TestHelper` and RSpec integration to make testing your TUI applications possible.
67
287
 
68
- [Unreleased]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.3.1...HEAD
288
+ [Unreleased]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.5.0...HEAD
289
+ [0.5.0]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.4.0...v0.5.0
290
+ [0.4.0]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.3.1...v0.4.0
69
291
  [0.3.1]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.3.0...v0.3.1
70
292
  [0.3.0]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.2.0...v0.3.0
71
293
  [0.2.0]: https://git.sr.ht/~kerrick/ratatui_ruby/compare/v0.1.0...v0.2.0
data/README.md CHANGED
@@ -9,7 +9,7 @@ builds.sr.ht status](https://builds.sr.ht/~kerrick/ratatui_ruby.svg)](https://bu
9
9
  License](https://img.shields.io/badge/dynamic/regex?url=https%3A%2F%2Fgit.sr.ht%2F~kerrick%2Fratatui_ruby%2Fblob%2Fmain%2Fratatui_ruby.gemspec&search=spec%5C.license%20%3D%20%22(.*)%22&replace=%241&label=License&color=a2c93e)](https://spdx.org/licenses/AGPL-3.0-or-later.html) [![
10
10
  Gem Total Downloads](https://img.shields.io/gem/dt/ratatui_ruby)](https://rubygems.org/gems/ratatui_ruby) [![
11
11
  Gem Version](https://img.shields.io/gem/v/ratatui_ruby)](https://rubygems.org/gems/ratatui_ruby) [![
12
- Ratatui Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fgit.sr.ht%2F~kerrick%2Fratatui_ruby%2Fblob%2Fmain%2Fext%2Fratatui_ruby%2FCargo.toml&query=%24.dependencies.ratatui.version&prefix=v&logo=ratatui&label=Ratatui)](https://crates.io/crates/ratatui/0.26.3) [![
12
+ Ratatui Version](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fgit.sr.ht%2F~kerrick%2Fratatui_ruby%2Fblob%2Fmain%2Fext%2Fratatui_ruby%2FCargo.toml&query=%24.dependencies.ratatui.version&prefix=v&logo=ratatui&label=Ratatui)](https://crates.io/crates/ratatui/0.30) [![
13
13
  Mailing List: Discussion](https://img.shields.io/badge/mailing_list-discussion-5865F2.svg?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iaWNvbiBpY29uLXRhYmxlciBpY29ucy10YWJsZXItb3V0bGluZSBpY29uLXRhYmxlci1tYWlsIj48cGF0aCBzdHJva2U9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMyA3YTIgMiAwIDAgMSAyIC0yaDE0YTIgMiAwIDAgMSAyIDJ2MTBhMiAyIDAgMCAxIC0yIDJoLTE0YTIgMiAwIDAgMSAtMiAtMnYtMTB6IiAvPjxwYXRoIGQ9Ik0zIDdsOSA2bDkgLTYiIC8+PC9zdmc+Cg==)](https://lists.sr.ht/~kerrick/ratatui_ruby-discuss) [![
14
14
  Mailing List: Development](https://img.shields.io/badge/mailing_list-development-4954d5.svg?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iaWNvbiBpY29uLXRhYmxlciBpY29ucy10YWJsZXItb3V0bGluZSBpY29uLXRhYmxlci1tYWlsIj48cGF0aCBzdHJva2U9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMyA3YTIgMiAwIDAgMSAyIC0yaDE0YTIgMiAwIDAgMSAyIDJ2MTBhMiAyIDAgMCAxIC0yIDJoLTE0YTIgMiAwIDAgMSAtMiAtMnYtMTB6IiAvPjxwYXRoIGQ9Ik0zIDdsOSA2bDkgLTYiIC8+PC9zdmc+Cg==)](https://lists.sr.ht/~kerrick/ratatui_ruby-devel) [![
15
15
  Mailing List: Announcements](https://img.shields.io/badge/mailing_list-announcements-3b44ac.svg?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0iaWNvbiBpY29uLXRhYmxlciBpY29ucy10YWJsZXItb3V0bGluZSBpY29uLXRhYmxlci1tYWlsIj48cGF0aCBzdHJva2U9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMyA3YTIgMiAwIDAgMSAyIC0yaDE0YTIgMiAwIDAgMSAyIDJ2MTBhMiAyIDAgMCAxIC0yIDJoLTE0YTIgMiAwIDAgMSAtMiAtMnYtMTB6IiAvPjxwYXRoIGQ9Ik0zIDdsOSA2bDkgLTYiIC8+PC9zdmc+Cg==)](https://lists.sr.ht/~kerrick/ratatui_ruby-announce)
@@ -20,8 +20,8 @@ Mailing List: Announcements](https://img.shields.io/badge/mailing_list-announcem
20
20
  **ratatui_ruby** is a Ruby wrapper for [Ratatui](https://ratatui.rs). It allows you to cook up Terminal User Interfaces in Ruby.
21
21
  **ratatui_ruby** is a community wrapper that is not affiliated with [the Ratatui team](https://github.com/orgs/ratatui/people).
22
22
 
23
- > [!WARNING]
24
- > **ratatui_ruby** is currently in an early stage of development. Use at your own risk.
23
+ > [!WARNING]
24
+ > **ratatui_ruby** is currently in **BETA**. The API may change between minor versions.
25
25
 
26
26
  Please join the **announce** mailing list at https://lists.sr.ht/~kerrick/ratatui_ruby-announce to stay up-to-date on new releases and announcements.
27
27
 
@@ -65,22 +65,29 @@ gem install ratatui_ruby
65
65
 
66
66
  ```ruby
67
67
  require "ratatui_ruby"
68
- RatatuiRuby.main_loop do |tui|
69
- tui.draw \
70
- tui.paragraph \
71
- text: "Hello, Ratatui! Press 'q' to quit.",
72
- align: :center,
73
- block: tui.block(
74
- title: "My Ruby TUI App",
75
- borders: [:all],
76
- border_color: "cyan"
77
- )
78
- event = tui.poll_event
79
- break if event && event[:type] == :key && event[:code] == "q"
68
+ RatatuiRuby.run do |tui|
69
+ loop do
70
+ tui.draw do |frame|
71
+ frame.render_widget(
72
+ tui.paragraph(
73
+ text: "Hello, Ratatui! Press 'q' to quit.",
74
+ alignment: :center,
75
+ block: tui.block(
76
+ title: "My Ruby TUI App",
77
+ borders: [:all],
78
+ border_color: "cyan"
79
+ )
80
+ ),
81
+ frame.area
82
+ )
83
+ end
84
+ event = tui.poll_event
85
+ break if event == "q" || event == :ctrl_c
86
+ end
80
87
  end
81
88
  ```
82
89
 
83
- For a full tutorial, see [the Quickstart](./doc/quickstart.md).
90
+ For a full tutorial, see [the Quickstart](./doc/quickstart.md). For an explanation of the application architecture, see [Application Architecture](./doc/application_architecture.md).
84
91
 
85
92
 
86
93
  ## Documentation
data/REUSE.toml CHANGED
@@ -15,3 +15,23 @@ SPDX-License-Identifier = "CC0-1.0"
15
15
  path = 'doc/images/*.png'
16
16
  SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
17
17
  SPDX-License-Identifier = "CC-BY-SA-4.0"
18
+
19
+ [[annotations]]
20
+ path = 'test/fixtures/*.txt'
21
+ SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
22
+ SPDX-License-Identifier = "AGPL-3.0-or-later"
23
+
24
+ [[annotations]]
25
+ path = 'test/snapshots/*.txt'
26
+ SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
27
+ SPDX-License-Identifier = "AGPL-3.0-or-later"
28
+
29
+ [[annotations]]
30
+ path = 'examples/app_all_events/test/snapshots/*.txt'
31
+ SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
32
+ SPDX-License-Identifier = "AGPL-3.0-or-later"
33
+
34
+ [[annotations]]
35
+ path = 'test/examples/app_all_events/snapshots/*.txt'
36
+ SPDX-FileCopyrightText = "2025 Kerrick Long <me@kerricklong.com>"
37
+ SPDX-License-Identifier = "AGPL-3.0-or-later"
@@ -0,0 +1,176 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+
4
+ SPDX-License-Identifier: AGPL-3.0-or-later
5
+ -->
6
+
7
+ # Application Architecture
8
+
9
+ Architect robust TUI applications using core lifecycle patterns and API best practices.
10
+
11
+ ## Core Concepts
12
+
13
+ Your app lives inside a terminal. You need to respect its rules.
14
+
15
+ ### Lifecycle Management
16
+
17
+ Terminals have state. They remember cursor positions, input modes, and screen buffers.
18
+
19
+ **The Problem:** If your app crashes or exits without cleaning up, it "breaks" the user's terminal. The cursor vanishes. Input echoes constantly. The alternate screen doesn't clear.
20
+
21
+ **The Solution:** The library's lifecycle manager handles this for you. It enters "raw mode" on startup and guarantees restoration on exit.
22
+
23
+ #### Use `RatatuiRuby.run`
24
+
25
+ This method acts as a safety net. It initializes the terminal, yields control to your block, and restores the terminal afterwards—even if your code raises an exception.
26
+
27
+ ```ruby
28
+ RatatuiRuby.run do |tui|
29
+ loop do
30
+ tui.draw do |frame|
31
+ frame.render_widget(tui.paragraph(text: "Hello"), frame.area)
32
+ end
33
+ break if tui.poll_event == "q"
34
+ end
35
+ end
36
+ # Terminal is restored here
37
+ ```
38
+
39
+ #### Manual Management
40
+
41
+ Need granular control? You can initialize and restore the terminal yourself. Use `ensure` blocks to guarantee cleanup.
42
+
43
+ ```ruby
44
+ RatatuiRuby.init_terminal
45
+ begin
46
+ RatatuiRuby.draw do |frame|
47
+ frame.render_widget(RatatuiRuby::Paragraph.new(text: "Hello"), frame.area)
48
+ end
49
+ ensure
50
+ RatatuiRuby.restore_terminal
51
+ # Terminal is restored here
52
+ end
53
+ ```
54
+
55
+ ### API Convenience
56
+
57
+ Writing UI trees involves nesting many widgets.
58
+
59
+ **The Problem:** Explicitly namespacing `RatatuiRuby::` for every widget (e.g., `RatatuiRuby::Paragraph.new`) is tedious. It creates visual noise that hides your layout structure.
60
+
61
+ **The Solution:** The Session API (`tui`) provides shorthand factories for every widget. It yields a session object to your block.
62
+
63
+ ```ruby
64
+ RatatuiRuby.run do |tui|
65
+ loop do
66
+ tui.draw do |frame|
67
+ # Split layout using Session helpes
68
+ sidebar_area, content_area = tui.layout_split(
69
+ frame.area,
70
+ direction: :horizontal,
71
+ constraints: [
72
+ tui.constraint_length(20),
73
+ tui.constraint_min(0)
74
+ ]
75
+ )
76
+
77
+ # Render sidebar
78
+ frame.render_widget(
79
+ tui.paragraph(
80
+ text: tui.text_line(spans: [
81
+ tui.text_span(content: "Side", style: tui.style(fg: :blue)),
82
+ tui.text_span(content: "bar")
83
+ ]),
84
+ block: tui.block(borders: [:all], title: "Nav")
85
+ ),
86
+ sidebar_area
87
+ )
88
+
89
+ # Render main content
90
+ frame.render_widget(
91
+ tui.paragraph(
92
+ text: "Main Content",
93
+ style: tui.style(fg: :green),
94
+ block: tui.block(borders: [:all], title: "Content")
95
+ ),
96
+ content_area
97
+ )
98
+ end
99
+
100
+ event = tui.poll_event
101
+ break if event == "q" || event == :ctrl_c
102
+ end
103
+ end
104
+ ```
105
+
106
+ #### Raw API
107
+
108
+ Building your own abstractions? You might prefer explicit class instantiation. The raw constants are always available.
109
+
110
+ ```ruby
111
+ RatatuiRuby.run do
112
+ loop do
113
+ RatatuiRuby.draw do |frame|
114
+ # Manual split
115
+ rects = RatatuiRuby::Layout.split(
116
+ frame.area,
117
+ direction: :horizontal,
118
+ constraints: [
119
+ RatatuiRuby::Constraint.length(20),
120
+ RatatuiRuby::Constraint.min(0)
121
+ ]
122
+ )
123
+
124
+ frame.render_widget(
125
+ RatatuiRuby::Paragraph.new(
126
+ text: RatatuiRuby::Text::Line.new(spans: [
127
+ RatatuiRuby::Text::Span.new(content: "Side", style: RatatuiRuby::Style.new(fg: :blue)),
128
+ RatatuiRuby::Text::Span.new(content: "bar")
129
+ ]),
130
+ block: RatatuiRuby::Block.new(borders: [:all], title: "Nav")
131
+ ),
132
+ rects[0]
133
+ )
134
+
135
+ frame.render_widget(
136
+ RatatuiRuby::Paragraph.new(
137
+ text: "Main Content",
138
+ style: RatatuiRuby::Style.new(fg: :green),
139
+ block: RatatuiRuby::Block.new(borders: [:all], title: "Content")
140
+ ),
141
+ rects[1]
142
+ )
143
+ end
144
+
145
+ event = RatatuiRuby.poll_event
146
+ break if event == "q" || event == :ctrl_c
147
+ end
148
+ end
149
+ ```
150
+
151
+ ## Reference Architectures
152
+
153
+ Simple scripts work well with valid linear code. Complex apps need structure.
154
+
155
+ We provide these reference architectures to inspire you:
156
+
157
+ ### MVVM (Model-View-ViewModel)
158
+
159
+ **Source:** [examples/app_all_events](../examples/app_all_events/README.md)
160
+
161
+ This pattern strictly separates concerns:
162
+ * **Model:** Pure business logic. It handles data and events.
163
+ * **View State (ViewModel):** An immutable data structure built fresh every frame. It calculates logic-dependent properties (like `border_color`) so the View doesn't have to.
164
+ * **View:** Pure rendering logic. It takes the View State and draws it.
165
+
166
+ Use this when your app has complex state rules that clutter your rendering code.
167
+
168
+ ### Scene-Orchestrated MVC
169
+
170
+ **Source:** [examples/app_color_picker](../examples/app_color_picker/README.md)
171
+
172
+ This pattern addresses the difficulty of mouse interaction and layout management:
173
+ * **Scene:** A specialized View that owns the layout *and* hit testing. It caches the screen coordinates of widgets during the draw phase.
174
+ * **App (Controller):** Handles events by querying the Scene (e.g., `scene.rect_at(x, y)`).
175
+
176
+ Use this when you need rich interactivity (mouse clicks, drag-and-drop) or complex dynamic layouts.
@@ -44,13 +44,15 @@ Wrap your test assertions in `with_test_terminal`. This sets up a temporary, in-
44
44
 
45
45
  ```ruby
46
46
  def test_rendering
47
- # Create a 80x24 terminal
48
- with_test_terminal(80, 24) do
49
- # 1. Instantiate your app/component
47
+ # Uses default 80x24 terminal
48
+ with_test_terminal do
49
+ # 1. Instantiate your widget
50
50
  widget = RatatuiRuby::Paragraph.new(text: "Hello World")
51
51
 
52
- # 2. Render it
53
- RatatuiRuby.draw(widget)
52
+ # 2. Render it using the Frame API
53
+ RatatuiRuby.draw do |frame|
54
+ frame.render_widget(widget, frame.area)
55
+ end
54
56
 
55
57
  # 3. Assert on the output
56
58
  assert_includes buffer_content[0], "Hello World"
@@ -82,13 +84,18 @@ assert_equal 2, pos[:y]
82
84
 
83
85
  Injects a mock event into the event queue. This is the preferred way to simulate user input instead of stubbing `poll_event`.
84
86
 
87
+ > [!IMPORTANT]
88
+ > You must call `inject_event` inside a `with_test_terminal` block. Calling it outside leads to race conditions where events are flushed before the application starts.
89
+
85
90
  ```ruby
86
- # Simulate 'q' key press
87
- inject_event("key", { code: "q" })
91
+ with_test_terminal do
92
+ # Simulate 'q' key press
93
+ inject_event("key", { code: "q" })
88
94
 
89
- # Now poll_event will return the 'q' key event
90
- event = RatatuiRuby.poll_event
91
- assert_equal "q", event[:code]
95
+ # Now poll_event will return the 'q' key event
96
+ event = RatatuiRuby.poll_event
97
+ assert_equal "q", event.code
98
+ end
92
99
  ```
93
100
 
94
101
  ## Example
@@ -9,6 +9,8 @@ This document describes the design philosophy and structure of the Ruby layer in
9
9
 
10
10
  ## Core Philosophy: Data-Driven UI
11
11
 
12
+
13
+
12
14
  The Ruby frontend is designed as a **thin, declarative layer** over the Rust backend. It uses an **Immediate Mode** paradigm where the user constructs a tree of pure data objects every frame to represent the desired UI state.
13
15
 
14
16
  ### 1. View Tree as Data
@@ -41,13 +43,15 @@ The application loop typically looks like this:
41
43
  loop do
42
44
  # 1. & 2. Handle events and update state
43
45
  event = RatatuiRuby.poll_event
44
- break if event[:type] == :key && event[:code] == "esc"
45
-
46
- # 3. Construct View Tree
47
- ui = RatatuiRuby::Paragraph.new(text: "Time: #{Time.now}")
48
-
49
- # 4. Draw
50
- RatatuiRuby.draw(ui)
46
+ break if event == :esc
47
+
48
+ # 3. Construct View Tree & Draw
49
+ RatatuiRuby.draw do |frame|
50
+ frame.render_widget(
51
+ RatatuiRuby::Paragraph.new(text: "Time: #{Time.now}"),
52
+ frame.area
53
+ )
54
+ end
51
55
  end
52
56
  ```
53
57