ratatui_ruby 0.4.0 → 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 (351) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +87 -171
  7. data/CHANGELOG.md +38 -1
  8. data/README.md +8 -3
  9. data/REUSE.toml +20 -0
  10. data/doc/application_architecture.md +105 -45
  11. data/doc/application_testing.md +5 -3
  12. data/doc/contributors/design/ruby_frontend.md +9 -5
  13. data/doc/contributors/developing_examples.md +76 -18
  14. data/doc/contributors/documentation_style.md +7 -0
  15. data/doc/contributors/index.md +2 -0
  16. data/doc/event_handling.md +10 -4
  17. data/doc/images/app_all_events.png +0 -0
  18. data/doc/images/app_color_picker.png +0 -0
  19. data/doc/images/verify_readme_usage.png +0 -0
  20. data/doc/images/widget_barchart_demo.png +0 -0
  21. data/doc/images/widget_block_padding.png +0 -0
  22. data/doc/images/widget_block_titles.png +0 -0
  23. data/doc/images/widget_box_demo.png +0 -0
  24. data/doc/images/widget_calendar_demo.png +0 -0
  25. data/doc/images/widget_cell_demo.png +0 -0
  26. data/doc/images/widget_chart_demo.png +0 -0
  27. data/doc/images/widget_gauge_demo.png +0 -0
  28. data/doc/images/widget_layout_split.png +0 -0
  29. data/doc/images/widget_line_gauge_demo.png +0 -0
  30. data/doc/images/widget_list_demo.png +0 -0
  31. data/doc/images/widget_ratatui_logo_demo.png +0 -0
  32. data/doc/images/widget_ratatui_mascot_demo.png +0 -0
  33. data/doc/images/widget_render.png +0 -0
  34. data/doc/images/widget_scrollbar_demo.png +0 -0
  35. data/doc/images/widget_sparkline_demo.png +0 -0
  36. data/doc/images/widget_style_colors.png +0 -0
  37. data/doc/images/widget_table_flex.png +0 -0
  38. data/doc/images/widget_tabs_demo.png +0 -0
  39. data/doc/interactive_design.md +25 -30
  40. data/doc/quickstart.md +147 -120
  41. data/examples/app_all_events/README.md +81 -0
  42. data/examples/app_all_events/app.rb +93 -0
  43. data/examples/app_all_events/model/event_color_cycle.rb +41 -0
  44. data/examples/app_all_events/model/event_entry.rb +75 -0
  45. data/examples/app_all_events/model/events.rb +180 -0
  46. data/examples/app_all_events/model/highlight.rb +57 -0
  47. data/examples/app_all_events/model/timestamp.rb +54 -0
  48. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
  49. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
  50. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
  51. data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
  52. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
  53. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
  54. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
  55. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
  56. data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
  57. data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
  58. data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
  59. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
  60. data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
  61. data/examples/app_all_events/view/app_view.rb +78 -0
  62. data/examples/app_all_events/view/controls_view.rb +50 -0
  63. data/examples/app_all_events/view/counts_view.rb +55 -0
  64. data/examples/app_all_events/view/live_view.rb +69 -0
  65. data/examples/app_all_events/view/log_view.rb +60 -0
  66. data/examples/app_all_events/view.rb +7 -0
  67. data/examples/app_all_events/view_state.rb +42 -0
  68. data/examples/app_color_picker/README.md +94 -0
  69. data/examples/app_color_picker/app.rb +112 -0
  70. data/examples/app_color_picker/clipboard.rb +84 -0
  71. data/examples/app_color_picker/color.rb +191 -0
  72. data/examples/app_color_picker/copy_dialog.rb +170 -0
  73. data/examples/app_color_picker/harmony.rb +56 -0
  74. data/examples/app_color_picker/input.rb +142 -0
  75. data/examples/app_color_picker/palette.rb +80 -0
  76. data/examples/app_color_picker/scene.rb +201 -0
  77. data/examples/{login_form → app_login_form}/app.rb +39 -42
  78. data/examples/{map_demo → app_map_demo}/app.rb +24 -21
  79. data/examples/{table_select → app_table_select}/app.rb +68 -65
  80. data/examples/{quickstart_dsl → verify_quickstart_dsl}/app.rb +15 -6
  81. data/examples/verify_quickstart_layout/app.rb +69 -0
  82. data/examples/{quickstart_lifecycle → verify_quickstart_lifecycle}/app.rb +19 -10
  83. data/examples/verify_readme_usage/app.rb +34 -0
  84. data/examples/widget_barchart_demo/app.rb +238 -0
  85. data/examples/{block_padding → widget_block_padding}/app.rb +17 -13
  86. data/examples/{block_titles → widget_block_titles}/app.rb +25 -17
  87. data/examples/{box_demo → widget_box_demo}/app.rb +99 -65
  88. data/examples/widget_calendar_demo/app.rb +109 -0
  89. data/examples/widget_cell_demo/app.rb +104 -0
  90. data/examples/widget_chart_demo/app.rb +213 -0
  91. data/examples/widget_gauge_demo/app.rb +212 -0
  92. data/examples/widget_layout_split/app.rb +246 -0
  93. data/examples/widget_line_gauge_demo/app.rb +217 -0
  94. data/examples/widget_list_demo/app.rb +382 -0
  95. data/examples/widget_list_styles/app.rb +141 -0
  96. data/examples/widget_popup_demo/app.rb +104 -0
  97. data/examples/widget_ratatui_logo_demo/app.rb +103 -0
  98. data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
  99. data/examples/widget_rect/app.rb +205 -0
  100. data/examples/widget_render/app.rb +184 -0
  101. data/examples/widget_rich_text/app.rb +137 -0
  102. data/examples/widget_scroll_text/app.rb +108 -0
  103. data/examples/widget_scrollbar_demo/app.rb +153 -0
  104. data/examples/widget_sparkline_demo/app.rb +274 -0
  105. data/examples/widget_style_colors/app.rb +19 -21
  106. data/examples/widget_table_flex/app.rb +95 -0
  107. data/examples/widget_tabs_demo/app.rb +167 -0
  108. data/ext/ratatui_ruby/Cargo.lock +1 -1
  109. data/ext/ratatui_ruby/Cargo.toml +1 -1
  110. data/ext/ratatui_ruby/src/events.rs +121 -36
  111. data/ext/ratatui_ruby/src/frame.rs +115 -0
  112. data/ext/ratatui_ruby/src/lib.rs +79 -26
  113. data/ext/ratatui_ruby/src/rendering.rs +8 -4
  114. data/ext/ratatui_ruby/src/style.rs +138 -57
  115. data/ext/ratatui_ruby/src/terminal.rs +5 -9
  116. data/ext/ratatui_ruby/src/text.rs +13 -6
  117. data/ext/ratatui_ruby/src/widgets/barchart.rs +56 -54
  118. data/ext/ratatui_ruby/src/widgets/block.rs +7 -6
  119. data/ext/ratatui_ruby/src/widgets/canvas.rs +21 -3
  120. data/ext/ratatui_ruby/src/widgets/chart.rs +20 -10
  121. data/ext/ratatui_ruby/src/widgets/layout.rs +9 -4
  122. data/ext/ratatui_ruby/src/widgets/list.rs +32 -9
  123. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  124. data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
  125. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +19 -8
  126. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +17 -10
  127. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +4 -2
  128. data/ext/ratatui_ruby/src/widgets/sparkline.rs +14 -11
  129. data/ext/ratatui_ruby/src/widgets/table.rs +8 -4
  130. data/ext/ratatui_ruby/src/widgets/tabs.rs +11 -11
  131. data/lib/ratatui_ruby/cell.rb +3 -3
  132. data/lib/ratatui_ruby/event/key.rb +1 -1
  133. data/lib/ratatui_ruby/event/none.rb +43 -0
  134. data/lib/ratatui_ruby/event.rb +56 -4
  135. data/lib/ratatui_ruby/frame.rb +87 -0
  136. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +11 -11
  137. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +1 -5
  138. data/lib/ratatui_ruby/schema/bar_chart.rb +217 -217
  139. data/lib/ratatui_ruby/schema/block.rb +163 -168
  140. data/lib/ratatui_ruby/schema/calendar.rb +66 -67
  141. data/lib/ratatui_ruby/schema/canvas.rb +63 -63
  142. data/lib/ratatui_ruby/schema/center.rb +46 -46
  143. data/lib/ratatui_ruby/schema/chart.rb +135 -143
  144. data/lib/ratatui_ruby/schema/clear.rb +42 -42
  145. data/lib/ratatui_ruby/schema/constraint.rb +76 -76
  146. data/lib/ratatui_ruby/schema/cursor.rb +25 -25
  147. data/lib/ratatui_ruby/schema/gauge.rb +53 -53
  148. data/lib/ratatui_ruby/schema/layout.rb +87 -87
  149. data/lib/ratatui_ruby/schema/line_gauge.rb +62 -62
  150. data/lib/ratatui_ruby/schema/list.rb +86 -84
  151. data/lib/ratatui_ruby/schema/overlay.rb +31 -31
  152. data/lib/ratatui_ruby/schema/paragraph.rb +80 -80
  153. data/lib/ratatui_ruby/schema/ratatui_logo.rb +10 -6
  154. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +10 -5
  155. data/lib/ratatui_ruby/schema/rect.rb +60 -60
  156. data/lib/ratatui_ruby/schema/scrollbar.rb +119 -119
  157. data/lib/ratatui_ruby/schema/shape/label.rb +1 -1
  158. data/lib/ratatui_ruby/schema/sparkline.rb +111 -110
  159. data/lib/ratatui_ruby/schema/style.rb +46 -46
  160. data/lib/ratatui_ruby/schema/table.rb +112 -119
  161. data/lib/ratatui_ruby/schema/tabs.rb +66 -67
  162. data/lib/ratatui_ruby/session/autodoc.rb +417 -0
  163. data/lib/ratatui_ruby/session.rb +40 -23
  164. data/lib/ratatui_ruby/test_helper.rb +185 -19
  165. data/lib/ratatui_ruby/version.rb +1 -1
  166. data/lib/ratatui_ruby.rb +65 -39
  167. data/{examples/sparkline_demo → sig/examples/app_all_events}/app.rbs +3 -2
  168. data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
  169. data/sig/examples/app_all_events/model/events.rbs +15 -0
  170. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  171. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  172. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  173. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  174. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  175. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  176. data/sig/examples/app_all_events/view.rbs +8 -0
  177. data/sig/examples/app_all_events/view_state.rbs +15 -0
  178. data/{examples/list_demo → sig/examples/app_color_picker}/app.rbs +2 -2
  179. data/sig/examples/app_login_form/app.rbs +11 -0
  180. data/sig/examples/app_map_demo/app.rbs +11 -0
  181. data/sig/examples/app_table_select/app.rbs +11 -0
  182. data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
  183. data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
  184. data/sig/examples/verify_readme_usage/app.rbs +11 -0
  185. data/sig/examples/widget_block_padding/app.rbs +11 -0
  186. data/sig/examples/widget_block_titles/app.rbs +11 -0
  187. data/sig/examples/widget_box_demo/app.rbs +11 -0
  188. data/sig/examples/widget_calendar_demo/app.rbs +11 -0
  189. data/sig/examples/widget_cell_demo/app.rbs +11 -0
  190. data/sig/examples/widget_chart_demo/app.rbs +11 -0
  191. data/{examples/gauge_demo → sig/examples/widget_gauge_demo}/app.rbs +4 -0
  192. data/sig/examples/widget_layout_split/app.rbs +10 -0
  193. data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
  194. data/sig/examples/widget_list_demo/app.rbs +12 -0
  195. data/sig/examples/widget_list_styles/app.rbs +11 -0
  196. data/sig/examples/widget_popup_demo/app.rbs +11 -0
  197. data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
  198. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
  199. data/sig/examples/widget_rect/app.rbs +12 -0
  200. data/sig/examples/widget_render/app.rbs +10 -0
  201. data/sig/examples/widget_rich_text/app.rbs +11 -0
  202. data/sig/examples/widget_scroll_text/app.rbs +11 -0
  203. data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
  204. data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
  205. data/{examples → sig/examples}/widget_style_colors/app.rbs +1 -1
  206. data/sig/examples/widget_table_flex/app.rbs +11 -0
  207. data/sig/ratatui_ruby/frame.rbs +9 -0
  208. data/sig/ratatui_ruby/ratatui_ruby.rbs +3 -2
  209. data/sig/ratatui_ruby/schema/draw.rbs +4 -0
  210. data/sig/ratatui_ruby/schema/layout.rbs +1 -1
  211. data/sig/ratatui_ruby/session.rbs +94 -0
  212. data/tasks/autodoc/inventory.rb +61 -0
  213. data/tasks/autodoc/member.rb +56 -0
  214. data/tasks/autodoc/name.rb +19 -0
  215. data/tasks/autodoc/notice.rb +26 -0
  216. data/tasks/autodoc/rbs.rb +38 -0
  217. data/tasks/autodoc/rdoc.rb +45 -0
  218. data/tasks/autodoc.rake +47 -0
  219. data/tasks/bump/history.rb +2 -2
  220. data/tasks/doc.rake +600 -6
  221. data/tasks/example_viewer.html.erb +172 -0
  222. data/tasks/lint.rake +8 -4
  223. data/tasks/resources/index.html.erb +6 -0
  224. data/tasks/sourcehut.rake +4 -4
  225. data/tasks/terminal_preview/app_screenshot.rb +1 -3
  226. data/tasks/terminal_preview/crash_report.rb +7 -9
  227. data/tasks/terminal_preview/launcher_script.rb +4 -6
  228. data/tasks/terminal_preview/preview_collection.rb +4 -6
  229. data/tasks/terminal_preview/safety_confirmation.rb +3 -5
  230. data/tasks/terminal_preview/saved_screenshot.rb +7 -9
  231. data/tasks/terminal_preview/terminal_window.rb +7 -9
  232. data/tasks/test.rake +1 -1
  233. data/tasks/website/index_page.rb +3 -3
  234. data/tasks/website/version.rb +10 -10
  235. data/tasks/website/version_menu.rb +10 -12
  236. data/tasks/website/versioned_documentation.rb +49 -17
  237. data/tasks/website/website.rb +6 -8
  238. data/tasks/website.rake +4 -4
  239. metadata +156 -125
  240. data/LICENSES/BSD-2-Clause.txt +0 -9
  241. data/doc/contributors/better_dx.md +0 -543
  242. data/doc/contributors/example_analysis.md +0 -82
  243. data/doc/images/all_events.png +0 -0
  244. data/doc/images/block_padding.png +0 -0
  245. data/doc/images/block_titles.png +0 -0
  246. data/doc/images/box_demo.png +0 -0
  247. data/doc/images/calendar_demo.png +0 -0
  248. data/doc/images/cell_demo.png +0 -0
  249. data/doc/images/chart_demo.png +0 -0
  250. data/doc/images/flex_layout.png +0 -0
  251. data/doc/images/gauge_demo.png +0 -0
  252. data/doc/images/line_gauge_demo.png +0 -0
  253. data/doc/images/list_demo.png +0 -0
  254. data/doc/images/readme_usage.png +0 -0
  255. data/doc/images/scrollbar_demo.png +0 -0
  256. data/doc/images/sparkline_demo.png +0 -0
  257. data/doc/images/table_flex.png +0 -0
  258. data/examples/all_events/app.rb +0 -169
  259. data/examples/all_events/app.rbs +0 -7
  260. data/examples/all_events/test_app.rb +0 -139
  261. data/examples/analytics/app.rb +0 -258
  262. data/examples/analytics/app.rbs +0 -7
  263. data/examples/analytics/test_app.rb +0 -132
  264. data/examples/block_padding/app.rbs +0 -7
  265. data/examples/block_padding/test_app.rb +0 -31
  266. data/examples/block_titles/app.rbs +0 -7
  267. data/examples/block_titles/test_app.rb +0 -34
  268. data/examples/box_demo/app.rbs +0 -7
  269. data/examples/box_demo/test_app.rb +0 -88
  270. data/examples/calendar_demo/app.rb +0 -101
  271. data/examples/calendar_demo/app.rbs +0 -7
  272. data/examples/calendar_demo/test_app.rb +0 -108
  273. data/examples/cell_demo/app.rb +0 -108
  274. data/examples/cell_demo/app.rbs +0 -7
  275. data/examples/cell_demo/test_app.rb +0 -36
  276. data/examples/chart_demo/app.rb +0 -203
  277. data/examples/chart_demo/app.rbs +0 -7
  278. data/examples/chart_demo/test_app.rb +0 -102
  279. data/examples/custom_widget/app.rb +0 -51
  280. data/examples/custom_widget/app.rbs +0 -7
  281. data/examples/custom_widget/test_app.rb +0 -30
  282. data/examples/flex_layout/app.rb +0 -156
  283. data/examples/flex_layout/app.rbs +0 -7
  284. data/examples/flex_layout/test_app.rb +0 -65
  285. data/examples/gauge_demo/app.rb +0 -182
  286. data/examples/gauge_demo/test_app.rb +0 -120
  287. data/examples/hit_test/app.rb +0 -175
  288. data/examples/hit_test/app.rbs +0 -7
  289. data/examples/hit_test/test_app.rb +0 -102
  290. data/examples/line_gauge_demo/app.rb +0 -190
  291. data/examples/line_gauge_demo/app.rbs +0 -7
  292. data/examples/line_gauge_demo/test_app.rb +0 -129
  293. data/examples/list_demo/app.rb +0 -253
  294. data/examples/list_demo/test_app.rb +0 -237
  295. data/examples/list_styles/app.rb +0 -140
  296. data/examples/list_styles/app.rbs +0 -7
  297. data/examples/list_styles/test_app.rb +0 -157
  298. data/examples/login_form/app.rbs +0 -7
  299. data/examples/login_form/test_app.rb +0 -51
  300. data/examples/map_demo/app.rbs +0 -7
  301. data/examples/map_demo/test_app.rb +0 -149
  302. data/examples/mouse_events/app.rb +0 -97
  303. data/examples/mouse_events/app.rbs +0 -7
  304. data/examples/mouse_events/test_app.rb +0 -53
  305. data/examples/popup_demo/app.rb +0 -103
  306. data/examples/popup_demo/app.rbs +0 -7
  307. data/examples/popup_demo/test_app.rb +0 -54
  308. data/examples/quickstart_dsl/app.rbs +0 -7
  309. data/examples/quickstart_dsl/test_app.rb +0 -29
  310. data/examples/quickstart_lifecycle/app.rbs +0 -7
  311. data/examples/quickstart_lifecycle/test_app.rb +0 -29
  312. data/examples/ratatui_logo_demo/app.rb +0 -79
  313. data/examples/ratatui_logo_demo/app.rbs +0 -7
  314. data/examples/ratatui_logo_demo/test_app.rb +0 -51
  315. data/examples/ratatui_mascot_demo/app.rb +0 -84
  316. data/examples/ratatui_mascot_demo/app.rbs +0 -7
  317. data/examples/ratatui_mascot_demo/test_app.rb +0 -47
  318. data/examples/readme_usage/app.rb +0 -29
  319. data/examples/readme_usage/app.rbs +0 -7
  320. data/examples/readme_usage/test_app.rb +0 -29
  321. data/examples/rich_text/app.rb +0 -141
  322. data/examples/rich_text/app.rbs +0 -7
  323. data/examples/rich_text/test_app.rb +0 -166
  324. data/examples/scroll_text/app.rb +0 -103
  325. data/examples/scroll_text/app.rbs +0 -7
  326. data/examples/scroll_text/test_app.rb +0 -110
  327. data/examples/scrollbar_demo/app.rb +0 -143
  328. data/examples/scrollbar_demo/app.rbs +0 -7
  329. data/examples/scrollbar_demo/test_app.rb +0 -77
  330. data/examples/sparkline_demo/app.rb +0 -240
  331. data/examples/sparkline_demo/test_app.rb +0 -107
  332. data/examples/table_flex/app.rb +0 -65
  333. data/examples/table_flex/app.rbs +0 -7
  334. data/examples/table_flex/test_app.rb +0 -36
  335. data/examples/table_select/app.rbs +0 -7
  336. data/examples/table_select/test_app.rb +0 -180
  337. data/examples/widget_style_colors/test_app.rb +0 -48
  338. /data/doc/images/{analytics.png → app_analytics.png} +0 -0
  339. /data/doc/images/{custom_widget.png → app_custom_widget.png} +0 -0
  340. /data/doc/images/{login_form.png → app_login_form.png} +0 -0
  341. /data/doc/images/{map_demo.png → app_map_demo.png} +0 -0
  342. /data/doc/images/{mouse_events.png → app_mouse_events.png} +0 -0
  343. /data/doc/images/{table_select.png → app_table_select.png} +0 -0
  344. /data/doc/images/{quickstart_dsl.png → verify_quickstart_dsl.png} +0 -0
  345. /data/doc/images/{ratatui_logo_demo.png → verify_quickstart_layout.png} +0 -0
  346. /data/doc/images/{quickstart_lifecycle.png → verify_quickstart_lifecycle.png} +0 -0
  347. /data/doc/images/{list_styles.png → widget_list_styles.png} +0 -0
  348. /data/doc/images/{popup_demo.png → widget_popup_demo.png} +0 -0
  349. /data/doc/images/{hit_test.png → widget_rect.png} +0 -0
  350. /data/doc/images/{rich_text.png → widget_rich_text.png} +0 -0
  351. /data/doc/images/{scroll_text.png → widget_scroll_text.png} +0 -0
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
+ require "ratatui_ruby"
8
+
9
+ # Styled List Example
10
+ # Demonstrates advanced styling options for the List widget.
11
+ class WidgetListStyles
12
+ attr_reader :selected_index, :highlight_spacing
13
+
14
+ def initialize
15
+ @items = ["Item 1", "Item 2", "Item 3"]
16
+ @selected_index = nil
17
+
18
+ @directions = [
19
+ { name: "Top to Bottom", direction: :top_to_bottom },
20
+ { name: "Bottom to Top", direction: :bottom_to_top },
21
+ ]
22
+ @direction_index = 0
23
+
24
+ @highlight_spacings = [
25
+ { name: "When Selected", spacing: :when_selected },
26
+ { name: "Always", spacing: :always },
27
+ { name: "Never", spacing: :never },
28
+ ]
29
+ @highlight_spacing_index = 0
30
+
31
+ @repeat_modes = [
32
+ { name: "Off", repeat: false },
33
+ { name: "On", repeat: true },
34
+ ]
35
+ @repeat_index = 0
36
+ end
37
+
38
+ def run
39
+ RatatuiRuby.run do |tui|
40
+ @tui = tui
41
+ @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
42
+
43
+ loop do
44
+ render
45
+ break if handle_input == :quit
46
+ sleep 0.05
47
+ end
48
+ end
49
+ end
50
+
51
+ private def render
52
+ selection_label = @selected_index.nil? ? "none" : @selected_index.to_s
53
+ direction_config = @directions[@direction_index]
54
+ spacing_config = @highlight_spacings[@highlight_spacing_index]
55
+ repeat_config = @repeat_modes[@repeat_index]
56
+
57
+ @tui.draw do |frame|
58
+ # Split into main content and control panel
59
+ main_area, control_area = @tui.layout_split(
60
+ frame.area,
61
+ direction: :vertical,
62
+ constraints: [
63
+ @tui.constraint_fill(1),
64
+ @tui.constraint_length(5),
65
+ ]
66
+ )
67
+
68
+ # Render list
69
+ main_list = @tui.list(
70
+ items: @items,
71
+ selected_index: @selected_index,
72
+ style: @tui.style(fg: :white, bg: :black),
73
+ highlight_style: @tui.style(fg: :blue, bg: :white, modifiers: [:bold]),
74
+ highlight_symbol: ">> ",
75
+ repeat_highlight_symbol: repeat_config[:repeat],
76
+ highlight_spacing: spacing_config[:spacing],
77
+ direction: direction_config[:direction],
78
+ block: @tui.block(
79
+ title: "Items",
80
+ borders: [:all]
81
+ )
82
+ )
83
+ frame.render_widget(main_list, main_area)
84
+
85
+ # Render control panel
86
+ control_panel = @tui.block(
87
+ title: "Controls",
88
+ borders: [:all],
89
+ children: [
90
+ @tui.paragraph(
91
+ text: [
92
+ @tui.text_line(spans: [
93
+ @tui.text_span(content: "↑/↓", style: @hotkey_style),
94
+ @tui.text_span(content: ": Select (#{selection_label}) "),
95
+ @tui.text_span(content: "x", style: @hotkey_style),
96
+ @tui.text_span(content: ": Toggle Selection "),
97
+ @tui.text_span(content: "q", style: @hotkey_style),
98
+ @tui.text_span(content: ": Quit"),
99
+ ]),
100
+ @tui.text_line(spans: [
101
+ @tui.text_span(content: "d", style: @hotkey_style),
102
+ @tui.text_span(content: ": Direction (#{direction_config[:name]}) "),
103
+ @tui.text_span(content: "s", style: @hotkey_style),
104
+ @tui.text_span(content: ": Spacing (#{spacing_config[:name]})"),
105
+ ]),
106
+ @tui.text_line(spans: [
107
+ @tui.text_span(content: "r", style: @hotkey_style),
108
+ @tui.text_span(content: ": Repeat Symbol (#{repeat_config[:name]})"),
109
+ ]),
110
+ ]
111
+ ),
112
+ ]
113
+ )
114
+ frame.render_widget(control_panel, control_area)
115
+ end
116
+ end
117
+
118
+ private def handle_input
119
+ case @tui.poll_event
120
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
121
+ :quit
122
+ in type: :key, code: "up"
123
+ @selected_index = (@selected_index || 0) - 1
124
+ @selected_index = @items.size - 1 if @selected_index.negative?
125
+ in type: :key, code: "down"
126
+ @selected_index = ((@selected_index || -1) + 1) % @items.size
127
+ in type: :key, code: "d"
128
+ @direction_index = (@direction_index + 1) % @directions.size
129
+ in type: :key, code: "s"
130
+ @highlight_spacing_index = (@highlight_spacing_index + 1) % @highlight_spacings.size
131
+ in type: :key, code: "r"
132
+ @repeat_index = (@repeat_index + 1) % @repeat_modes.size
133
+ in type: :key, code: "x"
134
+ @selected_index = @selected_index.nil? ? 0 : nil
135
+ else
136
+ nil
137
+ end
138
+ end
139
+ end
140
+
141
+ WidgetListStyles.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
+ require "ratatui_ruby"
8
+
9
+ # Popup Demo Example
10
+ # Demonstrates the Clear widget for creating opaque popups.
11
+
12
+ class WidgetPopupDemo
13
+ def initialize
14
+ @clear_enabled = false
15
+ end
16
+
17
+ def run
18
+ RatatuiRuby.run do |tui|
19
+ loop do
20
+ tui.draw do |frame|
21
+ render(tui, frame)
22
+ end
23
+ break if handle_input(tui) == :quit
24
+ sleep 0.05
25
+ end
26
+ end
27
+ end
28
+
29
+ private def render(tui, frame)
30
+ area = frame.area
31
+
32
+ # 1. Background: Loud Red Background
33
+ # This demonstrates "Style Bleed" where the background color persists
34
+ # unless explicitly cleared or overwritten.
35
+ background = tui.paragraph(
36
+ text: "BACKGROUND RED " * 100,
37
+ style: tui.style(bg: :red, fg: :white),
38
+ wrap: true
39
+ )
40
+ frame.render_widget(background, area)
41
+
42
+ # 2. Popup Area Calculation
43
+ # Center the popup vertically and horizontally
44
+ vertical_layout = tui.layout_split(
45
+ area,
46
+ direction: :vertical,
47
+ constraints: [
48
+ tui.constraint_percentage(25),
49
+ tui.constraint_percentage(50), # 50% height
50
+ tui.constraint_percentage(25),
51
+ ]
52
+ )
53
+ popup_area_vertical = vertical_layout[1]
54
+
55
+ horizontal_layout = tui.layout_split(
56
+ popup_area_vertical,
57
+ direction: :horizontal,
58
+ constraints: [
59
+ tui.constraint_percentage(20),
60
+ tui.constraint_percentage(60), # 60% width
61
+ tui.constraint_percentage(20),
62
+ ]
63
+ )
64
+ popup_area = horizontal_layout[1]
65
+
66
+ # 3. Popup Content
67
+ # Without Clear, this will "inherit" the red background from underneath.
68
+ popup_text = if @clear_enabled
69
+ "✓ Clear is ENABLED\n\nResets background to default\n(Usually Black/Transparent)\n\nPress Space to toggle"
70
+ else
71
+ "✗ Clear is DISABLED\n\nStyle Bleed: Popup is RED!\n(Inherits background style)\n\nPress Space to toggle"
72
+ end
73
+
74
+ popup_content = tui.paragraph(
75
+ text: popup_text,
76
+ alignment: :center,
77
+ block: tui.block(
78
+ title: "Popup Demo (q to quit, space to toggle)",
79
+ borders: [:all]
80
+ )
81
+ )
82
+
83
+ # 4. Render Popup
84
+ if @clear_enabled
85
+ # With Clear: Resets the style in the popup area before rendering content
86
+ frame.render_widget(tui.clear, popup_area)
87
+ end
88
+
89
+ frame.render_widget(popup_content, popup_area)
90
+ end
91
+
92
+ private def handle_input(tui)
93
+ case tui.poll_event
94
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
95
+ :quit
96
+ in type: :key, code: " "
97
+ @clear_enabled = !@clear_enabled
98
+ else
99
+ nil
100
+ end
101
+ end
102
+ end
103
+
104
+ WidgetPopupDemo.new.run if __FILE__ == $0
@@ -0,0 +1,103 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ # frozen_string_literal: true
6
+
7
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
8
+ require "ratatui_ruby"
9
+
10
+ # Demonstrates branding visualization with the official logo.
11
+ #
12
+ # Branding is important for identity. Users need to recognize the tools they use.
13
+ #
14
+ # This demo showcases the <tt>RatatuiLogo</tt> widget. It renders the logo in a centered layout.
15
+ #
16
+ # Use it to understand how to incorporate the project's visual identity into your terminal application.
17
+ #
18
+ # === Example
19
+ #
20
+ # Run the demo from the terminal:
21
+ #
22
+ # ruby examples/widget_ratatui_logo_demo/app.rb
23
+ #
24
+ # rdoc-image:/doc/images/widget_ratatui_logo_demo.png
25
+ class WidgetRatatuiLogoDemo
26
+ def run
27
+ RatatuiRuby.run do |tui|
28
+ loop do
29
+ render(tui)
30
+ break if handle_input(tui) == :quit
31
+ end
32
+ end
33
+ end
34
+
35
+ private def render(tui)
36
+ tui.draw do |frame|
37
+ # Layout
38
+ layout = tui.layout_split(
39
+ frame.area,
40
+ direction: :vertical,
41
+ constraints: [
42
+ tui.constraint_fill(1), # Fill remaining space
43
+ tui.constraint_length(3),
44
+ ]
45
+ )
46
+
47
+ # Main Area
48
+ main_area = layout[0]
49
+
50
+ # Center the logo using nested Layouts
51
+ # Logo is roughly 47x8
52
+ # Vertical Center
53
+ v_center_layout = tui.layout_split(
54
+ main_area,
55
+ direction: :vertical,
56
+ flex: :center,
57
+ constraints: [tui.constraint_length(10)] # Height + margin
58
+ )
59
+
60
+ # Horizontal Center
61
+ h_center_layout = tui.layout_split(
62
+ v_center_layout[0],
63
+ direction: :horizontal,
64
+ flex: :center,
65
+ constraints: [tui.constraint_length(50)] # Width + margin
66
+ )
67
+
68
+ # Main content: The Logo
69
+ logo = RatatuiRuby::RatatuiLogo.new
70
+ frame.render_widget(logo, h_center_layout[0])
71
+
72
+ # Control Panel
73
+ control_area = layout[1]
74
+
75
+ control_text = tui.text_line(spans: [
76
+ tui.text_span(content: "q", style: tui.style(modifiers: [:bold, :underlined])),
77
+ tui.text_span(content: ": Quit"),
78
+ ])
79
+
80
+ control_panel = tui.paragraph(
81
+ text: [control_text],
82
+ block: tui.block(
83
+ title: "Controls",
84
+ borders: [:top],
85
+ style: tui.style(fg: :dark_gray)
86
+ )
87
+ )
88
+
89
+ frame.render_widget(control_panel, control_area)
90
+ end
91
+ end
92
+
93
+ private def handle_input(tui)
94
+ case tui.poll_event
95
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
96
+ :quit
97
+ else
98
+ nil
99
+ end
100
+ end
101
+ end
102
+
103
+ WidgetRatatuiLogoDemo.new.run if __FILE__ == $0
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: CC-BY-SA-4.0
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
+ require "ratatui_ruby"
8
+
9
+ # Demonstrates personality and charm with the project mascot.
10
+ #
11
+ # Interfaces without personality feel clinical and dry. Users appreciate a friendly face in their terminal.
12
+ #
13
+ # This demo showcases the <tt>RatatuiMascot</tt> widget. It provides an interactive playground where you can toggle the surrounding block.
14
+ #
15
+ # Use it to understand how to add a playful touch to your terminal dashboards or about screens.
16
+ #
17
+ # === Example
18
+ #
19
+ # Run the demo from the terminal:
20
+ #
21
+ # ruby examples/widget_ratatui_mascot_demo/app.rb
22
+ #
23
+ # rdoc-image:/doc/images/widget_ratatui_mascot_demo.png
24
+ class WidgetRatatuiMascotDemo
25
+ def initialize
26
+ @show_block = true
27
+ end
28
+
29
+ def run
30
+ RatatuiRuby.run do |tui|
31
+ loop do
32
+ tui.draw do |frame|
33
+ # Layout: Top (Mascot), Bottom (Controls)
34
+ layout = tui.layout_split(
35
+ frame.area,
36
+ direction: :vertical,
37
+ constraints: [
38
+ tui.constraint_fill(1),
39
+ tui.constraint_length(4),
40
+ ]
41
+ )
42
+
43
+ mascot_area = layout[0]
44
+ controls_area = layout[1]
45
+
46
+ # Mascot Widget
47
+ block = if @show_block
48
+ tui.block(
49
+ title: "Ratatui Mascot",
50
+ borders: [:all],
51
+ border_type: :rounded,
52
+ border_style: { fg: :green }
53
+ )
54
+ end
55
+
56
+ mascot = tui.ratatui_mascot(block:)
57
+ frame.render_widget(mascot, mascot_area)
58
+
59
+ # Controls
60
+ controls_text = [
61
+ tui.text_span(content: "q", style: tui.style(modifiers: [:bold, :underlined])),
62
+ tui.text_span(content: " Quit"),
63
+ tui.text_span(content: " "),
64
+ tui.text_span(content: "b", style: tui.style(modifiers: [:bold, :underlined])),
65
+ tui.text_span(content: " Toggle Block #{@show_block ? '(On)' : '(Off)'}"),
66
+ ]
67
+
68
+ controls_paragraph = tui.paragraph(
69
+ text: tui.text_line(spans: controls_text),
70
+ block: tui.block(borders: [:top], title: "Controls")
71
+ )
72
+ frame.render_widget(controls_paragraph, controls_area)
73
+ end
74
+ break if handle_input(tui) == :quit
75
+ end
76
+ end
77
+ end
78
+
79
+ private def handle_input(tui)
80
+ event = tui.poll_event
81
+
82
+ if event.key?
83
+ case event.char
84
+ when "q" then :quit
85
+ when "b" then @show_block = !@show_block
86
+ end
87
+ elsif event.ctrl_c?
88
+ :quit
89
+ end
90
+ end
91
+ end
92
+
93
+ WidgetRatatuiMascotDemo.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
+ require "ratatui_ruby"
8
+
9
+ # Rect Widget Showcase
10
+ #
11
+ # Demonstrates the Rect class and the Cached Layout Pattern.
12
+ #
13
+ # Rect is the fundamental geometry primitive for TUI layout. This example shows:
14
+ # - Rect attributes: x, y, width, height
15
+ # - Rect#contains? for hit testing mouse clicks
16
+ # - Layout.split returning cached rects for reuse
17
+ # - The layout caching pattern: compute in draw, reuse in handle_input
18
+ #
19
+ # Controls:
20
+ # ←/→: Adjust sidebar width
21
+ # ↑/↓: Navigate menu items
22
+ # Mouse: Click panels to test Rect#contains?
23
+ # q: Quit
24
+ class WidgetRect
25
+ MENU_ITEMS = ["Dashboard", "Analytics", "Settings", "Logs", "Help"].freeze
26
+
27
+ def initialize
28
+ @sidebar_width = 20
29
+ @selected_index = 0
30
+ @last_action = "Click any panel to test Rect#contains?"
31
+ @click_count = 0
32
+ @sidebar_rect = nil
33
+ @main_rect = nil
34
+ @controls_rect = nil
35
+ end
36
+
37
+ def run
38
+ RatatuiRuby.run do |tui|
39
+ @tui = tui
40
+ @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
41
+ @label_style = @tui.style(modifiers: [:bold])
42
+ @dim_style = @tui.style(fg: :dark_gray)
43
+
44
+ loop do
45
+ render
46
+ break if handle_input == :quit
47
+ end
48
+ end
49
+ end
50
+
51
+ private def render
52
+ @tui.draw do |frame|
53
+ @main_rect, @controls_rect = @tui.layout_split(
54
+ frame.area,
55
+ direction: :vertical,
56
+ constraints: [
57
+ @tui.constraint_fill(1),
58
+ @tui.constraint_length(8),
59
+ ]
60
+ )
61
+
62
+ @sidebar_rect, @content_rect = @tui.layout_split(
63
+ @main_rect,
64
+ direction: :horizontal,
65
+ constraints: [
66
+ @tui.constraint_length(@sidebar_width),
67
+ @tui.constraint_fill(1),
68
+ ]
69
+ )
70
+
71
+ render_sidebar(frame)
72
+ render_content(frame)
73
+ render_controls(frame)
74
+ end
75
+ end
76
+
77
+ private def render_sidebar(frame)
78
+ sidebar = @tui.list(
79
+ items: MENU_ITEMS,
80
+ selected_index: @selected_index,
81
+ highlight_style: @tui.style(fg: :black, bg: :white, modifiers: [:bold]),
82
+ highlight_symbol: "> ",
83
+ block: @tui.block(title: "Menu", borders: [:all])
84
+ )
85
+ frame.render_widget(sidebar, @sidebar_rect)
86
+ end
87
+
88
+ private def render_content(frame)
89
+ text_content = [
90
+ @tui.text_line(spans: [
91
+ @tui.text_span(content: "Active View: ", style: @label_style),
92
+ @tui.text_span(content: MENU_ITEMS[@selected_index], style: @tui.style(fg: :green)),
93
+ ]),
94
+ "",
95
+ @tui.text_line(spans: [
96
+ @tui.text_span(content: "Rect Attributes ", style: @label_style),
97
+ @tui.text_span(content: "(from Layout.split):", style: @dim_style),
98
+ ]),
99
+ " Sidebar: Rect(x:#{@sidebar_rect.x}, y:#{@sidebar_rect.y}, " \
100
+ "width:#{@sidebar_rect.width}, height:#{@sidebar_rect.height})",
101
+ " Content: Rect(x:#{@content_rect.x}, y:#{@content_rect.y}, " \
102
+ "width:#{@content_rect.width}, height:#{@content_rect.height})",
103
+ "",
104
+ @tui.text_line(spans: [
105
+ @tui.text_span(content: "Hit Testing ", style: @label_style),
106
+ @tui.text_span(content: "(Rect#contains?):", style: @dim_style),
107
+ ]),
108
+ " Clicks: #{@click_count} | #{@last_action}",
109
+ ]
110
+
111
+ paragraph = @tui.paragraph(
112
+ text: text_content,
113
+ block: @tui.block(title: "Content", borders: [:all])
114
+ )
115
+ frame.render_widget(paragraph, @content_rect)
116
+ end
117
+
118
+ private def render_controls(frame)
119
+ controls = @tui.block(
120
+ title: "Controls",
121
+ borders: [:all],
122
+ children: [
123
+ @tui.paragraph(
124
+ text: [
125
+ @tui.text_line(spans: [
126
+ @tui.text_span(content: "LAYOUT", style: @label_style),
127
+ @tui.text_span(content: " "),
128
+ @tui.text_span(content: "←", style: @hotkey_style),
129
+ @tui.text_span(content: ": Shrink sidebar "),
130
+ @tui.text_span(content: "→", style: @hotkey_style),
131
+ @tui.text_span(content: ": Expand sidebar "),
132
+ @tui.text_span(content: "(width: #{@sidebar_width})"),
133
+ ]),
134
+ @tui.text_line(spans: [
135
+ @tui.text_span(content: "NAVIGATION", style: @label_style),
136
+ @tui.text_span(content: " "),
137
+ @tui.text_span(content: "↑↓", style: @hotkey_style),
138
+ @tui.text_span(content: ": Select menu item "),
139
+ @tui.text_span(content: "q", style: @hotkey_style),
140
+ @tui.text_span(content: ": Quit"),
141
+ ]),
142
+ "",
143
+ @tui.text_line(spans: [
144
+ @tui.text_span(content: "HIT TESTING", style: @label_style),
145
+ @tui.text_span(content: " Click any panel → Rect#contains?(x, y) determines which rect was hit."),
146
+ ]),
147
+ ]
148
+ ),
149
+ ]
150
+ )
151
+ frame.render_widget(controls, @controls_rect)
152
+ end
153
+
154
+ private def handle_input
155
+ case @tui.poll_event
156
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
157
+ :quit
158
+ in type: :key, code: "left"
159
+ @sidebar_width = [@sidebar_width - 2, 10].max
160
+ @last_action = "Layout changed: sidebar_width=#{@sidebar_width}"
161
+ nil
162
+ in type: :key, code: "right"
163
+ @sidebar_width = [@sidebar_width + 2, 40].min
164
+ @last_action = "Layout changed: sidebar_width=#{@sidebar_width}"
165
+ nil
166
+ in type: :key, code: "up"
167
+ @selected_index = (@selected_index - 1) % MENU_ITEMS.size
168
+ @last_action = "Selected: #{MENU_ITEMS[@selected_index]}"
169
+ nil
170
+ in type: :key, code: "down"
171
+ @selected_index = (@selected_index + 1) % MENU_ITEMS.size
172
+ @last_action = "Selected: #{MENU_ITEMS[@selected_index]}"
173
+ nil
174
+ in type: :mouse, kind: "down", x: click_x, y: click_y
175
+ handle_click(click_x, click_y)
176
+ nil
177
+ else
178
+ nil
179
+ end
180
+ end
181
+
182
+ private def handle_click(x, y)
183
+ @click_count += 1
184
+
185
+ if @sidebar_rect&.contains?(x, y)
186
+ relative_y = y - @sidebar_rect.y - 1
187
+ if relative_y >= 0 && relative_y < MENU_ITEMS.size
188
+ old_item = MENU_ITEMS[@selected_index]
189
+ @selected_index = relative_y
190
+ new_item = MENU_ITEMS[@selected_index]
191
+ @last_action = "sidebar.contains?(#{x},#{y})=true → #{old_item}→#{new_item}"
192
+ else
193
+ @last_action = "sidebar.contains?(#{x},#{y})=true (empty area)"
194
+ end
195
+ elsif @content_rect&.contains?(x, y)
196
+ @last_action = "content.contains?(#{x},#{y})=true"
197
+ elsif @controls_rect&.contains?(x, y)
198
+ @last_action = "controls.contains?(#{x},#{y})=true"
199
+ else
200
+ @last_action = "No rect contains (#{x},#{y})"
201
+ end
202
+ end
203
+ end
204
+
205
+ WidgetRect.new.run if __FILE__ == $0