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,180 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: AGPL-3.0-or-later
6
+
7
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
8
+ require "ratatui_ruby"
9
+ require "ratatui_ruby/test_helper"
10
+ require "minitest/autorun"
11
+ require_relative "app"
12
+
13
+ # Tests for the table_select example
14
+ class TestTableSelectApp < Minitest::Test
15
+ include RatatuiRuby::TestHelper
16
+
17
+ def setup
18
+ @app = TableSelectApp.new
19
+ end
20
+
21
+ def test_initial_render_no_selection
22
+ with_test_terminal do
23
+ # By default, selection is at index 1 and highlights are on.
24
+ inject_key(:q)
25
+ @app.run
26
+
27
+ content = buffer_content.join("\n")
28
+ # Row 1 (postgres) should be selected.
29
+ # Default style is Cyan.
30
+ # "Toggle Row (1)" should be visible in footer.
31
+ assert_includes content, "Toggle Row (1)"
32
+ assert_includes content, "Cyan"
33
+ assert_includes content, "PID"
34
+ end
35
+ end
36
+
37
+ def test_style_switching
38
+ second_style_name = TableSelectApp::STYLES[1][:name]
39
+
40
+ with_test_terminal do
41
+ inject_keys(:s, :q)
42
+ @app.run
43
+
44
+ content = buffer_content
45
+ assert content.any? { |line| line.include?(second_style_name) }
46
+ end
47
+ end
48
+
49
+ def test_toggle_selection_on
50
+ with_test_terminal do
51
+ # Default is ON (1 selected).
52
+ # Press x to toggle off (none), then quit
53
+ inject_keys(:x, :q)
54
+ @app.run
55
+
56
+ content = buffer_content.join("\n")
57
+ # "Toggle Row (none)" should be visible.
58
+ assert_includes content, "Toggle Row (none)"
59
+ end
60
+ end
61
+
62
+ def test_toggle_selection_off
63
+ with_test_terminal do
64
+ # Default is ON.
65
+ # Press x to toggle off, x again to toggle on (0 selected now because toggle logic resets to 0 if nil?), then quit
66
+ # Wait, app logic: @selected_index = @selected_index.nil? ? 0 : nil
67
+ # If default is 1, toggling off makes it nil. Toggling on makes it 0.
68
+ inject_keys(:x, :x, :q)
69
+ @app.run
70
+
71
+ content = buffer_content.join("\n")
72
+ assert_includes content, "Toggle Row (0)"
73
+ end
74
+ end
75
+
76
+ def test_navigation_selects_and_moves
77
+ with_test_terminal do
78
+ # Default is 1. Down -> 2.
79
+ inject_keys(:down, :q)
80
+ @app.run
81
+
82
+ content = buffer_content.join("\n")
83
+ assert_includes content, "Toggle Row (2)"
84
+ end
85
+ end
86
+
87
+ def test_navigation_wrapping
88
+ with_test_terminal do
89
+ # Start 1, down->2... wrap around logic needs check.
90
+ # Logic: (@selected_index + 1) % length.
91
+ # To test wrap, need to go to end. Length is 7.
92
+ # From 1, need 6 downs to wrap? No, just up from 0 wraps to last.
93
+ # So: up (1->0), up (0->6).
94
+ inject_keys(:up, :up, :q)
95
+ @app.run
96
+
97
+ content = buffer_content.join("\n")
98
+ last_index = PROCESSES.length - 1
99
+ assert_includes content, "Toggle Row (#{last_index})"
100
+ end
101
+ end
102
+
103
+ def test_column_navigation
104
+ with_test_terminal do
105
+ # Default col 1. Right -> 2.
106
+ inject_keys(:right, :q)
107
+ @app.run
108
+
109
+ content = buffer_content.join("\n")
110
+ assert_includes content, "Nav Col"
111
+ # We don't have a label for selected column in the footer anymore?
112
+ # Wait, I added it in app.rb but removed it in previous step?
113
+ # In Step 115 I added `col_label` but I didn't verify if I used it in the UI.
114
+ # Let me check app.rb again. I might have missed adding it to the UI in Step 87 layout fix or Step 115.
115
+ # Step 115 added `col_label = ...` but did NOT add it to the Paragraph text.
116
+ # I need to check app.rb to see if `col_label` is rendered.
117
+ end
118
+ end
119
+
120
+ def test_quit
121
+ with_test_terminal do
122
+ inject_key(:q)
123
+ @app.run
124
+ end
125
+ end
126
+
127
+ def test_highlight_spacing_cycles_to_next
128
+ with_test_terminal do
129
+ # Mode order: [:when_selected, :always, :never]
130
+ # 'p' goes to :always
131
+ inject_keys(:p, :q)
132
+ @app.run
133
+
134
+ content = buffer_content
135
+ assert content.any? { |line| line.include?("Always") }
136
+ end
137
+ end
138
+
139
+ def test_highlight_spacing_cycles_to_never
140
+ with_test_terminal do
141
+ # 'p' goes: when_selected -> always -> never
142
+ inject_keys(:p, :p, :q)
143
+ @app.run
144
+
145
+ content = buffer_content
146
+ assert content.any? { |line| line.include?("Never") }
147
+ end
148
+ end
149
+
150
+ def test_highlight_spacing_cycles_back_to_when_selected
151
+ with_test_terminal do
152
+ # 'p' goes: when_selected -> always -> never -> when_selected
153
+ inject_keys(:p, :p, :p, :q)
154
+ @app.run
155
+
156
+ content = buffer_content
157
+ assert content.any? { |line| line.include?("When Selected") }
158
+ end
159
+ end
160
+
161
+ def test_column_spacing_increases
162
+ with_test_terminal do
163
+ inject_keys("+", :q)
164
+ @app.run
165
+
166
+ content = buffer_content
167
+ assert content.any? { |line| line.include?("Col Space (2)") }
168
+ end
169
+ end
170
+
171
+ def test_column_spacing_decreases
172
+ with_test_terminal do
173
+ inject_keys("+", "-", :q)
174
+ @app.run
175
+
176
+ content = buffer_content
177
+ assert content.any? { |line| line.include?("Col Space (1)") }
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
4
+ # SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
7
+ require "ratatui_ruby"
8
+
9
+ class ColorGradientDemo
10
+ def initialize
11
+ @width = 80
12
+ @height = 24
13
+ end
14
+
15
+ def run
16
+ RatatuiRuby.run do |tui|
17
+ loop do
18
+ render(tui)
19
+ event = tui.poll_event
20
+ break if event&.key? && (event.ctrl_c? || event == :q)
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def render(tui)
28
+ lines = []
29
+
30
+ (0...@height).each do |row|
31
+ spans = []
32
+ (0...@width).each do |col|
33
+ hue = (col.to_f / @width) * 360.0
34
+ lightness = 50.0 - ((row.to_f / @height) * 50.0)
35
+
36
+ rgb = hsl_to_rgb(hue, 100.0, lightness)
37
+ hex = rgb_to_hex(rgb)
38
+
39
+ span = tui.text_span(
40
+ content: " ",
41
+ style: tui.style(bg: hex)
42
+ )
43
+ spans << span
44
+ end
45
+
46
+ lines << tui.text_line(spans: spans)
47
+ end
48
+
49
+ paragraph = tui.paragraph(
50
+ text: lines,
51
+ block: tui.block(
52
+ title: "Hex Color Gradient (Press 'q' or Ctrl+C to exit)",
53
+ borders: [:all],
54
+ border_type: :rounded
55
+ )
56
+ )
57
+
58
+ tui.draw(paragraph)
59
+ end
60
+
61
+ def hsl_to_rgb(hue, saturation, lightness)
62
+ h = hue / 360.0
63
+ s = saturation / 100.0
64
+ l = lightness / 100.0
65
+
66
+ if s == 0
67
+ r = g = b = l
68
+ else
69
+ q = l < 0.5 ? l * (1 + s) : l + s - (l * s)
70
+ p = 2 * l - q
71
+
72
+ r = hue_to_rgb(p, q, h + 1.0 / 3.0)
73
+ g = hue_to_rgb(p, q, h)
74
+ b = hue_to_rgb(p, q, h - 1.0 / 3.0)
75
+ end
76
+
77
+ [
78
+ (r * 255).round,
79
+ (g * 255).round,
80
+ (b * 255).round
81
+ ]
82
+ end
83
+
84
+ def hue_to_rgb(p, q, t)
85
+ t += 1 while t < 0
86
+ t -= 1 while t > 1
87
+
88
+ if t < 1.0 / 6.0
89
+ p + (q - p) * 6 * t
90
+ elsif t < 1.0 / 2.0
91
+ q
92
+ elsif t < 2.0 / 3.0
93
+ p + (q - p) * (2.0 / 3.0 - t) * 6
94
+ else
95
+ p
96
+ end
97
+ end
98
+
99
+ def rgb_to_hex(rgb)
100
+ "##{rgb.map { |c| c.to_s(16).upcase.rjust(2, '0') }.join}"
101
+ end
102
+ end
103
+
104
+ ColorGradientDemo.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,14 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ class ColorGradientDemo
5
+ def initialize: () -> void
6
+ def run: () -> void
7
+
8
+ private
9
+
10
+ def render: (RatatuiRuby::TUI) -> void
11
+ def hsl_to_rgb: (Float, Float, Float) -> [Integer, Integer, Integer]
12
+ def hue_to_rgb: (Float, Float, Float) -> Float
13
+ def rgb_to_hex: ([Integer, Integer, Integer]) -> String
14
+ end
@@ -0,0 +1,48 @@
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 TestColorGradientDemo < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = ColorGradientDemo.new
17
+ end
18
+
19
+ def test_renders_with_title
20
+ with_test_terminal do
21
+ inject_key(:q)
22
+ @app.run
23
+
24
+ content = buffer_content.join("\n")
25
+ assert_includes content, "Hex Color Gradient"
26
+ end
27
+ end
28
+
29
+ def test_renders_gradient_cells
30
+ with_test_terminal do
31
+ inject_key(:q)
32
+ @app.run
33
+
34
+ content = buffer_content.join("\n")
35
+ # Should have many spaces (gradient cells)
36
+ assert content.length > 500
37
+ end
38
+ end
39
+
40
+ def test_exit_on_q
41
+ with_test_terminal do
42
+ inject_key(:q)
43
+ @app.run
44
+ # If we get here without hanging, exit worked
45
+ assert true
46
+ end
47
+ end
48
+ end