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,275 @@
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 = 2
61
+ srand(12345) # Ensure reproducible "Random" data for snapshots
62
+
63
+ @directions = [
64
+ { name: "Left to Right", direction: :left_to_right },
65
+ { name: "Right to Left", direction: :right_to_left },
66
+ ]
67
+ @direction_index = 0
68
+
69
+ @styles = [
70
+ { name: "Green", style: @tui.style(fg: :green) },
71
+ { name: "Yellow", style: @tui.style(fg: :yellow) },
72
+ { name: "Red", style: @tui.style(fg: :red) },
73
+ { name: "Cyan", style: @tui.style(fg: :cyan) },
74
+ { name: "Magenta", style: @tui.style(fg: :magenta) },
75
+ ]
76
+ @style_index = 3
77
+
78
+ @absent_symbols = [
79
+ { name: "None", symbol: nil },
80
+ { name: "Dot (·)", symbol: "·" },
81
+ { name: "Square (▫)", symbol: "▫" },
82
+ { name: "Dash (-)", symbol: "-" },
83
+ { name: "Underscore (_)", symbol: "_" },
84
+ ]
85
+ @absent_symbol_index = 1
86
+
87
+ @absent_styles = [
88
+ { name: "Default", style: nil },
89
+ { name: "Dark Gray", style: @tui.style(fg: :dark_gray) },
90
+ { name: "Dim Red", style: @tui.style(fg: :red, modifiers: [:dim]) },
91
+ { name: "Dim Yellow", style: @tui.style(fg: :yellow, modifiers: [:dim]) },
92
+ ]
93
+ @absent_style_index = 2
94
+
95
+ @bar_sets = [
96
+ { name: "Default (Block)", set: nil },
97
+ {
98
+ name: "Numbers (0-8)",
99
+ set: {
100
+ 0 => "0", 1 => "1", 2 => "2", 3 => "3", 4 => "4", 5 => "5", 6 => "6", 7 => "7", 8 => "8"
101
+ },
102
+ },
103
+ { name: "ASCII (Heights)", set: [" ", "_", ".", "-", "=", "+", "*", "#", "@"] },
104
+ ]
105
+ @bar_set_index = 0
106
+
107
+ @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
108
+ end
109
+
110
+ private def render
111
+ @tui.draw do |frame|
112
+ data_set = @data_sets[@data_index]
113
+ direction = @directions[@direction_index][:direction]
114
+ style = @styles[@style_index][:style]
115
+ absent_symbol = @absent_symbols[@absent_symbol_index][:symbol]
116
+ absent_value_style = @absent_styles[@absent_style_index][:style]
117
+ bar_set = @bar_sets[@bar_set_index][:set]
118
+
119
+ # Use static data for clarity when cycling options
120
+ current_data = data_set[:data]
121
+
122
+ layout = @tui.layout_split(
123
+ frame.area,
124
+ direction: :vertical,
125
+ constraints: [
126
+ @tui.constraint_fill(1),
127
+ @tui.constraint_length(6),
128
+ ]
129
+ )
130
+
131
+ # Main content area with multiple sparkline examples
132
+ main_content_area = layout[0]
133
+ main_layout = @tui.layout_split(
134
+ main_content_area,
135
+ direction: :vertical,
136
+ constraints: [
137
+ @tui.constraint_length(1),
138
+ @tui.constraint_length(3),
139
+ @tui.constraint_length(3),
140
+ @tui.constraint_length(3),
141
+ @tui.constraint_length(3),
142
+ @tui.constraint_fill(1),
143
+ ]
144
+ )
145
+
146
+ frame.render_widget(
147
+ @tui.paragraph(text: "Sparkline Widget Demo - Cycle attributes with hotkeys"),
148
+ main_layout[0]
149
+ )
150
+
151
+ # Sparkline 1: Main interactive sparkline
152
+ frame.render_widget(
153
+ @tui.sparkline(
154
+ data: current_data,
155
+ direction:,
156
+ style:,
157
+ absent_value_symbol: absent_symbol,
158
+ absent_value_style:,
159
+ bar_set:,
160
+ block: @tui.block(title: "Interactive Sparkline")
161
+ ),
162
+ main_layout[1]
163
+ )
164
+
165
+ # Sparkline 2: Same data, opposite direction
166
+ frame.render_widget(
167
+ @tui.sparkline(
168
+ data: current_data.reverse,
169
+ direction:,
170
+ style:,
171
+ absent_value_symbol: absent_symbol,
172
+ absent_value_style:,
173
+ bar_set:,
174
+ block: @tui.block(title: "Reversed Data")
175
+ ),
176
+ main_layout[2]
177
+ )
178
+
179
+ # Sparkline 3: Without absent value symbol (for comparison)
180
+ frame.render_widget(
181
+ @tui.sparkline(
182
+ data: current_data,
183
+ direction:,
184
+ style:,
185
+ bar_set:,
186
+ block: @tui.block(title: "Without Absent Marker")
187
+ ),
188
+ main_layout[3]
189
+ )
190
+
191
+ # Sparkline 4: Gap pattern responsive to absent marker controls
192
+ frame.render_widget(
193
+ @tui.sparkline(
194
+ data: [5, nil, 8, nil, 6, nil, 9, nil, 7, nil, 10, nil],
195
+ direction:,
196
+ style: @tui.style(fg: :blue),
197
+ absent_value_symbol: absent_symbol,
198
+ absent_value_style:,
199
+ bar_set:,
200
+ block: @tui.block(title: "Gap Pattern (Responsive)")
201
+ ),
202
+ main_layout[4]
203
+ )
204
+
205
+ # Bottom control panel
206
+ control_area = layout[1]
207
+ frame.render_widget(
208
+ @tui.block(
209
+ title: "Controls",
210
+ borders: [:all],
211
+ children: [
212
+ @tui.paragraph(
213
+ text: [
214
+ # Line 1: Data
215
+ @tui.text_line(spans: [
216
+ @tui.text_span(content: "↑/↓", style: @hotkey_style),
217
+ @tui.text_span(content: ": Data (#{@data_sets[@data_index][:name]})"),
218
+ ]),
219
+ # Line 2: View
220
+ @tui.text_line(spans: [
221
+ @tui.text_span(content: "d", style: @hotkey_style),
222
+ @tui.text_span(content: ": Direction (#{@directions[@direction_index][:name]}) "),
223
+ @tui.text_span(content: "c", style: @hotkey_style),
224
+ @tui.text_span(content: ": Color (#{@styles[@style_index][:name]})"),
225
+ ]),
226
+ # Line 3: Markers
227
+ @tui.text_line(spans: [
228
+ @tui.text_span(content: "m", style: @hotkey_style),
229
+ @tui.text_span(content: ": Absent Value Symbol (#{@absent_symbols[@absent_symbol_index][:name]}) "),
230
+ @tui.text_span(content: "s", style: @hotkey_style),
231
+ @tui.text_span(content: ": Absent Value Style (#{@absent_styles[@absent_style_index][:name]})"),
232
+ ]),
233
+ # Line 4: General
234
+ @tui.text_line(spans: [
235
+ @tui.text_span(content: "b", style: @hotkey_style),
236
+ @tui.text_span(content: ": Bar Set (#{@bar_sets[@bar_set_index][:name]}) "),
237
+ @tui.text_span(content: "q", style: @hotkey_style),
238
+ @tui.text_span(content: ": Quit"),
239
+ ]),
240
+ ]
241
+ ),
242
+ ]
243
+ ),
244
+ control_area
245
+ )
246
+ end
247
+ end
248
+
249
+ private def handle_input
250
+ event = @tui.poll_event
251
+
252
+ case event
253
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
254
+ :quit
255
+ in type: :key, code: "up"
256
+ @data_index = (@data_index - 1) % @data_sets.length
257
+ in type: :key, code: "down"
258
+ @data_index = (@data_index + 1) % @data_sets.length
259
+ in type: :key, code: "d"
260
+ @direction_index = (@direction_index + 1) % @directions.length
261
+ in type: :key, code: "c"
262
+ @style_index = (@style_index + 1) % @styles.length
263
+ in type: :key, code: "m"
264
+ @absent_symbol_index = (@absent_symbol_index + 1) % @absent_symbols.length
265
+ in type: :key, code: "s"
266
+ @absent_style_index = (@absent_style_index + 1) % @absent_styles.length
267
+ in type: :key, code: "b"
268
+ @bar_set_index = (@bar_set_index + 1) % @bar_sets.length
269
+ else
270
+ nil
271
+ end
272
+ end
273
+ end
274
+
275
+ WidgetSparklineDemo.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,34 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Style Colors Example
7
+
8
+ Demonstrates high-fidelity color support.
9
+
10
+ Terminals support millions of colors. This example generates a mathematically precise HSL gradient to prove the rendering engine's color fidelity.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **HSL to RGB Conversion**: generating smooth color gradients programmatically.
15
+ - **TrueColor Support**: Rendering arbitrary HEX colors.
16
+
17
+ ## Hotkeys
18
+
19
+ - **q** / **Ctrl+c**: Quit
20
+
21
+ ## Usage
22
+
23
+ ```bash
24
+ ruby examples/widget_style_colors/app.rb
25
+ ```
26
+
27
+ ## Learning Outcomes
28
+
29
+ Use this example if you need to...
30
+ - Create meaningful heatmaps.
31
+ - Generate color palettes dynamically.
32
+ - Test your terminal's color support capabilities.
33
+
34
+ ![Demo](/doc/images/widget_style_colors.png)
@@ -6,7 +6,7 @@
6
6
  $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
7
  require "ratatui_ruby"
8
8
 
9
- class ColorGradientDemo
9
+ class WidgetStyleColors
10
10
  def initialize
11
11
  @width = 80
12
12
  @height = 24
@@ -15,16 +15,16 @@ class ColorGradientDemo
15
15
  def run
16
16
  RatatuiRuby.run do |tui|
17
17
  loop do
18
- render(tui)
18
+ tui.draw do |frame|
19
+ frame.render_widget(render(tui), frame.area)
20
+ end
19
21
  event = tui.poll_event
20
- break if event&.key? && (event.ctrl_c? || event == :q)
22
+ break if event.key? && (event.ctrl_c? || event == :q)
21
23
  end
22
24
  end
23
25
  end
24
26
 
25
- private
26
-
27
- def render(tui)
27
+ private def render(tui)
28
28
  lines = []
29
29
 
30
30
  (0...@height).each do |row|
@@ -43,10 +43,10 @@ class ColorGradientDemo
43
43
  spans << span
44
44
  end
45
45
 
46
- lines << tui.text_line(spans: spans)
46
+ lines << tui.text_line(spans:)
47
47
  end
48
48
 
49
- paragraph = tui.paragraph(
49
+ tui.paragraph(
50
50
  text: lines,
51
51
  block: tui.block(
52
52
  title: "Hex Color Gradient (Press 'q' or Ctrl+C to exit)",
@@ -54,11 +54,9 @@ class ColorGradientDemo
54
54
  border_type: :rounded
55
55
  )
56
56
  )
57
-
58
- tui.draw(paragraph)
59
57
  end
60
58
 
61
- def hsl_to_rgb(hue, saturation, lightness)
59
+ private def hsl_to_rgb(hue, saturation, lightness)
62
60
  h = hue / 360.0
63
61
  s = saturation / 100.0
64
62
  l = lightness / 100.0
@@ -66,39 +64,39 @@ class ColorGradientDemo
66
64
  if s == 0
67
65
  r = g = b = l
68
66
  else
69
- q = l < 0.5 ? l * (1 + s) : l + s - (l * s)
70
- p = 2 * l - q
67
+ q = (l < 0.5) ? l * (1 + s) : l + s - (l * s)
68
+ p = (2 * l) - q
71
69
 
72
- r = hue_to_rgb(p, q, h + 1.0 / 3.0)
70
+ r = hue_to_rgb(p, q, h + (1.0 / 3.0))
73
71
  g = hue_to_rgb(p, q, h)
74
- b = hue_to_rgb(p, q, h - 1.0 / 3.0)
72
+ b = hue_to_rgb(p, q, h - (1.0 / 3.0))
75
73
  end
76
74
 
77
75
  [
78
76
  (r * 255).round,
79
77
  (g * 255).round,
80
- (b * 255).round
78
+ (b * 255).round,
81
79
  ]
82
80
  end
83
81
 
84
- def hue_to_rgb(p, q, t)
82
+ private def hue_to_rgb(p, q, t)
85
83
  t += 1 while t < 0
86
84
  t -= 1 while t > 1
87
85
 
88
86
  if t < 1.0 / 6.0
89
- p + (q - p) * 6 * t
87
+ p + ((q - p) * 6 * t)
90
88
  elsif t < 1.0 / 2.0
91
89
  q
92
90
  elsif t < 2.0 / 3.0
93
- p + (q - p) * (2.0 / 3.0 - t) * 6
91
+ p + ((q - p) * ((2.0 / 3.0) - t) * 6)
94
92
  else
95
93
  p
96
94
  end
97
95
  end
98
96
 
99
- def rgb_to_hex(rgb)
97
+ private def rgb_to_hex(rgb)
100
98
  "##{rgb.map { |c| c.to_s(16).upcase.rjust(2, '0') }.join}"
101
99
  end
102
100
  end
103
101
 
104
- ColorGradientDemo.new.run if __FILE__ == $PROGRAM_NAME
102
+ WidgetStyleColors.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,48 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Table Widget Example
7
+
8
+ Demonstrates advanced options for the `Table` widget, including selection, row-level highlighting, and column-level highlighting.
9
+
10
+ Data grids are complex. Users expect to navigate them with keys, select rows, and clearly see which cell is active. The `Table` widget provides these features out of the box efficiently.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Selection State**: Managing `selected_row` and `selected_column` to track user focus.
15
+ - **Complex Highlighting**:
16
+ - **Row**: Highlight the entire active row.
17
+ - **Highlight Symbol:** Adding a visual indicator (like `> `) to the selected row.
18
+ - **Spacing:** Adjusting `column_spacing` and `highlight_spacing` to control layout density.
19
+ - **Flex Layout:** Switching between different column distribution modes (`legacy`, `start`, `space_between`, etc.).
20
+ - **Offset Control:** Manually controlling the scroll position using `offset`.
21
+
22
+ ## Hotkeys
23
+
24
+ - **Arrows (↑/↓)**: Navigate Rows (`selected_row`)
25
+ - **Arrows (←/→)**: Navigate Columns (`selected_column`)
26
+ - **x**: Toggle Row Selection (`selected_row` = nil)
27
+ - **s**: Cycle Table Style (`style`)
28
+ - **p**: Cycle Spacing (`highlight_spacing`)
29
+ - **c**: Toggle Column Highlight (`column_highlight_style`)
30
+ - **z**: Toggle Cell Highlight (`cell_highlight_style`)
31
+ - **o**: Cycle Offset Mode (`offset`)
32
+ - **f**: Cycle Flex Mode (`flex`)
33
+ - **q**: Quit
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ ruby examples/widget_table_demo/app.rb
39
+ ```
40
+
41
+ ## Learning Outcomes
42
+
43
+ Use this example if you need to...
44
+ - Build a file explorer or process list.
45
+ - Create a data-heavy dashboard.
46
+ - Handle conflicting style requirements (e.g., "Highlight this row, but make this error cell red").
47
+
48
+ ![Demo](/doc/images/widget_table_demo.png)
@@ -0,0 +1,239 @@
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 "bundler/setup"
8
+ require "ratatui_ruby"
9
+
10
+ # Sample process data
11
+ PROCESSES = [
12
+ { pid: 1234, name: "ruby", cpu: 15.2 },
13
+ { pid: 5678, name: "postgres", cpu: 8.7 },
14
+ { pid: 9012, name: "nginx", cpu: 3.1 },
15
+ { pid: 3456, name: "redis", cpu: 12.4 },
16
+ { pid: 7890, name: "sidekiq", cpu: 22.8 },
17
+ { pid: 2345, name: "webpack", cpu: 45.3 },
18
+ { pid: 6789, name: "node", cpu: 18.9 },
19
+ ].freeze
20
+
21
+ class WidgetTableDemo
22
+ attr_reader :selected_index, :selected_col, :current_style_index, :column_spacing, :highlight_spacing, :column_highlight_style, :cell_highlight_style
23
+
24
+ HIGHLIGHT_SPACINGS = [
25
+ { name: "When Selected", spacing: :when_selected },
26
+ { name: "Always", spacing: :always },
27
+ { name: "Never", spacing: :never },
28
+ ].freeze
29
+
30
+ OFFSET_MODES = [
31
+ { name: "Auto (No Offset)", offset: nil, allow_selection: true },
32
+ { name: "Offset Only (row 3)", offset: 3, allow_selection: false },
33
+ { name: "Selection + Offset (Conflict)", offset: 0, allow_selection: true },
34
+ ].freeze
35
+
36
+ FLEX_MODES = [
37
+ { name: "Legacy (Default)", flex: :legacy },
38
+ { name: "Start", flex: :start },
39
+ { name: "Center", flex: :center },
40
+ { name: "End", flex: :end },
41
+ { name: "Space Between", flex: :space_between },
42
+ { name: "Space Around", flex: :space_around },
43
+ { name: "Space Evenly", flex: :space_evenly },
44
+ ].freeze
45
+
46
+ def initialize
47
+ @selected_index = 1
48
+ @selected_col = 1
49
+ @current_style_index = 0
50
+ @column_spacing = 1
51
+ @highlight_spacing_index = 0
52
+ @show_column_highlight = true
53
+ @show_cell_highlight = true
54
+ @offset_mode_index = 0
55
+ @flex_mode_index = 0
56
+ end
57
+
58
+ def run
59
+ RatatuiRuby.run do |tui|
60
+ @tui = tui
61
+ setup_styles
62
+ loop do
63
+ @tui.draw do |frame|
64
+ render(frame)
65
+ end
66
+ break if handle_input == :quit
67
+ end
68
+ end
69
+ end
70
+
71
+ private def setup_styles
72
+ @styles = [
73
+ { name: "Cyan", style: @tui.style(fg: :cyan) },
74
+ { name: "Red", style: @tui.style(fg: :red) },
75
+ { name: "Green", style: @tui.style(fg: :green) },
76
+ { name: "Blue on White", style: @tui.style(fg: :blue, bg: :white) },
77
+ { name: "Magenta", style: @tui.style(fg: :magenta, modifiers: [:bold]) },
78
+ ]
79
+ @column_highlight_style = @tui.style(fg: :magenta)
80
+ @cell_highlight_style = @tui.style(fg: :white, bg: :red, modifiers: [:bold])
81
+ @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
82
+ end
83
+
84
+ private def render(frame)
85
+ # Create table rows from process data
86
+ rows = PROCESSES.map { |p| [p[:pid].to_s, p[:name], "#{p[:cpu]}%"] }
87
+
88
+ # Define column widths
89
+ widths = [
90
+ @tui.constraint_length(8),
91
+ @tui.constraint_length(15),
92
+ @tui.constraint_length(10),
93
+ ]
94
+
95
+ # Create highlight style (yellow text)
96
+ highlight_style = @tui.style(fg: :yellow)
97
+
98
+ current_style_entry = @styles[@current_style_index]
99
+ current_spacing_entry = HIGHLIGHT_SPACINGS[@highlight_spacing_index]
100
+ offset_mode_entry = OFFSET_MODES[@offset_mode_index]
101
+ flex_mode_entry = FLEX_MODES[@flex_mode_index]
102
+
103
+ # Determine selection/offset based on mode
104
+ effective_selection = offset_mode_entry[:allow_selection] ? @selected_index : nil
105
+ effective_offset = offset_mode_entry[:offset]
106
+ selection_label = effective_selection.nil? ? "none" : effective_selection.to_s
107
+ offset_label = effective_offset.nil? ? "auto" : effective_offset.to_s
108
+
109
+ # Main table
110
+ table = @tui.table(
111
+ header: ["PID", "Name", "CPU"],
112
+ rows:,
113
+ widths:,
114
+ selected_row: effective_selection,
115
+ selected_column: @selected_col,
116
+ offset: effective_offset,
117
+ highlight_style:,
118
+ highlight_symbol: "> ",
119
+ highlight_spacing: current_spacing_entry[:spacing],
120
+ column_highlight_style: @show_column_highlight ? @column_highlight_style : nil,
121
+ cell_highlight_style: @show_cell_highlight ? @cell_highlight_style : nil,
122
+ style: current_style_entry[:style],
123
+ column_spacing: @column_spacing,
124
+ flex: flex_mode_entry[:flex],
125
+ block: @tui.block(
126
+ title: "Processes | Sel: #{selection_label} | Offset: #{offset_label} | Flex: #{flex_mode_entry[:name]}",
127
+ borders: :all
128
+ ),
129
+ footer: ["Total: #{PROCESSES.length}", "Total CPU: #{PROCESSES.sum { |p| p[:cpu] }}%", ""]
130
+ )
131
+
132
+ # Bottom control panel
133
+ control_panel = @tui.block(
134
+ title: "Controls",
135
+ borders: [:all],
136
+ children: [
137
+ @tui.paragraph(
138
+ text: [
139
+ # Line 1: Navigation
140
+ @tui.text_line(spans: [
141
+ @tui.text_span(content: "↑/↓", style: @hotkey_style),
142
+ @tui.text_span(content: ": Nav Row "),
143
+ @tui.text_span(content: "←/→", style: @hotkey_style),
144
+ @tui.text_span(content: ": Nav Col "),
145
+ @tui.text_span(content: "x", style: @hotkey_style),
146
+ @tui.text_span(content: ": Toggle Row (#{selection_label}) "),
147
+ @tui.text_span(content: "q", style: @hotkey_style),
148
+ @tui.text_span(content: ": Quit"),
149
+ ]),
150
+ # Line 2: Table Controls
151
+ @tui.text_line(spans: [
152
+ @tui.text_span(content: "s", style: @hotkey_style),
153
+ @tui.text_span(content: ": Style (#{current_style_entry[:name]}) "),
154
+ @tui.text_span(content: "p", style: @hotkey_style),
155
+ @tui.text_span(content: ": Spacing (#{current_spacing_entry[:name]}) "),
156
+ ]),
157
+ # Line 3: More Controls
158
+ @tui.text_line(spans: [
159
+ @tui.text_span(content: "+/-", style: @hotkey_style),
160
+ @tui.text_span(content: ": Col Space (#{@column_spacing}) "),
161
+ @tui.text_span(content: "c", style: @hotkey_style),
162
+ @tui.text_span(content: ": Col Highlight (#{@show_column_highlight ? 'On' : 'Off'}) "),
163
+ @tui.text_span(content: "f", style: @hotkey_style),
164
+ @tui.text_span(content: ": Flex Mode (#{flex_mode_entry[:name]})"),
165
+ ]),
166
+ # Line 4: Offset Mode
167
+ @tui.text_line(spans: [
168
+ @tui.text_span(content: "z", style: @hotkey_style),
169
+ @tui.text_span(content: ": Cell Highlight (#{@show_cell_highlight ? 'On' : 'Off'}) "),
170
+ @tui.text_span(content: "o", style: @hotkey_style),
171
+ @tui.text_span(content: ": Offset Mode (#{offset_mode_entry[:name]})"),
172
+ ]),
173
+ ]
174
+ ),
175
+ ]
176
+ )
177
+
178
+ # Layout
179
+ layout = @tui.layout_split(
180
+ frame.area,
181
+ direction: :vertical,
182
+ constraints: [
183
+ @tui.constraint_fill(1),
184
+ @tui.constraint_length(6),
185
+ ]
186
+ )
187
+
188
+ frame.render_widget(table, layout[0])
189
+ frame.render_widget(control_panel, layout[1])
190
+ end
191
+
192
+ private def handle_input
193
+ event = @tui.poll_event
194
+
195
+ case event
196
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
197
+ :quit
198
+ in type: :key, code: "down" | "j"
199
+ @selected_index = ((@selected_index || -1) + 1) % PROCESSES.length
200
+ in type: :key, code: "up" | "k"
201
+ @selected_index = (@selected_index || 0) - 1
202
+ @selected_index = PROCESSES.length - 1 if @selected_index.negative?
203
+ in type: :key, code: "right" | "l"
204
+ @selected_col = ((@selected_col || -1) + 1) % 3 # 3 columns
205
+ in type: :key, code: "left" | "h"
206
+ # 'h' is already used for highlight spacing, but let's override it or ignore vim keys for left/right?
207
+ # Actually 'h' is used for spacing in this demo. Let's just use arrows for cols.
208
+ # Or map 'h' to left if user meant vim keys.
209
+ # The demo uses 'h' for "Spacing". Let's change Spacing key to 'p' (property/padding?) or something else.
210
+ # Or just stick to arrows for columns to avoid conflict.
211
+ @selected_col = (@selected_col || 0) - 1
212
+ @selected_col = 2 if @selected_col.negative?
213
+ in type: :key, code: "s"
214
+ @current_style_index = (@current_style_index + 1) % @styles.length
215
+ in type: :key, code: "+"
216
+ @column_spacing += 1
217
+ in type: :key, code: "-"
218
+ @column_spacing = [@column_spacing - 1, 0].max
219
+ in type: :key, code: "p"
220
+ @highlight_spacing_index = (@highlight_spacing_index + 1) % HIGHLIGHT_SPACINGS.length
221
+ in type: :key, code: "x"
222
+ @selected_index = @selected_index.nil? ? 0 : nil
223
+ in type: :key, code: "c"
224
+ @show_column_highlight = !@show_column_highlight
225
+ in type: :key, code: "z"
226
+ @show_cell_highlight = !@show_cell_highlight
227
+ in type: :key, code: "o"
228
+ @offset_mode_index = (@offset_mode_index + 1) % OFFSET_MODES.length
229
+ in type: :key, code: "f"
230
+ @flex_mode_index = (@flex_mode_index + 1) % FLEX_MODES.length
231
+ else
232
+ nil
233
+ end
234
+ end
235
+ end
236
+
237
+ if __FILE__ == $0
238
+ WidgetTableDemo.new.run
239
+ end