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,203 @@
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 Chart widget with interactive attribute cycling.
10
+ class ChartDemoApp
11
+ def initialize
12
+ @markers = [
13
+ { name: "Dot (·)", marker: :dot },
14
+ { name: "Braille", marker: :braille },
15
+ { name: "Block (█)", marker: :block },
16
+ { name: "Bar", marker: :bar }
17
+ ]
18
+ @marker_index = 0
19
+
20
+ @dataset_styles = [
21
+ { name: "Yellow", style: RatatuiRuby::Style.new(fg: :yellow) },
22
+ { name: "Green", style: RatatuiRuby::Style.new(fg: :green) },
23
+ { name: "Cyan", style: RatatuiRuby::Style.new(fg: :cyan) },
24
+ { name: "Red", style: RatatuiRuby::Style.new(fg: :red) },
25
+ { name: "Magenta", style: RatatuiRuby::Style.new(fg: :magenta) },
26
+ { name: "Bold Blue", style: RatatuiRuby::Style.new(fg: :blue, modifiers: [:bold]) },
27
+ { name: "Dim White", style: RatatuiRuby::Style.new(fg: :white, modifiers: [:dim]) },
28
+ { name: "Italic Green", style: RatatuiRuby::Style.new(fg: :green, modifiers: [:italic]) },
29
+ { name: "Alert (Red/White/Bar)", style: RatatuiRuby::Style.new(fg: :white, bg: :red, modifiers: [:bold]) }
30
+ ]
31
+ @dataset_style_index = 0
32
+
33
+ @x_alignments = [
34
+ { name: "Left", alignment: :left },
35
+ { name: "Center", alignment: :center },
36
+ { name: "Right", alignment: :right }
37
+ ]
38
+ @x_alignment_index = 1
39
+
40
+ @y_alignments = [
41
+ { name: "Left", alignment: :left },
42
+ { name: "Center", alignment: :center },
43
+ { name: "Right", alignment: :right }
44
+ ]
45
+ @y_alignment_index = 2
46
+
47
+ @legend_positions = [
48
+ { name: "Top Right", position: :top_right },
49
+ { name: "Top Left", position: :top_left },
50
+ { name: "Bottom Right", position: :bottom_right },
51
+ { name: "Bottom Left", position: :bottom_left }
52
+ ]
53
+ @legend_position_index = 0
54
+
55
+ @hotkey_style = RatatuiRuby::Style.new(modifiers: [:bold, :underlined])
56
+ end
57
+
58
+ def run
59
+ RatatuiRuby.run do
60
+ loop do
61
+ render
62
+ break if handle_input == :quit
63
+ sleep 0.05
64
+ end
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def render
71
+ # Static sample data: sine wave with wider range for better visibility
72
+ line_data = (0..50).map do |i|
73
+ x = i / 5.0
74
+ [x, Math.sin(x)]
75
+ end
76
+
77
+ # Scatter: Random points
78
+ scatter_data = (0..20).map do |_|
79
+ [rand(0.0..10.0), rand(-1.0..1.0)]
80
+ end
81
+
82
+ style = @dataset_styles[@dataset_style_index][:style]
83
+ # Ensure the second dataset has a different style
84
+ scatter_style = @dataset_styles[(@dataset_style_index + 2) % @dataset_styles.length][:style]
85
+
86
+ datasets = [
87
+ RatatuiRuby::Dataset.new(
88
+ name: "Line",
89
+ data: line_data,
90
+ style:,
91
+ marker: style.modifiers.include?(:bold) && style.bg ? :bar : @markers[@marker_index][:marker],
92
+ graph_type: :line
93
+ ),
94
+ RatatuiRuby::Dataset.new(
95
+ name: "Scatter",
96
+ data: scatter_data,
97
+ style: scatter_style,
98
+ marker: scatter_style.modifiers.include?(:bold) && scatter_style.bg ? :bar : @markers[@marker_index][:marker],
99
+ graph_type: :scatter
100
+ )
101
+ ]
102
+
103
+ x_alignment = @x_alignments[@x_alignment_index][:alignment]
104
+ y_alignment = @y_alignments[@y_alignment_index][:alignment]
105
+ legend_position = @legend_positions[@legend_position_index][:position]
106
+
107
+ chart = RatatuiRuby::Chart.new(
108
+ datasets:,
109
+ x_axis: RatatuiRuby::Axis.new(
110
+ title: "Time",
111
+ bounds: [0.0, 10.0],
112
+ labels: %w[0 5 10],
113
+ style: RatatuiRuby::Style.new(fg: :yellow),
114
+ labels_alignment: x_alignment
115
+ ),
116
+ y_axis: RatatuiRuby::Axis.new(
117
+ title: "Amplitude",
118
+ bounds: [-1.0, 1.0],
119
+ labels: %w[-1 0 1],
120
+ style: RatatuiRuby::Style.new(fg: :cyan),
121
+ labels_alignment: y_alignment
122
+ ),
123
+ block: RatatuiRuby::Block.new(
124
+ title: "Chart Widget Demo",
125
+ borders: [:all]
126
+ ),
127
+ legend_position:,
128
+ hidden_legend_constraints: [
129
+ RatatuiRuby::Constraint.min(20),
130
+ RatatuiRuby::Constraint.min(10)
131
+ ]
132
+ )
133
+
134
+ layout = RatatuiRuby::Layout.new(
135
+ direction: :vertical,
136
+ constraints: [
137
+ RatatuiRuby::Constraint.fill(1),
138
+ RatatuiRuby::Constraint.length(5)
139
+ ],
140
+ children: [
141
+ chart,
142
+ # Bottom control panel
143
+ RatatuiRuby::Block.new(
144
+ title: "Controls",
145
+ borders: [:all],
146
+ children: [
147
+ RatatuiRuby::Paragraph.new(
148
+ text: [
149
+ # Line 1: Markers & Colors
150
+ RatatuiRuby::Text::Line.new(spans: [
151
+ RatatuiRuby::Text::Span.new(content: "m", style: @hotkey_style),
152
+ RatatuiRuby::Text::Span.new(content: ": Marker (#{@markers[@marker_index][:name]}) "),
153
+ RatatuiRuby::Text::Span.new(content: "s", style: @hotkey_style),
154
+ RatatuiRuby::Text::Span.new(content: ": Style (#{@dataset_styles[@dataset_style_index][:name]})")
155
+ ]),
156
+ # Line 2: Axis alignments
157
+ RatatuiRuby::Text::Line.new(spans: [
158
+ RatatuiRuby::Text::Span.new(content: "x", style: @hotkey_style),
159
+ RatatuiRuby::Text::Span.new(content: ": X Align (#{@x_alignments[@x_alignment_index][:name]}) "),
160
+ RatatuiRuby::Text::Span.new(content: "y", style: @hotkey_style),
161
+ RatatuiRuby::Text::Span.new(content: ": Y Align (#{@y_alignments[@y_alignment_index][:name]}) "),
162
+ RatatuiRuby::Text::Span.new(content: "l", style: @hotkey_style),
163
+ RatatuiRuby::Text::Span.new(content: ": Legend (#{@legend_positions[@legend_position_index][:name]})")
164
+ ]),
165
+ # Line 3: Quit
166
+ RatatuiRuby::Text::Line.new(spans: [
167
+ RatatuiRuby::Text::Span.new(content: "q", style: @hotkey_style),
168
+ RatatuiRuby::Text::Span.new(content: ": Quit")
169
+ ])
170
+ ]
171
+ )
172
+ ]
173
+ )
174
+ ]
175
+ )
176
+
177
+ RatatuiRuby.draw(layout)
178
+ end
179
+
180
+ def handle_input
181
+ event = RatatuiRuby.poll_event
182
+ return unless event
183
+
184
+ case event
185
+ in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
186
+ :quit
187
+ in type: :key, code: "m"
188
+ @marker_index = (@marker_index + 1) % @markers.length
189
+ in type: :key, code: "s"
190
+ @dataset_style_index = (@dataset_style_index + 1) % @dataset_styles.length
191
+ in type: :key, code: "x"
192
+ @x_alignment_index = (@x_alignment_index + 1) % @x_alignments.length
193
+ in type: :key, code: "y"
194
+ @y_alignment_index = (@y_alignment_index + 1) % @y_alignments.length
195
+ in type: :key, code: "l"
196
+ @legend_position_index = (@legend_position_index + 1) % @legend_positions.length
197
+ else
198
+ nil
199
+ end
200
+ end
201
+ end
202
+
203
+ ChartDemoApp.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,7 @@
1
+ class ChartDemoApp
2
+ # @public
3
+ def self.new: () -> ChartDemoApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,102 @@
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 TestChartDemo < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = ChartDemoApp.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, "Chart Widget Demo"
26
+ assert_includes content, "Time"
27
+ assert_includes content, "Amplitude"
28
+ assert_includes content, "Line"
29
+ assert_includes content, "Scatter"
30
+ assert_includes content, "Controls"
31
+ # Verify controls are visible
32
+ assert_includes content, "Marker"
33
+ assert_includes content, "Style"
34
+ assert_includes content, "Align"
35
+ end
36
+ end
37
+
38
+ def test_marker_cycling
39
+ with_test_terminal do
40
+ inject_key("m") # Cycle marker
41
+ inject_key(:q)
42
+ @app.run
43
+
44
+ content = buffer_content.join("\n")
45
+ # Should show the chart still renders
46
+ assert_includes content, "Chart Widget Demo"
47
+ assert_includes content, "Time"
48
+ end
49
+ end
50
+
51
+ def test_style_cycling
52
+ with_test_terminal do
53
+ inject_key("s") # Cycle style
54
+ inject_key(:q)
55
+ @app.run
56
+
57
+ content = buffer_content.join("\n")
58
+ assert_includes content, "Chart Widget Demo"
59
+ assert_includes content, "Time"
60
+ end
61
+ end
62
+
63
+ def test_x_alignment_cycling
64
+ with_test_terminal do
65
+ inject_key("x") # Cycle X-axis label alignment
66
+ inject_key(:q)
67
+ @app.run
68
+
69
+ content = buffer_content.join("\n")
70
+ assert_includes content, "Chart Widget Demo"
71
+ assert_includes content, "Time"
72
+ end
73
+ end
74
+
75
+ def test_y_alignment_cycling
76
+ with_test_terminal do
77
+ inject_key("y") # Cycle Y-axis label alignment
78
+ inject_key(:q)
79
+ @app.run
80
+
81
+ content = buffer_content.join("\n")
82
+ assert_includes content, "Chart Widget Demo"
83
+ assert_includes content, "Amplitude"
84
+ end
85
+ end
86
+
87
+ def test_multiple_cycles
88
+ with_test_terminal do
89
+ inject_key("m")
90
+ inject_key("s")
91
+ inject_key("x")
92
+ inject_key("y")
93
+ inject_key(:q)
94
+ @app.run
95
+
96
+ content = buffer_content.join("\n")
97
+ assert_includes content, "Chart Widget Demo"
98
+ # Chart and controls should all render
99
+ assert_includes content, "Controls"
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,51 @@
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
+ # A custom widget that draws a diagonal line.
10
+ class DiagonalWidget
11
+ def render(area)
12
+ # Draw a diagonal line within the area
13
+ (0..10).filter_map do |i|
14
+ next if area.x + i >= area.x + area.width || area.y + i >= area.y + area.height
15
+
16
+ RatatuiRuby::Draw.string(
17
+ area.x + i,
18
+ area.y + i,
19
+ "\\",
20
+ { fg: :red, modifiers: [:bold] }
21
+ )
22
+ end
23
+ end
24
+ end
25
+
26
+ class CustomWidgetApp
27
+ def run
28
+ RatatuiRuby.run do |tui|
29
+ loop do
30
+ tui.draw(
31
+ RatatuiRuby::Layout.new(
32
+ direction: :vertical,
33
+ constraints: [
34
+ RatatuiRuby::Constraint.percentage(50),
35
+ RatatuiRuby::Constraint.percentage(50),
36
+ ],
37
+ children: [
38
+ RatatuiRuby::Paragraph.new(text: "Above custom widget"),
39
+ DiagonalWidget.new,
40
+ ]
41
+ )
42
+ )
43
+
44
+ event = RatatuiRuby.poll_event
45
+ break if event == "q" || event == :ctrl_c
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ CustomWidgetApp.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,7 @@
1
+ class CustomWidgetApp
2
+ # @public
3
+ def self.new: () -> CustomWidgetApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,30 @@
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 TestCustomWidget < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = CustomWidgetApp.new
17
+ end
18
+
19
+ def test_render
20
+ with_test_terminal do
21
+ inject_key(:q)
22
+ @app.run
23
+
24
+ content = buffer_content.join("\n")
25
+ assert_includes content, "Above custom widget"
26
+ # The custom widget draws backslashes
27
+ assert_includes content, "\\"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,156 @@
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 FlexLayoutApp
10
+ def run
11
+ RatatuiRuby.run do
12
+ loop do
13
+ render
14
+ break if handle_input == :quit
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def render
22
+ view_tree = RatatuiRuby::Layout.new(
23
+ direction: :vertical,
24
+ constraints: [
25
+ RatatuiRuby::Constraint.length(3),
26
+ RatatuiRuby::Constraint.fill(1),
27
+ RatatuiRuby::Constraint.fill(1),
28
+ RatatuiRuby::Constraint.fill(1),
29
+ RatatuiRuby::Constraint.fill(1)
30
+ ],
31
+ children: [
32
+ RatatuiRuby::Paragraph.new(
33
+ text: "Fill & Flex Layout Demo (press 'q' to quit)",
34
+ block: RatatuiRuby::Block.new(title: "Header", borders: [:all])
35
+ ),
36
+ fill_demo_row,
37
+ space_between_demo_row,
38
+ space_evenly_demo_row,
39
+ ratio_demo_row
40
+ ]
41
+ )
42
+
43
+ RatatuiRuby.draw(view_tree)
44
+ end
45
+
46
+ def fill_demo_row
47
+ RatatuiRuby::Layout.new(
48
+ direction: :horizontal,
49
+ constraints: [
50
+ RatatuiRuby::Constraint.fill(1),
51
+ RatatuiRuby::Constraint.fill(3)
52
+ ],
53
+ children: [
54
+ RatatuiRuby::Block.new(
55
+ title: "Fill(1)",
56
+ borders: [:all],
57
+ border_color: "red"
58
+ ),
59
+ RatatuiRuby::Block.new(
60
+ title: "Fill(3)",
61
+ borders: [:all],
62
+ border_color: "blue"
63
+ )
64
+ ]
65
+ )
66
+ end
67
+
68
+ def space_between_demo_row
69
+ RatatuiRuby::Layout.new(
70
+ direction: :horizontal,
71
+ flex: :space_between,
72
+ constraints: [
73
+ RatatuiRuby::Constraint.length(12),
74
+ RatatuiRuby::Constraint.length(12),
75
+ RatatuiRuby::Constraint.length(12)
76
+ ],
77
+ children: [
78
+ RatatuiRuby::Block.new(
79
+ title: "Block A",
80
+ borders: [:all],
81
+ border_color: "green"
82
+ ),
83
+ RatatuiRuby::Block.new(
84
+ title: "Block B",
85
+ borders: [:all],
86
+ border_color: "yellow"
87
+ ),
88
+ RatatuiRuby::Block.new(
89
+ title: "Block C",
90
+ borders: [:all],
91
+ border_color: "magenta"
92
+ )
93
+ ]
94
+ )
95
+ end
96
+
97
+ def space_evenly_demo_row
98
+ RatatuiRuby::Layout.new(
99
+ direction: :horizontal,
100
+ flex: :space_evenly,
101
+ constraints: [
102
+ RatatuiRuby::Constraint.length(12),
103
+ RatatuiRuby::Constraint.length(12),
104
+ RatatuiRuby::Constraint.length(12)
105
+ ],
106
+ children: [
107
+ RatatuiRuby::Block.new(
108
+ title: "Even A",
109
+ borders: [:all],
110
+ border_color: "cyan"
111
+ ),
112
+ RatatuiRuby::Block.new(
113
+ title: "Even B",
114
+ borders: [:all],
115
+ border_color: "blue"
116
+ ),
117
+ RatatuiRuby::Block.new(
118
+ title: "Even C",
119
+ borders: [:all],
120
+ border_color: "red"
121
+ )
122
+ ]
123
+ )
124
+ end
125
+
126
+ def ratio_demo_row
127
+ RatatuiRuby::Layout.new(
128
+ direction: :horizontal,
129
+ constraints: [
130
+ RatatuiRuby::Constraint.ratio(1, 4),
131
+ RatatuiRuby::Constraint.ratio(3, 4)
132
+ ],
133
+ children: [
134
+ RatatuiRuby::Block.new(
135
+ title: "Ratio(1, 4)",
136
+ borders: [:all],
137
+ border_color: "green"
138
+ ),
139
+ RatatuiRuby::Block.new(
140
+ title: "Ratio(3, 4)",
141
+ borders: [:all],
142
+ border_color: "magenta"
143
+ )
144
+ ]
145
+ )
146
+ end
147
+
148
+ def handle_input
149
+ event = RatatuiRuby.poll_event
150
+ return unless event
151
+
152
+ :quit if event == "q" || event == :ctrl_c
153
+ end
154
+ end
155
+
156
+ FlexLayoutApp.new.run if __FILE__ == $0
@@ -0,0 +1,7 @@
1
+ class FlexLayoutApp
2
+ # @public
3
+ def self.new: () -> FlexLayoutApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ 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
+ require "ratatui_ruby/test_helper"
9
+ require "minitest/autorun"
10
+ require_relative "app"
11
+
12
+ class TestFlexLayout < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = FlexLayoutApp.new
17
+ end
18
+
19
+ def test_render_header
20
+ with_test_terminal do
21
+ # Queue quit
22
+ inject_key(:q)
23
+
24
+ @app.run
25
+
26
+ assert_includes buffer_content[0], "Header"
27
+ assert buffer_content.any? { |line| line.include?("Fill & Flex Layout Demo") }
28
+ end
29
+ end
30
+
31
+ def test_fill_constraint_ratio
32
+ with_test_terminal do
33
+ # Queue quit
34
+ inject_key(:q)
35
+
36
+ @app.run
37
+
38
+ # Fill(1) and Fill(3) should split horizontally in a 1:3 ratio
39
+ assert buffer_content.any? { |line| line.include?("Fill(1)") }
40
+ assert buffer_content.any? { |line| line.include?("Fill(3)") }
41
+ end
42
+ end
43
+
44
+ def test_space_between_blocks
45
+ with_test_terminal do
46
+ # Queue quit
47
+ inject_key(:q)
48
+
49
+ @app.run
50
+
51
+ # Three blocks with space_between flex should have equal spacing
52
+ assert buffer_content.any? { |line| line.include?("Block A") }
53
+ assert buffer_content.any? { |line| line.include?("Block B") }
54
+ assert buffer_content.any? { |line| line.include?("Block C") }
55
+ end
56
+ end
57
+
58
+ def test_quit_on_q
59
+ with_test_terminal do
60
+ inject_key(:q)
61
+ @app.run
62
+ # Success if it returns
63
+ end
64
+ end
65
+ end