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,124 @@
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
+ module RatatuiRuby
7
+ class Event
8
+ # Reports a mouse interaction.
9
+ #
10
+ # Modern terminals support rich pointer input, but the protocols are complex and varied.
11
+ # Handling clicks, drags, and scrolls requires robust parsing.
12
+ #
13
+ # This event simplifies the complexity. It tells you exactly *what* happened (+kind+),
14
+ # *where* it happened (+x+, +y+), and *which* button was involved.
15
+ #
16
+ # Use this to build interactive UIs. Implement click handlers, draggable sliders, or
17
+ # scrollable viewports with confidence.
18
+ #
19
+ # === Example
20
+ #
21
+ # if event.mouse? && event.down? && event.button == "left"
22
+ # puts "Left click at #{event.x}, #{event.y}"
23
+ # end
24
+ class Mouse < Event
25
+ # The kind of event (<tt>"down"</tt>, <tt>"up"</tt>, <tt>"drag"</tt>, <tt>"moved"</tt>, <tt>"scroll_up"</tt>, <tt>"scroll_down"</tt>).
26
+ #
27
+ # puts event.kind # => "down"
28
+ attr_reader :kind
29
+ # X coordinate (column).
30
+ #
31
+ # puts event.x # => 10
32
+ attr_reader :x
33
+ # Y coordinate (row).
34
+ #
35
+ # puts event.y # => 5
36
+ attr_reader :y
37
+ # The button pressed (<tt>"left"</tt>, <tt>"right"</tt>, <tt>"middle"</tt>, <tt>"none"</tt>).
38
+ #
39
+ # puts event.button # => "left"
40
+ #
41
+ # Can be <tt>nil</tt>, which is treated as <tt>"none"</tt>.
42
+ attr_reader :button
43
+ # List of active modifiers.
44
+ #
45
+ # puts event.modifiers # => ["ctrl"]
46
+ attr_reader :modifiers
47
+
48
+ # Returns true for Mouse events.
49
+ #
50
+ # event.mouse? # => true
51
+ # event.key? # => false
52
+ # event.resize? # => false
53
+ def mouse?
54
+ true
55
+ end
56
+
57
+ # Creates a new Mouse event.
58
+ #
59
+ # [kind]
60
+ # Event kind (String).
61
+ # [x]
62
+ # X coordinate (Integer).
63
+ # [y]
64
+ # Y coordinate (Integer).
65
+ # [button]
66
+ # Button name (String or <tt>nil</tt>).
67
+ # [modifiers]
68
+ # List of modifiers (Array<String>).
69
+ def initialize(kind:, x:, y:, button:, modifiers: [])
70
+ @kind = kind
71
+ @x = x
72
+ @y = y
73
+ @button = button || "none"
74
+ @modifiers = modifiers.sort
75
+ end
76
+
77
+ # Returns true if mouse button was pressed down.
78
+ def down?
79
+ @kind == "down"
80
+ end
81
+
82
+ # Returns true if mouse button was released.
83
+ def up?
84
+ @kind == "up"
85
+ end
86
+
87
+ # Returns true if mouse is being dragged.
88
+ def drag?
89
+ @kind == "drag"
90
+ end
91
+
92
+ # Returns true if scroll wheel moved up.
93
+ def scroll_up?
94
+ @kind == "scroll_up"
95
+ end
96
+
97
+ # Returns true if scroll wheel moved down.
98
+ #
99
+ # if event.scroll_down?
100
+ # scroll_offset += 1
101
+ # end
102
+ def scroll_down?
103
+ @kind == "scroll_down"
104
+ end
105
+
106
+ # Deconstructs the event for pattern matching.
107
+ #
108
+ # case event
109
+ # in type: :mouse, kind: "down", x:, y:
110
+ # puts "Click at #{x}, #{y}"
111
+ # end
112
+ def deconstruct_keys(keys)
113
+ { type: :mouse, kind: @kind, x: @x, y: @y, button: @button, modifiers: @modifiers }
114
+ end
115
+
116
+ ##
117
+ # Compares this event with another for equality.
118
+ def ==(other)
119
+ return false unless other.is_a?(Mouse)
120
+ kind == other.kind && x == other.x && y == other.y && button == other.button && modifiers == other.modifiers
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ class Event
8
+ # {Null object}[https://en.wikipedia.org/wiki/Null_object_pattern] for absent events.
9
+ #
10
+ # Event loops poll for input 60 times per second. Usually nothing is happening.
11
+ # If <tt>RatatuiRuby.poll_event</tt> returned <tt>nil</tt>, you would need
12
+ # nil-checks: <tt>event&.key?</tt>, <tt>next unless event</tt>.
13
+ #
14
+ # This class eliminates that friction. It responds to every predicate with
15
+ # <tt>false</tt>. Call <tt>none?</tt> to detect it explicitly. Pattern-match on
16
+ # <tt>type: :none</tt> for exhaustive dispatch.
17
+ #
18
+ # Use it to simplify your event loop. No guards. Optional `else` clauses.
19
+ #
20
+ # See Martin Fowler's {Special Case}[https://martinfowler.com/eaaCatalog/specialCase.html] pattern.
21
+ #
22
+ # === Predicate Example
23
+ #
24
+ # event = RatatuiRuby.poll_event
25
+ # break if event.ctrl_c?
26
+ # redraw if event.none?
27
+ #
28
+ # === Pattern Matching Example
29
+ #
30
+ # redraw if RatatuiRuby.poll_event in type: :none
31
+ class None < Event
32
+ # Returns true for None events.
33
+ def none?
34
+ true
35
+ end
36
+
37
+ # Deconstructs the event for pattern matching.
38
+ def deconstruct_keys(keys)
39
+ { type: :none }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ module RatatuiRuby
7
+ class Event
8
+ # Encapsulates pasted text.
9
+ #
10
+ # Users frequently paste text into terminals. Without specific handling, a paste appears as
11
+ # a flood of rapid keystrokes, often triggering accidental commands or confusing the input state.
12
+ #
13
+ # This event makes pasting safe. It groups the entire inserted block into a single atomic action.
14
+ #
15
+ # Handle this event to support bulk text insertion cleanly. Insert the +content+ directly into
16
+ # your field or buffer without triggering per-character logic.
17
+ #
18
+ # === Examples
19
+ #
20
+ # Using predicates:
21
+ # if event.paste?
22
+ # puts "Pasted: #{event.content}"
23
+ # end
24
+ #
25
+ # Using pattern matching:
26
+ # case event
27
+ # in type: :paste, content:
28
+ # puts "Pasted: #{content}"
29
+ # end
30
+ class Paste < Event
31
+ # The pasted content.
32
+ #
33
+ # puts event.content # => "https://example.com"
34
+ attr_reader :content
35
+
36
+ # Returns true for Paste events.
37
+ #
38
+ # event.paste? # => true
39
+ # event.key? # => false
40
+ # event.resize? # => false
41
+ def paste?
42
+ true
43
+ end
44
+
45
+ # Creates a new Paste event.
46
+ #
47
+ # [content]
48
+ # Pasted text (String).
49
+ def initialize(content:)
50
+ @content = content
51
+ end
52
+
53
+ # Deconstructs the event for pattern matching.
54
+ #
55
+ # case event
56
+ # in type: :paste, content:
57
+ # puts "User pasted: #{content}"
58
+ # end
59
+ def deconstruct_keys(keys)
60
+ { type: :paste, content: @content }
61
+ end
62
+
63
+ ##
64
+ # Compares this event with another for equality.
65
+ def ==(other)
66
+ return false unless other.is_a?(Paste)
67
+ content == other.content
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,80 @@
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
+ module RatatuiRuby
7
+ class Event
8
+ # Signals a change in terminal dimensions.
9
+ #
10
+ # The terminal window is dynamic, not static. The user changes its dimensions at will,
11
+ # usually breaking a fixed layout.
12
+ #
13
+ # This event captures the new state. It delivers the updated +width+ and +height+
14
+ # immediately after the change.
15
+ #
16
+ # Use these dimensions to drive your layout logic. Recalculate constraints. Reallocate space.
17
+ # Fill the new canvas completely to maintain a responsive design.
18
+ #
19
+ # === Examples
20
+ #
21
+ # Using predicates:
22
+ # if event.resize?
23
+ # puts "Resized to #{event.width}x#{event.height}"
24
+ # end
25
+ #
26
+ # Using pattern matching:
27
+ # case event
28
+ # in type: :resize, width:, height:
29
+ # puts "Resized to #{width}x#{height}"
30
+ # end
31
+ class Resize < Event
32
+ # New terminal width in columns.
33
+ #
34
+ # puts event.width # => 80
35
+ attr_reader :width
36
+
37
+ # New terminal height in rows.
38
+ #
39
+ # puts event.height # => 24
40
+ attr_reader :height
41
+
42
+ # Returns true for Resize events.
43
+ #
44
+ # event.resize? # => true
45
+ # event.key? # => false
46
+ # event.mouse? # => false
47
+ def resize?
48
+ true
49
+ end
50
+
51
+ # Creates a new Resize event.
52
+ #
53
+ # [width]
54
+ # New width (Integer).
55
+ # [height]
56
+ # New height (Integer).
57
+ def initialize(width:, height:)
58
+ @width = width
59
+ @height = height
60
+ end
61
+
62
+ # Deconstructs the event for pattern matching.
63
+ #
64
+ # case event
65
+ # in type: :resize, width:, height:
66
+ # puts "Resized to #{width}x#{height}"
67
+ # end
68
+ def deconstruct_keys(keys)
69
+ { type: :resize, width: @width, height: @height }
70
+ end
71
+
72
+ ##
73
+ # Compares this event with another for equality.
74
+ def ==(other)
75
+ return false unless other.is_a?(Resize)
76
+ width == other.width && height == other.height
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,131 @@
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
+ module RatatuiRuby
7
+ # Base class for all RatatuiRuby events.
8
+ #
9
+ # Events represent terminal input: keyboard, mouse, resize, paste, focus changes.
10
+ # Returned by RatatuiRuby.poll_event. All events support Ruby 3.0+ pattern matching.
11
+ #
12
+ # == Event Types
13
+ #
14
+ # * <tt>Key</tt> — keyboard input
15
+ # * <tt>Mouse</tt> — mouse clicks, movement, wheel
16
+ # * <tt>Resize</tt> — terminal resized
17
+ # * <tt>Paste</tt> — clipboard paste
18
+ # * <tt>FocusGained</tt> — terminal gained focus
19
+ # * <tt>FocusLost</tt> — terminal lost focus
20
+ # * <tt>None</tt> — no event available (Null Object)
21
+ #
22
+ # == Pattern Matching (Exhaustive)
23
+ #
24
+ # Use <tt>case...in</tt> to dispatch on every possible event type. This ensures
25
+ # you handle every case without needing an +else+ clause:
26
+ #
27
+ # case RatatuiRuby.poll_event
28
+ # in { type: :key, code: "q" }
29
+ # break
30
+ # in { type: :key, code: code, modifiers: }
31
+ # handle_key(code, modifiers)
32
+ # in { type: :mouse, kind: "down", x:, y: }
33
+ # handle_click(x, y)
34
+ # in { type: :mouse, kind:, x:, y: }
35
+ # # handle other mouse activities
36
+ # in { type: :resize, width:, height: }
37
+ # handle_resize(width, height)
38
+ # in { type: :paste, content: }
39
+ # handle_paste(content)
40
+ # in { type: :focus_gained }
41
+ # handle_focus_gain
42
+ # in { type: :focus_lost }
43
+ # handle_focus_loss
44
+ # in { type: :none }
45
+ # # Idle
46
+ # end
47
+ #
48
+ # == Predicates
49
+ #
50
+ # Check event types with predicates without pattern matching:
51
+ #
52
+ # event = RatatuiRuby.poll_event
53
+ # if event.key?
54
+ # puts "Key pressed"
55
+ # elsif event.none?
56
+ # # Idle
57
+ # elsif event.mouse?
58
+ # puts "Mouse event"
59
+ # end
60
+ class Event
61
+ # Returns true if this is a None event.
62
+ def none?
63
+ false
64
+ end
65
+
66
+ # Returns true if this is a Key event.
67
+ def key?
68
+ false
69
+ end
70
+
71
+ # Returns true if this is a Mouse event.
72
+ def mouse?
73
+ false
74
+ end
75
+
76
+ # Returns true if this is a Resize event.
77
+ def resize?
78
+ false
79
+ end
80
+
81
+ # Returns true if this is a Paste event.
82
+ def paste?
83
+ false
84
+ end
85
+
86
+ # Returns true if this is a FocusGained event.
87
+ def focus_gained?
88
+ false
89
+ end
90
+
91
+ # Returns true if this is a FocusLost event.
92
+ def focus_lost?
93
+ false
94
+ end
95
+
96
+ # Responds to dynamic predicate methods for key checks.
97
+ # All non-Key events return false for any key predicate.
98
+ def method_missing(name, *args, &block)
99
+ if name.to_s.end_with?("?")
100
+ false
101
+ else
102
+ super
103
+ end
104
+ end
105
+
106
+ # Declares that this class responds to dynamic predicate methods.
107
+ def respond_to_missing?(name, *args)
108
+ name.to_s.end_with?("?") || super
109
+ end
110
+
111
+ # Deconstructs the event for pattern matching.
112
+ #
113
+ # Keys argument is unused but required by the protocol.
114
+ #
115
+ # case event
116
+ # in type: :key, code:
117
+ # puts "Key: #{code}"
118
+ # end
119
+ def deconstruct_keys(keys)
120
+ {}
121
+ end
122
+ end
123
+ end
124
+
125
+ require_relative "event/none"
126
+ require_relative "event/key"
127
+ require_relative "event/mouse"
128
+ require_relative "event/resize"
129
+ require_relative "event/paste"
130
+ require_relative "event/focus_gained"
131
+ require_relative "event/focus_lost"
@@ -0,0 +1,87 @@
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
+ module RatatuiRuby
7
+ # Provides access to the terminal buffer for rendering widgets.
8
+ #
9
+ # Rendering in immediate-mode TUIs requires knowing the terminal dimensions and
10
+ # placing widgets at specific positions. Without explicit control, layout
11
+ # calculations become duplicated between rendering and hit testing.
12
+ #
13
+ # This class exposes the terminal frame during a draw call. It provides the
14
+ # current area and methods to render widgets at precise locations.
15
+ #
16
+ # Use it inside a <tt>RatatuiRuby.draw</tt> block to render widgets with full
17
+ # control over placement.
18
+ #
19
+ # === Examples
20
+ #
21
+ # Basic usage with a single widget:
22
+ #
23
+ # RatatuiRuby.draw do |frame|
24
+ # paragraph = RatatuiRuby::Paragraph.new(text: "Hello, world!")
25
+ # frame.render_widget(paragraph, frame.area)
26
+ # end
27
+ #
28
+ # Using Layout.split for multi-region layouts:
29
+ #
30
+ # RatatuiRuby.draw do |frame|
31
+ # sidebar, main = RatatuiRuby::Layout.split(
32
+ # frame.area,
33
+ # direction: :horizontal,
34
+ # constraints: [
35
+ # RatatuiRuby::Constraint.length(20),
36
+ # RatatuiRuby::Constraint.fill(1)
37
+ # ]
38
+ # )
39
+ #
40
+ # frame.render_widget(sidebar_widget, sidebar)
41
+ # frame.render_widget(main_widget, main)
42
+ #
43
+ # # Store rects for hit testing — no duplication!
44
+ # @regions = { sidebar: sidebar, main: main }
45
+ # end
46
+ class Frame
47
+ ##
48
+ # :method: area
49
+ # :call-seq: area() -> Rect
50
+ #
51
+ # Returns the full terminal area as a Rect.
52
+ #
53
+ # The returned Rect represents the entire drawable area of the terminal.
54
+ # Use it as the starting point for layout calculations.
55
+ #
56
+ # === Example
57
+ #
58
+ # RatatuiRuby.draw do |frame|
59
+ # puts "Terminal size: #{frame.area.width}x#{frame.area.height}"
60
+ # end
61
+ #
62
+ # (Native method implemented in Rust)
63
+
64
+ ##
65
+ # :method: render_widget
66
+ # :call-seq: render_widget(widget, area) -> nil
67
+ #
68
+ # Renders a widget at the specified area.
69
+ #
70
+ # Widgets in RatatuiRuby are immutable Data objects. This method takes a
71
+ # widget and a Rect, rendering the widget's content within that region.
72
+ #
73
+ # [widget]
74
+ # The widget to render (Paragraph, Layout, List, Table, etc.).
75
+ # [area]
76
+ # A Rect specifying where to render the widget.
77
+ #
78
+ # === Example
79
+ #
80
+ # RatatuiRuby.draw do |frame|
81
+ # para = RatatuiRuby::Paragraph.new(text: "Content")
82
+ # frame.render_widget(para, frame.area)
83
+ # end
84
+ #
85
+ # (Native method implemented in Rust)
86
+ end
87
+ end
@@ -0,0 +1,45 @@
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
+ module RatatuiRuby
7
+ class BarChart
8
+ # A bar in a grouped bar chart.
9
+ #
10
+ # === Examples
11
+ #
12
+ # BarChart::Bar.new(value: 10, style: Style.new(fg: :red), label: "A")
13
+ class Bar < Data.define(:value, :label, :style, :value_style, :text_value)
14
+ ##
15
+ # :attr_reader: value
16
+ # The value of the bar (Integer).
17
+
18
+ ##
19
+ # :attr_reader: label
20
+ # The label of the bar (optional String).
21
+
22
+ ##
23
+ # :attr_reader: style
24
+ # The style of the bar (optional Style).
25
+
26
+ ##
27
+ # :attr_reader: value_style
28
+ # The style of the value (optional Style).
29
+
30
+ ##
31
+ # :attr_reader: text_value
32
+ # The text to display as the value (optional String).
33
+
34
+ def initialize(value:, label: nil, style: nil, value_style: nil, text_value: nil)
35
+ super(
36
+ value: Integer(value),
37
+ label:,
38
+ style:,
39
+ value_style:,
40
+ text_value:
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
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
+ module RatatuiRuby
7
+ class BarChart
8
+ # A group of bars in a grouped bar chart.
9
+ #
10
+ # === Examples
11
+ #
12
+ # BarChart::BarGroup.new(label: "Q1", bars: [BarChart::Bar.new(value: 10), BarChart::Bar.new(value: 20)])
13
+ class BarGroup < Data.define(:label, :bars)
14
+ ##
15
+ # :attr_reader: label
16
+ # The label of the group (String).
17
+
18
+ ##
19
+ # :attr_reader: bars
20
+ # The bars in the group (Array of Bar).
21
+ end
22
+ end
23
+ end