ratatui_ruby 0.5.0 → 0.7.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 (311) 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 +10 -4
  7. data/CHANGELOG.md +79 -7
  8. data/README.md +37 -5
  9. data/REUSE.toml +2 -7
  10. data/doc/application_architecture.md +96 -22
  11. data/doc/application_testing.md +76 -30
  12. data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
  13. data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
  14. data/doc/contributors/architectural_overhaul/task.md +37 -0
  15. data/doc/contributors/design/ruby_frontend.md +288 -56
  16. data/doc/contributors/design/rust_backend.md +349 -54
  17. data/doc/contributors/developing_examples.md +134 -49
  18. data/doc/contributors/index.md +7 -5
  19. data/doc/contributors/v1.0.0_blockers.md +1729 -0
  20. data/doc/event_handling.md +11 -3
  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_canvas_demo.png +0 -0
  32. data/doc/images/widget_cell_demo.png +0 -0
  33. data/doc/images/widget_center_demo.png +0 -0
  34. data/doc/images/widget_chart_demo.png +0 -0
  35. data/doc/images/widget_list_demo.png +0 -0
  36. data/doc/images/widget_overlay_demo.png +0 -0
  37. data/doc/images/widget_render.png +0 -0
  38. data/doc/images/widget_rich_text.png +0 -0
  39. data/doc/images/widget_scroll_text.png +0 -0
  40. data/doc/images/widget_sparkline_demo.png +0 -0
  41. data/doc/images/widget_table_demo.png +0 -0
  42. data/doc/images/widget_tabs_demo.png +0 -0
  43. data/doc/images/widget_text_width.png +0 -0
  44. data/doc/index.md +11 -6
  45. data/doc/interactive_design.md +2 -2
  46. data/doc/quickstart.md +127 -165
  47. data/doc/terminal_limitations.md +92 -0
  48. data/doc/v0.7.0_migration.md +236 -0
  49. data/doc/why.md +93 -0
  50. data/examples/app_all_events/README.md +47 -27
  51. data/examples/app_all_events/app.rb +38 -35
  52. data/examples/app_all_events/model/app_model.rb +157 -0
  53. data/examples/app_all_events/model/event_entry.rb +17 -0
  54. data/examples/app_all_events/model/msg.rb +37 -0
  55. data/examples/app_all_events/update.rb +73 -0
  56. data/examples/app_all_events/view/app_view.rb +9 -9
  57. data/examples/app_all_events/view/controls_view.rb +9 -7
  58. data/examples/app_all_events/view/counts_view.rb +13 -9
  59. data/examples/app_all_events/view/live_view.rb +9 -8
  60. data/examples/app_all_events/view/log_view.rb +11 -16
  61. data/examples/app_color_picker/README.md +84 -42
  62. data/examples/app_color_picker/app.rb +24 -62
  63. data/examples/app_color_picker/controls.rb +90 -0
  64. data/examples/app_color_picker/copy_dialog.rb +45 -49
  65. data/examples/app_color_picker/export_pane.rb +126 -0
  66. data/examples/app_color_picker/input.rb +99 -67
  67. data/examples/app_color_picker/main_container.rb +178 -0
  68. data/examples/app_color_picker/palette.rb +55 -26
  69. data/examples/app_login_form/README.md +49 -0
  70. data/examples/app_login_form/app.rb +2 -3
  71. data/examples/app_stateful_interaction/README.md +33 -0
  72. data/examples/app_stateful_interaction/app.rb +272 -0
  73. data/examples/timeout_demo.rb +43 -0
  74. data/examples/verify_quickstart_dsl/README.md +49 -0
  75. data/examples/verify_quickstart_dsl/app.rb +2 -0
  76. data/examples/verify_quickstart_layout/README.md +71 -0
  77. data/examples/verify_quickstart_layout/app.rb +2 -0
  78. data/examples/verify_quickstart_lifecycle/README.md +56 -0
  79. data/examples/verify_quickstart_lifecycle/app.rb +10 -4
  80. data/examples/verify_readme_usage/README.md +43 -0
  81. data/examples/verify_readme_usage/app.rb +8 -2
  82. data/examples/widget_barchart_demo/README.md +50 -0
  83. data/examples/widget_barchart_demo/app.rb +5 -5
  84. data/examples/widget_block_demo/README.md +36 -0
  85. data/examples/widget_block_demo/app.rb +256 -0
  86. data/examples/widget_box_demo/README.md +45 -0
  87. data/examples/widget_calendar_demo/README.md +39 -0
  88. data/examples/widget_calendar_demo/app.rb +5 -1
  89. data/examples/widget_canvas_demo/README.md +27 -0
  90. data/examples/widget_canvas_demo/app.rb +123 -0
  91. data/examples/widget_cell_demo/README.md +36 -0
  92. data/examples/widget_cell_demo/app.rb +31 -24
  93. data/examples/widget_center_demo/README.md +29 -0
  94. data/examples/widget_center_demo/app.rb +116 -0
  95. data/examples/widget_chart_demo/README.md +41 -0
  96. data/examples/widget_chart_demo/app.rb +7 -2
  97. data/examples/widget_gauge_demo/README.md +41 -0
  98. data/examples/widget_layout_split/README.md +44 -0
  99. data/examples/widget_line_gauge_demo/README.md +41 -0
  100. data/examples/widget_list_demo/README.md +49 -0
  101. data/examples/widget_list_demo/app.rb +91 -107
  102. data/examples/widget_map_demo/README.md +39 -0
  103. data/examples/{app_map_demo → widget_map_demo}/app.rb +4 -4
  104. data/examples/widget_overlay_demo/README.md +36 -0
  105. data/examples/widget_overlay_demo/app.rb +248 -0
  106. data/examples/widget_popup_demo/README.md +36 -0
  107. data/examples/widget_ratatui_logo_demo/README.md +34 -0
  108. data/examples/widget_ratatui_logo_demo/app.rb +1 -1
  109. data/examples/widget_ratatui_mascot_demo/README.md +34 -0
  110. data/examples/widget_rect/README.md +38 -0
  111. data/examples/widget_render/README.md +37 -0
  112. data/examples/widget_render/app.rb +3 -3
  113. data/examples/widget_rich_text/README.md +35 -0
  114. data/examples/widget_rich_text/app.rb +62 -33
  115. data/examples/widget_scroll_text/README.md +37 -0
  116. data/examples/widget_scroll_text/app.rb +0 -1
  117. data/examples/widget_scrollbar_demo/README.md +37 -0
  118. data/examples/widget_sparkline_demo/README.md +42 -0
  119. data/examples/widget_sparkline_demo/app.rb +4 -3
  120. data/examples/widget_style_colors/README.md +34 -0
  121. data/examples/widget_table_demo/README.md +48 -0
  122. data/examples/{app_table_select → widget_table_demo}/app.rb +65 -12
  123. data/examples/widget_tabs_demo/README.md +41 -0
  124. data/examples/widget_tabs_demo/app.rb +15 -1
  125. data/examples/widget_text_width/README.md +35 -0
  126. data/examples/widget_text_width/app.rb +113 -0
  127. data/exe/.gitkeep +0 -0
  128. data/ext/ratatui_ruby/Cargo.lock +11 -4
  129. data/ext/ratatui_ruby/Cargo.toml +2 -1
  130. data/ext/ratatui_ruby/src/events.rs +238 -26
  131. data/ext/ratatui_ruby/src/frame.rs +116 -3
  132. data/ext/ratatui_ruby/src/lib.rs +37 -6
  133. data/ext/ratatui_ruby/src/rendering.rs +22 -21
  134. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  135. data/ext/ratatui_ruby/src/terminal.rs +39 -15
  136. data/ext/ratatui_ruby/src/text.rs +13 -4
  137. data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
  138. data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
  139. data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
  140. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
  141. data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
  142. data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
  143. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  144. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
  145. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  146. data/ext/ratatui_ruby/src/widgets/table.rs +191 -34
  147. data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
  148. data/lib/ratatui_ruby/buffer/cell.rb +168 -0
  149. data/lib/ratatui_ruby/buffer.rb +15 -0
  150. data/lib/ratatui_ruby/cell.rb +4 -4
  151. data/lib/ratatui_ruby/event/key/character.rb +35 -0
  152. data/lib/ratatui_ruby/event/key/media.rb +44 -0
  153. data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
  154. data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
  155. data/lib/ratatui_ruby/event/key/system.rb +45 -0
  156. data/lib/ratatui_ruby/event/key.rb +111 -51
  157. data/lib/ratatui_ruby/event/mouse.rb +3 -3
  158. data/lib/ratatui_ruby/event/paste.rb +1 -1
  159. data/lib/ratatui_ruby/frame.rb +100 -4
  160. data/lib/ratatui_ruby/layout/constraint.rb +95 -0
  161. data/lib/ratatui_ruby/layout/layout.rb +106 -0
  162. data/lib/ratatui_ruby/layout/rect.rb +118 -0
  163. data/lib/ratatui_ruby/layout.rb +19 -0
  164. data/lib/ratatui_ruby/list_state.rb +88 -0
  165. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
  166. data/lib/ratatui_ruby/schema/cursor.rb +5 -0
  167. data/lib/ratatui_ruby/schema/gauge.rb +3 -1
  168. data/lib/ratatui_ruby/schema/layout.rb +1 -1
  169. data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
  170. data/lib/ratatui_ruby/schema/list.rb +25 -4
  171. data/lib/ratatui_ruby/schema/list_item.rb +41 -0
  172. data/lib/ratatui_ruby/schema/rect.rb +43 -0
  173. data/lib/ratatui_ruby/schema/row.rb +66 -0
  174. data/lib/ratatui_ruby/schema/style.rb +24 -4
  175. data/lib/ratatui_ruby/schema/table.rb +29 -11
  176. data/lib/ratatui_ruby/schema/text.rb +96 -3
  177. data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
  178. data/lib/ratatui_ruby/style/style.rb +81 -0
  179. data/lib/ratatui_ruby/style.rb +15 -0
  180. data/lib/ratatui_ruby/table_state.rb +90 -0
  181. data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
  182. data/lib/ratatui_ruby/test_helper/snapshot.rb +414 -0
  183. data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
  184. data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
  185. data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
  186. data/lib/ratatui_ruby/test_helper.rb +65 -358
  187. data/lib/ratatui_ruby/tui/buffer_factories.rb +20 -0
  188. data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
  189. data/lib/ratatui_ruby/tui/core.rb +38 -0
  190. data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
  191. data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
  192. data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
  193. data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
  194. data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
  195. data/lib/ratatui_ruby/tui.rb +75 -0
  196. data/lib/ratatui_ruby/version.rb +1 -1
  197. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
  198. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
  199. data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
  200. data/lib/ratatui_ruby/widgets/block.rb +192 -0
  201. data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
  202. data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
  203. data/lib/ratatui_ruby/widgets/cell.rb +47 -0
  204. data/lib/ratatui_ruby/widgets/center.rb +59 -0
  205. data/lib/ratatui_ruby/widgets/chart.rb +185 -0
  206. data/lib/ratatui_ruby/widgets/clear.rb +54 -0
  207. data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
  208. data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
  209. data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
  210. data/lib/ratatui_ruby/widgets/list.rb +127 -0
  211. data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
  212. data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
  213. data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
  214. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
  215. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
  216. data/lib/ratatui_ruby/widgets/row.rb +68 -0
  217. data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
  218. data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
  219. data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
  220. data/lib/ratatui_ruby/widgets/table.rb +141 -0
  221. data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
  222. data/lib/ratatui_ruby/widgets.rb +40 -0
  223. data/lib/ratatui_ruby.rb +64 -57
  224. data/sig/examples/app_all_events/view.rbs +1 -1
  225. data/sig/examples/app_all_events/view_state.rbs +1 -1
  226. data/sig/examples/app_stateful_interaction/app.rbs +33 -0
  227. data/sig/examples/widget_block_demo/app.rbs +32 -0
  228. data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
  229. data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
  230. data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
  231. data/sig/ratatui_ruby/event.rbs +11 -1
  232. data/sig/ratatui_ruby/frame.rbs +2 -0
  233. data/sig/ratatui_ruby/list_state.rbs +13 -0
  234. data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
  235. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
  236. data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
  237. data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
  238. data/sig/ratatui_ruby/schema/list.rbs +4 -2
  239. data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
  240. data/sig/ratatui_ruby/schema/rect.rbs +3 -0
  241. data/sig/ratatui_ruby/schema/row.rbs +22 -0
  242. data/sig/ratatui_ruby/schema/style.rbs +3 -3
  243. data/sig/ratatui_ruby/schema/table.rbs +3 -1
  244. data/sig/ratatui_ruby/schema/text.rbs +9 -6
  245. data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
  246. data/sig/ratatui_ruby/session.rbs +41 -48
  247. data/sig/ratatui_ruby/table_state.rbs +15 -0
  248. data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
  249. data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
  250. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
  251. data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
  252. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
  253. data/sig/ratatui_ruby/test_helper.rbs +5 -4
  254. data/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
  255. data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
  256. data/sig/ratatui_ruby/tui/core.rbs +14 -0
  257. data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
  258. data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
  259. data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
  260. data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
  261. data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
  262. data/sig/ratatui_ruby/tui.rbs +19 -0
  263. data/tasks/autodoc/examples.rb +79 -0
  264. data/tasks/autodoc.rake +7 -35
  265. data/tasks/bump/changelog.rb +3 -3
  266. data/tasks/bump/links.rb +67 -0
  267. data/tasks/sourcehut.rake +64 -21
  268. data/tasks/terminal_preview/app_screenshot.rb +13 -3
  269. data/tasks/terminal_preview/saved_screenshot.rb +4 -3
  270. metadata +169 -48
  271. data/doc/contributors/dwim_dx.md +0 -366
  272. data/doc/images/app_analytics.png +0 -0
  273. data/doc/images/app_custom_widget.png +0 -0
  274. data/doc/images/app_mouse_events.png +0 -0
  275. data/doc/images/app_table_select.png +0 -0
  276. data/doc/images/widget_block_padding.png +0 -0
  277. data/doc/images/widget_block_titles.png +0 -0
  278. data/doc/images/widget_list_styles.png +0 -0
  279. data/doc/images/widget_table_flex.png +0 -0
  280. data/examples/app_all_events/model/events.rb +0 -180
  281. data/examples/app_all_events/model/highlight.rb +0 -57
  282. data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
  283. data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
  284. data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
  285. data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
  286. data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
  287. data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
  288. data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
  289. data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
  290. data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
  291. data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
  292. data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
  293. data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
  294. data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
  295. data/examples/app_all_events/view_state.rb +0 -42
  296. data/examples/app_color_picker/scene.rb +0 -201
  297. data/examples/widget_block_padding/app.rb +0 -67
  298. data/examples/widget_block_titles/app.rb +0 -69
  299. data/examples/widget_list_styles/app.rb +0 -141
  300. data/examples/widget_table_flex/app.rb +0 -95
  301. data/lib/ratatui_ruby/session/autodoc.rb +0 -417
  302. data/lib/ratatui_ruby/session.rb +0 -163
  303. data/sig/examples/widget_block_padding/app.rbs +0 -11
  304. data/sig/examples/widget_block_titles/app.rbs +0 -11
  305. data/sig/examples/widget_list_styles/app.rbs +0 -11
  306. data/tasks/autodoc/inventory.rb +0 -61
  307. data/tasks/autodoc/notice.rb +0 -26
  308. data/tasks/autodoc/rbs.rb +0 -38
  309. data/tasks/autodoc/rdoc.rb +0 -45
  310. data/tasks/bump/comparison_links.rb +0 -41
  311. /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
@@ -5,6 +5,7 @@
5
5
 
6
6
  $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
7
  require "ratatui_ruby"
8
+ require "faker" # Use Faker for large, realistic datasets
8
9
 
9
10
  # Demonstrates a selectable list of items with interactive attribute cycling.
10
11
  #
@@ -24,111 +25,18 @@ require "ratatui_ruby"
24
25
  class WidgetListDemo
25
26
  # Initializes the demo with example data and default configuration.
26
27
  def initialize
27
- @selected_index = nil
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
28
31
 
29
32
  @item_sets = [
30
- {
31
- name: "Large List",
32
- items: (1..200).map { |i| "Item #{i}" },
33
- },
34
- {
35
- name: "Colors",
36
- items: [
37
- "Red",
38
- "Orange",
39
- "Yellow",
40
- "Green",
41
- "Cyan",
42
- "Blue",
43
- "Indigo",
44
- "Violet",
45
- "Scarlet",
46
- "Crimson",
47
- "Maroon",
48
- "Brown",
49
- "Tan",
50
- "Beige",
51
- "Khaki",
52
- "Gold",
53
- "Silver",
54
- "White",
55
- "Gray",
56
- "Black",
57
- "Pink",
58
- "Magenta",
59
- "Turquoise",
60
- "Teal",
61
- "Coral",
62
- "Salmon",
63
- "Peach",
64
- "Lavender",
65
- "Lilac",
66
- "Olive",
67
- "Lime",
68
- "Navy",
69
- "Charcoal",
70
- "Ivory",
71
- "Azure",
72
- ],
73
- },
74
- {
75
- name: "Fruits",
76
- items: [
77
- "Apple",
78
- "Apricot",
79
- "Avocado",
80
- "Banana",
81
- "Blueberry",
82
- "Blackberry",
83
- "Cherry",
84
- "Cranberry",
85
- "Cucumber",
86
- "Date",
87
- "Dragonfruit",
88
- "Elderberry",
89
- "Fig",
90
- "Grape",
91
- "Grapefruit",
92
- "Guava",
93
- "Honeydew",
94
- "Huckleberry",
95
- "Jackfruit",
96
- "Kiwi",
97
- "Kumquat",
98
- "Lemon",
99
- "Lime",
100
- "Lychee",
101
- "Mango",
102
- "Melon",
103
- "Mulberry",
104
- "Nectarine",
105
- "Olive",
106
- "Orange",
107
- "Papaya",
108
- "Passion Fruit",
109
- "Peach",
110
- "Pear",
111
- "Persimmon",
112
- "Pineapple",
113
- "Plum",
114
- "Pomegranate",
115
- "Prune",
116
- "Rambutan",
117
- "Raspberry",
118
- "Starfruit",
119
- "Strawberry",
120
- "Tangerine",
121
- "Watermelon",
122
- "Ugli Fruit",
123
- ],
124
- },
125
33
  {
126
34
  name: "Programming",
127
35
  items: [
128
- "Ruby",
129
- "Rust",
130
- "Python",
131
- "JavaScript",
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()
132
40
  "Go",
133
41
  "C++",
134
42
  "C#",
@@ -175,6 +83,24 @@ class WidgetListDemo
175
83
  "BASIC",
176
84
  ],
177
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
+ },
178
104
  ]
179
105
  @item_set_index = 0
180
106
 
@@ -192,7 +118,7 @@ class WidgetListDemo
192
118
  { name: "Always", spacing: :always },
193
119
  { name: "Never", spacing: :never },
194
120
  ]
195
- @highlight_spacing_index = 0
121
+ @highlight_spacing_index = 1
196
122
 
197
123
  @repeat_modes = [
198
124
  { name: "Off", repeat: false },
@@ -205,7 +131,15 @@ class WidgetListDemo
205
131
  { name: "1 item", padding: 1 },
206
132
  { name: "2 items", padding: 2 },
207
133
  ]
208
- @scroll_padding_index = 0
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
209
143
  end
210
144
 
211
145
  # Runs the demo application.
@@ -214,8 +148,45 @@ class WidgetListDemo
214
148
  def run
215
149
  RatatuiRuby.run do |tui|
216
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
+
217
187
  # Initialize styles that require @tui
218
188
  @highlight_styles = [
189
+ { name: "Blue on White Bold", style: @tui.style(fg: :blue, bg: :white, modifiers: [:bold]) },
219
190
  { name: "Blue Bold", style: @tui.style(fg: :blue, modifiers: [:bold]) },
220
191
  { name: "Yellow on Black", style: @tui.style(fg: :yellow, bg: :black) },
221
192
  { name: "Green Italic", style: @tui.style(fg: :green, modifiers: [:italic]) },
@@ -245,7 +216,6 @@ class WidgetListDemo
245
216
  # :nodoc:
246
217
  private def render
247
218
  items = @item_sets[@item_set_index][:items]
248
- selection_label = @selected_index.nil? ? "none" : @selected_index.to_s
249
219
  direction_config = @direction_configs[@direction_index]
250
220
  spacing_config = @highlight_spacing_configs[@highlight_spacing_index]
251
221
  repeat_config = @repeat_modes[@repeat_index]
@@ -253,6 +223,13 @@ class WidgetListDemo
253
223
  highlight_symbol = @highlight_symbol_names[@highlight_symbol_index]
254
224
  base_style_config = @base_styles[@base_style_index]
255
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
256
233
 
257
234
  @tui.draw do |frame|
258
235
  # Split into main content and control panel
@@ -261,7 +238,7 @@ class WidgetListDemo
261
238
  direction: :vertical,
262
239
  constraints: [
263
240
  @tui.constraint_fill(1),
264
- @tui.constraint_length(7),
241
+ @tui.constraint_length(8),
265
242
  ]
266
243
  )
267
244
 
@@ -282,7 +259,8 @@ class WidgetListDemo
282
259
  # Render list
283
260
  list = @tui.list(
284
261
  items:,
285
- selected_index: @selected_index,
262
+ selected_index: effective_selection,
263
+ offset: effective_offset,
286
264
  style: base_style_config[:style],
287
265
  highlight_style: highlight_style_config[:style],
288
266
  highlight_symbol:,
@@ -291,7 +269,7 @@ class WidgetListDemo
291
269
  direction: direction_config[:direction],
292
270
  scroll_padding: scroll_padding_config[:padding],
293
271
  block: @tui.block(
294
- title: "#{@item_sets[@item_set_index][:name]} (Selection: #{selection_label})",
272
+ title: "#{@item_sets[@item_set_index][:name]} | Sel: #{selection_label} | Offset: #{offset_label}",
295
273
  borders: [:all]
296
274
  )
297
275
  )
@@ -330,7 +308,11 @@ class WidgetListDemo
330
308
  @tui.text_span(content: "b", style: @hotkey_style),
331
309
  @tui.text_span(content: ": Base (#{base_style_config[:name]}) "),
332
310
  @tui.text_span(content: "r", style: @hotkey_style),
333
- @tui.text_span(content: ": Repeat (#{repeat_config[:name]}) "),
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]}) "),
334
316
  @tui.text_span(content: "q", style: @hotkey_style),
335
317
  @tui.text_span(content: ": Quit"),
336
318
  ]),
@@ -373,6 +355,8 @@ class WidgetListDemo
373
355
  @repeat_index = (@repeat_index + 1) % @repeat_modes.size
374
356
  in type: :key, code: "p"
375
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
376
360
  else
377
361
  nil
378
362
  end
@@ -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
+ [![widget_map_demo](../../doc/images/widget_map_demo.png)](app.rb)
9
+
10
+ Demonstrates drawing custom graphics and maps using the standard Braille and Block patterns.
11
+
12
+ 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.
13
+
14
+ ## Features Demonstrated
15
+
16
+ - **High-Resolution Drawing**: Using Braille patterns (`⣿`) to effectively double the vertical and horizontal resolution of the terminal.
17
+ - **Layers**: Drawing multiple shapes (Map, Circles, Lines) in a specific order.
18
+ - **Animation**: Updating coordinates in a loop to create smooth motion.
19
+ - **World Map**: Using the built-in `Map` shape for geographic data.
20
+
21
+ ## Hotkeys
22
+
23
+ - **b**: Cycle Background Color (`background_color`)
24
+ - **m**: Cycle Marker Type (`marker`)
25
+ - **l**: Toggle Labels (modifies `shapes`)
26
+ - **q**: Quit
27
+
28
+ ## Usage
29
+
30
+ ```bash
31
+ ruby examples/widget_map_demo/app.rb
32
+ ```
33
+
34
+ ## Learning Outcomes
35
+
36
+ Use this example if you need to...
37
+ - Render geographic data (World, USA, Europe).
38
+ - Overlay custom labels and markers on a map.
39
+ - Animate visual elements on top of a static background.
@@ -7,15 +7,15 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
7
  require "ratatui_ruby"
8
8
 
9
9
  # An example of the Canvas widget showing a world map and animated shapes.
10
- class AppMapDemo
11
- include RatatuiRuby
10
+ class WidgetMapDemo
11
+ include RatatuiRuby::Widgets
12
12
 
13
13
  COLORS = [:black, :blue, :white, nil].freeze
14
14
  MARKERS = [:braille, :half_block, :dot, :block, :bar, :quadrant, :sextant, :octant].freeze
15
15
 
16
16
  # Returns a Canvas view for the map demo with the given circle radius.
17
17
  #
18
- # +tui+:: The RatatuiRuby::Session instance.
18
+ # +tui+:: The RatatuiRuby::TUI instance.
19
19
  # +radius+:: The radius of the animated circle.
20
20
  # +marker+:: The marker type.
21
21
  # +background_color+:: The background color of the canvas.
@@ -90,4 +90,4 @@ class AppMapDemo
90
90
  end
91
91
  end
92
92
 
93
- AppMapDemo.new.run if __FILE__ == $PROGRAM_NAME
93
+ WidgetMapDemo.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,36 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+ # Overlay Widget Demo
6
+
7
+ [![Overlay Demo](../../doc/images/widget_overlay_demo.png)](app.rb)
8
+
9
+ This example demonstrates the `Overlay` composition pattern for layering widgets with depth. Modals, notifications, and floating panels all require stacking widgets on top of each other.
10
+
11
+ ## Key Concepts
12
+
13
+ - **Layer Composition:** Rendering widgets in order creates visual depth — later renders appear "on top."
14
+ - **Clear Widget:** Using `tui.clear` before rendering a modal erases the background, preventing content bleed-through.
15
+ - **Dynamic Layer Control:** Toggle the number of visible overlay layers at runtime.
16
+ - **Layer Ordering:** Swap which overlay appears in front to demonstrate z-ordering.
17
+
18
+ ## Hotkeys
19
+
20
+ - `0`/`1`/`2`: Set number of visible overlay layers
21
+ - `space`: Swap overlay order (which modal is on top)
22
+ - `c`: Toggle Clear widget (on/off)
23
+ - `q`: Quit
24
+
25
+ ## Usage
26
+
27
+ ```bash
28
+ ruby examples/widget_overlay_demo/app.rb
29
+ ```
30
+
31
+ ## Learning Outcomes
32
+
33
+ Use this example if you need to...
34
+ - Build modal dialogs or confirmation popups.
35
+ - Layer notifications over existing content.
36
+ - Understand the Clear widget's role in opaque overlays.
@@ -0,0 +1,248 @@
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
+ HEADLINES = [
10
+ "Scientists Discover New Species of Deep-Sea Octopus Near Hawaii",
11
+ "Global Climate Summit Reaches Historic Agreement on Emissions",
12
+ "Tech Giant Announces Breakthrough in Quantum Computing Research",
13
+ "Local Community Garden Initiative Expands to Ten More Cities",
14
+ "Astronomers Detect Unusual Radio Signals from Distant Galaxy",
15
+ "New Study Links Mediterranean Diet to Improved Heart Health",
16
+ "Electric Vehicle Sales Surge as Battery Technology Improves",
17
+ "Ancient Manuscripts Reveal Previously Unknown Trading Routes",
18
+ "Renewable Energy Now Powers 40% of National Grid",
19
+ "Robotics Team Develops AI System for Disaster Response",
20
+ "Archaeological Dig Uncovers Evidence of Early Human Settlement",
21
+ "Major Airline Commits to Carbon-Neutral Flights by 2035",
22
+ "Breakthrough Treatment Shows Promise for Rare Genetic Disease",
23
+ "City Council Approves Expanded Public Transportation Network",
24
+ "Marine Biologists Track Migration Patterns of Endangered Whales",
25
+ "New App Helps Farmers Optimize Water Usage During Drought",
26
+ "International Space Station Extends Mission Timeline to 2030",
27
+ "Local Schools Implement Innovative STEM Education Program",
28
+ "Wildlife Conservation Efforts Lead to Species Population Recovery",
29
+ "Research Team Creates Biodegradable Alternative to Plastic Packaging",
30
+ "Historic Theater Restoration Project Nears Completion",
31
+ "Cybersecurity Experts Warn of Emerging Online Threats",
32
+ "Community Food Bank Serves Record Number of Families This Year",
33
+ "Innovative Urban Planning Reduces Traffic Congestion by 30%",
34
+ ].freeze
35
+
36
+ # Overlay Demo Example
37
+ # Demonstrates the Overlay widget for layering widgets with depth.
38
+ class WidgetOverlayDemo
39
+ def initialize
40
+ @layer_count = 2 # Start with 2 layers visible
41
+ @swapped = false
42
+ @clear = true
43
+ end
44
+
45
+ def run
46
+ RatatuiRuby.run do |tui|
47
+ @tui = tui
48
+ loop do
49
+ tui.draw do |frame|
50
+ render(frame)
51
+ end
52
+ break if handle_input == :quit
53
+ sleep 0.05
54
+ end
55
+ end
56
+ end
57
+
58
+ private def render(frame)
59
+ area = frame.area
60
+
61
+ # Split into main area and control panel
62
+ layout = @tui.layout_split(
63
+ area,
64
+ direction: :vertical,
65
+ constraints: [
66
+ @tui.constraint_fill(1),
67
+ @tui.constraint_length(5),
68
+ ]
69
+ )
70
+
71
+ main_area = layout[0]
72
+ control_area = layout[1]
73
+
74
+ # Render background layer - RSS reader
75
+ frame.render_widget(background_layer, main_area)
76
+
77
+ # Render upper layers based on layer_count and swap state
78
+ if @swapped
79
+ render_beta_layer(frame, main_area) if @layer_count >= 2
80
+ render_notification_layer(frame, main_area) if @layer_count >= 1
81
+ else
82
+ render_notification_layer(frame, main_area) if @layer_count >= 1
83
+ render_beta_layer(frame, main_area) if @layer_count >= 2
84
+ end
85
+
86
+ # Render control panel
87
+ frame.render_widget(control_panel, control_area)
88
+ end
89
+
90
+ def background_layer
91
+ @background_layer ||= @tui.list(
92
+ items: HEADLINES,
93
+ block: @tui.block(
94
+ title: "RSS Reader",
95
+ borders: [:all]
96
+ )
97
+ )
98
+ end
99
+
100
+ def render_notification_layer(frame, area)
101
+ # Position modal: 20% from top, 60% height, 15% from left, 70% width
102
+
103
+ vertical_sections = @tui.layout_split(
104
+ area,
105
+ direction: :vertical,
106
+ constraints: [
107
+ @tui.constraint_fill(2),
108
+ @tui.constraint_fill(5),
109
+ @tui.constraint_fill(3),
110
+ ]
111
+ )
112
+
113
+ horizontal_sections = @tui.layout_split(
114
+ vertical_sections[1],
115
+ direction: :horizontal,
116
+ constraints: [
117
+ @tui.constraint_fill(1),
118
+ @tui.constraint_fill(5),
119
+ @tui.constraint_fill(1),
120
+ ]
121
+ )
122
+
123
+ modal_rect = horizontal_sections[1]
124
+
125
+ frame.render_widget(@tui.clear, modal_rect) if @clear
126
+
127
+ # Render the modal content
128
+ frame.render_widget(
129
+ @tui.paragraph(
130
+ text: "Your feeds have been updated",
131
+ wrap: true,
132
+ alignment: :center,
133
+ block: @tui.block(
134
+ title: "Notification",
135
+ borders: [:all],
136
+ border_style: @tui.style(fg: :black),
137
+ style: @tui.style(bg: :red, fg: :black)
138
+ )
139
+ ),
140
+ modal_rect
141
+ )
142
+ end
143
+
144
+ def render_beta_layer(frame, area)
145
+ # Position modal: 30% from top, 40% height, 25% from left, 50% width
146
+
147
+ vertical_sections = @tui.layout_split(
148
+ area,
149
+ direction: :vertical,
150
+ constraints: [
151
+ @tui.constraint_fill(3),
152
+ @tui.constraint_fill(4),
153
+ @tui.constraint_fill(2),
154
+ ]
155
+ )
156
+
157
+ horizontal_sections = @tui.layout_split(
158
+ vertical_sections[1],
159
+ direction: :horizontal,
160
+ constraints: [
161
+ @tui.constraint_fill(2),
162
+ @tui.constraint_fill(3),
163
+ @tui.constraint_fill(2),
164
+ ]
165
+ )
166
+
167
+ modal_rect = horizontal_sections[1]
168
+
169
+ frame.render_widget(@tui.clear, modal_rect) if @clear
170
+
171
+ # Render the modal content
172
+ frame.render_widget(
173
+ beta_paragraph,
174
+ modal_rect
175
+ )
176
+ end
177
+
178
+ def beta_paragraph
179
+ @beta_paragraph ||= @tui.paragraph(
180
+ text: "Thank you for being a beta tester. To give feedback, shout very loudly and we will hear you. Be careful not to scare the llamas.",
181
+ wrap: true,
182
+ alignment: :left,
183
+ block: @tui.block(
184
+ title: "Beta Program",
185
+ borders: [:all],
186
+ border_style: @tui.style(fg: :black),
187
+ style: @tui.style(bg: :blue, fg: :black)
188
+ )
189
+ )
190
+ end
191
+
192
+ def control_panel
193
+ bold_underline = @tui.style(modifiers: [:bold, :underlined])
194
+
195
+ first_controls = [
196
+ @tui.text_span(content: "0", style: bold_underline),
197
+ @tui.text_span(content: "/"),
198
+ @tui.text_span(content: "1", style: bold_underline),
199
+ @tui.text_span(content: "/"),
200
+ @tui.text_span(content: "2", style: bold_underline),
201
+ @tui.text_span(content: ": Change number of overlays | "),
202
+ @tui.text_span(content: "space", style: bold_underline),
203
+ @tui.text_span(content: ": Swap overlay order"),
204
+ ]
205
+ second_controls = [
206
+ @tui.text_span(content: "c", style: bold_underline),
207
+ @tui.text_span(content: ": Toggle clear (currently #{@clear ? 'on' : 'off'})"),
208
+ ]
209
+ third_controls = [
210
+ @tui.text_span(content: "q", style: bold_underline),
211
+ @tui.text_span(content: ": Quit"),
212
+ ]
213
+
214
+ first = @tui.text_line(spans: first_controls)
215
+ second = @tui.text_line(spans: second_controls)
216
+ third = @tui.text_line(spans: third_controls)
217
+
218
+ @tui.paragraph(
219
+ text: [first, second, third],
220
+ alignment: :center,
221
+ block: @tui.block(
222
+ title: "Controls",
223
+ borders: [:all]
224
+ )
225
+ )
226
+ end
227
+
228
+ def handle_input
229
+ case @tui.poll_event
230
+ in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
231
+ :quit
232
+ in { type: :key, code: "0" }
233
+ @layer_count = 0
234
+ in { type: :key, code: "1" }
235
+ @layer_count = 1
236
+ in { type: :key, code: "2" }
237
+ @layer_count = 2
238
+ in { type: :key, code: " " }
239
+ @swapped = !@swapped
240
+ in { type: :key, code: "c" }
241
+ @clear = !@clear
242
+ else
243
+ nil
244
+ end
245
+ end
246
+ end
247
+
248
+ WidgetOverlayDemo.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,36 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
+ SPDX-License-Identifier: CC-BY-SA-4.0
4
+ -->
5
+
6
+ # Popup (Clear) Widget Example
7
+
8
+ [![widget_popup_demo](../../doc/images/widget_popup_demo.png)](app.rb)
9
+
10
+ Demonstrates how to render opaque overlays on top of content.
11
+
12
+ Terminal renders are additive. If you draw a new widget over an old one, the background colors might mix if not handled correctly. The `Clear` widget resets the area to default (usually transparent/black) to ensure a clean canvas for popups.
13
+
14
+ ## Features Demonstrated
15
+
16
+ - **The `Clear` Widget**: Printing spaces over an area to "erase" what was underneath.
17
+ - **Centering**: Using `Layout` constraints to perfectly center a block on screen.
18
+ - **Style Bleed**: showing what happens when you *don't* use `Clear` (background colors leak through).
19
+
20
+ ## Hotkeys
21
+
22
+ - **Space**: Toggle Clear Widget (Observe the red background effect when disabled)
23
+ - **q**: Quit
24
+
25
+ ## Usage
26
+
27
+ ```bash
28
+ ruby examples/widget_popup_demo/app.rb
29
+ ```
30
+
31
+ ## Learning Outcomes
32
+
33
+ Use this example if you need to...
34
+ - Create a modal dialog (Confirm, Alert, Form).
35
+ - Implement a dropdown menu that overlays other content.
36
+ - Fix visual artifacts where old text shows through new widgets.