ratatui_ruby 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +14 -12
  3. data/.builds/ruby-3.3.yml +14 -12
  4. data/.builds/ruby-3.4.yml +14 -12
  5. data/.builds/ruby-4.0.0.yml +14 -12
  6. data/AGENTS.md +89 -132
  7. data/CHANGELOG.md +223 -1
  8. data/README.md +23 -16
  9. data/REUSE.toml +20 -0
  10. data/doc/application_architecture.md +176 -0
  11. data/doc/application_testing.md +17 -10
  12. data/doc/contributors/design/ruby_frontend.md +11 -7
  13. data/doc/contributors/developing_examples.md +261 -0
  14. data/doc/contributors/documentation_style.md +104 -0
  15. data/doc/contributors/dwim_dx.md +366 -0
  16. data/doc/contributors/index.md +2 -0
  17. data/doc/custom.css +14 -0
  18. data/doc/event_handling.md +125 -0
  19. data/doc/images/app_all_events.png +0 -0
  20. data/doc/images/app_analytics.png +0 -0
  21. data/doc/images/app_color_picker.png +0 -0
  22. data/doc/images/app_custom_widget.png +0 -0
  23. data/doc/images/app_login_form.png +0 -0
  24. data/doc/images/app_map_demo.png +0 -0
  25. data/doc/images/app_mouse_events.png +0 -0
  26. data/doc/images/app_table_select.png +0 -0
  27. data/doc/images/verify_quickstart_dsl.png +0 -0
  28. data/doc/images/verify_quickstart_layout.png +0 -0
  29. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  30. data/doc/images/verify_readme_usage.png +0 -0
  31. data/doc/images/widget_barchart_demo.png +0 -0
  32. data/doc/images/widget_block_padding.png +0 -0
  33. data/doc/images/widget_block_titles.png +0 -0
  34. data/doc/images/widget_box_demo.png +0 -0
  35. data/doc/images/widget_calendar_demo.png +0 -0
  36. data/doc/images/widget_cell_demo.png +0 -0
  37. data/doc/images/widget_chart_demo.png +0 -0
  38. data/doc/images/widget_gauge_demo.png +0 -0
  39. data/doc/images/widget_layout_split.png +0 -0
  40. data/doc/images/widget_line_gauge_demo.png +0 -0
  41. data/doc/images/widget_list_demo.png +0 -0
  42. data/doc/images/widget_list_styles.png +0 -0
  43. data/doc/images/widget_popup_demo.png +0 -0
  44. data/doc/images/widget_ratatui_logo_demo.png +0 -0
  45. data/doc/images/widget_ratatui_mascot_demo.png +0 -0
  46. data/doc/images/widget_rect.png +0 -0
  47. data/doc/images/widget_render.png +0 -0
  48. data/doc/images/widget_rich_text.png +0 -0
  49. data/doc/images/widget_scroll_text.png +0 -0
  50. data/doc/images/widget_scrollbar_demo.png +0 -0
  51. data/doc/images/widget_sparkline_demo.png +0 -0
  52. data/doc/images/widget_style_colors.png +0 -0
  53. data/doc/images/widget_table_flex.png +0 -0
  54. data/doc/images/widget_tabs_demo.png +0 -0
  55. data/doc/index.md +1 -0
  56. data/doc/interactive_design.md +116 -0
  57. data/doc/quickstart.md +186 -84
  58. data/examples/app_all_events/README.md +81 -0
  59. data/examples/app_all_events/app.rb +93 -0
  60. data/examples/app_all_events/model/event_color_cycle.rb +41 -0
  61. data/examples/app_all_events/model/event_entry.rb +75 -0
  62. data/examples/app_all_events/model/events.rb +180 -0
  63. data/examples/app_all_events/model/highlight.rb +57 -0
  64. data/examples/app_all_events/model/timestamp.rb +54 -0
  65. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +24 -0
  66. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +24 -0
  67. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +24 -0
  68. data/examples/app_all_events/test/snapshots/after_key_a.txt +24 -0
  69. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +24 -0
  70. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +24 -0
  71. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +24 -0
  72. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +24 -0
  73. data/examples/app_all_events/test/snapshots/after_paste.txt +24 -0
  74. data/examples/app_all_events/test/snapshots/after_resize.txt +24 -0
  75. data/examples/app_all_events/test/snapshots/after_right_click.txt +24 -0
  76. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +24 -0
  77. data/examples/app_all_events/test/snapshots/initial_state.txt +24 -0
  78. data/examples/app_all_events/view/app_view.rb +78 -0
  79. data/examples/app_all_events/view/controls_view.rb +50 -0
  80. data/examples/app_all_events/view/counts_view.rb +55 -0
  81. data/examples/app_all_events/view/live_view.rb +69 -0
  82. data/examples/app_all_events/view/log_view.rb +60 -0
  83. data/{lib/ratatui_ruby/output.rb → examples/app_all_events/view.rb} +1 -1
  84. data/examples/app_all_events/view_state.rb +42 -0
  85. data/examples/app_color_picker/README.md +94 -0
  86. data/examples/app_color_picker/app.rb +112 -0
  87. data/examples/app_color_picker/clipboard.rb +84 -0
  88. data/examples/app_color_picker/color.rb +191 -0
  89. data/examples/app_color_picker/copy_dialog.rb +170 -0
  90. data/examples/app_color_picker/harmony.rb +56 -0
  91. data/examples/app_color_picker/input.rb +142 -0
  92. data/examples/app_color_picker/palette.rb +80 -0
  93. data/examples/app_color_picker/scene.rb +201 -0
  94. data/examples/app_login_form/app.rb +108 -0
  95. data/examples/app_map_demo/app.rb +93 -0
  96. data/examples/app_table_select/app.rb +201 -0
  97. data/examples/verify_quickstart_dsl/app.rb +45 -0
  98. data/examples/verify_quickstart_layout/app.rb +69 -0
  99. data/examples/verify_quickstart_lifecycle/app.rb +48 -0
  100. data/examples/verify_readme_usage/app.rb +34 -0
  101. data/examples/widget_barchart_demo/app.rb +238 -0
  102. data/examples/widget_block_padding/app.rb +67 -0
  103. data/examples/widget_block_titles/app.rb +69 -0
  104. data/examples/widget_box_demo/app.rb +250 -0
  105. data/examples/widget_calendar_demo/app.rb +109 -0
  106. data/examples/widget_cell_demo/app.rb +104 -0
  107. data/examples/widget_chart_demo/app.rb +213 -0
  108. data/examples/widget_gauge_demo/app.rb +212 -0
  109. data/examples/widget_layout_split/app.rb +246 -0
  110. data/examples/widget_line_gauge_demo/app.rb +217 -0
  111. data/examples/widget_list_demo/app.rb +382 -0
  112. data/examples/widget_list_styles/app.rb +141 -0
  113. data/examples/widget_popup_demo/app.rb +104 -0
  114. data/examples/widget_ratatui_logo_demo/app.rb +103 -0
  115. data/examples/widget_ratatui_mascot_demo/app.rb +93 -0
  116. data/examples/widget_rect/app.rb +205 -0
  117. data/examples/widget_render/app.rb +184 -0
  118. data/examples/widget_rich_text/app.rb +137 -0
  119. data/examples/widget_scroll_text/app.rb +108 -0
  120. data/examples/widget_scrollbar_demo/app.rb +153 -0
  121. data/examples/widget_sparkline_demo/app.rb +274 -0
  122. data/examples/widget_style_colors/app.rb +102 -0
  123. data/examples/widget_table_flex/app.rb +95 -0
  124. data/examples/widget_tabs_demo/app.rb +167 -0
  125. data/ext/ratatui_ruby/Cargo.lock +889 -115
  126. data/ext/ratatui_ruby/Cargo.toml +4 -3
  127. data/ext/ratatui_ruby/clippy.toml +7 -0
  128. data/ext/ratatui_ruby/extconf.rb +7 -0
  129. data/ext/ratatui_ruby/src/events.rs +293 -219
  130. data/ext/ratatui_ruby/src/frame.rs +115 -0
  131. data/ext/ratatui_ruby/src/lib.rs +105 -24
  132. data/ext/ratatui_ruby/src/rendering.rs +94 -10
  133. data/ext/ratatui_ruby/src/style.rs +357 -93
  134. data/ext/ratatui_ruby/src/terminal.rs +121 -31
  135. data/ext/ratatui_ruby/src/text.rs +178 -0
  136. data/ext/ratatui_ruby/src/widgets/barchart.rs +99 -24
  137. data/ext/ratatui_ruby/src/widgets/block.rs +32 -3
  138. data/ext/ratatui_ruby/src/widgets/calendar.rs +45 -44
  139. data/ext/ratatui_ruby/src/widgets/canvas.rs +44 -9
  140. data/ext/ratatui_ruby/src/widgets/chart.rs +79 -27
  141. data/ext/ratatui_ruby/src/widgets/clear.rs +3 -1
  142. data/ext/ratatui_ruby/src/widgets/gauge.rs +11 -4
  143. data/ext/ratatui_ruby/src/widgets/layout.rs +223 -15
  144. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +92 -0
  145. data/ext/ratatui_ruby/src/widgets/list.rs +114 -11
  146. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  147. data/ext/ratatui_ruby/src/widgets/overlay.rs +4 -2
  148. data/ext/ratatui_ruby/src/widgets/paragraph.rs +35 -13
  149. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
  150. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +51 -0
  151. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +61 -7
  152. data/ext/ratatui_ruby/src/widgets/sparkline.rs +73 -6
  153. data/ext/ratatui_ruby/src/widgets/table.rs +177 -64
  154. data/ext/ratatui_ruby/src/widgets/tabs.rs +105 -5
  155. data/lib/ratatui_ruby/cell.rb +166 -0
  156. data/lib/ratatui_ruby/event/focus_gained.rb +49 -0
  157. data/lib/ratatui_ruby/event/focus_lost.rb +50 -0
  158. data/lib/ratatui_ruby/event/key.rb +211 -0
  159. data/lib/ratatui_ruby/event/mouse.rb +124 -0
  160. data/lib/ratatui_ruby/event/none.rb +43 -0
  161. data/lib/ratatui_ruby/event/paste.rb +71 -0
  162. data/lib/ratatui_ruby/event/resize.rb +80 -0
  163. data/lib/ratatui_ruby/event.rb +131 -0
  164. data/lib/ratatui_ruby/frame.rb +87 -0
  165. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +45 -0
  166. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +23 -0
  167. data/lib/ratatui_ruby/schema/bar_chart.rb +226 -17
  168. data/lib/ratatui_ruby/schema/block.rb +178 -11
  169. data/lib/ratatui_ruby/schema/calendar.rb +70 -14
  170. data/lib/ratatui_ruby/schema/canvas.rb +213 -46
  171. data/lib/ratatui_ruby/schema/center.rb +46 -8
  172. data/lib/ratatui_ruby/schema/chart.rb +134 -32
  173. data/lib/ratatui_ruby/schema/clear.rb +22 -53
  174. data/lib/ratatui_ruby/schema/constraint.rb +72 -12
  175. data/lib/ratatui_ruby/schema/cursor.rb +23 -5
  176. data/lib/ratatui_ruby/schema/draw.rb +53 -0
  177. data/lib/ratatui_ruby/schema/gauge.rb +56 -12
  178. data/lib/ratatui_ruby/schema/layout.rb +91 -9
  179. data/lib/ratatui_ruby/schema/line_gauge.rb +78 -0
  180. data/lib/ratatui_ruby/schema/list.rb +92 -16
  181. data/lib/ratatui_ruby/schema/overlay.rb +29 -3
  182. data/lib/ratatui_ruby/schema/paragraph.rb +82 -25
  183. data/lib/ratatui_ruby/schema/ratatui_logo.rb +29 -0
  184. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +34 -0
  185. data/lib/ratatui_ruby/schema/rect.rb +59 -10
  186. data/lib/ratatui_ruby/schema/scrollbar.rb +127 -19
  187. data/lib/ratatui_ruby/schema/shape/label.rb +66 -0
  188. data/lib/ratatui_ruby/schema/sparkline.rb +120 -12
  189. data/lib/ratatui_ruby/schema/style.rb +39 -11
  190. data/lib/ratatui_ruby/schema/table.rb +109 -18
  191. data/lib/ratatui_ruby/schema/tabs.rb +71 -10
  192. data/lib/ratatui_ruby/schema/text.rb +90 -0
  193. data/lib/ratatui_ruby/session/autodoc.rb +417 -0
  194. data/lib/ratatui_ruby/session.rb +163 -0
  195. data/lib/ratatui_ruby/test_helper.rb +322 -13
  196. data/lib/ratatui_ruby/version.rb +1 -1
  197. data/lib/ratatui_ruby.rb +184 -38
  198. data/sig/examples/app_all_events/app.rbs +11 -0
  199. data/sig/examples/app_all_events/model/event_entry.rbs +16 -0
  200. data/sig/examples/app_all_events/model/events.rbs +15 -0
  201. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  202. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  203. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  204. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  205. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  206. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  207. data/sig/examples/app_all_events/view.rbs +8 -0
  208. data/sig/examples/app_all_events/view_state.rbs +15 -0
  209. data/sig/examples/app_color_picker/app.rbs +12 -0
  210. data/sig/examples/app_login_form/app.rbs +11 -0
  211. data/sig/examples/app_map_demo/app.rbs +11 -0
  212. data/sig/examples/app_table_select/app.rbs +11 -0
  213. data/sig/examples/verify_quickstart_dsl/app.rbs +11 -0
  214. data/sig/examples/verify_quickstart_lifecycle/app.rbs +11 -0
  215. data/sig/examples/verify_readme_usage/app.rbs +11 -0
  216. data/sig/examples/widget_block_padding/app.rbs +11 -0
  217. data/sig/examples/widget_block_titles/app.rbs +11 -0
  218. data/sig/examples/widget_box_demo/app.rbs +11 -0
  219. data/sig/examples/widget_calendar_demo/app.rbs +11 -0
  220. data/sig/examples/widget_cell_demo/app.rbs +11 -0
  221. data/sig/examples/widget_chart_demo/app.rbs +11 -0
  222. data/sig/examples/widget_gauge_demo/app.rbs +11 -0
  223. data/sig/examples/widget_layout_split/app.rbs +10 -0
  224. data/sig/examples/widget_line_gauge_demo/app.rbs +11 -0
  225. data/sig/examples/widget_list_demo/app.rbs +12 -0
  226. data/sig/examples/widget_list_styles/app.rbs +11 -0
  227. data/sig/examples/widget_popup_demo/app.rbs +11 -0
  228. data/sig/examples/widget_ratatui_logo_demo/app.rbs +11 -0
  229. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +11 -0
  230. data/sig/examples/widget_rect/app.rbs +12 -0
  231. data/sig/examples/widget_render/app.rbs +10 -0
  232. data/sig/examples/widget_rich_text/app.rbs +11 -0
  233. data/sig/examples/widget_scroll_text/app.rbs +11 -0
  234. data/sig/examples/widget_scrollbar_demo/app.rbs +11 -0
  235. data/sig/examples/widget_sparkline_demo/app.rbs +10 -0
  236. data/sig/examples/widget_style_colors/app.rbs +14 -0
  237. data/sig/examples/widget_table_flex/app.rbs +11 -0
  238. data/sig/ratatui_ruby/event.rbs +69 -0
  239. data/sig/ratatui_ruby/frame.rbs +9 -0
  240. data/sig/ratatui_ruby/ratatui_ruby.rbs +5 -3
  241. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +16 -0
  242. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +13 -0
  243. data/sig/ratatui_ruby/schema/bar_chart.rbs +20 -2
  244. data/sig/ratatui_ruby/schema/block.rbs +5 -4
  245. data/sig/ratatui_ruby/schema/calendar.rbs +6 -2
  246. data/sig/ratatui_ruby/schema/canvas.rbs +52 -39
  247. data/sig/ratatui_ruby/schema/center.rbs +3 -3
  248. data/sig/ratatui_ruby/schema/chart.rbs +8 -5
  249. data/sig/ratatui_ruby/schema/constraint.rbs +8 -5
  250. data/sig/ratatui_ruby/schema/cursor.rbs +1 -1
  251. data/sig/ratatui_ruby/schema/draw.rbs +27 -0
  252. data/sig/ratatui_ruby/schema/gauge.rbs +4 -2
  253. data/sig/ratatui_ruby/schema/layout.rbs +11 -1
  254. data/sig/ratatui_ruby/schema/line_gauge.rbs +16 -0
  255. data/sig/ratatui_ruby/schema/list.rbs +5 -1
  256. data/sig/ratatui_ruby/schema/paragraph.rbs +4 -1
  257. data/sig/ratatui_ruby/schema/ratatui_logo.rbs +8 -0
  258. data/sig/ratatui_ruby/{buffer.rbs → schema/ratatui_mascot.rbs} +4 -3
  259. data/sig/ratatui_ruby/schema/rect.rbs +2 -1
  260. data/sig/ratatui_ruby/schema/scrollbar.rbs +18 -2
  261. data/sig/ratatui_ruby/schema/sparkline.rbs +6 -2
  262. data/sig/ratatui_ruby/schema/table.rbs +8 -1
  263. data/sig/ratatui_ruby/schema/tabs.rbs +5 -1
  264. data/sig/ratatui_ruby/schema/text.rbs +22 -0
  265. data/sig/ratatui_ruby/session.rbs +94 -0
  266. data/tasks/autodoc/inventory.rb +61 -0
  267. data/tasks/autodoc/member.rb +56 -0
  268. data/tasks/autodoc/name.rb +19 -0
  269. data/tasks/autodoc/notice.rb +26 -0
  270. data/tasks/autodoc/rbs.rb +38 -0
  271. data/tasks/autodoc/rdoc.rb +45 -0
  272. data/tasks/autodoc.rake +47 -0
  273. data/tasks/bump/history.rb +2 -2
  274. data/tasks/doc.rake +600 -6
  275. data/tasks/example_viewer.html.erb +172 -0
  276. data/tasks/lint.rake +8 -4
  277. data/tasks/resources/build.yml.erb +13 -11
  278. data/tasks/resources/index.html.erb +6 -0
  279. data/tasks/sourcehut.rake +4 -4
  280. data/tasks/terminal_preview/app_screenshot.rb +33 -0
  281. data/tasks/terminal_preview/crash_report.rb +52 -0
  282. data/tasks/terminal_preview/example_app.rb +25 -0
  283. data/tasks/terminal_preview/launcher_script.rb +46 -0
  284. data/tasks/terminal_preview/preview_collection.rb +58 -0
  285. data/tasks/terminal_preview/preview_timing.rb +22 -0
  286. data/tasks/terminal_preview/safety_confirmation.rb +56 -0
  287. data/tasks/terminal_preview/saved_screenshot.rb +53 -0
  288. data/tasks/terminal_preview/system_appearance.rb +11 -0
  289. data/tasks/terminal_preview/terminal_window.rb +136 -0
  290. data/tasks/terminal_preview/window_id.rb +14 -0
  291. data/tasks/terminal_preview.rake +28 -0
  292. data/tasks/test.rake +2 -2
  293. data/tasks/website/index_page.rb +3 -3
  294. data/tasks/website/version.rb +10 -10
  295. data/tasks/website/version_menu.rb +10 -12
  296. data/tasks/website/versioned_documentation.rb +49 -17
  297. data/tasks/website/website.rb +6 -8
  298. data/tasks/website.rake +4 -4
  299. metadata +206 -54
  300. data/LICENSES/BSD-2-Clause.txt +0 -9
  301. data/doc/images/examples-analytics.rb.png +0 -0
  302. data/doc/images/examples-box_demo.rb.png +0 -0
  303. data/doc/images/examples-calendar_demo.rb.png +0 -0
  304. data/doc/images/examples-chart_demo.rb.png +0 -0
  305. data/doc/images/examples-custom_widget.rb.png +0 -0
  306. data/doc/images/examples-dashboard.rb.png +0 -0
  307. data/doc/images/examples-list_styles.rb.png +0 -0
  308. data/doc/images/examples-login_form.rb.png +0 -0
  309. data/doc/images/examples-map_demo.rb.png +0 -0
  310. data/doc/images/examples-mouse_events.rb.png +0 -0
  311. data/doc/images/examples-popup_demo.rb.gif +0 -0
  312. data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
  313. data/doc/images/examples-scroll_text.rb.png +0 -0
  314. data/doc/images/examples-scrollbar_demo.rb.png +0 -0
  315. data/doc/images/examples-stock_ticker.rb.png +0 -0
  316. data/doc/images/examples-system_monitor.rb.png +0 -0
  317. data/doc/images/examples-table_select.rb.png +0 -0
  318. data/examples/analytics.rb +0 -88
  319. data/examples/box_demo.rb +0 -71
  320. data/examples/calendar_demo.rb +0 -55
  321. data/examples/chart_demo.rb +0 -84
  322. data/examples/custom_widget.rb +0 -43
  323. data/examples/dashboard.rb +0 -72
  324. data/examples/list_styles.rb +0 -66
  325. data/examples/login_form.rb +0 -115
  326. data/examples/map_demo.rb +0 -58
  327. data/examples/mouse_events.rb +0 -95
  328. data/examples/popup_demo.rb +0 -105
  329. data/examples/quickstart_dsl.rb +0 -30
  330. data/examples/quickstart_lifecycle.rb +0 -40
  331. data/examples/readme_usage.rb +0 -21
  332. data/examples/scroll_text.rb +0 -74
  333. data/examples/scrollbar_demo.rb +0 -75
  334. data/examples/stock_ticker.rb +0 -93
  335. data/examples/system_monitor.rb +0 -94
  336. data/examples/table_select.rb +0 -70
  337. data/examples/test_analytics.rb +0 -65
  338. data/examples/test_box_demo.rb +0 -38
  339. data/examples/test_calendar_demo.rb +0 -66
  340. data/examples/test_dashboard.rb +0 -38
  341. data/examples/test_list_styles.rb +0 -61
  342. data/examples/test_login_form.rb +0 -63
  343. data/examples/test_map_demo.rb +0 -100
  344. data/examples/test_popup_demo.rb +0 -62
  345. data/examples/test_scroll_text.rb +0 -130
  346. data/examples/test_stock_ticker.rb +0 -39
  347. data/examples/test_system_monitor.rb +0 -40
  348. data/examples/test_table_select.rb +0 -37
  349. data/ext/ratatui_ruby/src/buffer.rs +0 -54
  350. data/lib/ratatui_ruby/dsl.rb +0 -64
@@ -0,0 +1,274 @@
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 high-density data visualization with interactive attribute cycling.
10
+ #
11
+ # Users need context. A single value ("90% CPU") tells you current status, but not the trend. Full charts take up too much room.
12
+ #
13
+ # This demo showcases the <tt>Sparkline</tt> widget. It provides an interactive playground where you can cycle through data sets, directions, colors, and custom bar sets.
14
+ #
15
+ # Use it to understand how to condense history into a single line for dashboards or headers.
16
+ #
17
+ # === Example
18
+ #
19
+ # Run the demo from the terminal:
20
+ #
21
+ # ruby examples/widget_sparkline_demo/app.rb
22
+ #
23
+ # rdoc-image:/doc/images/widget_sparkline_demo.png
24
+ class WidgetSparklineDemo
25
+ def run
26
+ RatatuiRuby.run do |tui|
27
+ @tui = tui
28
+ setup
29
+ loop do
30
+ render
31
+ break if handle_input == :quit
32
+ end
33
+ end
34
+ end
35
+
36
+ private def setup
37
+ # Data sets with different characteristics
38
+ @data_sets = [
39
+ {
40
+ name: "Steady Growth",
41
+ data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
42
+ },
43
+ {
44
+ name: "With Gaps",
45
+ data: [5, nil, 8, nil, 6, nil, 9, nil, 7, nil, 10, nil],
46
+ },
47
+ {
48
+ name: "Random",
49
+ data: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8],
50
+ },
51
+ {
52
+ name: "Sawtooth",
53
+ data: [1, 2, 3, 4, 5, 4, 3, 2, 1, 2, 3, 4],
54
+ },
55
+ {
56
+ name: "Peaks",
57
+ data: [1, 5, 1, 8, 1, 6, 1, 9, 1, 7, 1, 10],
58
+ },
59
+ ]
60
+ @data_index = 0
61
+
62
+ @directions = [
63
+ { name: "Left to Right", direction: :left_to_right },
64
+ { name: "Right to Left", direction: :right_to_left },
65
+ ]
66
+ @direction_index = 0
67
+
68
+ @styles = [
69
+ { name: "Green", style: @tui.style(fg: :green) },
70
+ { name: "Yellow", style: @tui.style(fg: :yellow) },
71
+ { name: "Red", style: @tui.style(fg: :red) },
72
+ { name: "Cyan", style: @tui.style(fg: :cyan) },
73
+ { name: "Magenta", style: @tui.style(fg: :magenta) },
74
+ ]
75
+ @style_index = 0
76
+
77
+ @absent_symbols = [
78
+ { name: "None", symbol: nil },
79
+ { name: "Dot (·)", symbol: "·" },
80
+ { name: "Square (▫)", symbol: "▫" },
81
+ { name: "Dash (-)", symbol: "-" },
82
+ { name: "Underscore (_)", symbol: "_" },
83
+ ]
84
+ @absent_symbol_index = 1
85
+
86
+ @absent_styles = [
87
+ { name: "Default", style: nil },
88
+ { name: "Dark Gray", style: @tui.style(fg: :dark_gray) },
89
+ { name: "Dim Red", style: @tui.style(fg: :red, modifiers: [:dim]) },
90
+ { name: "Dim Yellow", style: @tui.style(fg: :yellow, modifiers: [:dim]) },
91
+ ]
92
+ @absent_style_index = 0
93
+
94
+ @bar_sets = [
95
+ { name: "Default (Block)", set: nil },
96
+ {
97
+ name: "Numbers (0-8)",
98
+ set: {
99
+ 0 => "0", 1 => "1", 2 => "2", 3 => "3", 4 => "4", 5 => "5", 6 => "6", 7 => "7", 8 => "8"
100
+ },
101
+ },
102
+ { name: "ASCII (Heights)", set: [" ", "_", ".", "-", "=", "+", "*", "#", "@"] },
103
+ ]
104
+ @bar_set_index = 0
105
+
106
+ @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
107
+ end
108
+
109
+ private def render
110
+ @tui.draw do |frame|
111
+ data_set = @data_sets[@data_index]
112
+ direction = @directions[@direction_index][:direction]
113
+ style = @styles[@style_index][:style]
114
+ absent_symbol = @absent_symbols[@absent_symbol_index][:symbol]
115
+ absent_value_style = @absent_styles[@absent_style_index][:style]
116
+ bar_set = @bar_sets[@bar_set_index][:set]
117
+
118
+ # Use static data for clarity when cycling options
119
+ current_data = data_set[:data]
120
+
121
+ layout = @tui.layout_split(
122
+ frame.area,
123
+ direction: :vertical,
124
+ constraints: [
125
+ @tui.constraint_fill(1),
126
+ @tui.constraint_length(6),
127
+ ]
128
+ )
129
+
130
+ # Main content area with multiple sparkline examples
131
+ main_content_area = layout[0]
132
+ main_layout = @tui.layout_split(
133
+ main_content_area,
134
+ direction: :vertical,
135
+ constraints: [
136
+ @tui.constraint_length(1),
137
+ @tui.constraint_length(3),
138
+ @tui.constraint_length(3),
139
+ @tui.constraint_length(3),
140
+ @tui.constraint_length(3),
141
+ @tui.constraint_fill(1),
142
+ ]
143
+ )
144
+
145
+ frame.render_widget(
146
+ @tui.paragraph(text: "Sparkline Widget Demo - Cycle attributes with hotkeys"),
147
+ main_layout[0]
148
+ )
149
+
150
+ # Sparkline 1: Main interactive sparkline
151
+ frame.render_widget(
152
+ @tui.sparkline(
153
+ data: current_data,
154
+ direction:,
155
+ style:,
156
+ absent_value_symbol: absent_symbol,
157
+ absent_value_style:,
158
+ bar_set:,
159
+ block: @tui.block(title: "Interactive Sparkline")
160
+ ),
161
+ main_layout[1]
162
+ )
163
+
164
+ # Sparkline 2: Same data, opposite direction
165
+ frame.render_widget(
166
+ @tui.sparkline(
167
+ data: current_data.reverse,
168
+ direction:,
169
+ style:,
170
+ absent_value_symbol: absent_symbol,
171
+ absent_value_style:,
172
+ bar_set:,
173
+ block: @tui.block(title: "Reversed Data")
174
+ ),
175
+ main_layout[2]
176
+ )
177
+
178
+ # Sparkline 3: Without absent value symbol (for comparison)
179
+ frame.render_widget(
180
+ @tui.sparkline(
181
+ data: current_data,
182
+ direction:,
183
+ style:,
184
+ bar_set:,
185
+ block: @tui.block(title: "Without Absent Marker")
186
+ ),
187
+ main_layout[3]
188
+ )
189
+
190
+ # Sparkline 4: Gap pattern responsive to absent marker controls
191
+ frame.render_widget(
192
+ @tui.sparkline(
193
+ data: [5, nil, 8, nil, 6, nil, 9, nil, 7, nil, 10, nil],
194
+ direction:,
195
+ style: @tui.style(fg: :blue),
196
+ absent_value_symbol: absent_symbol,
197
+ absent_value_style:,
198
+ bar_set:,
199
+ block: @tui.block(title: "Gap Pattern (Responsive)")
200
+ ),
201
+ main_layout[4]
202
+ )
203
+
204
+ # Bottom control panel
205
+ control_area = layout[1]
206
+ frame.render_widget(
207
+ @tui.block(
208
+ title: "Controls",
209
+ borders: [:all],
210
+ children: [
211
+ @tui.paragraph(
212
+ text: [
213
+ # Line 1: Data
214
+ @tui.text_line(spans: [
215
+ @tui.text_span(content: "↑/↓", style: @hotkey_style),
216
+ @tui.text_span(content: ": Data (#{@data_sets[@data_index][:name]})"),
217
+ ]),
218
+ # Line 2: View
219
+ @tui.text_line(spans: [
220
+ @tui.text_span(content: "d", style: @hotkey_style),
221
+ @tui.text_span(content: ": Direction (#{@directions[@direction_index][:name]}) "),
222
+ @tui.text_span(content: "c", style: @hotkey_style),
223
+ @tui.text_span(content: ": Color (#{@styles[@style_index][:name]})"),
224
+ ]),
225
+ # Line 3: Markers
226
+ @tui.text_line(spans: [
227
+ @tui.text_span(content: "m", style: @hotkey_style),
228
+ @tui.text_span(content: ": Absent Value Symbol (#{@absent_symbols[@absent_symbol_index][:name]}) "),
229
+ @tui.text_span(content: "s", style: @hotkey_style),
230
+ @tui.text_span(content: ": Absent Value Style (#{@absent_styles[@absent_style_index][:name]})"),
231
+ ]),
232
+ # Line 4: General
233
+ @tui.text_line(spans: [
234
+ @tui.text_span(content: "b", style: @hotkey_style),
235
+ @tui.text_span(content: ": Bar Set (#{@bar_sets[@bar_set_index][:name]}) "),
236
+ @tui.text_span(content: "q", style: @hotkey_style),
237
+ @tui.text_span(content: ": Quit"),
238
+ ]),
239
+ ]
240
+ ),
241
+ ]
242
+ ),
243
+ control_area
244
+ )
245
+ end
246
+ end
247
+
248
+ private def handle_input
249
+ event = @tui.poll_event
250
+
251
+ case event
252
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
253
+ :quit
254
+ in type: :key, code: "up"
255
+ @data_index = (@data_index - 1) % @data_sets.length
256
+ in type: :key, code: "down"
257
+ @data_index = (@data_index + 1) % @data_sets.length
258
+ in type: :key, code: "d"
259
+ @direction_index = (@direction_index + 1) % @directions.length
260
+ in type: :key, code: "c"
261
+ @style_index = (@style_index + 1) % @styles.length
262
+ in type: :key, code: "m"
263
+ @absent_symbol_index = (@absent_symbol_index + 1) % @absent_symbols.length
264
+ in type: :key, code: "s"
265
+ @absent_style_index = (@absent_style_index + 1) % @absent_styles.length
266
+ in type: :key, code: "b"
267
+ @bar_set_index = (@bar_set_index + 1) % @bar_sets.length
268
+ else
269
+ nil
270
+ end
271
+ end
272
+ end
273
+
274
+ WidgetSparklineDemo.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,102 @@
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
+ class WidgetStyleColors
10
+ def initialize
11
+ @width = 80
12
+ @height = 24
13
+ end
14
+
15
+ def run
16
+ RatatuiRuby.run do |tui|
17
+ loop do
18
+ tui.draw do |frame|
19
+ frame.render_widget(render(tui), frame.area)
20
+ end
21
+ event = tui.poll_event
22
+ break if event.key? && (event.ctrl_c? || event == :q)
23
+ end
24
+ end
25
+ end
26
+
27
+ private def render(tui)
28
+ lines = []
29
+
30
+ (0...@height).each do |row|
31
+ spans = []
32
+ (0...@width).each do |col|
33
+ hue = (col.to_f / @width) * 360.0
34
+ lightness = 50.0 - ((row.to_f / @height) * 50.0)
35
+
36
+ rgb = hsl_to_rgb(hue, 100.0, lightness)
37
+ hex = rgb_to_hex(rgb)
38
+
39
+ span = tui.text_span(
40
+ content: " ",
41
+ style: tui.style(bg: hex)
42
+ )
43
+ spans << span
44
+ end
45
+
46
+ lines << tui.text_line(spans:)
47
+ end
48
+
49
+ tui.paragraph(
50
+ text: lines,
51
+ block: tui.block(
52
+ title: "Hex Color Gradient (Press 'q' or Ctrl+C to exit)",
53
+ borders: [:all],
54
+ border_type: :rounded
55
+ )
56
+ )
57
+ end
58
+
59
+ private def hsl_to_rgb(hue, saturation, lightness)
60
+ h = hue / 360.0
61
+ s = saturation / 100.0
62
+ l = lightness / 100.0
63
+
64
+ if s == 0
65
+ r = g = b = l
66
+ else
67
+ q = (l < 0.5) ? l * (1 + s) : l + s - (l * s)
68
+ p = (2 * l) - q
69
+
70
+ r = hue_to_rgb(p, q, h + (1.0 / 3.0))
71
+ g = hue_to_rgb(p, q, h)
72
+ b = hue_to_rgb(p, q, h - (1.0 / 3.0))
73
+ end
74
+
75
+ [
76
+ (r * 255).round,
77
+ (g * 255).round,
78
+ (b * 255).round,
79
+ ]
80
+ end
81
+
82
+ private def hue_to_rgb(p, q, t)
83
+ t += 1 while t < 0
84
+ t -= 1 while t > 1
85
+
86
+ if t < 1.0 / 6.0
87
+ p + ((q - p) * 6 * t)
88
+ elsif t < 1.0 / 2.0
89
+ q
90
+ elsif t < 2.0 / 3.0
91
+ p + ((q - p) * ((2.0 / 3.0) - t) * 6)
92
+ else
93
+ p
94
+ end
95
+ end
96
+
97
+ private def rgb_to_hex(rgb)
98
+ "##{rgb.map { |c| c.to_s(16).upcase.rjust(2, '0') }.join}"
99
+ end
100
+ end
101
+
102
+ WidgetStyleColors.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,95 @@
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 structured data layout with flexible column distribution.
10
+ #
11
+ # Data is often multidimensional. You need to show relationships between fields (Name, Age, ID). Aligning columns manually in a monospaced environment is painful and error-prone.
12
+ #
13
+ # This demo showcases the <tt>Table</tt> widget's flex modes. It renders multiple tables demonstrating how data is distributed across the available space using <tt>:legacy</tt>, <tt>:space_between</tt>, and <tt>:space_around</tt> settings.
14
+ #
15
+ # Use it to understand how to build perfectly aligned grids for database records, logs, or file lists.
16
+ #
17
+ # === Example
18
+ #
19
+ # Run the demo from the terminal:
20
+ #
21
+ # ruby examples/widget_table_flex/app.rb
22
+ #
23
+ # rdoc-image:/doc/images/widget_table_flex.png
24
+ class WidgetTableFlex
25
+ def run
26
+ RatatuiRuby.run do |tui|
27
+ loop do
28
+ render(tui)
29
+ break if handle_input(tui) == :quit
30
+ end
31
+ end
32
+ end
33
+
34
+ def render(tui)
35
+ tui.draw do |frame|
36
+ chunks = tui.layout_split(
37
+ frame.area,
38
+ direction: :vertical,
39
+ constraints: [
40
+ tui.constraint_length(3),
41
+ tui.constraint_fill(1),
42
+ tui.constraint_fill(1),
43
+ tui.constraint_fill(1),
44
+ ]
45
+ )
46
+
47
+ frame.render_widget(
48
+ tui.paragraph(
49
+ text: "Table Flex Layout (press 'q' to quit)",
50
+ block: tui.block(title: "Header", borders: [:all])
51
+ ),
52
+ chunks[0]
53
+ )
54
+
55
+ frame.render_widget(
56
+ tui.table(
57
+ header: ["Legacy (Default)", "Table"],
58
+ rows: [["Item 1", "Item 2"], ["Item 3", "Item 4"]],
59
+ widths: [tui.constraint_length(20), tui.constraint_length(20)],
60
+ block: tui.block(title: "Flex: :legacy (Default)", borders: [:all])
61
+ ),
62
+ chunks[1]
63
+ )
64
+
65
+ frame.render_widget(
66
+ tui.table(
67
+ header: ["Space", "Between"],
68
+ rows: [["A", "B"], ["C", "D"]],
69
+ widths: [tui.constraint_length(20), tui.constraint_length(20)],
70
+ block: tui.block(title: "Flex: :space_between", borders: [:all]),
71
+ flex: :space_between
72
+ ),
73
+ chunks[2]
74
+ )
75
+
76
+ frame.render_widget(
77
+ tui.table(
78
+ header: ["Space", "Around"],
79
+ rows: [["E", "F"], ["G", "H"]],
80
+ widths: [tui.constraint_length(20), tui.constraint_length(20)],
81
+ block: tui.block(title: "Flex: :space_around", borders: [:all]),
82
+ flex: :space_around
83
+ ),
84
+ chunks[3]
85
+ )
86
+ end
87
+ end
88
+
89
+ def handle_input(tui)
90
+ event = tui.poll_event
91
+ :quit if event == "q" || event == :ctrl_c
92
+ end
93
+ end
94
+
95
+ WidgetTableFlex.new.run if __FILE__ == $0
@@ -0,0 +1,167 @@
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 view segregation with interactive tab navigation.
10
+ #
11
+ # Screen real estate is limited. You cannot show everything at once. Segregating content into views is necessary for complex apps.
12
+ #
13
+ # This demo showcases the <tt>Tabs</tt> widget. It provides an interactive playground where you can select tabs, cycle through dividers and styles, and adjust padding in real-time.
14
+ #
15
+ # Use it to understand how to build major mode switches or context navigation for your interface.
16
+ #
17
+ # === Example
18
+ #
19
+ # Run the demo from the terminal:
20
+ #
21
+ # ruby examples/widget_tabs_demo/app.rb
22
+ #
23
+ # rdoc-image:/doc/images/widget_tabs_demo.png
24
+ class WidgetTabsDemo
25
+ def initialize
26
+ @selected_tab = 0
27
+ @tabs = ["Revenue", "Traffic", "Errors", "Quarterly"]
28
+ @highlight_styles = nil
29
+ @highlight_style_index = 0
30
+ @divider_index = 0
31
+ @dividers = [" | ", " • ", " > ", " / "]
32
+ @base_styles = nil
33
+ @base_style_index = 0
34
+ @padding_left = 0
35
+ @padding_right = 0
36
+ @width_constraint_index = 0
37
+ @hotkey_style = nil
38
+ end
39
+
40
+ def run
41
+ RatatuiRuby.run do |tui|
42
+ @tui = tui
43
+ init_styles
44
+
45
+ loop do
46
+ render
47
+ break if handle_input == :quit
48
+ end
49
+ end
50
+ end
51
+
52
+ private def init_styles
53
+ @highlight_styles = [
54
+ { name: "Yellow Bold", style: @tui.style(fg: :yellow, modifiers: [:bold]) },
55
+ { name: "Italic Blue on White", style: @tui.style(fg: :blue, bg: :white, modifiers: [:italic]) },
56
+ { name: "Underlined Red", style: @tui.style(fg: :red, modifiers: [:underlined]) },
57
+ { name: "Reversed", style: @tui.style(modifiers: [:reversed]) },
58
+ ]
59
+ @base_styles = [
60
+ { name: "Default", style: nil },
61
+ { name: "White on Gray", style: @tui.style(fg: :white, bg: :dark_gray) },
62
+ { name: "White on Blue", style: @tui.style(fg: :white, bg: :blue) },
63
+ { name: "Italic", style: @tui.style(modifiers: [:italic]) },
64
+ ]
65
+ @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
66
+ end
67
+
68
+ private def render
69
+ @tui.draw do |frame|
70
+ main_area, controls_area = @tui.layout_split(
71
+ frame.area,
72
+ direction: :vertical,
73
+ constraints: [
74
+ @tui.constraint_fill(1),
75
+ @tui.constraint_length(5),
76
+ ]
77
+ )
78
+
79
+ # Center the tabs vertically in the main area
80
+ tabs_area, = @tui.layout_split(
81
+ main_area,
82
+ direction: :vertical,
83
+ constraints: [
84
+ @tui.constraint_length(3),
85
+ ]
86
+ )
87
+
88
+ tabs = @tui.tabs(
89
+ titles: @tabs,
90
+ selected_index: @selected_tab,
91
+ block: @tui.block(title: "Tabs Demo", borders: [:all]),
92
+ divider: @dividers[@divider_index],
93
+ highlight_style: @highlight_styles[@highlight_style_index][:style],
94
+ style: @base_styles[@base_style_index][:style],
95
+ padding_left: @padding_left,
96
+ padding_right: @padding_right
97
+ )
98
+ frame.render_widget(tabs, tabs_area)
99
+
100
+ render_controls(frame, controls_area, tabs.width)
101
+ end
102
+ end
103
+
104
+ private def render_controls(frame, area, current_width)
105
+ controls = @tui.block(
106
+ title: "Controls",
107
+ borders: [:all],
108
+ children: [
109
+ @tui.paragraph(
110
+ text: [
111
+ @tui.text_line(spans: [
112
+ @tui.text_span(content: "←/→", style: @hotkey_style),
113
+ @tui.text_span(content: ": Select Tab "),
114
+ @tui.text_span(content: "h/l", style: @hotkey_style),
115
+ @tui.text_span(content: ": Pad Left (#{@padding_left}) "),
116
+ @tui.text_span(content: "j/k", style: @hotkey_style),
117
+ @tui.text_span(content: ": Pad Right (#{@padding_right}) "),
118
+ @tui.text_span(content: "q", style: @hotkey_style),
119
+ @tui.text_span(content: ": Quit"),
120
+ ]),
121
+ @tui.text_line(spans: [
122
+ @tui.text_span(content: "d", style: @hotkey_style),
123
+ @tui.text_span(content: ": Divider (#{@dividers[@divider_index]}) "),
124
+ @tui.text_span(content: "s", style: @hotkey_style),
125
+ @tui.text_span(content: ": Highlight (#{@highlight_styles[@highlight_style_index][:name]}) "),
126
+ @tui.text_span(content: "b", style: @hotkey_style),
127
+ @tui.text_span(content: ": Base Style (#{@base_styles[@base_style_index][:name]}) "),
128
+ ]),
129
+ @tui.text_line(spans: [
130
+ @tui.text_span(content: "Width: #{current_width}"),
131
+ ]),
132
+ ]
133
+ ),
134
+ ]
135
+ )
136
+ frame.render_widget(controls, area)
137
+ end
138
+
139
+ private def handle_input
140
+ case @tui.poll_event
141
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
142
+ :quit
143
+ in type: :key, code: "right"
144
+ @selected_tab = (@selected_tab + 1) % @tabs.size
145
+ in type: :key, code: "left"
146
+ @selected_tab = (@selected_tab - 1) % @tabs.size
147
+ in type: :key, code: "d"
148
+ @divider_index = (@divider_index + 1) % @dividers.size
149
+ in type: :key, code: "s"
150
+ @highlight_style_index = (@highlight_style_index + 1) % @highlight_styles.size
151
+ in type: :key, code: "b"
152
+ @base_style_index = (@base_style_index + 1) % @base_styles.size
153
+ in type: :key, code: "h"
154
+ @padding_left = [@padding_left - 1, 0].max
155
+ in type: :key, code: "l"
156
+ @padding_left += 1
157
+ in type: :key, code: "j"
158
+ @padding_right = [@padding_right - 1, 0].max
159
+ in type: :key, code: "k"
160
+ @padding_right += 1
161
+ else
162
+ # Ignore other events
163
+ end
164
+ end
165
+ end
166
+
167
+ WidgetTabsDemo.new.run if __FILE__ == $0