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,217 @@
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 compact status visualization with interactive attribute cycling.
10
+ #
11
+ # Screen space is precious. Standard block gauges are bulky and consume multiple rows.
12
+ #
13
+ # This demo showcases the <tt>LineGauge</tt> widget. It provides an interactive playground where you can cycle through different ratios, symbols, and styling for both filled and unfilled portions in real-time.
14
+ #
15
+ # Use it to understand how to provide status feedback in constrained layouts without consuming vertical space.
16
+ #
17
+ # === Example
18
+ #
19
+ # Run the demo from the terminal:
20
+ #
21
+ # ruby examples/widget_line_gauge_demo/app.rb
22
+ #
23
+ # rdoc-image:/doc/images/widget_line_gauge_demo.png
24
+ class WidgetLineGaugeDemo
25
+ def initialize
26
+ @ratio = 0.5
27
+ @ratios = [0.2, 0.35, 0.5, 0.65, 0.8, 0.95]
28
+ @ratio_index = 2
29
+
30
+ @filled_symbols = [
31
+ { name: "█ (Block)", symbol: "█" },
32
+ { name: "▓ (Dark Shade)", symbol: "▓" },
33
+ { name: "▒ (Medium Shade)", symbol: "▒" },
34
+ { name: "= (Equals)", symbol: "=" },
35
+ { name: "# (Hash)", symbol: "#" },
36
+ ]
37
+ @filled_symbol_index = 0
38
+
39
+ @unfilled_symbols = [
40
+ { name: "░ (Light Shade)", symbol: "░" },
41
+ { name: "· (Dot)", symbol: "·" },
42
+ { name: "- (Dash)", symbol: "-" },
43
+ { name: "~ (Tilde)", symbol: "~" },
44
+ ]
45
+ @unfilled_symbol_index = 0
46
+
47
+ @filled_colors = [
48
+ { name: "Red", color: :red },
49
+ { name: "Yellow", color: :yellow },
50
+ { name: "Green", color: :green },
51
+ { name: "Cyan", color: :cyan },
52
+ { name: "Blue", color: :blue },
53
+ ]
54
+ @filled_color_index = 2
55
+
56
+ @unfilled_colors = [
57
+ { name: "Default", color: nil },
58
+ { name: "Dark Gray", color: :dark_gray },
59
+ { name: "Gray", color: :gray },
60
+ ]
61
+ @unfilled_color_index = 1
62
+
63
+ @base_styles = nil # Initialized in run when @tui is available
64
+ @base_style_index = 0
65
+ @hotkey_style = nil # Initialized in run when @tui is available
66
+ end
67
+
68
+ def run
69
+ RatatuiRuby.run do |tui|
70
+ @tui = tui
71
+
72
+ # Initialize styles using tui helpers
73
+ @base_styles = [
74
+ { name: "None", style: nil },
75
+ { name: "Bold White", style: tui.style(fg: :white, modifiers: [:bold]) },
76
+ { name: "White on Blue", style: tui.style(fg: :white, bg: :blue) },
77
+ { name: "Italic Cyan", style: tui.style(fg: :cyan, modifiers: [:italic]) },
78
+ ]
79
+ @hotkey_style = tui.style(modifiers: [:bold, :underlined])
80
+
81
+ loop do
82
+ render
83
+ break if handle_input == :quit
84
+ sleep 0.05
85
+ end
86
+ end
87
+ end
88
+
89
+ private def render
90
+ @ratio = @ratios[@ratio_index]
91
+
92
+ filled_color = @filled_colors[@filled_color_index][:color]
93
+ unfilled_color = @unfilled_colors[@unfilled_color_index][:color]
94
+
95
+ filled_style = filled_color ? @tui.style(fg: filled_color) : @tui.style(fg: :white)
96
+ unfilled_style = unfilled_color ? @tui.style(fg: unfilled_color) : @tui.style(fg: :dark_gray)
97
+
98
+ @tui.draw do |frame|
99
+ # Split into main content and control panel
100
+ main_area, controls_area = @tui.layout_split(
101
+ frame.area,
102
+ direction: :vertical,
103
+ constraints: [
104
+ @tui.constraint_fill(1),
105
+ @tui.constraint_length(5),
106
+ ]
107
+ )
108
+
109
+ # Split main area into title, gauges, and spacer
110
+ title_area, gauge1_area, gauge2_area, spacer_area = @tui.layout_split(
111
+ main_area,
112
+ direction: :vertical,
113
+ constraints: [
114
+ @tui.constraint_length(1),
115
+ @tui.constraint_length(4),
116
+ @tui.constraint_length(4),
117
+ @tui.constraint_fill(1),
118
+ ]
119
+ )
120
+
121
+ # Render title
122
+ title = @tui.paragraph(text: "LineGauge Widget Demo - Cycle attributes with hotkeys")
123
+ frame.render_widget(title, title_area)
124
+
125
+ # Example 1: Static gauge showing all features
126
+ gauge1 = @tui.line_gauge(
127
+ ratio: @ratio,
128
+ label: "#{(@ratio * 100).to_i}%",
129
+ style: @base_styles[@base_style_index][:style],
130
+ filled_style:,
131
+ unfilled_style:,
132
+ filled_symbol: @filled_symbols[@filled_symbol_index][:symbol],
133
+ unfilled_symbol: @unfilled_symbols[@unfilled_symbol_index][:symbol],
134
+ block: @tui.block(title: "Interactive Gauge")
135
+ )
136
+ frame.render_widget(gauge1, gauge1_area)
137
+
138
+ # Example 2: Inverted colors for contrast demonstration
139
+ gauge2 = @tui.line_gauge(
140
+ ratio: 1.0 - @ratio,
141
+ label: "#{((1.0 - @ratio) * 100).to_i}%",
142
+ filled_style: @tui.style(fg: :black, bg: :yellow),
143
+ unfilled_style: @tui.style(fg: :white, bg: :dark_gray),
144
+ filled_symbol: @filled_symbols[@filled_symbol_index][:symbol],
145
+ unfilled_symbol: @unfilled_symbols[@unfilled_symbol_index][:symbol],
146
+ block: @tui.block(title: "Inverse (100% - ratio)")
147
+ )
148
+ frame.render_widget(gauge2, gauge2_area)
149
+
150
+ # Render empty spacer
151
+ spacer = @tui.paragraph(text: "")
152
+ frame.render_widget(spacer, spacer_area)
153
+
154
+ # Bottom control panel
155
+ controls = @tui.block(
156
+ title: "Controls",
157
+ borders: [:all],
158
+ children: [
159
+ @tui.paragraph(
160
+ text: [
161
+ # Line 1: General
162
+ @tui.text_line(spans: [
163
+ @tui.text_span(content: "←/→", style: @hotkey_style),
164
+ @tui.text_span(content: ": Ratio (#{(@ratio * 100).to_i}%) "),
165
+ @tui.text_span(content: "b", style: @hotkey_style),
166
+ @tui.text_span(content: ": Base Style (#{@base_styles[@base_style_index][:name]}) "),
167
+ @tui.text_span(content: "q", style: @hotkey_style),
168
+ @tui.text_span(content: ": Quit"),
169
+ ]),
170
+ # Line 2: Filled
171
+ @tui.text_line(spans: [
172
+ @tui.text_span(content: "f", style: @hotkey_style),
173
+ @tui.text_span(content: ": Filled Symbol (#{@filled_symbols[@filled_symbol_index][:name]}) "),
174
+ @tui.text_span(content: "c", style: @hotkey_style),
175
+ @tui.text_span(content: ": Filled Color (#{@filled_colors[@filled_color_index][:name]})"),
176
+ ]),
177
+ # Line 3: Unfilled
178
+ @tui.text_line(spans: [
179
+ @tui.text_span(content: "u", style: @hotkey_style),
180
+ @tui.text_span(content: ": Unfilled Symbol (#{@unfilled_symbols[@unfilled_symbol_index][:name]}) "),
181
+ @tui.text_span(content: "x", style: @hotkey_style),
182
+ @tui.text_span(content: ": Unfilled Color (#{@unfilled_colors[@unfilled_color_index][:name]})"),
183
+ ]),
184
+ ]
185
+ ),
186
+ ]
187
+ )
188
+ frame.render_widget(controls, controls_area)
189
+ end
190
+ end
191
+
192
+ private def handle_input
193
+ case @tui.poll_event
194
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
195
+ :quit
196
+ in type: :key, code: "right"
197
+ @ratio_index = (@ratio_index + 1) % @ratios.length
198
+ in type: :key, code: "left"
199
+ @ratio_index = (@ratio_index - 1) % @ratios.length
200
+ in type: :key, code: "b"
201
+ @base_style_index = (@base_style_index + 1) % @base_styles.length
202
+ in type: :key, code: "f"
203
+ @filled_symbol_index = (@filled_symbol_index + 1) % @filled_symbols.length
204
+ in type: :key, code: "c"
205
+ @filled_color_index = (@filled_color_index + 1) % @filled_colors.length
206
+ in type: :key, code: "u"
207
+ @unfilled_symbol_index = (@unfilled_symbol_index + 1) % @unfilled_symbols.length
208
+ in type: :key, code: "x"
209
+ @unfilled_color_index = (@unfilled_color_index + 1) % @unfilled_colors.length
210
+ else
211
+ # Ignore other events
212
+ nil
213
+ end
214
+ end
215
+ end
216
+
217
+ WidgetLineGaugeDemo.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,49 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # List Widget Example
7
+
8
+ Demonstrates a selectable list with extensive configuration options.
9
+
10
+ Lists are the workhorse of terminal interfaces. Managing selection state, scrolling windows, and highlight styles logic is complex. The `List` widget handles all of this.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **Scrolling**: Automatically handles lists larger than the view area.
15
+ - **Selection**: Maintains selected index and supports "no selection" state.
16
+ - **Highlighting**: Custom styles and symbols (e.g., `>>`) for the selected item.
17
+ - **Offset Modes**: Manual control over the scroll offset vs automatic "scroll to selection" behavior.
18
+ - **Scroll Padding**: Keeping a margin of items visible above/below the selection.
19
+
20
+ ## Hotkeys
21
+
22
+ - **i**: Cycle Item Data (`items`)
23
+ - **Arrow Keys (↑/↓)**: Navigate (`selected_index`)
24
+ - **x**: Toggle Selection (`selected_index`)
25
+ - **h**: Cycle Highlight Style (`highlight_style`)
26
+ - **y**: Cycle Highlight Symbol (`highlight_symbol`)
27
+ - **d**: Toggle Direction (`direction`)
28
+ - **s**: Cycle Highlight Spacing (`highlight_spacing`)
29
+ - **p**: Cycle Scroll Padding (`scroll_padding`)
30
+ - **b**: Cycle Base Style (`style`)
31
+ - **r**: Toggle Repeat Highlight Symbol (`repeat_highlight_symbol`)
32
+ - **o**: Cycle Offset Mode (`offset`)
33
+ - **q**: Quit
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ ruby examples/widget_list_demo/app.rb
39
+ ```
40
+
41
+ ## Learning Outcomes
42
+
43
+ Use this example if you need to...
44
+ - Create a file explorer.
45
+ - Build a navigation menu.
46
+ - Display a log where users can scroll back to read history.
47
+ - Implement "infinite select" behaviors.
48
+
49
+ ![Demo](/doc/images/widget_list_demo.png)
@@ -0,0 +1,366 @@
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
+ require "faker" # Use Faker for large, realistic datasets
9
+
10
+ # Demonstrates a selectable list of items with interactive attribute cycling.
11
+ #
12
+ # Users need to browse and select from collections of data. Lists are fundamental to terminal interfaces, but managing selection state, scrolling, and styling can be complex.
13
+ #
14
+ # This demo showcases the <tt>List</tt> widget. It provides an interactive playground where you can cycle through different configurations, styles, and behaviors in real-time.
15
+ #
16
+ # Use it to understand how to implement menus, file browsers, or any selectable collection of items.
17
+ #
18
+ # === Examples
19
+ #
20
+ # Run the demo from the terminal:
21
+ #
22
+ # ruby examples/widget_list_demo/app.rb
23
+ #
24
+ # rdoc-image:/doc/images/widget_list_demo.png
25
+ class WidgetListDemo
26
+ # Initializes the demo with example data and default configuration.
27
+ def initialize
28
+ Faker::Config.random = Random.new(12345)
29
+ @selected_index = 6 # Start at C# to avoid highlighting the rich text examples
30
+ @tui_for_setup = nil
31
+
32
+ @item_sets = [
33
+ {
34
+ name: "Programming",
35
+ items: [
36
+ :ruby_styled, # Will be replaced with rich text in run()
37
+ :rust_styled, # Will be replaced with rich text in run()
38
+ :python_styled, # Will be replaced with rich text in run()
39
+ :javascript_styled, # Will be replaced with rich text in run()
40
+ "Go",
41
+ "C++",
42
+ "C#",
43
+ "Java",
44
+ "Kotlin",
45
+ "Swift",
46
+ "Objective-C",
47
+ "PHP",
48
+ "TypeScript",
49
+ "Perl",
50
+ "Lua",
51
+ "R",
52
+ "Scala",
53
+ "Haskell",
54
+ "Elixir",
55
+ "Clojure",
56
+ "Groovy",
57
+ "Closure",
58
+ "VB.NET",
59
+ "F#",
60
+ "Erlang",
61
+ "Lisp",
62
+ "Scheme",
63
+ "Prolog",
64
+ "Fortran",
65
+ "COBOL",
66
+ "Pascal",
67
+ "Delphi",
68
+ "Ada",
69
+ "Bash",
70
+ "Sh",
71
+ "Tcl",
72
+ "Awk",
73
+ "sed",
74
+ "Vim Script",
75
+ "PowerShell",
76
+ "Batch",
77
+ "Assembly",
78
+ "Wasm",
79
+ "WebAssembly",
80
+ "Julia",
81
+ "Matlab",
82
+ "Octave",
83
+ "BASIC",
84
+ ],
85
+ },
86
+ {
87
+ name: "Large List",
88
+ items: (1..200).map { |i| "Item #{i}" },
89
+ },
90
+ {
91
+ name: "Colors",
92
+ items: begin
93
+ Faker::Color.unique.clear
94
+ Array.new(100) { Faker::Color.color_name }
95
+ end,
96
+ },
97
+ {
98
+ name: "Fruits",
99
+ items: begin
100
+ Faker::Food.unique.clear
101
+ Array.new(100) { Faker::Food.fruits }
102
+ end,
103
+ },
104
+ ]
105
+ @item_set_index = 0
106
+
107
+ @highlight_symbol_names = [">> ", "▶ ", "→ ", "• ", "★ "]
108
+ @highlight_symbol_index = 0
109
+
110
+ @direction_configs = [
111
+ { name: "Top to Bottom", direction: :top_to_bottom },
112
+ { name: "Bottom to Top", direction: :bottom_to_top },
113
+ ]
114
+ @direction_index = 0
115
+
116
+ @highlight_spacing_configs = [
117
+ { name: "When Selected", spacing: :when_selected },
118
+ { name: "Always", spacing: :always },
119
+ { name: "Never", spacing: :never },
120
+ ]
121
+ @highlight_spacing_index = 1
122
+
123
+ @repeat_modes = [
124
+ { name: "Off", repeat: false },
125
+ { name: "On", repeat: true },
126
+ ]
127
+ @repeat_index = 0
128
+
129
+ @scroll_padding_configs = [
130
+ { name: "None", padding: nil },
131
+ { name: "1 item", padding: 1 },
132
+ { name: "2 items", padding: 2 },
133
+ ]
134
+ @scroll_padding_index = 1
135
+
136
+ # Offset mode configurations to demonstrate offset + selection interaction
137
+ @offset_modes = [
138
+ { name: "Auto (No Offset)", offset: nil, allow_selection: true },
139
+ { name: "Offset Only", offset: 10, allow_selection: false },
140
+ { name: "Selection + Offset (Conflict)", offset: 0, allow_selection: true },
141
+ ]
142
+ @offset_mode_index = 0
143
+ end
144
+
145
+ # Runs the demo application.
146
+ #
147
+ # This method enters the terminal alternate screen, starts the main loop, and handles cleanup on exit.
148
+ def run
149
+ RatatuiRuby.run do |tui|
150
+ @tui = tui
151
+
152
+ # Create rich text for "Ruby" - each letter with a different red style
153
+ ruby_line = @tui.text_line(spans: [
154
+ @tui.text_span(content: "R", style: @tui.style(fg: :red, modifiers: [:underlined])),
155
+ @tui.text_span(content: "u", style: @tui.style(fg: :light_red, modifiers: [:bold])),
156
+ @tui.text_span(content: "b", style: @tui.style(fg: :red, modifiers: [:italic])),
157
+ @tui.text_span(content: "y", style: @tui.style(fg: :light_red, modifiers: [:reversed])),
158
+ ])
159
+
160
+ # Create rich text for "Rust" - single styled Span
161
+ rust_span = @tui.text_span(
162
+ content: "Rust",
163
+ style: @tui.style(fg: :magenta, modifiers: [:bold, :underlined])
164
+ )
165
+
166
+ # Create ListItem for "Python" - demonstrates content + row background
167
+ python_item = @tui.list_item(
168
+ content: @tui.text_span(content: "Python", style: @tui.style(fg: :yellow)),
169
+ style: @tui.style(bg: :dark_gray)
170
+ )
171
+
172
+ # Create ListItem for "JavaScript" - demonstrates styled text with row background
173
+ javascript_item = @tui.list_item(
174
+ content: @tui.text_line(spans: [
175
+ @tui.text_span(content: "Java", style: @tui.style(fg: :yellow, modifiers: [:bold])),
176
+ @tui.text_span(content: "Script", style: @tui.style(fg: :light_yellow, modifiers: [:italic])),
177
+ ]),
178
+ style: @tui.style(bg: :blue)
179
+ )
180
+
181
+ # Replace the styled placeholders
182
+ @item_sets[0][:items][0] = ruby_line
183
+ @item_sets[0][:items][1] = rust_span
184
+ @item_sets[0][:items][2] = python_item
185
+ @item_sets[0][:items][3] = javascript_item
186
+
187
+ # Initialize styles that require @tui
188
+ @highlight_styles = [
189
+ { name: "Blue on White Bold", style: @tui.style(fg: :blue, bg: :white, modifiers: [:bold]) },
190
+ { name: "Blue Bold", style: @tui.style(fg: :blue, modifiers: [:bold]) },
191
+ { name: "Yellow on Black", style: @tui.style(fg: :yellow, bg: :black) },
192
+ { name: "Green Italic", style: @tui.style(fg: :green, modifiers: [:italic]) },
193
+ { name: "White Reversed", style: @tui.style(fg: :white, modifiers: [:reversed]) },
194
+ { name: "Cyan Bold", style: @tui.style(fg: :cyan, modifiers: [:bold]) },
195
+ ]
196
+ @highlight_style_index = 0
197
+
198
+ @base_styles = [
199
+ { name: "None", style: nil },
200
+ { name: "Dark Gray", style: @tui.style(fg: :dark_gray) },
201
+ { name: "White on Black", style: @tui.style(fg: :white, bg: :black) },
202
+ ]
203
+ @base_style_index = 0
204
+
205
+ @hotkey_style = @tui.style(modifiers: [:bold, :underlined])
206
+
207
+ loop do
208
+ render
209
+ break if handle_input == :quit
210
+
211
+ sleep 0.05
212
+ end
213
+ end
214
+ end
215
+
216
+ # :nodoc:
217
+ private def render
218
+ items = @item_sets[@item_set_index][:items]
219
+ direction_config = @direction_configs[@direction_index]
220
+ spacing_config = @highlight_spacing_configs[@highlight_spacing_index]
221
+ repeat_config = @repeat_modes[@repeat_index]
222
+ highlight_style_config = @highlight_styles[@highlight_style_index]
223
+ highlight_symbol = @highlight_symbol_names[@highlight_symbol_index]
224
+ base_style_config = @base_styles[@base_style_index]
225
+ scroll_padding_config = @scroll_padding_configs[@scroll_padding_index]
226
+ offset_mode_config = @offset_modes[@offset_mode_index]
227
+
228
+ # Determine selection/offset based on mode
229
+ effective_selection = offset_mode_config[:allow_selection] ? @selected_index : nil
230
+ effective_offset = offset_mode_config[:offset]
231
+ selection_label = effective_selection.nil? ? "none" : effective_selection.to_s
232
+ offset_label = effective_offset.nil? ? "auto" : effective_offset.to_s
233
+
234
+ @tui.draw do |frame|
235
+ # Split into main content and control panel
236
+ main_area, control_area = @tui.layout_split(
237
+ frame.area,
238
+ direction: :vertical,
239
+ constraints: [
240
+ @tui.constraint_fill(1),
241
+ @tui.constraint_length(8),
242
+ ]
243
+ )
244
+
245
+ # Split main content into title and list
246
+ title_area, list_area = @tui.layout_split(
247
+ main_area,
248
+ direction: :vertical,
249
+ constraints: [
250
+ @tui.constraint_length(1),
251
+ @tui.constraint_fill(1),
252
+ ]
253
+ )
254
+
255
+ # Render title
256
+ title = @tui.paragraph(text: "List Widget Demo - Interactive Attribute Cycling")
257
+ frame.render_widget(title, title_area)
258
+
259
+ # Render list
260
+ list = @tui.list(
261
+ items:,
262
+ selected_index: effective_selection,
263
+ offset: effective_offset,
264
+ style: base_style_config[:style],
265
+ highlight_style: highlight_style_config[:style],
266
+ highlight_symbol:,
267
+ repeat_highlight_symbol: repeat_config[:repeat],
268
+ highlight_spacing: spacing_config[:spacing],
269
+ direction: direction_config[:direction],
270
+ scroll_padding: scroll_padding_config[:padding],
271
+ block: @tui.block(
272
+ title: "#{@item_sets[@item_set_index][:name]} | Sel: #{selection_label} | Offset: #{offset_label}",
273
+ borders: [:all]
274
+ )
275
+ )
276
+ frame.render_widget(list, list_area)
277
+
278
+ # Render control panel
279
+ control_panel = @tui.block(
280
+ title: "Controls",
281
+ borders: [:all],
282
+ children: [
283
+ @tui.paragraph(
284
+ text: [
285
+ @tui.text_line(spans: [
286
+ @tui.text_span(content: "i", style: @hotkey_style),
287
+ @tui.text_span(content: ": Items "),
288
+ @tui.text_span(content: "↑/↓", style: @hotkey_style),
289
+ @tui.text_span(content: ": Navigate "),
290
+ @tui.text_span(content: "x", style: @hotkey_style),
291
+ @tui.text_span(content: ": Select "),
292
+ @tui.text_span(content: "h", style: @hotkey_style),
293
+ @tui.text_span(content: ": Highlight (#{highlight_style_config[:name]})"),
294
+ ]),
295
+ @tui.text_line(spans: [
296
+ @tui.text_span(content: "y", style: @hotkey_style),
297
+ @tui.text_span(content: ": Symbol (#{highlight_symbol}) "),
298
+ @tui.text_span(content: "d", style: @hotkey_style),
299
+ @tui.text_span(content: ": Direction (#{direction_config[:name]})"),
300
+ ]),
301
+ @tui.text_line(spans: [
302
+ @tui.text_span(content: "s", style: @hotkey_style),
303
+ @tui.text_span(content: ": Spacing (#{spacing_config[:name]}) "),
304
+ @tui.text_span(content: "p", style: @hotkey_style),
305
+ @tui.text_span(content: ": Scroll Padding (#{scroll_padding_config[:name]})"),
306
+ ]),
307
+ @tui.text_line(spans: [
308
+ @tui.text_span(content: "b", style: @hotkey_style),
309
+ @tui.text_span(content: ": Base (#{base_style_config[:name]}) "),
310
+ @tui.text_span(content: "r", style: @hotkey_style),
311
+ @tui.text_span(content: ": Repeat (#{repeat_config[:name]})"),
312
+ ]),
313
+ @tui.text_line(spans: [
314
+ @tui.text_span(content: "o", style: @hotkey_style),
315
+ @tui.text_span(content: ": Offset Mode (#{offset_mode_config[:name]}) "),
316
+ @tui.text_span(content: "q", style: @hotkey_style),
317
+ @tui.text_span(content: ": Quit"),
318
+ ]),
319
+ ]
320
+ ),
321
+ ]
322
+ )
323
+ frame.render_widget(control_panel, control_area)
324
+ end
325
+ end
326
+
327
+ # :nodoc:
328
+ private def handle_input
329
+ case @tui.poll_event
330
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
331
+ :quit
332
+ in type: :key, code: "i"
333
+ @item_set_index = (@item_set_index + 1) % @item_sets.size
334
+ @selected_index = nil
335
+ in type: :key, code: "up"
336
+ items = @item_sets[@item_set_index][:items]
337
+ @selected_index = (@selected_index || 0) - 1
338
+ @selected_index = items.size - 1 if @selected_index.negative?
339
+ in type: :key, code: "down"
340
+ items = @item_sets[@item_set_index][:items]
341
+ @selected_index = ((@selected_index || -1) + 1) % items.size
342
+ in type: :key, code: "x"
343
+ @selected_index = @selected_index.nil? ? 0 : nil
344
+ in type: :key, code: "h"
345
+ @highlight_style_index = (@highlight_style_index + 1) % @highlight_styles.size
346
+ in type: :key, code: "y"
347
+ @highlight_symbol_index = (@highlight_symbol_index + 1) % @highlight_symbol_names.size
348
+ in type: :key, code: "d"
349
+ @direction_index = (@direction_index + 1) % @direction_configs.size
350
+ in type: :key, code: "s"
351
+ @highlight_spacing_index = (@highlight_spacing_index + 1) % @highlight_spacing_configs.size
352
+ in type: :key, code: "b"
353
+ @base_style_index = (@base_style_index + 1) % @base_styles.size
354
+ in type: :key, code: "r"
355
+ @repeat_index = (@repeat_index + 1) % @repeat_modes.size
356
+ in type: :key, code: "p"
357
+ @scroll_padding_index = (@scroll_padding_index + 1) % @scroll_padding_configs.size
358
+ in type: :key, code: "o"
359
+ @offset_mode_index = (@offset_mode_index + 1) % @offset_modes.size
360
+ else
361
+ nil
362
+ end
363
+ end
364
+ end
365
+
366
+ WidgetListDemo.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,39 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Canvas Widget Example
7
+
8
+ Demonstrates drawing custom graphics and maps using the standard Braille and Block patterns.
9
+
10
+ Standard widgets are great for text, but sometimes you need to draw. The `Canvas` widget gives you a high-resolution coordinate system (x, y) to render shapes, lines, and data visualizations that go beyond the grid.
11
+
12
+ ## Features Demonstrated
13
+
14
+ - **High-Resolution Drawing**: Using Braille patterns (`⣿`) to effectively double the vertical and horizontal resolution of the terminal.
15
+ - **Layers**: Drawing multiple shapes (Map, Circles, Lines) in a specific order.
16
+ - **Animation**: Updating coordinates in a loop to create smooth motion.
17
+ - **World Map**: Using the built-in `Map` shape for geographic data.
18
+
19
+ ## Hotkeys
20
+
21
+ - **b**: Cycle Background Color (`background_color`)
22
+ - **m**: Cycle Marker Type (`marker`)
23
+ - **l**: Toggle Labels (modifies `shapes`)
24
+ - **q**: Quit
25
+
26
+ ## Usage
27
+
28
+ ```bash
29
+ ruby examples/widget_map_demo/app.rb
30
+ ```
31
+
32
+ ## Learning Outcomes
33
+
34
+ Use this example if you need to...
35
+ - Render geographic data (World, USA, Europe).
36
+ - Overlay custom labels and markers on a map.
37
+ - Animate visual elements on top of a static background.
38
+
39
+ ![Demo](/doc/images/widget_map_demo.png)