ratatui_ruby 0.3.1 → 0.4.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 (300) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +14 -12
  3. data/.builds/ruby-3.3.yml +14 -12
  4. data/.builds/ruby-3.4.yml +14 -12
  5. data/.builds/ruby-4.0.0.yml +14 -12
  6. data/AGENTS.md +54 -13
  7. data/CHANGELOG.md +186 -1
  8. data/README.md +17 -15
  9. data/doc/application_architecture.md +116 -0
  10. data/doc/application_testing.md +12 -7
  11. data/doc/contributors/better_dx.md +543 -0
  12. data/doc/contributors/design/ruby_frontend.md +1 -1
  13. data/doc/contributors/developing_examples.md +203 -0
  14. data/doc/contributors/documentation_style.md +97 -0
  15. data/doc/contributors/dwim_dx.md +366 -0
  16. data/doc/contributors/example_analysis.md +82 -0
  17. data/doc/custom.css +14 -0
  18. data/doc/event_handling.md +119 -0
  19. data/doc/images/all_events.png +0 -0
  20. data/doc/images/analytics.png +0 -0
  21. data/doc/images/block_padding.png +0 -0
  22. data/doc/images/block_titles.png +0 -0
  23. data/doc/images/box_demo.png +0 -0
  24. data/doc/images/calendar_demo.png +0 -0
  25. data/doc/images/cell_demo.png +0 -0
  26. data/doc/images/chart_demo.png +0 -0
  27. data/doc/images/custom_widget.png +0 -0
  28. data/doc/images/flex_layout.png +0 -0
  29. data/doc/images/gauge_demo.png +0 -0
  30. data/doc/images/hit_test.png +0 -0
  31. data/doc/images/line_gauge_demo.png +0 -0
  32. data/doc/images/list_demo.png +0 -0
  33. data/doc/images/list_styles.png +0 -0
  34. data/doc/images/login_form.png +0 -0
  35. data/doc/images/map_demo.png +0 -0
  36. data/doc/images/mouse_events.png +0 -0
  37. data/doc/images/popup_demo.png +0 -0
  38. data/doc/images/quickstart_dsl.png +0 -0
  39. data/doc/images/quickstart_lifecycle.png +0 -0
  40. data/doc/images/ratatui_logo_demo.png +0 -0
  41. data/doc/images/readme_usage.png +0 -0
  42. data/doc/images/rich_text.png +0 -0
  43. data/doc/images/scroll_text.png +0 -0
  44. data/doc/images/scrollbar_demo.png +0 -0
  45. data/doc/images/sparkline_demo.png +0 -0
  46. data/doc/images/table_flex.png +0 -0
  47. data/doc/images/table_select.png +0 -0
  48. data/doc/images/widget_style_colors.png +0 -0
  49. data/doc/index.md +1 -0
  50. data/doc/interactive_design.md +121 -0
  51. data/doc/quickstart.md +147 -72
  52. data/examples/all_events/app.rb +169 -0
  53. data/examples/all_events/app.rbs +7 -0
  54. data/examples/all_events/test_app.rb +139 -0
  55. data/examples/analytics/app.rb +258 -0
  56. data/examples/analytics/app.rbs +7 -0
  57. data/examples/analytics/test_app.rb +132 -0
  58. data/examples/block_padding/app.rb +63 -0
  59. data/examples/block_padding/app.rbs +7 -0
  60. data/examples/block_padding/test_app.rb +31 -0
  61. data/examples/block_titles/app.rb +61 -0
  62. data/examples/block_titles/app.rbs +7 -0
  63. data/examples/block_titles/test_app.rb +34 -0
  64. data/examples/box_demo/app.rb +216 -0
  65. data/examples/box_demo/app.rbs +7 -0
  66. data/examples/box_demo/test_app.rb +88 -0
  67. data/examples/calendar_demo/app.rb +101 -0
  68. data/examples/calendar_demo/app.rbs +7 -0
  69. data/examples/calendar_demo/test_app.rb +108 -0
  70. data/examples/cell_demo/app.rb +108 -0
  71. data/examples/cell_demo/app.rbs +7 -0
  72. data/examples/cell_demo/test_app.rb +36 -0
  73. data/examples/chart_demo/app.rb +203 -0
  74. data/examples/chart_demo/app.rbs +7 -0
  75. data/examples/chart_demo/test_app.rb +102 -0
  76. data/examples/custom_widget/app.rb +51 -0
  77. data/examples/custom_widget/app.rbs +7 -0
  78. data/examples/custom_widget/test_app.rb +30 -0
  79. data/examples/flex_layout/app.rb +156 -0
  80. data/examples/flex_layout/app.rbs +7 -0
  81. data/examples/flex_layout/test_app.rb +65 -0
  82. data/examples/gauge_demo/app.rb +182 -0
  83. data/examples/gauge_demo/app.rbs +7 -0
  84. data/examples/gauge_demo/test_app.rb +120 -0
  85. data/examples/hit_test/app.rb +175 -0
  86. data/examples/hit_test/app.rbs +7 -0
  87. data/examples/hit_test/test_app.rb +102 -0
  88. data/examples/line_gauge_demo/app.rb +190 -0
  89. data/examples/line_gauge_demo/app.rbs +7 -0
  90. data/examples/line_gauge_demo/test_app.rb +129 -0
  91. data/examples/list_demo/app.rb +253 -0
  92. data/examples/list_demo/app.rbs +12 -0
  93. data/examples/list_demo/test_app.rb +237 -0
  94. data/examples/list_styles/app.rb +140 -0
  95. data/examples/list_styles/app.rbs +7 -0
  96. data/examples/list_styles/test_app.rb +157 -0
  97. data/examples/{login_form.rb → login_form/app.rb} +12 -16
  98. data/examples/login_form/app.rbs +7 -0
  99. data/examples/login_form/test_app.rb +51 -0
  100. data/examples/map_demo/app.rb +90 -0
  101. data/examples/map_demo/app.rbs +7 -0
  102. data/examples/map_demo/test_app.rb +149 -0
  103. data/examples/{mouse_events.rb → mouse_events/app.rb} +29 -27
  104. data/examples/mouse_events/app.rbs +7 -0
  105. data/examples/mouse_events/test_app.rb +53 -0
  106. data/examples/{popup_demo.rb → popup_demo/app.rb} +15 -17
  107. data/examples/popup_demo/app.rbs +7 -0
  108. data/examples/{test_popup_demo.rb → popup_demo/test_app.rb} +18 -26
  109. data/examples/quickstart_dsl/app.rb +36 -0
  110. data/examples/quickstart_dsl/app.rbs +7 -0
  111. data/examples/quickstart_dsl/test_app.rb +29 -0
  112. data/examples/quickstart_lifecycle/app.rb +39 -0
  113. data/examples/quickstart_lifecycle/app.rbs +7 -0
  114. data/examples/quickstart_lifecycle/test_app.rb +29 -0
  115. data/examples/ratatui_logo_demo/app.rb +79 -0
  116. data/examples/ratatui_logo_demo/app.rbs +7 -0
  117. data/examples/ratatui_logo_demo/test_app.rb +51 -0
  118. data/examples/ratatui_mascot_demo/app.rb +84 -0
  119. data/examples/ratatui_mascot_demo/app.rbs +7 -0
  120. data/examples/ratatui_mascot_demo/test_app.rb +47 -0
  121. data/examples/readme_usage/app.rb +29 -0
  122. data/examples/readme_usage/app.rbs +7 -0
  123. data/examples/readme_usage/test_app.rb +29 -0
  124. data/examples/rich_text/app.rb +141 -0
  125. data/examples/rich_text/app.rbs +7 -0
  126. data/examples/rich_text/test_app.rb +166 -0
  127. data/examples/scroll_text/app.rb +103 -0
  128. data/examples/scroll_text/app.rbs +7 -0
  129. data/examples/scroll_text/test_app.rb +110 -0
  130. data/examples/scrollbar_demo/app.rb +143 -0
  131. data/examples/scrollbar_demo/app.rbs +7 -0
  132. data/examples/scrollbar_demo/test_app.rb +77 -0
  133. data/examples/sparkline_demo/app.rb +240 -0
  134. data/examples/sparkline_demo/app.rbs +10 -0
  135. data/examples/sparkline_demo/test_app.rb +107 -0
  136. data/examples/table_flex/app.rb +65 -0
  137. data/examples/table_flex/app.rbs +7 -0
  138. data/examples/table_flex/test_app.rb +36 -0
  139. data/examples/table_select/app.rb +198 -0
  140. data/examples/table_select/app.rbs +7 -0
  141. data/examples/table_select/test_app.rb +180 -0
  142. data/examples/widget_style_colors/app.rb +104 -0
  143. data/examples/widget_style_colors/app.rbs +14 -0
  144. data/examples/widget_style_colors/test_app.rb +48 -0
  145. data/ext/ratatui_ruby/Cargo.lock +889 -115
  146. data/ext/ratatui_ruby/Cargo.toml +4 -3
  147. data/ext/ratatui_ruby/clippy.toml +7 -0
  148. data/ext/ratatui_ruby/extconf.rb +7 -0
  149. data/ext/ratatui_ruby/src/events.rs +218 -229
  150. data/ext/ratatui_ruby/src/lib.rs +38 -10
  151. data/ext/ratatui_ruby/src/rendering.rs +90 -10
  152. data/ext/ratatui_ruby/src/style.rs +281 -98
  153. data/ext/ratatui_ruby/src/terminal.rs +119 -25
  154. data/ext/ratatui_ruby/src/text.rs +171 -0
  155. data/ext/ratatui_ruby/src/widgets/barchart.rs +97 -24
  156. data/ext/ratatui_ruby/src/widgets/block.rs +31 -3
  157. data/ext/ratatui_ruby/src/widgets/calendar.rs +45 -44
  158. data/ext/ratatui_ruby/src/widgets/canvas.rs +46 -29
  159. data/ext/ratatui_ruby/src/widgets/chart.rs +69 -27
  160. data/ext/ratatui_ruby/src/widgets/clear.rs +3 -1
  161. data/ext/ratatui_ruby/src/widgets/gauge.rs +11 -4
  162. data/ext/ratatui_ruby/src/widgets/layout.rs +218 -15
  163. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +92 -0
  164. data/ext/ratatui_ruby/src/widgets/list.rs +91 -11
  165. data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
  166. data/ext/ratatui_ruby/src/widgets/overlay.rs +3 -2
  167. data/ext/ratatui_ruby/src/widgets/paragraph.rs +35 -13
  168. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +29 -0
  169. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +44 -0
  170. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +59 -7
  171. data/ext/ratatui_ruby/src/widgets/sparkline.rs +70 -6
  172. data/ext/ratatui_ruby/src/widgets/table.rs +173 -64
  173. data/ext/ratatui_ruby/src/widgets/tabs.rs +105 -5
  174. data/lib/ratatui_ruby/cell.rb +166 -0
  175. data/lib/ratatui_ruby/event/focus_gained.rb +49 -0
  176. data/lib/ratatui_ruby/event/focus_lost.rb +50 -0
  177. data/lib/ratatui_ruby/event/key.rb +211 -0
  178. data/lib/ratatui_ruby/event/mouse.rb +124 -0
  179. data/lib/ratatui_ruby/event/paste.rb +71 -0
  180. data/lib/ratatui_ruby/event/resize.rb +80 -0
  181. data/lib/ratatui_ruby/event.rb +79 -0
  182. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +45 -0
  183. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +27 -0
  184. data/lib/ratatui_ruby/schema/bar_chart.rb +228 -19
  185. data/lib/ratatui_ruby/schema/block.rb +186 -14
  186. data/lib/ratatui_ruby/schema/calendar.rb +74 -17
  187. data/lib/ratatui_ruby/schema/canvas.rb +215 -48
  188. data/lib/ratatui_ruby/schema/center.rb +49 -11
  189. data/lib/ratatui_ruby/schema/chart.rb +151 -41
  190. data/lib/ratatui_ruby/schema/clear.rb +41 -72
  191. data/lib/ratatui_ruby/schema/constraint.rb +82 -22
  192. data/lib/ratatui_ruby/schema/cursor.rb +27 -9
  193. data/lib/ratatui_ruby/schema/draw.rb +53 -0
  194. data/lib/ratatui_ruby/schema/gauge.rb +59 -15
  195. data/lib/ratatui_ruby/schema/layout.rb +95 -13
  196. data/lib/ratatui_ruby/schema/line_gauge.rb +78 -0
  197. data/lib/ratatui_ruby/schema/list.rb +93 -19
  198. data/lib/ratatui_ruby/schema/overlay.rb +34 -8
  199. data/lib/ratatui_ruby/schema/paragraph.rb +87 -30
  200. data/lib/ratatui_ruby/schema/ratatui_logo.rb +25 -0
  201. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +29 -0
  202. data/lib/ratatui_ruby/schema/rect.rb +64 -15
  203. data/lib/ratatui_ruby/schema/scrollbar.rb +132 -24
  204. data/lib/ratatui_ruby/schema/shape/label.rb +66 -0
  205. data/lib/ratatui_ruby/schema/sparkline.rb +122 -15
  206. data/lib/ratatui_ruby/schema/style.rb +49 -21
  207. data/lib/ratatui_ruby/schema/table.rb +119 -21
  208. data/lib/ratatui_ruby/schema/tabs.rb +75 -13
  209. data/lib/ratatui_ruby/schema/text.rb +90 -0
  210. data/lib/ratatui_ruby/session.rb +146 -0
  211. data/lib/ratatui_ruby/test_helper.rb +156 -13
  212. data/lib/ratatui_ruby/version.rb +1 -1
  213. data/lib/ratatui_ruby.rb +143 -23
  214. data/sig/ratatui_ruby/event.rbs +69 -0
  215. data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -1
  216. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +16 -0
  217. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +13 -0
  218. data/sig/ratatui_ruby/schema/bar_chart.rbs +20 -2
  219. data/sig/ratatui_ruby/schema/block.rbs +5 -4
  220. data/sig/ratatui_ruby/schema/calendar.rbs +6 -2
  221. data/sig/ratatui_ruby/schema/canvas.rbs +52 -39
  222. data/sig/ratatui_ruby/schema/center.rbs +3 -3
  223. data/sig/ratatui_ruby/schema/chart.rbs +8 -5
  224. data/sig/ratatui_ruby/schema/constraint.rbs +8 -5
  225. data/sig/ratatui_ruby/schema/cursor.rbs +1 -1
  226. data/sig/ratatui_ruby/schema/draw.rbs +23 -0
  227. data/sig/ratatui_ruby/schema/gauge.rbs +4 -2
  228. data/sig/ratatui_ruby/schema/layout.rbs +11 -1
  229. data/sig/ratatui_ruby/schema/line_gauge.rbs +16 -0
  230. data/sig/ratatui_ruby/schema/list.rbs +5 -1
  231. data/sig/ratatui_ruby/schema/paragraph.rbs +4 -1
  232. data/{lib/ratatui_ruby/output.rb → sig/ratatui_ruby/schema/ratatui_logo.rbs} +3 -2
  233. data/sig/ratatui_ruby/{buffer.rbs → schema/ratatui_mascot.rbs} +4 -3
  234. data/sig/ratatui_ruby/schema/rect.rbs +2 -1
  235. data/sig/ratatui_ruby/schema/scrollbar.rbs +18 -2
  236. data/sig/ratatui_ruby/schema/sparkline.rbs +6 -2
  237. data/sig/ratatui_ruby/schema/table.rbs +8 -1
  238. data/sig/ratatui_ruby/schema/tabs.rbs +5 -1
  239. data/sig/ratatui_ruby/schema/text.rbs +22 -0
  240. data/tasks/resources/build.yml.erb +13 -11
  241. data/tasks/terminal_preview/app_screenshot.rb +35 -0
  242. data/tasks/terminal_preview/crash_report.rb +54 -0
  243. data/tasks/terminal_preview/example_app.rb +25 -0
  244. data/tasks/terminal_preview/launcher_script.rb +48 -0
  245. data/tasks/terminal_preview/preview_collection.rb +60 -0
  246. data/tasks/terminal_preview/preview_timing.rb +22 -0
  247. data/tasks/terminal_preview/safety_confirmation.rb +58 -0
  248. data/tasks/terminal_preview/saved_screenshot.rb +55 -0
  249. data/tasks/terminal_preview/system_appearance.rb +11 -0
  250. data/tasks/terminal_preview/terminal_window.rb +138 -0
  251. data/tasks/terminal_preview/window_id.rb +14 -0
  252. data/tasks/terminal_preview.rake +28 -0
  253. data/tasks/test.rake +1 -1
  254. metadata +174 -53
  255. data/doc/images/examples-analytics.rb.png +0 -0
  256. data/doc/images/examples-box_demo.rb.png +0 -0
  257. data/doc/images/examples-calendar_demo.rb.png +0 -0
  258. data/doc/images/examples-chart_demo.rb.png +0 -0
  259. data/doc/images/examples-custom_widget.rb.png +0 -0
  260. data/doc/images/examples-dashboard.rb.png +0 -0
  261. data/doc/images/examples-list_styles.rb.png +0 -0
  262. data/doc/images/examples-login_form.rb.png +0 -0
  263. data/doc/images/examples-map_demo.rb.png +0 -0
  264. data/doc/images/examples-mouse_events.rb.png +0 -0
  265. data/doc/images/examples-popup_demo.rb.gif +0 -0
  266. data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
  267. data/doc/images/examples-scroll_text.rb.png +0 -0
  268. data/doc/images/examples-scrollbar_demo.rb.png +0 -0
  269. data/doc/images/examples-stock_ticker.rb.png +0 -0
  270. data/doc/images/examples-system_monitor.rb.png +0 -0
  271. data/doc/images/examples-table_select.rb.png +0 -0
  272. data/examples/analytics.rb +0 -88
  273. data/examples/box_demo.rb +0 -71
  274. data/examples/calendar_demo.rb +0 -55
  275. data/examples/chart_demo.rb +0 -84
  276. data/examples/custom_widget.rb +0 -43
  277. data/examples/dashboard.rb +0 -72
  278. data/examples/list_styles.rb +0 -66
  279. data/examples/map_demo.rb +0 -58
  280. data/examples/quickstart_dsl.rb +0 -30
  281. data/examples/quickstart_lifecycle.rb +0 -40
  282. data/examples/readme_usage.rb +0 -21
  283. data/examples/scroll_text.rb +0 -74
  284. data/examples/scrollbar_demo.rb +0 -75
  285. data/examples/stock_ticker.rb +0 -93
  286. data/examples/system_monitor.rb +0 -94
  287. data/examples/table_select.rb +0 -70
  288. data/examples/test_analytics.rb +0 -65
  289. data/examples/test_box_demo.rb +0 -38
  290. data/examples/test_calendar_demo.rb +0 -66
  291. data/examples/test_dashboard.rb +0 -38
  292. data/examples/test_list_styles.rb +0 -61
  293. data/examples/test_login_form.rb +0 -63
  294. data/examples/test_map_demo.rb +0 -100
  295. data/examples/test_scroll_text.rb +0 -130
  296. data/examples/test_stock_ticker.rb +0 -39
  297. data/examples/test_system_monitor.rb +0 -40
  298. data/examples/test_table_select.rb +0 -37
  299. data/ext/ratatui_ruby/src/buffer.rs +0 -54
  300. data/lib/ratatui_ruby/dsl.rb +0 -64
@@ -0,0 +1,190 @@
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 LineGauge widget with interactive attribute cycling.
10
+ class LineGaugeDemoApp
11
+ def initialize
12
+ @ratio = 0.5
13
+ @ratios = [0.2, 0.35, 0.5, 0.65, 0.8, 0.95]
14
+ @ratio_index = 2
15
+
16
+ @filled_symbols = [
17
+ { name: "█ (Block)", symbol: "█" },
18
+ { name: "▓ (Dark Shade)", symbol: "▓" },
19
+ { name: "▒ (Medium Shade)", symbol: "▒" },
20
+ { name: "= (Equals)", symbol: "=" },
21
+ { name: "# (Hash)", symbol: "#" }
22
+ ]
23
+ @filled_symbol_index = 0
24
+
25
+ @unfilled_symbols = [
26
+ { name: "░ (Light Shade)", symbol: "░" },
27
+ { name: "· (Dot)", symbol: "·" },
28
+ { name: "- (Dash)", symbol: "-" },
29
+ { name: "~ (Tilde)", symbol: "~" }
30
+ ]
31
+ @unfilled_symbol_index = 0
32
+
33
+ @filled_colors = [
34
+ { name: "Red", color: :red },
35
+ { name: "Yellow", color: :yellow },
36
+ { name: "Green", color: :green },
37
+ { name: "Cyan", color: :cyan },
38
+ { name: "Blue", color: :blue }
39
+ ]
40
+ @filled_color_index = 2
41
+
42
+ @unfilled_colors = [
43
+ { name: "Default", color: nil },
44
+ { name: "Dark Gray", color: :dark_gray },
45
+ { name: "Gray", color: :gray }
46
+ ]
47
+ @unfilled_color_index = 1
48
+
49
+ @base_styles = [
50
+ { name: "None", style: nil },
51
+ { name: "Bold White", style: RatatuiRuby::Style.new(fg: :white, modifiers: [:bold]) },
52
+ { name: "White on Blue", style: RatatuiRuby::Style.new(fg: :white, bg: :blue) },
53
+ { name: "Italic Cyan", style: RatatuiRuby::Style.new(fg: :cyan, modifiers: [:italic]) }
54
+ ]
55
+ @base_style_index = 0
56
+ @hotkey_style = RatatuiRuby::Style.new(modifiers: [:bold, :underlined])
57
+ end
58
+
59
+ def run
60
+ RatatuiRuby.run do
61
+ loop do
62
+ render
63
+ break if handle_input == :quit
64
+ sleep 0.05
65
+ end
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def render
72
+ @ratio = @ratios[@ratio_index]
73
+
74
+ filled_color = @filled_colors[@filled_color_index][:color]
75
+ unfilled_color = @unfilled_colors[@unfilled_color_index][:color]
76
+
77
+ filled_style = filled_color ? RatatuiRuby::Style.new(fg: filled_color) : RatatuiRuby::Style.new(fg: :white)
78
+ unfilled_style = unfilled_color ? RatatuiRuby::Style.new(fg: unfilled_color) : RatatuiRuby::Style.new(fg: :dark_gray)
79
+
80
+ layout = RatatuiRuby::Layout.new(
81
+ direction: :vertical,
82
+ constraints: [
83
+ RatatuiRuby::Constraint.fill(1),
84
+ RatatuiRuby::Constraint.length(5)
85
+ ],
86
+ children: [
87
+ # Main content area
88
+ RatatuiRuby::Layout.new(
89
+ direction: :vertical,
90
+ constraints: [
91
+ RatatuiRuby::Constraint.length(1),
92
+ RatatuiRuby::Constraint.length(4),
93
+ RatatuiRuby::Constraint.length(4),
94
+ RatatuiRuby::Constraint.fill(1)
95
+ ],
96
+ children: [
97
+ RatatuiRuby::Paragraph.new(
98
+ text: "LineGauge Widget Demo - Cycle attributes with hotkeys"
99
+ ),
100
+ # Example 1: Static gauge showing all features
101
+ RatatuiRuby::LineGauge.new(
102
+ ratio: @ratio,
103
+ label: "#{(@ratio * 100).to_i}%",
104
+ style: @base_styles[@base_style_index][:style],
105
+ filled_style:,
106
+ unfilled_style:,
107
+ filled_symbol: @filled_symbols[@filled_symbol_index][:symbol],
108
+ unfilled_symbol: @unfilled_symbols[@unfilled_symbol_index][:symbol],
109
+ block: RatatuiRuby::Block.new(title: "Interactive Gauge")
110
+ ),
111
+ # Example 2: Inverted colors for contrast demonstration
112
+ RatatuiRuby::LineGauge.new(
113
+ ratio: 1.0 - @ratio,
114
+ label: "#{((1.0 - @ratio) * 100).to_i}%",
115
+ filled_style: RatatuiRuby::Style.new(fg: :black, bg: :yellow),
116
+ unfilled_style: RatatuiRuby::Style.new(fg: :white, bg: :dark_gray),
117
+ filled_symbol: @filled_symbols[@filled_symbol_index][:symbol],
118
+ unfilled_symbol: @unfilled_symbols[@unfilled_symbol_index][:symbol],
119
+ block: RatatuiRuby::Block.new(title: "Inverse (100% - ratio)")
120
+ ),
121
+ RatatuiRuby::Paragraph.new(text: "")
122
+ ]
123
+ ),
124
+ # Bottom control panel
125
+ RatatuiRuby::Block.new(
126
+ title: "Controls",
127
+ borders: [:all],
128
+ children: [
129
+ RatatuiRuby::Paragraph.new(
130
+ text: [
131
+ # Line 1: General
132
+ RatatuiRuby::Text::Line.new(spans: [
133
+ RatatuiRuby::Text::Span.new(content: "←/→", style: @hotkey_style),
134
+ RatatuiRuby::Text::Span.new(content: ": Ratio (#{(@ratio * 100).to_i}%) "),
135
+ RatatuiRuby::Text::Span.new(content: "b", style: @hotkey_style),
136
+ RatatuiRuby::Text::Span.new(content: ": Base Style (#{@base_styles[@base_style_index][:name]}) "),
137
+ RatatuiRuby::Text::Span.new(content: "q", style: @hotkey_style),
138
+ RatatuiRuby::Text::Span.new(content: ": Quit")
139
+ ]),
140
+ # Line 2: Filled
141
+ RatatuiRuby::Text::Line.new(spans: [
142
+ RatatuiRuby::Text::Span.new(content: "f", style: @hotkey_style),
143
+ RatatuiRuby::Text::Span.new(content: ": Filled Symbol (#{@filled_symbols[@filled_symbol_index][:name]}) "),
144
+ RatatuiRuby::Text::Span.new(content: "c", style: @hotkey_style),
145
+ RatatuiRuby::Text::Span.new(content: ": Filled Color (#{@filled_colors[@filled_color_index][:name]})")
146
+ ]),
147
+ # Line 3: Unfilled
148
+ RatatuiRuby::Text::Line.new(spans: [
149
+ RatatuiRuby::Text::Span.new(content: "u", style: @hotkey_style),
150
+ RatatuiRuby::Text::Span.new(content: ": Unfilled Symbol (#{@unfilled_symbols[@unfilled_symbol_index][:name]}) "),
151
+ RatatuiRuby::Text::Span.new(content: "x", style: @hotkey_style),
152
+ RatatuiRuby::Text::Span.new(content: ": Unfilled Color (#{@unfilled_colors[@unfilled_color_index][:name]})")
153
+ ])
154
+ ]
155
+ )
156
+ ]
157
+ )
158
+ ]
159
+ )
160
+
161
+
162
+ RatatuiRuby.draw(layout)
163
+ end
164
+
165
+ def handle_input
166
+ event = RatatuiRuby.poll_event
167
+ return unless event
168
+
169
+ case event
170
+ in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
171
+ :quit
172
+ in type: :key, code: "right"
173
+ @ratio_index = (@ratio_index + 1) % @ratios.length
174
+ in type: :key, code: "left"
175
+ @ratio_index = (@ratio_index - 1) % @ratios.length
176
+ in type: :key, code: "b"
177
+ @base_style_index = (@base_style_index + 1) % @base_styles.length
178
+ in type: :key, code: "f"
179
+ @filled_symbol_index = (@filled_symbol_index + 1) % @filled_symbols.length
180
+ in type: :key, code: "c"
181
+ @filled_color_index = (@filled_color_index + 1) % @filled_colors.length
182
+ in type: :key, code: "u"
183
+ @unfilled_symbol_index = (@unfilled_symbol_index + 1) % @unfilled_symbols.length
184
+ in type: :key, code: "x"
185
+ @unfilled_color_index = (@unfilled_color_index + 1) % @unfilled_colors.length
186
+ end
187
+ end
188
+ end
189
+
190
+ LineGaugeDemoApp.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,7 @@
1
+ class LineGaugeDemoApp
2
+ # @public
3
+ def self.new: () -> LineGaugeDemoApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,129 @@
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 "ratatui_ruby/test_helper"
9
+ require "minitest/autorun"
10
+ require_relative "app"
11
+
12
+ class TestLineGaugeDemo < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = LineGaugeDemoApp.new
17
+ end
18
+
19
+ def test_initial_render
20
+ with_test_terminal do
21
+ inject_key(:q)
22
+ @app.run
23
+
24
+ content = buffer_content.join("\n")
25
+ assert_includes content, "LineGauge Widget Demo"
26
+ assert_includes content, "Interactive Gauge"
27
+ assert_includes content, "Inverse"
28
+ assert_includes content, "←/→: Ratio"
29
+ assert_includes content, "50%"
30
+ end
31
+ end
32
+
33
+ def test_ratio_cycling_right
34
+ with_test_terminal do
35
+ inject_keys(:right, :right, :q)
36
+ @app.run
37
+
38
+ content = buffer_content.join("\n")
39
+ # After 2 right presses: index 2 -> 3 -> 4 (80%)
40
+ assert_includes content, "80%"
41
+ end
42
+ end
43
+
44
+ def test_ratio_cycling_left
45
+ with_test_terminal do
46
+ inject_keys(:left, :q)
47
+ @app.run
48
+
49
+ content = buffer_content.join("\n")
50
+ # After 1 left press: index 2 -> 1 (35%)
51
+ assert_includes content, "35%"
52
+ end
53
+ end
54
+
55
+ def test_filled_symbol_cycling
56
+ with_test_terminal do
57
+ inject_keys(:f, :q)
58
+ @app.run
59
+
60
+ content = buffer_content.join("\n")
61
+ # Default filled_symbol_index is 0 (█), after press becomes 1 (▓)
62
+ assert_includes content, "Dark Shade"
63
+ end
64
+ end
65
+
66
+ def test_filled_color_cycling
67
+ with_test_terminal do
68
+ inject_keys(:c, :c, :q)
69
+ @app.run
70
+
71
+ content = buffer_content.join("\n")
72
+ # Default is index 2 (Green), after 2 presses -> 4 (Blue)
73
+ assert_includes content, "Blue"
74
+ end
75
+ end
76
+
77
+ def test_unfilled_symbol_cycling
78
+ with_test_terminal do
79
+ inject_keys(:u, :q)
80
+ @app.run
81
+
82
+ content = buffer_content.join("\n")
83
+ # Default is index 0 (Light Shade), after press becomes 1 (Dot)
84
+ assert_includes content, "Dot"
85
+ end
86
+ end
87
+
88
+ def test_unfilled_color_cycling
89
+ with_test_terminal do
90
+ inject_keys(:x, :q)
91
+ @app.run
92
+
93
+ content = buffer_content.join("\n")
94
+ # Default is index 1 (Dark Gray), after press becomes 2 (Gray)
95
+ assert_includes content, "Gray"
96
+ end
97
+ end
98
+
99
+ def test_base_style_cycling
100
+ with_test_terminal do
101
+ inject_keys(:b, :q)
102
+ @app.run
103
+
104
+ content = buffer_content.join("\n")
105
+ # Default is index 0 (None), after press becomes 1 (Bold White)
106
+ assert_includes content, "Bold White"
107
+ end
108
+ end
109
+
110
+ def test_quit_with_ctrl_c
111
+ with_test_terminal do
112
+ inject_key(:ctrl_c)
113
+ @app.run
114
+ # Success if returns without hanging
115
+ end
116
+ end
117
+
118
+ def test_multiple_attribute_changes
119
+ with_test_terminal do
120
+ inject_keys(:right, :f, :c, :b, :q)
121
+ @app.run
122
+
123
+ content = buffer_content.join("\n")
124
+ # Verify some of the changes took effect
125
+ assert_includes content, "65%" # ratio changed
126
+ assert_includes content, "Dark Shade" # filled_symbol changed
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,253 @@
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 List widget with interactive attribute cycling.
10
+ class ListDemoApp
11
+ def initialize
12
+ @selected_index = nil
13
+
14
+ @item_sets = [
15
+ {
16
+ name: "Large List",
17
+ items: (1..200).map { |i| "Item #{i}" }
18
+ },
19
+ {
20
+ name: "Colors",
21
+ items: [
22
+ "Red", "Orange", "Yellow", "Green", "Cyan", "Blue", "Indigo", "Violet",
23
+ "Scarlet", "Crimson", "Maroon", "Brown", "Tan", "Beige", "Khaki",
24
+ "Gold", "Silver", "White", "Gray", "Black", "Pink", "Magenta",
25
+ "Turquoise", "Teal", "Coral", "Salmon", "Peach", "Lavender", "Lilac",
26
+ "Olive", "Lime", "Navy", "Charcoal", "Ivory", "Azure"
27
+ ]
28
+ },
29
+ {
30
+ name: "Fruits",
31
+ items: [
32
+ "Apple", "Apricot", "Avocado", "Banana", "Blueberry", "Blackberry",
33
+ "Cherry", "Cranberry", "Cucumber", "Date", "Dragonfruit", "Elderberry",
34
+ "Fig", "Grape", "Grapefruit", "Guava", "Honeydew", "Huckleberry",
35
+ "Jackfruit", "Kiwi", "Kumquat", "Lemon", "Lime", "Lychee",
36
+ "Mango", "Melon", "Mulberry", "Nectarine", "Olive", "Orange",
37
+ "Papaya", "Passion Fruit", "Peach", "Pear", "Persimmon", "Pineapple",
38
+ "Plum", "Pomegranate", "Prune", "Rambutan", "Raspberry", "Starfruit",
39
+ "Strawberry", "Tangerine", "Watermelon", "Ugli Fruit"
40
+ ]
41
+ },
42
+ {
43
+ name: "Programming",
44
+ items: [
45
+ "Ruby", "Rust", "Python", "JavaScript", "Go", "C++", "C#", "Java",
46
+ "Kotlin", "Swift", "Objective-C", "PHP", "TypeScript", "Perl", "Lua",
47
+ "R", "Scala", "Haskell", "Elixir", "Clojure", "Groovy", "Closure",
48
+ "VB.NET", "F#", "Erlang", "Lisp", "Scheme", "Prolog", "Fortran",
49
+ "COBOL", "Pascal", "Delphi", "Ada", "Bash", "Sh", "Tcl",
50
+ "Awk", "sed", "Vim Script", "PowerShell", "Batch", "Assembly",
51
+ "Wasm", "WebAssembly", "Julia", "Matlab", "Octave", "BASIC"
52
+ ]
53
+ }
54
+ ]
55
+ @item_set_index = 0
56
+
57
+ @highlight_styles = [
58
+ { name: "Blue Bold", style: RatatuiRuby::Style.new(fg: :blue, modifiers: [:bold]) },
59
+ { name: "Yellow on Black", style: RatatuiRuby::Style.new(fg: :yellow, bg: :black) },
60
+ { name: "Green Italic", style: RatatuiRuby::Style.new(fg: :green, modifiers: [:italic]) },
61
+ { name: "White Reversed", style: RatatuiRuby::Style.new(fg: :white, modifiers: [:reversed]) },
62
+ { name: "Cyan Bold", style: RatatuiRuby::Style.new(fg: :cyan, modifiers: [:bold]) }
63
+ ]
64
+ @highlight_style_index = 0
65
+
66
+ @highlight_symbols = [
67
+ { name: ">> ", symbol: ">> " },
68
+ { name: "▶ ", symbol: "▶ " },
69
+ { name: "→ ", symbol: "→ " },
70
+ { name: "• ", symbol: "• " },
71
+ { name: "★ ", symbol: "★ " }
72
+ ]
73
+ @highlight_symbol_index = 0
74
+
75
+ @directions = [
76
+ { name: "Top to Bottom", direction: :top_to_bottom },
77
+ { name: "Bottom to Top", direction: :bottom_to_top }
78
+ ]
79
+ @direction_index = 0
80
+
81
+ @highlight_spacings = [
82
+ { name: "When Selected", spacing: :when_selected },
83
+ { name: "Always", spacing: :always },
84
+ { name: "Never", spacing: :never }
85
+ ]
86
+ @highlight_spacing_index = 0
87
+
88
+ @repeat_modes = [
89
+ { name: "Off", repeat: false },
90
+ { name: "On", repeat: true }
91
+ ]
92
+ @repeat_index = 0
93
+
94
+ @base_styles = [
95
+ { name: "None", style: nil },
96
+ { name: "Dark Gray", style: RatatuiRuby::Style.new(fg: :dark_gray) },
97
+ { name: "White on Black", style: RatatuiRuby::Style.new(fg: :white, bg: :black) }
98
+ ]
99
+ @base_style_index = 0
100
+
101
+ @scroll_paddings = [
102
+ { name: "None", padding: nil },
103
+ { name: "1 item", padding: 1 },
104
+ { name: "2 items", padding: 2 }
105
+ ]
106
+ @scroll_padding_index = 0
107
+ end
108
+
109
+ def run
110
+ RatatuiRuby.run do
111
+ loop do
112
+ render
113
+ break if handle_input == :quit
114
+ sleep 0.05
115
+ end
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def render
122
+ items = @item_sets[@item_set_index][:items]
123
+ selection_label = @selected_index.nil? ? "none" : @selected_index.to_s
124
+ direction_config = @directions[@direction_index]
125
+ spacing_config = @highlight_spacings[@highlight_spacing_index]
126
+ repeat_config = @repeat_modes[@repeat_index]
127
+ highlight_style_config = @highlight_styles[@highlight_style_index]
128
+ highlight_symbol_config = @highlight_symbols[@highlight_symbol_index]
129
+ base_style_config = @base_styles[@base_style_index]
130
+ scroll_padding_config = @scroll_paddings[@scroll_padding_index]
131
+
132
+ # Main list area
133
+ main_content = RatatuiRuby::Layout.new(
134
+ direction: :vertical,
135
+ constraints: [
136
+ RatatuiRuby::Constraint.length(1),
137
+ RatatuiRuby::Constraint.fill(1)
138
+ ],
139
+ children: [
140
+ RatatuiRuby::Paragraph.new(
141
+ text: "List Widget Demo - Interactive Attribute Cycling"
142
+ ),
143
+ RatatuiRuby::List.new(
144
+ items:,
145
+ selected_index: @selected_index,
146
+ style: base_style_config[:style],
147
+ highlight_style: highlight_style_config[:style],
148
+ highlight_symbol: highlight_symbol_config[:symbol],
149
+ repeat_highlight_symbol: repeat_config[:repeat],
150
+ highlight_spacing: spacing_config[:spacing],
151
+ direction: direction_config[:direction],
152
+ scroll_padding: scroll_padding_config[:padding],
153
+ block: RatatuiRuby::Block.new(
154
+ title: "#{@item_sets[@item_set_index][:name]} (Selection: #{selection_label})",
155
+ borders: [:all]
156
+ )
157
+ )
158
+ ]
159
+ )
160
+
161
+ # Bottom control panel with full width
162
+ hotkey_style = RatatuiRuby::Style.new(modifiers: [:bold, :underlined])
163
+ control_panel = RatatuiRuby::Block.new(
164
+ title: "Controls",
165
+ borders: [:all],
166
+ children: [
167
+ RatatuiRuby::Paragraph.new(
168
+ text: [
169
+ RatatuiRuby::Text::Line.new(spans: [
170
+ RatatuiRuby::Text::Span.new(content: "i", style: hotkey_style),
171
+ RatatuiRuby::Text::Span.new(content: ": Items "),
172
+ RatatuiRuby::Text::Span.new(content: "↑/↓", style: hotkey_style),
173
+ RatatuiRuby::Text::Span.new(content: ": Navigate "),
174
+ RatatuiRuby::Text::Span.new(content: "x", style: hotkey_style),
175
+ RatatuiRuby::Text::Span.new(content: ": Select "),
176
+ RatatuiRuby::Text::Span.new(content: "h", style: hotkey_style),
177
+ RatatuiRuby::Text::Span.new(content: ": Highlight (#{highlight_style_config[:name]})")
178
+ ]),
179
+ RatatuiRuby::Text::Line.new(spans: [
180
+ RatatuiRuby::Text::Span.new(content: "y", style: hotkey_style),
181
+ RatatuiRuby::Text::Span.new(content: ": Symbol (#{highlight_symbol_config[:name]}) "),
182
+ RatatuiRuby::Text::Span.new(content: "d", style: hotkey_style),
183
+ RatatuiRuby::Text::Span.new(content: ": Direction (#{direction_config[:name]})")
184
+ ]),
185
+ RatatuiRuby::Text::Line.new(spans: [
186
+ RatatuiRuby::Text::Span.new(content: "s", style: hotkey_style),
187
+ RatatuiRuby::Text::Span.new(content: ": Spacing (#{spacing_config[:name]}) "),
188
+ RatatuiRuby::Text::Span.new(content: "p", style: hotkey_style),
189
+ RatatuiRuby::Text::Span.new(content: ": Scroll Padding (#{scroll_padding_config[:name]})")
190
+ ]),
191
+ RatatuiRuby::Text::Line.new(spans: [
192
+ RatatuiRuby::Text::Span.new(content: "b", style: hotkey_style),
193
+ RatatuiRuby::Text::Span.new(content: ": Base (#{base_style_config[:name]}) "),
194
+ RatatuiRuby::Text::Span.new(content: "r", style: hotkey_style),
195
+ RatatuiRuby::Text::Span.new(content: ": Repeat (#{repeat_config[:name]}) "),
196
+ RatatuiRuby::Text::Span.new(content: "q", style: hotkey_style),
197
+ RatatuiRuby::Text::Span.new(content: ": Quit")
198
+ ])
199
+ ]
200
+ )
201
+ ]
202
+ )
203
+
204
+ # Combine layouts vertically
205
+ layout = RatatuiRuby::Layout.new(
206
+ direction: :vertical,
207
+ constraints: [
208
+ RatatuiRuby::Constraint.fill(1),
209
+ RatatuiRuby::Constraint.length(7)
210
+ ],
211
+ children: [main_content, control_panel]
212
+ )
213
+
214
+ RatatuiRuby.draw(layout)
215
+ end
216
+
217
+ def handle_input
218
+ case RatatuiRuby.poll_event
219
+ in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
220
+ :quit
221
+ in type: :key, code: "i"
222
+ @item_set_index = (@item_set_index + 1) % @item_sets.size
223
+ @selected_index = nil
224
+ in type: :key, code: "up"
225
+ items = @item_sets[@item_set_index][:items]
226
+ @selected_index = (@selected_index || 0) - 1
227
+ @selected_index = items.size - 1 if @selected_index.negative?
228
+ in type: :key, code: "down"
229
+ items = @item_sets[@item_set_index][:items]
230
+ @selected_index = ((@selected_index || -1) + 1) % items.size
231
+ in type: :key, code: "x"
232
+ @selected_index = @selected_index.nil? ? 0 : nil
233
+ in type: :key, code: "h"
234
+ @highlight_style_index = (@highlight_style_index + 1) % @highlight_styles.size
235
+ in type: :key, code: "y"
236
+ @highlight_symbol_index = (@highlight_symbol_index + 1) % @highlight_symbols.size
237
+ in type: :key, code: "d"
238
+ @direction_index = (@direction_index + 1) % @directions.size
239
+ in type: :key, code: "s"
240
+ @highlight_spacing_index = (@highlight_spacing_index + 1) % @highlight_spacings.size
241
+ in type: :key, code: "b"
242
+ @base_style_index = (@base_style_index + 1) % @base_styles.size
243
+ in type: :key, code: "r"
244
+ @repeat_index = (@repeat_index + 1) % @repeat_modes.size
245
+ in type: :key, code: "p"
246
+ @scroll_padding_index = (@scroll_padding_index + 1) % @scroll_paddings.size
247
+ else
248
+ nil
249
+ end
250
+ end
251
+ end
252
+
253
+ ListDemoApp.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,12 @@
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
+ class ListDemoApp
7
+ # @public
8
+ def self.new: () -> ListDemoApp
9
+
10
+ # @public
11
+ def run: () -> void
12
+ end