ratatui_ruby 0.4.0 → 0.6.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 (441) 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 +98 -176
  7. data/CHANGELOG.md +80 -6
  8. data/README.md +19 -7
  9. data/REUSE.toml +15 -0
  10. data/doc/application_architecture.md +179 -45
  11. data/doc/application_testing.md +80 -32
  12. data/doc/contributors/design/ruby_frontend.md +48 -8
  13. data/doc/contributors/design/rust_backend.md +1 -0
  14. data/doc/contributors/developing_examples.md +191 -48
  15. data/doc/contributors/documentation_style.md +7 -0
  16. data/doc/contributors/examples_audit/p1_high.md +21 -0
  17. data/doc/contributors/examples_audit/p2_moderate.md +81 -0
  18. data/doc/contributors/examples_audit.md +41 -0
  19. data/doc/contributors/index.md +2 -0
  20. data/doc/event_handling.md +21 -7
  21. data/doc/images/app_all_events.png +0 -0
  22. data/doc/images/app_color_picker.png +0 -0
  23. data/doc/images/app_login_form.png +0 -0
  24. data/doc/images/app_stateful_interaction.png +0 -0
  25. data/doc/images/verify_quickstart_dsl.png +0 -0
  26. data/doc/images/verify_quickstart_layout.png +0 -0
  27. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  28. data/doc/images/verify_readme_usage.png +0 -0
  29. data/doc/images/widget_barchart_demo.png +0 -0
  30. data/doc/images/widget_block_demo.png +0 -0
  31. data/doc/images/widget_box_demo.png +0 -0
  32. data/doc/images/widget_calendar_demo.png +0 -0
  33. data/doc/images/widget_canvas_demo.png +0 -0
  34. data/doc/images/widget_cell_demo.png +0 -0
  35. data/doc/images/widget_center_demo.png +0 -0
  36. data/doc/images/widget_chart_demo.png +0 -0
  37. data/doc/images/widget_gauge_demo.png +0 -0
  38. data/doc/images/widget_layout_split.png +0 -0
  39. data/doc/images/widget_line_gauge_demo.png +0 -0
  40. data/doc/images/widget_list_demo.png +0 -0
  41. data/doc/images/widget_overlay_demo.png +0 -0
  42. data/doc/images/widget_ratatui_logo_demo.png +0 -0
  43. data/doc/images/widget_ratatui_mascot_demo.png +0 -0
  44. data/doc/images/widget_render.png +0 -0
  45. data/doc/images/widget_rich_text.png +0 -0
  46. data/doc/images/widget_scroll_text.png +0 -0
  47. data/doc/images/widget_scrollbar_demo.png +0 -0
  48. data/doc/images/widget_sparkline_demo.png +0 -0
  49. data/doc/images/widget_style_colors.png +0 -0
  50. data/doc/images/widget_table_demo.png +0 -0
  51. data/doc/images/widget_table_flex.png +0 -0
  52. data/doc/images/widget_tabs_demo.png +0 -0
  53. data/doc/images/widget_text_width.png +0 -0
  54. data/doc/interactive_design.md +25 -30
  55. data/doc/quickstart.md +150 -130
  56. data/doc/terminal_limitations.md +92 -0
  57. data/examples/app_all_events/README.md +99 -0
  58. data/examples/app_all_events/app.rb +96 -0
  59. data/examples/app_all_events/model/app_model.rb +157 -0
  60. data/examples/app_all_events/model/event_color_cycle.rb +41 -0
  61. data/examples/app_all_events/model/event_entry.rb +92 -0
  62. data/examples/app_all_events/model/msg.rb +37 -0
  63. data/examples/app_all_events/model/timestamp.rb +54 -0
  64. data/examples/app_all_events/update.rb +73 -0
  65. data/examples/app_all_events/view/app_view.rb +78 -0
  66. data/examples/app_all_events/view/controls_view.rb +52 -0
  67. data/examples/app_all_events/view/counts_view.rb +59 -0
  68. data/examples/app_all_events/view/live_view.rb +70 -0
  69. data/examples/app_all_events/view/log_view.rb +55 -0
  70. data/examples/app_all_events/view.rb +7 -0
  71. data/examples/app_color_picker/README.md +134 -0
  72. data/examples/app_color_picker/app.rb +74 -0
  73. data/examples/app_color_picker/clipboard.rb +84 -0
  74. data/examples/app_color_picker/color.rb +191 -0
  75. data/examples/app_color_picker/controls.rb +90 -0
  76. data/examples/app_color_picker/copy_dialog.rb +166 -0
  77. data/examples/app_color_picker/export_pane.rb +126 -0
  78. data/examples/app_color_picker/harmony.rb +56 -0
  79. data/examples/app_color_picker/input.rb +174 -0
  80. data/examples/app_color_picker/main_container.rb +178 -0
  81. data/examples/app_color_picker/palette.rb +109 -0
  82. data/examples/app_login_form/README.md +47 -0
  83. data/examples/{login_form → app_login_form}/app.rb +38 -42
  84. data/examples/app_stateful_interaction/README.md +31 -0
  85. data/examples/app_stateful_interaction/app.rb +272 -0
  86. data/examples/timeout_demo.rb +43 -0
  87. data/examples/verify_quickstart_dsl/README.md +48 -0
  88. data/examples/{quickstart_dsl → verify_quickstart_dsl}/app.rb +17 -6
  89. data/examples/verify_quickstart_layout/README.md +71 -0
  90. data/examples/verify_quickstart_layout/app.rb +71 -0
  91. data/examples/verify_quickstart_lifecycle/README.md +56 -0
  92. data/examples/verify_quickstart_lifecycle/app.rb +54 -0
  93. data/examples/verify_readme_usage/README.md +43 -0
  94. data/examples/verify_readme_usage/app.rb +40 -0
  95. data/examples/widget_barchart_demo/README.md +49 -0
  96. data/examples/widget_barchart_demo/app.rb +238 -0
  97. data/examples/widget_block_demo/README.md +34 -0
  98. data/examples/widget_block_demo/app.rb +256 -0
  99. data/examples/widget_box_demo/README.md +45 -0
  100. data/examples/{box_demo → widget_box_demo}/app.rb +99 -65
  101. data/examples/widget_calendar_demo/README.md +39 -0
  102. data/examples/widget_calendar_demo/app.rb +109 -0
  103. data/examples/widget_canvas_demo/README.md +27 -0
  104. data/examples/widget_canvas_demo/app.rb +123 -0
  105. data/examples/widget_cell_demo/README.md +36 -0
  106. data/examples/widget_cell_demo/app.rb +111 -0
  107. data/examples/widget_center_demo/README.md +29 -0
  108. data/examples/widget_center_demo/app.rb +116 -0
  109. data/examples/widget_chart_demo/README.md +41 -0
  110. data/examples/widget_chart_demo/app.rb +218 -0
  111. data/examples/widget_gauge_demo/README.md +41 -0
  112. data/examples/widget_gauge_demo/app.rb +212 -0
  113. data/examples/widget_layout_split/README.md +44 -0
  114. data/examples/widget_layout_split/app.rb +246 -0
  115. data/examples/widget_line_gauge_demo/README.md +41 -0
  116. data/examples/widget_line_gauge_demo/app.rb +217 -0
  117. data/examples/widget_list_demo/README.md +49 -0
  118. data/examples/widget_list_demo/app.rb +366 -0
  119. data/examples/widget_map_demo/README.md +39 -0
  120. data/examples/{map_demo → widget_map_demo}/app.rb +24 -21
  121. data/examples/widget_overlay_demo/app.rb +248 -0
  122. data/examples/widget_popup_demo/README.md +36 -0
  123. data/examples/widget_popup_demo/app.rb +104 -0
  124. data/examples/widget_ratatui_logo_demo/README.md +34 -0
  125. data/examples/widget_ratatui_logo_demo/app.rb +103 -0
  126. data/examples/widget_ratatui_mascot_demo/README.md +34 -0
  127. data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
  128. data/examples/widget_rect/README.md +38 -0
  129. data/examples/widget_rect/app.rb +205 -0
  130. data/examples/widget_render/README.md +37 -0
  131. data/examples/widget_render/app.rb +184 -0
  132. data/examples/widget_rich_text/README.md +35 -0
  133. data/examples/widget_rich_text/app.rb +166 -0
  134. data/examples/widget_scroll_text/README.md +37 -0
  135. data/examples/widget_scroll_text/app.rb +107 -0
  136. data/examples/widget_scrollbar_demo/README.md +37 -0
  137. data/examples/widget_scrollbar_demo/app.rb +153 -0
  138. data/examples/widget_sparkline_demo/README.md +42 -0
  139. data/examples/widget_sparkline_demo/app.rb +275 -0
  140. data/examples/widget_style_colors/README.md +34 -0
  141. data/examples/widget_style_colors/app.rb +19 -21
  142. data/examples/widget_table_demo/README.md +48 -0
  143. data/examples/widget_table_demo/app.rb +239 -0
  144. data/examples/widget_tabs_demo/README.md +41 -0
  145. data/examples/widget_tabs_demo/app.rb +181 -0
  146. data/examples/widget_text_width/README.md +35 -0
  147. data/examples/widget_text_width/app.rb +106 -0
  148. data/ext/ratatui_ruby/Cargo.lock +11 -4
  149. data/ext/ratatui_ruby/Cargo.toml +2 -1
  150. data/ext/ratatui_ruby/src/events.rs +359 -62
  151. data/ext/ratatui_ruby/src/frame.rs +227 -0
  152. data/ext/ratatui_ruby/src/lib.rs +110 -27
  153. data/ext/ratatui_ruby/src/rendering.rs +8 -4
  154. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  155. data/ext/ratatui_ruby/src/style.rs +138 -57
  156. data/ext/ratatui_ruby/src/terminal.rs +42 -22
  157. data/ext/ratatui_ruby/src/text.rs +14 -7
  158. data/ext/ratatui_ruby/src/widgets/barchart.rs +74 -54
  159. data/ext/ratatui_ruby/src/widgets/block.rs +7 -6
  160. data/ext/ratatui_ruby/src/widgets/canvas.rs +21 -3
  161. data/ext/ratatui_ruby/src/widgets/chart.rs +20 -10
  162. data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
  163. data/ext/ratatui_ruby/src/widgets/layout.rs +9 -4
  164. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
  165. data/ext/ratatui_ruby/src/widgets/list.rs +211 -12
  166. data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
  167. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  168. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  169. data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
  170. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +19 -8
  171. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +17 -10
  172. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +97 -3
  173. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  174. data/ext/ratatui_ruby/src/widgets/sparkline.rs +14 -11
  175. data/ext/ratatui_ruby/src/widgets/table.rs +121 -5
  176. data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
  177. data/ext/ratatui_ruby/src/widgets/tabs.rs +11 -11
  178. data/lib/ratatui_ruby/cell.rb +7 -7
  179. data/lib/ratatui_ruby/event/key/character.rb +35 -0
  180. data/lib/ratatui_ruby/event/key/media.rb +44 -0
  181. data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
  182. data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
  183. data/lib/ratatui_ruby/event/key/system.rb +45 -0
  184. data/lib/ratatui_ruby/event/key.rb +112 -52
  185. data/lib/ratatui_ruby/event/mouse.rb +3 -3
  186. data/lib/ratatui_ruby/event/none.rb +43 -0
  187. data/lib/ratatui_ruby/event/paste.rb +1 -1
  188. data/lib/ratatui_ruby/event.rb +56 -4
  189. data/lib/ratatui_ruby/frame.rb +183 -0
  190. data/lib/ratatui_ruby/list_state.rb +88 -0
  191. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +13 -13
  192. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +1 -5
  193. data/lib/ratatui_ruby/schema/bar_chart.rb +217 -217
  194. data/lib/ratatui_ruby/schema/block.rb +163 -168
  195. data/lib/ratatui_ruby/schema/calendar.rb +66 -67
  196. data/lib/ratatui_ruby/schema/canvas.rb +63 -63
  197. data/lib/ratatui_ruby/schema/center.rb +46 -46
  198. data/lib/ratatui_ruby/schema/chart.rb +135 -143
  199. data/lib/ratatui_ruby/schema/clear.rb +42 -42
  200. data/lib/ratatui_ruby/schema/constraint.rb +76 -76
  201. data/lib/ratatui_ruby/schema/cursor.rb +30 -25
  202. data/lib/ratatui_ruby/schema/gauge.rb +54 -52
  203. data/lib/ratatui_ruby/schema/layout.rb +87 -87
  204. data/lib/ratatui_ruby/schema/line_gauge.rb +62 -62
  205. data/lib/ratatui_ruby/schema/list.rb +103 -80
  206. data/lib/ratatui_ruby/schema/list_item.rb +41 -0
  207. data/lib/ratatui_ruby/schema/overlay.rb +31 -31
  208. data/lib/ratatui_ruby/schema/paragraph.rb +80 -80
  209. data/lib/ratatui_ruby/schema/ratatui_logo.rb +10 -6
  210. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +10 -5
  211. data/lib/ratatui_ruby/schema/rect.rb +99 -56
  212. data/lib/ratatui_ruby/schema/scrollbar.rb +119 -119
  213. data/lib/ratatui_ruby/schema/shape/label.rb +1 -1
  214. data/lib/ratatui_ruby/schema/sparkline.rb +111 -110
  215. data/lib/ratatui_ruby/schema/style.rb +66 -46
  216. data/lib/ratatui_ruby/schema/table.rb +126 -115
  217. data/lib/ratatui_ruby/schema/tabs.rb +66 -67
  218. data/lib/ratatui_ruby/schema/text.rb +69 -1
  219. data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
  220. data/lib/ratatui_ruby/session/autodoc.rb +482 -0
  221. data/lib/ratatui_ruby/session.rb +55 -23
  222. data/lib/ratatui_ruby/table_state.rb +90 -0
  223. data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
  224. data/lib/ratatui_ruby/test_helper/snapshot.rb +390 -0
  225. data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
  226. data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
  227. data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
  228. data/lib/ratatui_ruby/test_helper.rb +66 -193
  229. data/lib/ratatui_ruby/version.rb +1 -1
  230. data/lib/ratatui_ruby.rb +100 -51
  231. data/{examples/sparkline_demo → sig/examples/app_all_events}/app.rbs +3 -2
  232. data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
  233. data/sig/examples/app_all_events/model/events.rbs +15 -0
  234. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  235. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  236. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  237. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  238. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  239. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  240. data/sig/examples/app_all_events/view.rbs +8 -0
  241. data/sig/examples/app_all_events/view_state.rbs +15 -0
  242. data/{examples/list_demo → sig/examples/app_color_picker}/app.rbs +2 -2
  243. data/sig/examples/app_login_form/app.rbs +11 -0
  244. data/sig/examples/app_stateful_interaction/app.rbs +33 -0
  245. data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
  246. data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
  247. data/sig/examples/verify_readme_usage/app.rbs +11 -0
  248. data/sig/examples/widget_block_demo/app.rbs +32 -0
  249. data/sig/examples/widget_box_demo/app.rbs +11 -0
  250. data/sig/examples/widget_calendar_demo/app.rbs +11 -0
  251. data/sig/examples/widget_cell_demo/app.rbs +11 -0
  252. data/sig/examples/widget_chart_demo/app.rbs +11 -0
  253. data/{examples/gauge_demo → sig/examples/widget_gauge_demo}/app.rbs +4 -0
  254. data/sig/examples/widget_layout_split/app.rbs +10 -0
  255. data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
  256. data/sig/examples/widget_list_demo/app.rbs +12 -0
  257. data/sig/examples/widget_map_demo/app.rbs +11 -0
  258. data/sig/examples/widget_popup_demo/app.rbs +11 -0
  259. data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
  260. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
  261. data/sig/examples/widget_rect/app.rbs +12 -0
  262. data/sig/examples/widget_render/app.rbs +10 -0
  263. data/sig/examples/widget_rich_text/app.rbs +11 -0
  264. data/sig/examples/widget_scroll_text/app.rbs +11 -0
  265. data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
  266. data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
  267. data/{examples → sig/examples}/widget_style_colors/app.rbs +1 -1
  268. data/sig/examples/widget_table_demo/app.rbs +11 -0
  269. data/sig/examples/widget_text_width/app.rbs +10 -0
  270. data/sig/ratatui_ruby/event.rbs +11 -1
  271. data/sig/ratatui_ruby/frame.rbs +11 -0
  272. data/sig/ratatui_ruby/list_state.rbs +13 -0
  273. data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -4
  274. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
  275. data/sig/ratatui_ruby/schema/draw.rbs +4 -0
  276. data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
  277. data/sig/ratatui_ruby/schema/layout.rbs +1 -1
  278. data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
  279. data/sig/ratatui_ruby/schema/list.rbs +4 -2
  280. data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
  281. data/sig/ratatui_ruby/schema/rect.rbs +3 -0
  282. data/sig/ratatui_ruby/schema/style.rbs +3 -3
  283. data/sig/ratatui_ruby/schema/table.rbs +3 -1
  284. data/sig/ratatui_ruby/schema/text.rbs +8 -6
  285. data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
  286. data/sig/ratatui_ruby/session.rbs +107 -0
  287. data/sig/ratatui_ruby/table_state.rbs +15 -0
  288. data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
  289. data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
  290. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
  291. data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
  292. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
  293. data/sig/ratatui_ruby/test_helper.rbs +5 -4
  294. data/tasks/autodoc/examples.rb +79 -0
  295. data/tasks/autodoc/inventory.rb +63 -0
  296. data/tasks/autodoc/member.rb +56 -0
  297. data/tasks/autodoc/name.rb +19 -0
  298. data/tasks/autodoc/notice.rb +26 -0
  299. data/tasks/autodoc/rbs.rb +38 -0
  300. data/tasks/autodoc/rdoc.rb +45 -0
  301. data/tasks/autodoc.rake +53 -0
  302. data/tasks/bump/changelog.rb +3 -3
  303. data/tasks/bump/history.rb +2 -2
  304. data/tasks/bump/links.rb +67 -0
  305. data/tasks/doc.rake +600 -6
  306. data/tasks/example_viewer.html.erb +172 -0
  307. data/tasks/lint.rake +8 -4
  308. data/tasks/resources/index.html.erb +6 -0
  309. data/tasks/sourcehut.rake +70 -30
  310. data/tasks/terminal_preview/app_screenshot.rb +14 -6
  311. data/tasks/terminal_preview/crash_report.rb +7 -9
  312. data/tasks/terminal_preview/launcher_script.rb +4 -6
  313. data/tasks/terminal_preview/preview_collection.rb +4 -6
  314. data/tasks/terminal_preview/safety_confirmation.rb +3 -5
  315. data/tasks/terminal_preview/saved_screenshot.rb +10 -11
  316. data/tasks/terminal_preview/terminal_window.rb +7 -9
  317. data/tasks/test.rake +1 -1
  318. data/tasks/website/index_page.rb +3 -3
  319. data/tasks/website/version.rb +10 -10
  320. data/tasks/website/version_menu.rb +10 -12
  321. data/tasks/website/versioned_documentation.rb +49 -17
  322. data/tasks/website/website.rb +6 -8
  323. data/tasks/website.rake +4 -4
  324. metadata +232 -127
  325. data/LICENSES/BSD-2-Clause.txt +0 -9
  326. data/doc/contributors/better_dx.md +0 -543
  327. data/doc/contributors/example_analysis.md +0 -82
  328. data/doc/images/all_events.png +0 -0
  329. data/doc/images/block_padding.png +0 -0
  330. data/doc/images/block_titles.png +0 -0
  331. data/doc/images/box_demo.png +0 -0
  332. data/doc/images/calendar_demo.png +0 -0
  333. data/doc/images/cell_demo.png +0 -0
  334. data/doc/images/chart_demo.png +0 -0
  335. data/doc/images/flex_layout.png +0 -0
  336. data/doc/images/gauge_demo.png +0 -0
  337. data/doc/images/line_gauge_demo.png +0 -0
  338. data/doc/images/list_demo.png +0 -0
  339. data/doc/images/list_styles.png +0 -0
  340. data/doc/images/login_form.png +0 -0
  341. data/doc/images/quickstart_dsl.png +0 -0
  342. data/doc/images/quickstart_lifecycle.png +0 -0
  343. data/doc/images/readme_usage.png +0 -0
  344. data/doc/images/rich_text.png +0 -0
  345. data/doc/images/scroll_text.png +0 -0
  346. data/doc/images/scrollbar_demo.png +0 -0
  347. data/doc/images/sparkline_demo.png +0 -0
  348. data/doc/images/table_flex.png +0 -0
  349. data/doc/images/table_select.png +0 -0
  350. data/examples/all_events/app.rb +0 -169
  351. data/examples/all_events/app.rbs +0 -7
  352. data/examples/all_events/test_app.rb +0 -139
  353. data/examples/analytics/app.rb +0 -258
  354. data/examples/analytics/app.rbs +0 -7
  355. data/examples/analytics/test_app.rb +0 -132
  356. data/examples/block_padding/app.rb +0 -63
  357. data/examples/block_padding/app.rbs +0 -7
  358. data/examples/block_padding/test_app.rb +0 -31
  359. data/examples/block_titles/app.rb +0 -61
  360. data/examples/block_titles/app.rbs +0 -7
  361. data/examples/block_titles/test_app.rb +0 -34
  362. data/examples/box_demo/app.rbs +0 -7
  363. data/examples/box_demo/test_app.rb +0 -88
  364. data/examples/calendar_demo/app.rb +0 -101
  365. data/examples/calendar_demo/app.rbs +0 -7
  366. data/examples/calendar_demo/test_app.rb +0 -108
  367. data/examples/cell_demo/app.rb +0 -108
  368. data/examples/cell_demo/app.rbs +0 -7
  369. data/examples/cell_demo/test_app.rb +0 -36
  370. data/examples/chart_demo/app.rb +0 -203
  371. data/examples/chart_demo/app.rbs +0 -7
  372. data/examples/chart_demo/test_app.rb +0 -102
  373. data/examples/custom_widget/app.rb +0 -51
  374. data/examples/custom_widget/app.rbs +0 -7
  375. data/examples/custom_widget/test_app.rb +0 -30
  376. data/examples/flex_layout/app.rb +0 -156
  377. data/examples/flex_layout/app.rbs +0 -7
  378. data/examples/flex_layout/test_app.rb +0 -65
  379. data/examples/gauge_demo/app.rb +0 -182
  380. data/examples/gauge_demo/test_app.rb +0 -120
  381. data/examples/hit_test/app.rb +0 -175
  382. data/examples/hit_test/app.rbs +0 -7
  383. data/examples/hit_test/test_app.rb +0 -102
  384. data/examples/line_gauge_demo/app.rb +0 -190
  385. data/examples/line_gauge_demo/app.rbs +0 -7
  386. data/examples/line_gauge_demo/test_app.rb +0 -129
  387. data/examples/list_demo/app.rb +0 -253
  388. data/examples/list_demo/test_app.rb +0 -237
  389. data/examples/list_styles/app.rb +0 -140
  390. data/examples/list_styles/app.rbs +0 -7
  391. data/examples/list_styles/test_app.rb +0 -157
  392. data/examples/login_form/app.rbs +0 -7
  393. data/examples/login_form/test_app.rb +0 -51
  394. data/examples/map_demo/app.rbs +0 -7
  395. data/examples/map_demo/test_app.rb +0 -149
  396. data/examples/mouse_events/app.rb +0 -97
  397. data/examples/mouse_events/app.rbs +0 -7
  398. data/examples/mouse_events/test_app.rb +0 -53
  399. data/examples/popup_demo/app.rb +0 -103
  400. data/examples/popup_demo/app.rbs +0 -7
  401. data/examples/popup_demo/test_app.rb +0 -54
  402. data/examples/quickstart_dsl/app.rbs +0 -7
  403. data/examples/quickstart_dsl/test_app.rb +0 -29
  404. data/examples/quickstart_lifecycle/app.rb +0 -39
  405. data/examples/quickstart_lifecycle/app.rbs +0 -7
  406. data/examples/quickstart_lifecycle/test_app.rb +0 -29
  407. data/examples/ratatui_logo_demo/app.rb +0 -79
  408. data/examples/ratatui_logo_demo/app.rbs +0 -7
  409. data/examples/ratatui_logo_demo/test_app.rb +0 -51
  410. data/examples/ratatui_mascot_demo/app.rb +0 -84
  411. data/examples/ratatui_mascot_demo/app.rbs +0 -7
  412. data/examples/ratatui_mascot_demo/test_app.rb +0 -47
  413. data/examples/readme_usage/app.rb +0 -29
  414. data/examples/readme_usage/app.rbs +0 -7
  415. data/examples/readme_usage/test_app.rb +0 -29
  416. data/examples/rich_text/app.rb +0 -141
  417. data/examples/rich_text/app.rbs +0 -7
  418. data/examples/rich_text/test_app.rb +0 -166
  419. data/examples/scroll_text/app.rb +0 -103
  420. data/examples/scroll_text/app.rbs +0 -7
  421. data/examples/scroll_text/test_app.rb +0 -110
  422. data/examples/scrollbar_demo/app.rb +0 -143
  423. data/examples/scrollbar_demo/app.rbs +0 -7
  424. data/examples/scrollbar_demo/test_app.rb +0 -77
  425. data/examples/sparkline_demo/app.rb +0 -240
  426. data/examples/sparkline_demo/test_app.rb +0 -107
  427. data/examples/table_flex/app.rb +0 -65
  428. data/examples/table_flex/app.rbs +0 -7
  429. data/examples/table_flex/test_app.rb +0 -36
  430. data/examples/table_select/app.rb +0 -198
  431. data/examples/table_select/app.rbs +0 -7
  432. data/examples/table_select/test_app.rb +0 -180
  433. data/examples/widget_style_colors/test_app.rb +0 -48
  434. data/tasks/bump/comparison_links.rb +0 -41
  435. /data/doc/images/{analytics.png → app_analytics.png} +0 -0
  436. /data/doc/images/{custom_widget.png → app_custom_widget.png} +0 -0
  437. /data/doc/images/{mouse_events.png → app_mouse_events.png} +0 -0
  438. /data/doc/images/{map_demo.png → widget_map_demo.png} +0 -0
  439. /data/doc/images/{popup_demo.png → widget_popup_demo.png} +0 -0
  440. /data/doc/images/{hit_test.png → widget_rect.png} +0 -0
  441. /data/{doc/images/ratatui_logo_demo.png → exe/.gitkeep} +0 -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,38 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Rect (Geometry) Widget Example
7
+
8
+ Demonstrates the Rect geometry primitive and hit-testing patterns.
9
+
10
+ TUI layouts are composed of rectangles. Understanding how to manipulate `Rect` objects, reuse them from the layout phase, and use them for mouse interaction is critical for building interactive apps.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Rect Attributes**: Investigating x, y, width, and height.
15
+ - **Cached Layout Pattern**: Computing constraints in the render loop and reusing the resulting `Rect`s in the event loop for logic.
16
+ - **Hit Testing**: Using `Rect#contains?(x, y)` to determine if a mouse click happened inside a specific panel.
17
+
18
+ ## Hotkeys
19
+
20
+ - **Arrows (←/→)**: Expand/Shrink Sidebar Width (Layout Constraint)
21
+ - **Arrows (↑/↓)**: Navigate Menu Selection (`selected_index`)
22
+ - **Mouse Click**: Click anywhere to see which Rect detects the hit (`contains?`)
23
+ - **q**: Quit
24
+
25
+ ## Usage
26
+
27
+ ```bash
28
+ ruby examples/widget_rect/app.rb
29
+ ```
30
+
31
+ ## Learning Outcomes
32
+
33
+ Use this example if you need to...
34
+ - Handle mouse clicks on specific buttons or areas.
35
+ - Create resizable panes (like a split pane in an IDE).
36
+ - Debug layout issues by inspecting Rect coordinates.
37
+
38
+ ![Demo](/doc/images/widget_rect.png)
@@ -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
@@ -0,0 +1,37 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Render (Custom Widget) Example
7
+
8
+ Demonstrates how to build Custom Widgets using absolute coordinates.
9
+
10
+ Sometimes standard widgets aren't enough. You need to draw custom shapes, games, or graphs. This example shows how to implement the `render(area)` contract to draw anything you want while respecting layout boundaries.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Custom Widget Contract**: Implementing a class with `render(area)`.
15
+ - **Coordinate Offsets**: Creating drawing logic that works regardless of where the widget is placed on screen (using `area.x + offset`).
16
+ - **Composability**: Wrapping custom widgets in standard `Block`s with borders.
17
+
18
+ ## Hotkeys
19
+
20
+ - **n**: Next Widget (Diagonal -> Checkerboard -> Border)
21
+ - **p**: Previous Widget
22
+ - **q**: Quit
23
+
24
+ ## Usage
25
+
26
+ ```bash
27
+ ruby examples/widget_render/app.rb
28
+ ```
29
+
30
+ ## Learning Outcomes
31
+
32
+ Use this example if you need to...
33
+ - Build a game (Snake, Tetris) inside the terminal.
34
+ - Create a specialized visualization (Network topology graph).
35
+ - Draw custom UI elements not provided by the library.
36
+
37
+ ![Demo](/doc/images/widget_render.png)
@@ -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,35 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Rich Text Example
7
+
8
+ Demonstrates styling individual words and characters.
9
+
10
+ Standard strings are monochromatic. "Rich Text" is composed of `Lines` containing multiple `Spans`, where each Span has its own style. This allows for multi-colored, multi-styled text blocks.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Spans**: Chunks of text with a specific style (e.g., "Bold Red Word").
15
+ - **Lines**: ordered collections of Spans that form a single row of text.
16
+ - **Paragraphs**: Rendering lines of rich text.
17
+
18
+ ## Hotkeys
19
+
20
+ - **q**: Quit
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ ruby examples/widget_rich_text/app.rb
26
+ ```
27
+
28
+ ## Learning Outcomes
29
+
30
+ Use this example if you need to...
31
+ - Highlight keywords in code (Syntax highlighting).
32
+ - Create status lines with icons (e.g., "✔ Success" where the checkmark is green).
33
+ - Emphasize specific data points in a paragraph.
34
+
35
+ ![Demo](/doc/images/widget_rich_text.png)