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,184 @@
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
+ # Custom widget that draws a diagonal line.
10
+ #
11
+ # Demonstrates absolute coordinate rendering respecting the given area bounds.
12
+ # This pattern is essential when custom widgets need to coexist with bordered blocks.
13
+ class DiagonalWidget
14
+ def render(area)
15
+ # Draw a diagonal line within the area's bounds.
16
+ # The area parameter respects parent block borders and padding automatically.
17
+ (0..10).filter_map do |i|
18
+ next if i >= area.width || i >= area.height
19
+
20
+ RatatuiRuby::Draw.string(
21
+ area.x + i,
22
+ area.y + i,
23
+ "\\",
24
+ RatatuiRuby::Style.new(fg: :red, modifiers: [:bold])
25
+ )
26
+ end
27
+ end
28
+ end
29
+
30
+ # Custom widget that draws a checkerboard pattern.
31
+ #
32
+ # This pattern shows using the area's x, y offset correctly when rendering
33
+ # absolute coordinates. The area parameter may have x, y > 0 when rendered
34
+ # inside a positioned block. Always use area.x and area.y as offsets.
35
+ class CheckerboardWidget
36
+ def initialize(char = "□")
37
+ @char = char
38
+ end
39
+
40
+ def render(area)
41
+ result = []
42
+ (0...area.height).each do |row| # rubocop:disable Lint/AmbiguousRange
43
+ (0...area.width).each do |col| # rubocop:disable Lint/AmbiguousRange
44
+ next if (row + col).even?
45
+
46
+ result << RatatuiRuby::Draw.string(
47
+ area.x + col,
48
+ area.y + row,
49
+ @char,
50
+ RatatuiRuby::Style.new(fg: :cyan)
51
+ )
52
+ end
53
+ end
54
+ result
55
+ end
56
+ end
57
+
58
+ # Custom widget that draws a border inside the area.
59
+ #
60
+ # Demonstrates that custom widgets can compose complex shapes using the area's bounds.
61
+ # Here we draw a complete box (corners and edges) that fits within the area,
62
+ # respecting width and height constraints automatically.
63
+ class BorderWidget
64
+ def render(area)
65
+ result = []
66
+ style = RatatuiRuby::Style.new(fg: :green)
67
+
68
+ # Top and bottom
69
+ (0...area.width).each do |x| # rubocop:disable Lint/AmbiguousRange
70
+ result << RatatuiRuby::Draw.string(area.x + x, area.y, "─", style)
71
+ result << RatatuiRuby::Draw.string(area.x + x, area.y + area.height - 1, "─", style)
72
+ end
73
+
74
+ # Left and right
75
+ (0...area.height).each do |y| # rubocop:disable Lint/AmbiguousRange
76
+ result << RatatuiRuby::Draw.string(area.x, area.y + y, "│", style)
77
+ result << RatatuiRuby::Draw.string(area.x + area.width - 1, area.y + y, "│", style)
78
+ end
79
+
80
+ # Corners
81
+ result << RatatuiRuby::Draw.string(area.x, area.y, "┌", style)
82
+ result << RatatuiRuby::Draw.string(area.x + area.width - 1, area.y, "┐", style)
83
+ result << RatatuiRuby::Draw.string(area.x, area.y + area.height - 1, "└", style)
84
+ result << RatatuiRuby::Draw.string(area.x + area.width - 1, area.y + area.height - 1, "┘", style)
85
+
86
+ result
87
+ end
88
+ end
89
+
90
+ class WidgetRender
91
+ def initialize
92
+ @widget_index = 0
93
+ @widgets = [
94
+ { name: "Diagonal", widget: DiagonalWidget.new },
95
+ { name: "Checkerboard", widget: CheckerboardWidget.new("□") },
96
+ { name: "Border", widget: BorderWidget.new },
97
+ ]
98
+ end
99
+
100
+ def run
101
+ RatatuiRuby.run do |tui|
102
+ @tui = tui
103
+ loop do
104
+ render
105
+ break if handle_input == :quit
106
+ end
107
+ end
108
+ end
109
+
110
+ private def render
111
+ @tui.draw do |frame|
112
+ layout = @tui.layout_split(
113
+ frame.area,
114
+ direction: :vertical,
115
+ constraints: [
116
+ @tui.constraint_fill(1),
117
+ @tui.constraint_length(4),
118
+ ]
119
+ )
120
+
121
+ # Render a border block to frame widget area
122
+ current_name = @widgets[@widget_index][:name]
123
+ widget_block = @tui.block(
124
+ title: "Custom Widget: #{current_name}",
125
+ borders: [:all]
126
+ )
127
+ frame.render_widget(widget_block, layout[0])
128
+
129
+ # Calculate the inner area, accounting for the block's 1-character border on all sides.
130
+ # This is the key pattern: compute the available space INSIDE the block before
131
+ # passing it to the custom widget's render method.
132
+ # When the custom widget receives this area, all its absolute coordinates will
133
+ # respect the block's boundaries automatically.
134
+ inner_area = @tui.rect(
135
+ x: layout[0].x + 1,
136
+ y: layout[0].y + 1,
137
+ width: [layout[0].width - 2, 0].max,
138
+ height: [layout[0].height - 2, 0].max
139
+ )
140
+
141
+ # Render the custom widget inside the bordered area.
142
+ # The widget's render method receives the inner_area and draws within it.
143
+ frame.render_widget(@widgets[@widget_index][:widget], inner_area)
144
+
145
+ # Render control panel with current widget info
146
+ control_lines = [
147
+ @tui.text_line(
148
+ spans: [
149
+ @tui.text_span(content: "n", style: @tui.style(modifiers: [:bold, :underlined])),
150
+ @tui.text_span(content: ": Next "),
151
+ @tui.text_span(content: "p", style: @tui.style(modifiers: [:bold, :underlined])),
152
+ @tui.text_span(content: ": Previous "),
153
+ @tui.text_span(content: "q", style: @tui.style(modifiers: [:bold, :underlined])),
154
+ @tui.text_span(content: ": Quit"),
155
+ ]
156
+ ),
157
+ ]
158
+ controls = @tui.paragraph(
159
+ text: control_lines,
160
+ block: @tui.block(
161
+ title: "Controls",
162
+ borders: [:all]
163
+ )
164
+ )
165
+ frame.render_widget(controls, layout[1])
166
+ end
167
+ end
168
+
169
+ private def handle_input
170
+ event = @tui.poll_event
171
+ case event
172
+ in { type: :key, code: "q" }
173
+ :quit
174
+ in { type: :key, code: "n" }
175
+ @widget_index = (@widget_index + 1) % @widgets.length
176
+ in { type: :key, code: "p" }
177
+ @widget_index = (@widget_index - 1) % @widgets.length
178
+ else
179
+ # Ignore other events
180
+ end
181
+ end
182
+ end
183
+
184
+ WidgetRender.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,137 @@
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
+ # Rich Text Example
10
+ # Demonstrates the Span and Line objects for styling individual words
11
+ # within a block of text.
12
+ class WidgetRichText
13
+ def initialize
14
+ @scroll_pos = 0
15
+ end
16
+
17
+ def run
18
+ RatatuiRuby.run do |tui|
19
+ @tui = tui
20
+ loop do
21
+ render
22
+ event = handle_input
23
+ break if event == :quit
24
+ sleep 0.05
25
+ end
26
+ end
27
+ end
28
+
29
+ private def render
30
+ @tui.draw do |frame|
31
+ layout = @tui.layout_split(
32
+ frame.area,
33
+ direction: :vertical,
34
+ constraints: [
35
+ @tui.constraint_percentage(50),
36
+ @tui.constraint_percentage(50),
37
+ ]
38
+ )
39
+ frame.render_widget(simple_text_line_example, layout[0])
40
+ frame.render_widget(complex_example, layout[1])
41
+ end
42
+ end
43
+
44
+ private def simple_text_line_example
45
+ # Example 1: A line with mixed styles
46
+ @tui.paragraph(
47
+ text: @tui.text_line(
48
+ spans: [
49
+ @tui.text_span(
50
+ content: "Normal text, ",
51
+ style: nil
52
+ ),
53
+ @tui.text_span(
54
+ content: "Bold Text",
55
+ style: @tui.style(modifiers: [:bold])
56
+ ),
57
+ @tui.text_span(
58
+ content: ", ",
59
+ style: nil
60
+ ),
61
+ @tui.text_span(
62
+ content: "Italic Text",
63
+ style: @tui.style(modifiers: [:italic])
64
+ ),
65
+ @tui.text_span(
66
+ content: ", ",
67
+ style: nil
68
+ ),
69
+ @tui.text_span(
70
+ content: "Red Text",
71
+ style: @tui.style(fg: :red)
72
+ ),
73
+ @tui.text_span(
74
+ content: ".",
75
+ style: nil
76
+ ),
77
+ ]
78
+ ),
79
+ block: @tui.block(
80
+ title: "Simple Rich Text",
81
+ borders: [:all]
82
+ )
83
+ )
84
+ end
85
+
86
+ private def complex_example
87
+ # Example 2: Multiple lines with different styles
88
+ @tui.paragraph(
89
+ text: [
90
+ @tui.text_line(
91
+ spans: [
92
+ @tui.text_span(content: "✓ ", style: @tui.style(fg: :green, modifiers: [:bold])),
93
+ @tui.text_span(content: "Feature Complete", style: nil),
94
+ @tui.text_span(content: " - All tests passing", style: @tui.style(fg: :gray)),
95
+ ]
96
+ ),
97
+ @tui.text_line(
98
+ spans: [
99
+ @tui.text_span(content: "⚠ ", style: @tui.style(fg: :yellow, modifiers: [:bold])),
100
+ @tui.text_span(content: "Warning", style: nil),
101
+ @tui.text_span(content: " - Documentation pending", style: @tui.style(fg: :gray)),
102
+ ]
103
+ ),
104
+ @tui.text_line(
105
+ spans: [
106
+ @tui.text_span(content: "✗ ", style: @tui.style(fg: :red, modifiers: [:bold])),
107
+ @tui.text_span(content: "Not Started", style: nil),
108
+ @tui.text_span(content: " - Performance benchmarks", style: @tui.style(fg: :gray)),
109
+ ]
110
+ ),
111
+ @tui.text_line(spans: []),
112
+ @tui.text_line(
113
+ spans: [
114
+ @tui.text_span(content: "Press ", style: nil),
115
+ @tui.text_span(content: "Q", style: @tui.style(modifiers: [:bold])),
116
+ @tui.text_span(content: " to quit", style: nil),
117
+ ]
118
+ ),
119
+ ],
120
+ block: @tui.block(
121
+ title: "Status Report",
122
+ borders: [:all]
123
+ )
124
+ )
125
+ end
126
+
127
+ private def handle_input
128
+ event = @tui.poll_event
129
+ return :quit if event == "q" || event == :esc || event == :ctrl_c
130
+
131
+ nil
132
+ end
133
+ end
134
+
135
+ if __FILE__ == $0
136
+ WidgetRichText.new.run
137
+ end
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: AGPL-3.0-or-later
6
+
7
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
8
+ require "ratatui_ruby"
9
+
10
+ # Demo: Scrollable Paragraph
11
+ # Shows how to scroll through long text content using arrow keys
12
+ #
13
+ # Helper: Disable experimental warnings since we use line_count/line_width
14
+ RatatuiRuby.experimental_warnings = false
15
+
16
+ class WidgetScrollText
17
+ def run
18
+ RatatuiRuby.run do |tui|
19
+ @tui = tui
20
+ @scroll_x = 0
21
+ @scroll_y = 0
22
+
23
+ @lines = (1..100).map do |i|
24
+ "Line #{i}: " + ("This is a long line of text that can be scrolled horizontally. " * 3) + "End of line #{i}"
25
+ end
26
+ @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
27
+
28
+ loop do
29
+ draw
30
+ break if handle_input == :quit
31
+ end
32
+ end
33
+ end
34
+
35
+ def render
36
+ # No-op for compatibility if needed, or alias to draw, but draw now uses @tui
37
+ draw
38
+ end
39
+
40
+ def handle_input
41
+ case @tui.poll_event
42
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
43
+ :quit
44
+ in type: :key, code: "up"
45
+ @scroll_y = [@scroll_y - 1, 0].max
46
+ in type: :key, code: "down"
47
+ @scroll_y = [@scroll_y + 1, @lines.length].min
48
+ in type: :key, code: "left"
49
+ @scroll_x = [@scroll_x - 1, 0].max
50
+ in type: :key, code: "right"
51
+ @scroll_x = [@scroll_x + 1, 100].min
52
+ else
53
+ nil
54
+ end
55
+ end
56
+
57
+ private def draw
58
+ @tui.draw do |frame|
59
+ layout = @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
+ text = @lines.join("\n")
69
+
70
+ # Main content
71
+ main_paragraph = @tui.paragraph(
72
+ text:,
73
+ scroll: [@scroll_y, @scroll_x],
74
+ block: @tui.block(
75
+ title: "Scrollable Text (#{text.lines.count} lines)",
76
+ borders: [:all]
77
+ )
78
+ )
79
+ frame.render_widget(main_paragraph, layout[0])
80
+
81
+ # Bottom control panel
82
+ control_text = [
83
+ @tui.text_line(spans: [
84
+ @tui.text_span(content: "NAVIGATION (Size: #{main_paragraph.line_count(65535)}x#{main_paragraph.line_width})", style: @tui.style(modifiers: [:bold])),
85
+ ]),
86
+ @tui.text_line(spans: [
87
+ @tui.text_span(content: "↑/↓", style: @hotkey_style),
88
+ @tui.text_span(content: ": Vert Scroll (#{@scroll_y}/#{main_paragraph.line_count(65535)}) "),
89
+ @tui.text_span(content: "←/→", style: @hotkey_style),
90
+ @tui.text_span(content: ": Horz Scroll (#{@scroll_x}/#{main_paragraph.line_width}) "),
91
+ @tui.text_span(content: "q", style: @hotkey_style),
92
+ @tui.text_span(content: ": Quit"),
93
+ ]),
94
+ ]
95
+
96
+ control_paragraph = @tui.paragraph(
97
+ text: control_text,
98
+ block: @tui.block(
99
+ title: "Controls",
100
+ borders: [:all]
101
+ )
102
+ )
103
+ frame.render_widget(control_paragraph, layout[1])
104
+ end
105
+ end
106
+ end
107
+
108
+ WidgetScrollText.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,153 @@
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
+ # Demonstrates viewport navigation with interactive theme and orientation cycling.
10
+ #
11
+ # Content overflows. Users get lost in long lists without landmarks. They need to know where they are and how much is left.
12
+ #
13
+ # This demo showcases the <tt>Scrollbar</tt> widget. It provides an interactive playground where you can toggle orientations and cycle through different themes (Standard, Rounded, ASCII, Minimal) in real-time.
14
+ #
15
+ # Use it to understand how to provide spatial awareness and navigation cues for overflowing content.
16
+ #
17
+ # === Example
18
+ #
19
+ # Run the demo from the terminal:
20
+ #
21
+ # ruby examples/widget_scrollbar_demo/app.rb
22
+ #
23
+ # rdoc-image:/doc/images/widget_scrollbar_demo.png
24
+ class WidgetScrollbarDemo
25
+ def initialize
26
+ @scroll_position = 0
27
+ @content_length = 50
28
+ @lines = (1..@content_length).map { |i| "Line #{i}" }
29
+ @orientation_index = 0
30
+ @orientations = [
31
+ :vertical,
32
+ :vertical_right,
33
+ :vertical_left,
34
+ :horizontal,
35
+ :horizontal_bottom,
36
+ :horizontal_top,
37
+ ]
38
+ @theme_index = 0
39
+ @themes = [
40
+ {
41
+ name: "Standard",
42
+ track_symbol: nil,
43
+ thumb_symbol: "█",
44
+ track_style: nil,
45
+ thumb_style: nil,
46
+ begin_symbol: nil,
47
+ end_symbol: nil,
48
+ },
49
+ {
50
+ name: "Rounded",
51
+ track_symbol: "│",
52
+ thumb_symbol: "┃",
53
+ track_style: { fg: "dark_gray" },
54
+ thumb_style: { fg: "cyan" },
55
+ begin_symbol: "▲",
56
+ end_symbol: "▼",
57
+ },
58
+ {
59
+ name: "ASCII",
60
+ track_symbol: "|",
61
+ thumb_symbol: "#",
62
+ track_style: { fg: "white" },
63
+ thumb_style: { fg: "red" },
64
+ begin_symbol: "^",
65
+ end_symbol: "v",
66
+ },
67
+ {
68
+ name: "Minimal",
69
+ track_symbol: " ",
70
+ thumb_symbol: "▐",
71
+ track_style: nil,
72
+ thumb_style: { fg: "yellow" },
73
+ begin_symbol: nil,
74
+ end_symbol: nil,
75
+ },
76
+ ]
77
+ end
78
+
79
+ def run
80
+ RatatuiRuby.run do |tui|
81
+ @tui = tui
82
+ loop do
83
+ draw
84
+ event = @tui.poll_event
85
+ break if event == "q" || event == :ctrl_c
86
+
87
+ handle_event(event)
88
+ end
89
+ end
90
+ end
91
+
92
+ private def handle_event(event)
93
+ if event.mouse?
94
+ case event.kind
95
+ when "scroll_up"
96
+ @scroll_position = [@scroll_position - 1, 0].max
97
+ when "scroll_down"
98
+ @scroll_position = [@scroll_position + 1, @content_length].min
99
+ end
100
+ end
101
+
102
+ if event.key? && event.to_s == "s"
103
+ @theme_index = (@theme_index + 1) % @themes.length
104
+ end
105
+
106
+ if event.key? && event.to_s == "o"
107
+ @orientation_index = (@orientation_index + 1) % @orientations.length
108
+ end
109
+ end
110
+
111
+ private def draw
112
+ @tui.draw do |frame|
113
+ # Calculate visible lines based on scroll position
114
+ # In a real app, you'd want to know the height of the available area.
115
+ # For this demo, we'll just show all lines but offset the text.
116
+ visible_lines = @lines[@scroll_position..-1] || []
117
+
118
+ # Paragraph with content
119
+ theme = @themes[@theme_index]
120
+ orientation = @orientations[@orientation_index]
121
+
122
+ p = @tui.paragraph(
123
+ text: visible_lines.join("\n"),
124
+ block: @tui.block(
125
+ titles: [
126
+ { content: "Scroll with Mouse Wheel | Theme: #{theme[:name]} | Orientation: #{orientation}" },
127
+ { content: "Press 's' to cycle theme, 'o' to cycle orientation", position: :bottom, alignment: :center },
128
+ ],
129
+ borders: [:all]
130
+ )
131
+ )
132
+
133
+ # Scrollbar
134
+ s = @tui.scrollbar(
135
+ content_length: @content_length,
136
+ position: @scroll_position,
137
+ orientation:,
138
+ track_symbol: theme[:track_symbol],
139
+ thumb_symbol: theme[:thumb_symbol],
140
+ track_style: theme[:track_style],
141
+ thumb_style: theme[:thumb_style],
142
+ begin_symbol: theme[:begin_symbol],
143
+ end_symbol: theme[:end_symbol]
144
+ )
145
+
146
+ # Render paragraph first, then scrollbar on top
147
+ frame.render_widget(p, frame.area)
148
+ frame.render_widget(s, frame.area)
149
+ end
150
+ end
151
+ end
152
+
153
+ WidgetScrollbarDemo.new.run if __FILE__ == $PROGRAM_NAME