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,240 @@
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 Sparkline widget with interactive attribute cycling.
10
+ class SparklineDemoApp
11
+ def initialize
12
+ # Data sets with different characteristics
13
+ @data_sets = [
14
+ {
15
+ name: "Steady Growth",
16
+ data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
17
+ },
18
+ {
19
+ name: "With Gaps",
20
+ data: [5, nil, 8, nil, 6, nil, 9, nil, 7, nil, 10, nil]
21
+ },
22
+ {
23
+ name: "Random",
24
+ data: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8]
25
+ },
26
+ {
27
+ name: "Sawtooth",
28
+ data: [1, 2, 3, 4, 5, 4, 3, 2, 1, 2, 3, 4]
29
+ },
30
+ {
31
+ name: "Peaks",
32
+ data: [1, 5, 1, 8, 1, 6, 1, 9, 1, 7, 1, 10]
33
+ }
34
+ ]
35
+ @data_index = 0
36
+
37
+ @directions = [
38
+ { name: "Left to Right", direction: :left_to_right },
39
+ { name: "Right to Left", direction: :right_to_left }
40
+ ]
41
+ @direction_index = 0
42
+
43
+ @styles = [
44
+ { name: "Green", style: RatatuiRuby::Style.new(fg: :green) },
45
+ { name: "Yellow", style: RatatuiRuby::Style.new(fg: :yellow) },
46
+ { name: "Red", style: RatatuiRuby::Style.new(fg: :red) },
47
+ { name: "Cyan", style: RatatuiRuby::Style.new(fg: :cyan) },
48
+ { name: "Magenta", style: RatatuiRuby::Style.new(fg: :magenta) }
49
+ ]
50
+ @style_index = 0
51
+
52
+ @absent_symbols = [
53
+ { name: "None", symbol: nil },
54
+ { name: "Dot (·)", symbol: "·" },
55
+ { name: "Square (▫)", symbol: "▫" },
56
+ { name: "Dash (-)", symbol: "-" },
57
+ { name: "Underscore (_)", symbol: "_" }
58
+ ]
59
+ @absent_symbol_index = 1
60
+
61
+ @absent_styles = [
62
+ { name: "Default", style: nil },
63
+ { name: "Dark Gray", style: RatatuiRuby::Style.new(fg: :dark_gray) },
64
+ { name: "Dim Red", style: RatatuiRuby::Style.new(fg: :red, modifiers: [:dim]) },
65
+ { name: "Dim Yellow", style: RatatuiRuby::Style.new(fg: :yellow, modifiers: [:dim]) }
66
+ ]
67
+ @absent_style_index = 0
68
+
69
+ @bar_sets = [
70
+ { name: "Default (Block)", set: nil },
71
+ { name: "Numbers (0-8)", set: {
72
+ 0 => "0", 1 => "1", 2 => "2", 3 => "3", 4 => "4", 5 => "5", 6 => "6", 7 => "7", 8 => "8"
73
+ }},
74
+ { name: "ASCII (Heights)", set: [" ", "_", ".", "-", "=", "+", "*", "#", "@"] }
75
+ ]
76
+ @bar_set_index = 0
77
+
78
+ @counter = 0
79
+ @hotkey_style = RatatuiRuby::Style.new(modifiers: [:bold, :underlined])
80
+ end
81
+
82
+ def run
83
+ RatatuiRuby.run do
84
+ loop do
85
+ render
86
+ break if handle_input == :quit
87
+ end
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def render
94
+ @counter += 1
95
+
96
+ data_set = @data_sets[@data_index]
97
+ direction = @directions[@direction_index][:direction]
98
+ style = @styles[@style_index][:style]
99
+ absent_symbol = @absent_symbols[@absent_symbol_index][:symbol]
100
+ absent_value_style = @absent_styles[@absent_style_index][:style]
101
+ bar_set = @bar_sets[@bar_set_index][:set]
102
+
103
+ # Use static data for clarity when cycling options
104
+ current_data = data_set[:data]
105
+
106
+ layout = RatatuiRuby::Layout.new(
107
+ direction: :vertical,
108
+ constraints: [
109
+ RatatuiRuby::Constraint.fill(1),
110
+ RatatuiRuby::Constraint.length(6),
111
+ ],
112
+ children: [
113
+ # Main content area with multiple sparkline examples
114
+ RatatuiRuby::Layout.new(
115
+ direction: :vertical,
116
+ constraints: [
117
+ RatatuiRuby::Constraint.length(1),
118
+ RatatuiRuby::Constraint.length(3),
119
+ RatatuiRuby::Constraint.length(3),
120
+ RatatuiRuby::Constraint.length(3),
121
+ RatatuiRuby::Constraint.length(3),
122
+ RatatuiRuby::Constraint.fill(1)
123
+ ],
124
+ children: [
125
+ RatatuiRuby::Paragraph.new(
126
+ text: "Sparkline Widget Demo - Cycle attributes with hotkeys"
127
+ ),
128
+ # Sparkline 1: Main interactive sparkline
129
+ RatatuiRuby::Sparkline.new(
130
+ data: current_data,
131
+ direction:,
132
+ style:,
133
+ absent_value_symbol: absent_symbol,
134
+ absent_value_style:,
135
+ bar_set:,
136
+ block: RatatuiRuby::Block.new(title: "Interactive Sparkline")
137
+ ),
138
+ # Sparkline 2: Same data, opposite direction
139
+ RatatuiRuby::Sparkline.new(
140
+ data: current_data.reverse,
141
+ direction:,
142
+ style:,
143
+ absent_value_symbol: absent_symbol,
144
+ absent_value_style:,
145
+ bar_set:,
146
+ block: RatatuiRuby::Block.new(title: "Reversed Data")
147
+ ),
148
+ # Sparkline 3: Without absent value symbol (for comparison)
149
+ RatatuiRuby::Sparkline.new(
150
+ data: current_data,
151
+ direction:,
152
+ style:,
153
+ bar_set:,
154
+ block: RatatuiRuby::Block.new(title: "Without Absent Marker")
155
+ ),
156
+ # Sparkline 4: Gap pattern responsive to absent marker controls
157
+ RatatuiRuby::Sparkline.new(
158
+ data: [5, nil, 8, nil, 6, nil, 9, nil, 7, nil, 10, nil],
159
+ direction:,
160
+ style: RatatuiRuby::Style.new(fg: :blue),
161
+ absent_value_symbol: absent_symbol,
162
+ absent_value_style:,
163
+ bar_set:,
164
+ block: RatatuiRuby::Block.new(title: "Gap Pattern (Responsive)")
165
+ ),
166
+ RatatuiRuby::Paragraph.new(text: "")
167
+ ]
168
+ ),
169
+ # Bottom control panel
170
+ RatatuiRuby::Block.new(
171
+ title: "Controls",
172
+ borders: [:all],
173
+ children: [
174
+ RatatuiRuby::Paragraph.new(
175
+ text: [
176
+ # Line 1: Data
177
+ RatatuiRuby::Text::Line.new(spans: [
178
+ RatatuiRuby::Text::Span.new(content: "↑/↓", style: @hotkey_style),
179
+ RatatuiRuby::Text::Span.new(content: ": Data (#{@data_sets[@data_index][:name]})")
180
+ ]),
181
+ # Line 2: View
182
+ RatatuiRuby::Text::Line.new(spans: [
183
+ RatatuiRuby::Text::Span.new(content: "d", style: @hotkey_style),
184
+ RatatuiRuby::Text::Span.new(content: ": Direction (#{@directions[@direction_index][:name]}) "),
185
+ RatatuiRuby::Text::Span.new(content: "c", style: @hotkey_style),
186
+ RatatuiRuby::Text::Span.new(content: ": Color (#{@styles[@style_index][:name]})")
187
+ ]),
188
+ # Line 3: Markers
189
+ RatatuiRuby::Text::Line.new(spans: [
190
+ RatatuiRuby::Text::Span.new(content: "m", style: @hotkey_style),
191
+ RatatuiRuby::Text::Span.new(content: ": Absent Value Symbol (#{@absent_symbols[@absent_symbol_index][:name]}) "),
192
+ RatatuiRuby::Text::Span.new(content: "s", style: @hotkey_style),
193
+ RatatuiRuby::Text::Span.new(content: ": Absent Value Style (#{@absent_styles[@absent_style_index][:name]})")
194
+ ]),
195
+ # Line 4: General
196
+ RatatuiRuby::Text::Line.new(spans: [
197
+ RatatuiRuby::Text::Span.new(content: "b", style: @hotkey_style),
198
+ RatatuiRuby::Text::Span.new(content: ": Bar Set (#{@bar_sets[@bar_set_index][:name]}) "),
199
+ RatatuiRuby::Text::Span.new(content: "q", style: @hotkey_style),
200
+ RatatuiRuby::Text::Span.new(content: ": Quit")
201
+ ])
202
+ ]
203
+ )
204
+ ]
205
+ )
206
+ ]
207
+ )
208
+
209
+
210
+ RatatuiRuby.draw(layout)
211
+ end
212
+
213
+ def handle_input
214
+ event = RatatuiRuby.poll_event
215
+ return unless event
216
+
217
+ case event
218
+ in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
219
+ :quit
220
+ in type: :key, code: "up"
221
+ @data_index = (@data_index - 1) % @data_sets.length
222
+ in type: :key, code: "down"
223
+ @data_index = (@data_index + 1) % @data_sets.length
224
+ in type: :key, code: "d"
225
+ @direction_index = (@direction_index + 1) % @directions.length
226
+ in type: :key, code: "c"
227
+ @style_index = (@style_index + 1) % @styles.length
228
+ in type: :key, code: "m"
229
+ @absent_symbol_index = (@absent_symbol_index + 1) % @absent_symbols.length
230
+ in type: :key, code: "s"
231
+ @absent_style_index = (@absent_style_index + 1) % @absent_styles.length
232
+ in type: :key, code: "b"
233
+ @bar_set_index = (@bar_set_index + 1) % @bar_sets.length
234
+ else
235
+ nil
236
+ end
237
+ end
238
+ end
239
+
240
+ SparklineDemoApp.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,10 @@
1
+ # SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ # SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ class SparklineDemoApp
5
+ # @public
6
+ def self.new: () -> SparklineDemoApp
7
+
8
+ # @public
9
+ def run: () -> void
10
+ end
@@ -0,0 +1,107 @@
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 TestSparklineDemoApp < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = SparklineDemoApp.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, "Sparkline Widget Demo"
26
+ assert_includes content, "Interactive Sparkline"
27
+ assert_includes content, "Reversed Data"
28
+ end
29
+ end
30
+
31
+ def test_cycle_data_set_up
32
+ with_test_terminal do
33
+ inject_keys(:up, :q)
34
+ @app.run
35
+
36
+ content = buffer_content.join("\n")
37
+ # Should still render after cycling
38
+ assert_includes content, "Sparkline Widget Demo"
39
+ end
40
+ end
41
+
42
+ def test_cycle_data_set_down
43
+ with_test_terminal do
44
+ inject_keys(:down, :down, :q)
45
+ @app.run
46
+
47
+ content = buffer_content.join("\n")
48
+ assert_includes content, "Sparkline Widget Demo"
49
+ end
50
+ end
51
+
52
+ def test_cycle_direction
53
+ with_test_terminal do
54
+ inject_keys(:d, :q)
55
+ @app.run
56
+
57
+ content = buffer_content.join("\n")
58
+ assert_includes content, "Sparkline Widget Demo"
59
+ end
60
+ end
61
+
62
+ def test_cycle_color
63
+ with_test_terminal do
64
+ inject_keys(:c, :c, :q)
65
+ @app.run
66
+
67
+ content = buffer_content.join("\n")
68
+ assert_includes content, "Sparkline Widget Demo"
69
+ end
70
+ end
71
+
72
+ def test_cycle_absent_marker
73
+ with_test_terminal do
74
+ inject_keys(:m, :m, :m, :q)
75
+ @app.run
76
+
77
+ content = buffer_content.join("\n")
78
+ assert_includes content, "Sparkline Widget Demo"
79
+ end
80
+ end
81
+
82
+ def test_cycle_absent_style
83
+ with_test_terminal do
84
+ inject_keys(:s, :s, :q)
85
+ @app.run
86
+
87
+ content = buffer_content.join("\n")
88
+ assert_includes content, "Sparkline Widget Demo"
89
+ end
90
+ end
91
+
92
+ def test_cycle_bar_set
93
+ with_test_terminal do
94
+ inject_keys(:b, :q)
95
+ @app.run
96
+
97
+ content = buffer_content.join("\n")
98
+ assert_includes content, "Sparkline Widget Demo"
99
+ # Verify render with Numbers set (one_eighth="1", etc.)
100
+ # Input data [1..12] with implicit max 12.
101
+ # With Numbers set, we expect digits 1-8 to appear.
102
+ # Just asserting no crash is a start, but let's check for the label update if possible
103
+ # The controls footer should update.
104
+ # assert_includes content, ": Bar Set" # Included in controls
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,65 @@
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 TableFlexApp
10
+ def run
11
+ RatatuiRuby.run do |tui|
12
+ loop do
13
+ render(tui)
14
+ break if handle_input(tui) == :quit
15
+ end
16
+ end
17
+ end
18
+
19
+ def render(tui)
20
+ layout = tui.layout(
21
+ direction: :vertical,
22
+ constraints: [
23
+ RatatuiRuby::Constraint.length(3),
24
+ RatatuiRuby::Constraint.fill(1),
25
+ RatatuiRuby::Constraint.fill(1),
26
+ RatatuiRuby::Constraint.fill(1)
27
+ ],
28
+ children: [
29
+ tui.paragraph(
30
+ text: "Table Flex Layout (press 'q' to quit)",
31
+ block: tui.block(title: "Header", borders: [:all])
32
+ ),
33
+ tui.table(
34
+ header: ["Legacy (Default)", "Table"],
35
+ rows: [["Item 1", "Item 2"], ["Item 3", "Item 4"]],
36
+ widths: [RatatuiRuby::Constraint.length(20), RatatuiRuby::Constraint.length(20)],
37
+ block: tui.block(title: "Flex: :legacy (Default)", borders: [:all])
38
+ ),
39
+ tui.table(
40
+ header: ["Space", "Between"],
41
+ rows: [["A", "B"], ["C", "D"]],
42
+ widths: [RatatuiRuby::Constraint.length(20), RatatuiRuby::Constraint.length(20)],
43
+ block: tui.block(title: "Flex: :space_between", borders: [:all]),
44
+ flex: :space_between
45
+ ),
46
+ tui.table(
47
+ header: ["Space", "Around"],
48
+ rows: [["E", "F"], ["G", "H"]],
49
+ widths: [RatatuiRuby::Constraint.length(20), RatatuiRuby::Constraint.length(20)],
50
+ block: tui.block(title: "Flex: :space_around", borders: [:all]),
51
+ flex: :space_around
52
+ )
53
+ ]
54
+ )
55
+ tui.draw(layout)
56
+ end
57
+
58
+ def handle_input(tui)
59
+ event = tui.poll_event
60
+ return unless event
61
+ :quit if event == "q" || event == :ctrl_c
62
+ end
63
+ end
64
+
65
+ TableFlexApp.new.run if __FILE__ == $0
@@ -0,0 +1,7 @@
1
+ class TableFlexApp
2
+ # @public
3
+ def self.new: () -> TableFlexApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,36 @@
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 TestTableFlexApp < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = TableFlexApp.new
17
+ end
18
+
19
+ def test_render
20
+ with_test_terminal do
21
+ # Queue quit
22
+ inject_key(:q)
23
+
24
+ @app.run
25
+
26
+ content = buffer_content.join("\n")
27
+ assert_includes content, "Table Flex Layout"
28
+ assert_includes content, "Flex: :space_between"
29
+ assert_includes content, "Flex: :space_around"
30
+
31
+ # Verify some table content
32
+ assert_includes content, "Legacy (Default)"
33
+ assert_includes content, "Item 1"
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,198 @@
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 "bundler/setup"
9
+ require "ratatui_ruby"
10
+
11
+ # Sample process data
12
+ PROCESSES = [
13
+ { pid: 1234, name: "ruby", cpu: 15.2 },
14
+ { pid: 5678, name: "postgres", cpu: 8.7 },
15
+ { pid: 9012, name: "nginx", cpu: 3.1 },
16
+ { pid: 3456, name: "redis", cpu: 12.4 },
17
+ { pid: 7890, name: "sidekiq", cpu: 22.8 },
18
+ { pid: 2345, name: "webpack", cpu: 45.3 },
19
+ { pid: 6789, name: "node", cpu: 18.9 }
20
+ ].freeze
21
+
22
+ class TableSelectApp
23
+ attr_reader :selected_index, :selected_col, :current_style_index, :column_spacing, :highlight_spacing, :column_highlight_style, :cell_highlight_style
24
+
25
+ STYLES = [
26
+ { name: "Cyan", style: RatatuiRuby::Style.new(fg: :cyan) },
27
+ { name: "Red", style: RatatuiRuby::Style.new(fg: :red) },
28
+ { name: "Green", style: RatatuiRuby::Style.new(fg: :green) },
29
+ { name: "Blue on White", style: RatatuiRuby::Style.new(fg: :blue, bg: :white) },
30
+ { name: "Magenta", style: RatatuiRuby::Style.new(fg: :magenta, modifiers: [:bold]) }
31
+ ].freeze
32
+
33
+ HIGHLIGHT_SPACINGS = [
34
+ { name: "When Selected", spacing: :when_selected },
35
+ { name: "Always", spacing: :always },
36
+ { name: "Never", spacing: :never }
37
+ ].freeze
38
+
39
+ def initialize
40
+ @selected_index = 1
41
+ @selected_col = 1
42
+ @current_style_index = 0
43
+ @column_spacing = 1
44
+ @highlight_spacing_index = 0
45
+ @column_highlight_style = RatatuiRuby::Style.new(fg: :magenta)
46
+ @show_column_highlight = true
47
+ @cell_highlight_style = RatatuiRuby::Style.new(fg: :white, bg: :red, modifiers: [:bold])
48
+ @show_cell_highlight = true
49
+ @hotkey_style = RatatuiRuby::Style.new(modifiers: [:bold, :underlined])
50
+ end
51
+
52
+ def run
53
+ RatatuiRuby.run do
54
+ loop do
55
+ render
56
+ break if handle_input == :quit
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def render
64
+ # Create table rows from process data
65
+ rows = PROCESSES.map { |p| [p[:pid].to_s, p[:name], "#{p[:cpu]}%"] }
66
+
67
+ # Define column widths
68
+ widths = [
69
+ RatatuiRuby::Constraint.length(8),
70
+ RatatuiRuby::Constraint.length(15),
71
+ RatatuiRuby::Constraint.length(10)
72
+ ]
73
+
74
+ # Create highlight style (yellow text)
75
+ highlight_style = RatatuiRuby::Style.new(fg: :yellow)
76
+
77
+ current_style_entry = STYLES[@current_style_index]
78
+ current_spacing_entry = HIGHLIGHT_SPACINGS[@highlight_spacing_index]
79
+ selection_label = @selected_index.nil? ? "none" : @selected_index.to_s
80
+
81
+ # Main table
82
+ table = RatatuiRuby::Table.new(
83
+ header: ["PID", "Name", "CPU"],
84
+ rows: rows,
85
+ widths: widths,
86
+ selected_row: @selected_index,
87
+ selected_column: @selected_col,
88
+ highlight_style: highlight_style,
89
+ highlight_symbol: "> ",
90
+ highlight_spacing: current_spacing_entry[:spacing],
91
+ column_highlight_style: @show_column_highlight ? @column_highlight_style : nil,
92
+ cell_highlight_style: @show_cell_highlight ? @cell_highlight_style : nil,
93
+ style: current_style_entry[:style],
94
+ column_spacing: @column_spacing,
95
+ block: RatatuiRuby::Block.new(
96
+ title: "Processes",
97
+ borders: :all
98
+ ),
99
+ footer: ["Total: #{PROCESSES.length}", "Total CPU: #{PROCESSES.sum { |p| p[:cpu] }}%", ""]
100
+ )
101
+
102
+ # Bottom control panel
103
+ control_panel = RatatuiRuby::Block.new(
104
+ title: "Controls",
105
+ borders: [:all],
106
+ children: [
107
+ RatatuiRuby::Paragraph.new(
108
+ text: [
109
+ # Line 1: Navigation
110
+ RatatuiRuby::Text::Line.new(spans: [
111
+ RatatuiRuby::Text::Span.new(content: "↑/↓", style: @hotkey_style),
112
+ RatatuiRuby::Text::Span.new(content: ": Nav Row "),
113
+ RatatuiRuby::Text::Span.new(content: "←/→", style: @hotkey_style),
114
+ RatatuiRuby::Text::Span.new(content: ": Nav Col "),
115
+ RatatuiRuby::Text::Span.new(content: "x", style: @hotkey_style),
116
+ RatatuiRuby::Text::Span.new(content: ": Toggle Row (#{selection_label}) "),
117
+ RatatuiRuby::Text::Span.new(content: "q", style: @hotkey_style),
118
+ RatatuiRuby::Text::Span.new(content: ": Quit")
119
+ ]),
120
+ # Line 2: Table Controls
121
+ RatatuiRuby::Text::Line.new(spans: [
122
+ RatatuiRuby::Text::Span.new(content: "s", style: @hotkey_style),
123
+ RatatuiRuby::Text::Span.new(content: ": Style (#{current_style_entry[:name]}) "),
124
+ RatatuiRuby::Text::Span.new(content: "p", style: @hotkey_style),
125
+ RatatuiRuby::Text::Span.new(content: ": Spacing (#{current_spacing_entry[:name]}) ")
126
+ ]),
127
+ # Line 3: More Controls
128
+ RatatuiRuby::Text::Line.new(spans: [
129
+ RatatuiRuby::Text::Span.new(content: ": Col Space (#{@column_spacing}) "),
130
+ RatatuiRuby::Text::Span.new(content: "c", style: @hotkey_style),
131
+ RatatuiRuby::Text::Span.new(content: ": Col Highlight (#{@show_column_highlight ? 'On' : 'Off'}) "),
132
+ RatatuiRuby::Text::Span.new(content: "z", style: @hotkey_style),
133
+ RatatuiRuby::Text::Span.new(content: ": Cell Highlight (#{@show_cell_highlight ? 'On' : 'Off'})")
134
+ ])
135
+ ]
136
+ )
137
+ ]
138
+ )
139
+
140
+ # Layout
141
+ layout = RatatuiRuby::Layout.new(
142
+ direction: :vertical,
143
+ constraints: [
144
+ RatatuiRuby::Constraint.fill(1),
145
+ RatatuiRuby::Constraint.length(5),
146
+ ],
147
+ children: [table, control_panel]
148
+ )
149
+
150
+ # Draw the table
151
+ RatatuiRuby.draw(layout)
152
+ end
153
+
154
+ def handle_input
155
+ event = RatatuiRuby.poll_event
156
+ return unless event
157
+
158
+ case event
159
+ in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
160
+ :quit
161
+ in type: :key, code: "down" | "j"
162
+ @selected_index = ((@selected_index || -1) + 1) % PROCESSES.length
163
+ in type: :key, code: "up" | "k"
164
+ @selected_index = (@selected_index || 0) - 1
165
+ @selected_index = PROCESSES.length - 1 if @selected_index.negative?
166
+ in type: :key, code: "right" | "l"
167
+ @selected_col = ((@selected_col || -1) + 1) % 3 # 3 columns
168
+ in type: :key, code: "left" | "h"
169
+ # 'h' is already used for highlight spacing, but let's override it or ignore vim keys for left/right?
170
+ # Actually 'h' is used for spacing in this demo. Let's just use arrows for cols.
171
+ # Or map 'h' to left if user meant vim keys.
172
+ # The demo uses 'h' for "Spacing". Let's change Spacing key to 'p' (property/padding?) or something else.
173
+ # Or just stick to arrows for columns to avoid conflict.
174
+ @selected_col = (@selected_col || 0) - 1
175
+ @selected_col = 2 if @selected_col.negative?
176
+ in type: :key, code: "s"
177
+ @current_style_index = (@current_style_index + 1) % STYLES.length
178
+ in type: :key, code: "+"
179
+ @column_spacing += 1
180
+ in type: :key, code: "-"
181
+ @column_spacing = [@column_spacing - 1, 0].max
182
+ in type: :key, code: "p"
183
+ @highlight_spacing_index = (@highlight_spacing_index + 1) % HIGHLIGHT_SPACINGS.length
184
+ in type: :key, code: "x"
185
+ @selected_index = @selected_index.nil? ? 0 : nil
186
+ in type: :key, code: "c"
187
+ @show_column_highlight = !@show_column_highlight
188
+ in type: :key, code: "z"
189
+ @show_cell_highlight = !@show_cell_highlight
190
+ else
191
+ nil
192
+ end
193
+ end
194
+ end
195
+
196
+ if __FILE__ == $0
197
+ TableSelectApp.new.run
198
+ end
@@ -0,0 +1,7 @@
1
+ class TableSelectApp
2
+ # @public
3
+ def self.new: () -> TableSelectApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end