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,258 @@
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
+ # Analytics Dashboard Example
10
+ # Demonstrates Tabs and BarChart widgets.
11
+
12
+ class AnalyticsApp
13
+ def initialize
14
+ @selected_tab = 0
15
+ @tabs = ["Revenue", "Traffic", "Errors", "Quarterly"]
16
+ @styles = [
17
+ { name: "Yellow Bold", style: RatatuiRuby::Style.new(fg: :yellow, modifiers: [:bold]) },
18
+ { name: "Italic Blue on White", style: RatatuiRuby::Style.new(fg: :blue, bg: :white, modifiers: [:italic]) },
19
+ { name: "Underlined Red", style: RatatuiRuby::Style.new(fg: :red, modifiers: [:underlined]) },
20
+ { name: "Reversed", style: RatatuiRuby::Style.new(modifiers: [:reversed]) }
21
+ ]
22
+ @style_index = 0
23
+ @divider_index = 0
24
+ @dividers = [" | ", " • ", " > ", " / "]
25
+ @base_styles = [
26
+ { name: "Default", style: nil },
27
+ { name: "White on Gray", style: RatatuiRuby::Style.new(fg: :white, bg: :dark_gray) },
28
+ { name: "White on Blue", style: RatatuiRuby::Style.new(fg: :white, bg: :blue) },
29
+ { name: "Italic", style: RatatuiRuby::Style.new(modifiers: [:italic]) }
30
+ ]
31
+ @base_style_index = 0
32
+ @padding_left = 0
33
+ @padding_right = 0
34
+ @direction = :vertical
35
+ @label_style_index = 0
36
+ @value_style_index = 0
37
+ @bar_sets = [
38
+ { name: "Default", set: nil },
39
+ { name: "Numbers (Short)", set: { 8 => "8", 7 => "7", 6 => "6", 5 => "5", 4 => "4", 3 => "3", 2 => "2", 1 => "1", 0 => "0" } },
40
+ { name: "Letters (Long)", set: { full: "H", seven_eighths: "G", three_quarters: "F", five_eighths: "E", half: "D", three_eighths: "C", one_quarter: "B", one_eighth: "A", empty: " " } },
41
+ { name: "ASCII (Heights)", set: [" ", "_", ".", "-", "=", "+", "*", "#", "@"] }
42
+ ]
43
+ @bar_set_index = 0
44
+ @height_mode = :full # :full or :mini
45
+ @group_gap = 0
46
+
47
+ @hotkey_style = RatatuiRuby::Style.new(modifiers: [:bold, :underlined])
48
+ end
49
+
50
+ def run
51
+ RatatuiRuby.run do
52
+ loop do
53
+ render
54
+ break if handle_input == :quit
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def render
62
+ # Data for different tabs
63
+ data = case @selected_tab
64
+ when 0 # Revenue
65
+ {
66
+ "Q1" => 50, "Q2" => 80, "Q3" => 45, "Q4" => 60,
67
+ "Q1'" => 55, "Q2'" => 85, "Q3'" => 50, "Q4'" => 65
68
+ }
69
+ when 1 # Traffic
70
+ [
71
+ ["Mon", 120], ["Tue", 150], ["Wed", 130], ["Thu", 160],
72
+ ["Fri", 140],
73
+ ["Sat", 110, RatatuiRuby::Style.new(fg: :red)],
74
+ ["Sun", 100, RatatuiRuby::Style.new(fg: :red)]
75
+ ]
76
+ when 2 # Errors
77
+ { DB: 5, UI: 2, API: 8, Auth: 3, Net: 4, "I/O": 1, Mem: 6, CPU: 7 }
78
+ when 3 # Quarterly
79
+ [
80
+ RatatuiRuby::BarChart::BarGroup.new(label: "2024", bars: [
81
+ RatatuiRuby::BarChart::Bar.new(value: 40, label: "Q1"),
82
+ RatatuiRuby::BarChart::Bar.new(value: 45, label: "Q2"),
83
+ RatatuiRuby::BarChart::Bar.new(value: 50, label: "Q3"),
84
+ RatatuiRuby::BarChart::Bar.new(value: 55, label: "Q4")
85
+ ]),
86
+ RatatuiRuby::BarChart::BarGroup.new(label: "2025", bars: [
87
+ RatatuiRuby::BarChart::Bar.new(value: 60, label: "Q1", style: RatatuiRuby::Style.new(fg: :yellow)),
88
+ RatatuiRuby::BarChart::Bar.new(value: 65, label: "Q2", style: RatatuiRuby::Style.new(fg: :yellow)),
89
+ RatatuiRuby::BarChart::Bar.new(value: 70, label: "Q3", style: RatatuiRuby::Style.new(fg: :yellow)),
90
+ RatatuiRuby::BarChart::Bar.new(value: 75, label: "Q4", style: RatatuiRuby::Style.new(fg: :yellow))
91
+ ])
92
+ ]
93
+ end
94
+
95
+ bar_style = case @selected_tab
96
+ when 0 then RatatuiRuby::Style.new(fg: "green")
97
+ when 1 then RatatuiRuby::Style.new(fg: "blue")
98
+ when 2 then RatatuiRuby::Style.new(fg: "red")
99
+ when 3 then RatatuiRuby::Style.new(fg: "cyan")
100
+ end
101
+
102
+ # Define the BarChart widget
103
+ bar_chart = RatatuiRuby::BarChart.new(
104
+ data:,
105
+ bar_width: @direction == :vertical ? 8 : 1,
106
+ style: bar_style,
107
+ bar_gap: 1,
108
+ group_gap: @group_gap,
109
+ direction: @direction,
110
+ label_style: @styles[@label_style_index][:style],
111
+ value_style: @styles[@value_style_index][:style],
112
+ bar_set: @bar_sets[@bar_set_index][:set],
113
+ block: RatatuiRuby::Block.new(
114
+ title: "Analytics: #{@tabs[@selected_tab]}",
115
+ borders: [:all]
116
+ )
117
+ )
118
+
119
+ # Wrap chart in a layout to control height if in mini mode
120
+ chart_area = if @height_mode == :mini
121
+ # Height 3: 1 row content + 2 borders
122
+ RatatuiRuby::Layout.new(
123
+ direction: :vertical,
124
+ constraints: [RatatuiRuby::Constraint.length(3), RatatuiRuby::Constraint.fill(1)],
125
+ children: [bar_chart, RatatuiRuby::Block.new] # Empty block to fill rest
126
+ )
127
+ else
128
+ bar_chart
129
+ end
130
+
131
+ # Build the UI
132
+ ui = RatatuiRuby::Layout.new(
133
+ direction: :vertical,
134
+ constraints: [
135
+ RatatuiRuby::Constraint.fill(1),
136
+ RatatuiRuby::Constraint.length(6),
137
+ ],
138
+ children: [
139
+ # Main Area
140
+ RatatuiRuby::Layout.new(
141
+ direction: :vertical,
142
+ constraints: [
143
+ RatatuiRuby::Constraint.new(type: :length, value: 3),
144
+ RatatuiRuby::Constraint.new(type: :min, value: 0),
145
+ ],
146
+ children: [
147
+ tabs = RatatuiRuby::Tabs.new(
148
+ titles: @tabs,
149
+ selected_index: @selected_tab,
150
+ block: RatatuiRuby::Block.new(title: "Views", borders: [:all]),
151
+ divider: @dividers[@divider_index],
152
+ highlight_style: @styles[@style_index][:style],
153
+ style: @base_styles[@base_style_index][:style],
154
+ padding_left: @padding_left,
155
+ padding_right: @padding_right
156
+ ),
157
+ chart_area
158
+ ]
159
+ ),
160
+ # Bottom control panel
161
+ RatatuiRuby::Block.new(
162
+ title: "Controls",
163
+ borders: [:all],
164
+ children: [
165
+ RatatuiRuby::Paragraph.new(
166
+ text: [
167
+ # Line 1: Navigation & General
168
+ RatatuiRuby::Text::Line.new(spans: [
169
+ RatatuiRuby::Text::Span.new(content: "←/→", style: @hotkey_style),
170
+ RatatuiRuby::Text::Span.new(content: ": Navigate Tab "),
171
+ RatatuiRuby::Text::Span.new(content: "v", style: @hotkey_style),
172
+ RatatuiRuby::Text::Span.new(content: ": Direction (#{@direction}) "),
173
+ RatatuiRuby::Text::Span.new(content: "q", style: @hotkey_style),
174
+ RatatuiRuby::Text::Span.new(content: ": Quit")
175
+ ]),
176
+ # Line 2: Padding & Divider
177
+ RatatuiRuby::Text::Line.new(spans: [
178
+ RatatuiRuby::Text::Span.new(content: "h/l", style: @hotkey_style),
179
+ RatatuiRuby::Text::Span.new(content: ": Pad Left (#{@padding_left}) "),
180
+ RatatuiRuby::Text::Span.new(content: "j/k", style: @hotkey_style),
181
+ RatatuiRuby::Text::Span.new(content: ": Pad Right (#{@padding_right}) "),
182
+ RatatuiRuby::Text::Span.new(content: "d", style: @hotkey_style),
183
+ RatatuiRuby::Text::Span.new(content: ": Divider (#{@dividers[@divider_index]}) "),
184
+ RatatuiRuby::Text::Span.new(content: "Width: #{tabs.width}")
185
+ ]),
186
+ # Line 3: Styles
187
+ RatatuiRuby::Text::Line.new(spans: [
188
+ RatatuiRuby::Text::Span.new(content: "space", style: @hotkey_style),
189
+ RatatuiRuby::Text::Span.new(content: ": Highlight (#{@styles[@style_index][:name]}) "),
190
+ RatatuiRuby::Text::Span.new(content: "x", style: @hotkey_style),
191
+ RatatuiRuby::Text::Span.new(content: ": Label (#{@styles[@label_style_index][:name]}) "),
192
+ RatatuiRuby::Text::Span.new(content: "space", style: @hotkey_style),
193
+ RatatuiRuby::Text::Span.new(content: ": Highlight (#{@styles[@style_index][:name]}) "),
194
+ RatatuiRuby::Text::Span.new(content: "x", style: @hotkey_style),
195
+ RatatuiRuby::Text::Span.new(content: ": Label (#{@styles[@label_style_index][:name]})")
196
+ ]),
197
+ # Line 4: More Styles
198
+ RatatuiRuby::Text::Line.new(spans: [
199
+ RatatuiRuby::Text::Span.new(content: "z", style: @hotkey_style),
200
+ RatatuiRuby::Text::Span.new(content: ": Value (#{@styles[@value_style_index][:name]}) "),
201
+ RatatuiRuby::Text::Span.new(content: "b", style: @hotkey_style),
202
+ RatatuiRuby::Text::Span.new(content: ": Bar Set (#{@bar_sets[@bar_set_index][:name]}) "),
203
+ RatatuiRuby::Text::Span.new(content: "m", style: @hotkey_style),
204
+ RatatuiRuby::Text::Span.new(content: ": Mode (#{@height_mode == :full ? 'Full' : 'Mini'}) "),
205
+ RatatuiRuby::Text::Span.new(content: "g", style: @hotkey_style),
206
+ RatatuiRuby::Text::Span.new(content: ": Group Gap (#{@group_gap})")
207
+ ])
208
+ ]
209
+ )
210
+ ]
211
+ )
212
+ ]
213
+ )
214
+
215
+ RatatuiRuby.draw(ui)
216
+ end
217
+
218
+ def handle_input
219
+ case RatatuiRuby.poll_event
220
+ in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
221
+ :quit
222
+ in type: :key, code: "right"
223
+ @selected_tab = (@selected_tab + 1) % @tabs.size
224
+ in type: :key, code: "left"
225
+ @selected_tab = (@selected_tab - 1) % @tabs.size
226
+ in type: :key, code: " "
227
+ @style_index = (@style_index + 1) % @styles.size
228
+ in type: :key, code: "d"
229
+ @divider_index = (@divider_index + 1) % @dividers.size
230
+ in type: :key, code: "s"
231
+ @base_style_index = (@base_style_index + 1) % @base_styles.size
232
+ in type: :key, code: "h"
233
+ @padding_left = [@padding_left - 1, 0].max
234
+ in type: :key, code: "l"
235
+ @padding_left += 1
236
+ in type: :key, code: "v"
237
+ @direction = @direction == :vertical ? :horizontal : :vertical
238
+ in type: :key, code: "j"
239
+ @padding_right = [@padding_right - 1, 0].max
240
+ in type: :key, code: "k"
241
+ @padding_right += 1
242
+ in type: :key, code: "x"
243
+ @label_style_index = (@label_style_index + 1) % @styles.size
244
+ in type: :key, code: "z"
245
+ @value_style_index = (@value_style_index + 1) % @styles.size
246
+ in type: :key, code: "b"
247
+ @bar_set_index = (@bar_set_index + 1) % @bar_sets.size
248
+ in type: :key, code: "m"
249
+ @height_mode = @height_mode == :full ? :mini : :full
250
+ in type: :key, code: "g"
251
+ @group_gap = (@group_gap + 1) % 4
252
+ else
253
+ # Ignore other events
254
+ end
255
+ end
256
+ end
257
+
258
+ AnalyticsApp.new.run if __FILE__ == $0
@@ -0,0 +1,7 @@
1
+ class AnalyticsApp
2
+ # @public
3
+ def self.new: () -> AnalyticsApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,132 @@
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 TestAnalytics < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = AnalyticsApp.new
17
+ end
18
+
19
+ def test_render_initial_state
20
+ with_test_terminal do
21
+ # Queue quit
22
+ inject_key(:q)
23
+
24
+ @app.run
25
+
26
+ # Check Tabs
27
+ assert buffer_content.any? { |line| line.include?("Revenue") }
28
+ assert buffer_content.any? { |line| line.include?("Traffic") }
29
+ assert buffer_content.any? { |line| line.include?("Errors") }
30
+
31
+ # Check initial selected tab content
32
+ assert buffer_content.any? { |line| line.include?("Analytics: Revenue") }
33
+
34
+ # Check help text visibility (join to handle potential wrapping)
35
+ content_str = buffer_content.join("\n")
36
+ assert_includes content_str, "q: Quit"
37
+ end
38
+ end
39
+
40
+ def test_navigation_right
41
+ with_test_terminal do
42
+ inject_keys(:right, :q)
43
+
44
+ @app.run
45
+
46
+ assert buffer_content.any? { |line| line.include?("Analytics: Traffic") }
47
+ end
48
+ end
49
+
50
+ def test_navigation_left
51
+ with_test_terminal do
52
+ # Move right to Traffic
53
+ inject_keys(:a, :b, :c)
54
+
55
+ # Then queue quit
56
+ inject_key(:q)
57
+
58
+ @app.run
59
+
60
+ assert buffer_content.any? { |line| line.include?("Analytics: Revenue") }
61
+ end
62
+ end
63
+
64
+ def test_quit
65
+ with_test_terminal do
66
+ inject_key(:q)
67
+ @app.run
68
+ # Success if returns
69
+ end
70
+ end
71
+
72
+ def test_switch_divider
73
+ with_test_terminal do
74
+ # Switch divider (d) then quit (q)
75
+ inject_keys(:d, :q)
76
+ @app.run
77
+
78
+ # Default is " | ", next is " • "
79
+ assert buffer_content.any? { |line| line.include?(" • ") }
80
+ end
81
+ end
82
+
83
+ def test_switch_style
84
+ with_test_terminal do
85
+ # Switch style (space) then quit (q)
86
+ inject_keys(:" ", :q)
87
+ @app.run
88
+
89
+ # Verify it runs without error.
90
+ # Style visual verification is limited with simple string buffer checks,
91
+ # but this ensures the event handling works.
92
+ end
93
+ end
94
+
95
+ def test_padding_controls
96
+ with_test_terminal do
97
+ # Increase padding_left (l) and padding_right (k) then quit
98
+ inject_keys(:l, :l, :k, :k, :k, :q)
99
+ @app.run
100
+
101
+ # Verify that padding values are shown in status with hotkeys
102
+ # Use join to match even if wrapped or if "Pad Left" logic changed
103
+ content_str = buffer_content.join("\n")
104
+ assert_includes content_str, "h/l: Pad Left (2)"
105
+ assert_includes content_str, "j/k: Pad Right (3)"
106
+ end
107
+ end
108
+
109
+ def test_styling_controls
110
+ with_test_terminal do
111
+ # Cycle label style (x) and value style (z)
112
+ inject_keys(:x, :z, :z, :q)
113
+ @app.run
114
+ # Verify expected content
115
+ content = buffer_content.join
116
+ assert_includes content, "Analytics: Revenue"
117
+ assert_includes content, "Views"
118
+ assert_includes content, "Controls"
119
+ assert_includes content, "Width:"
120
+ end
121
+ end
122
+
123
+ def test_direction_toggle
124
+ with_test_terminal do
125
+ # Switch to horizontal (v) then quit
126
+ inject_keys(:v, :q)
127
+ @app.run
128
+
129
+ assert buffer_content.any? { |line| line.include?("v: Direction (horizontal)") }
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,63 @@
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 demo showing Block padding capabilities
10
+ class BlockPaddingApp
11
+ def run
12
+ RatatuiRuby.run do |tui|
13
+ loop do
14
+ # 1. Uniform Padding
15
+ block1 = RatatuiRuby::Block.new(
16
+ title: "Uniform Padding (2)",
17
+ borders: [:all],
18
+ padding: 2
19
+ )
20
+ para1 = RatatuiRuby::Paragraph.new(
21
+ text: "This text is padded by 2 on all sides.\nNotice the space between the border and this text.",
22
+ block: block1
23
+ )
24
+
25
+ # 2. Directional Padding
26
+ block2 = RatatuiRuby::Block.new(
27
+ title: "Directional Padding [Left: 4, Right: 0, Top: 2, Bottom: 0]",
28
+ borders: [:all],
29
+ padding: [4, 0, 2, 0]
30
+ )
31
+ para2 = RatatuiRuby::Paragraph.new(
32
+ text: "This text has different padding per side.\nLeft: 4, Top: 2.",
33
+ block: block2
34
+ )
35
+
36
+ # Instructions
37
+ para3 = RatatuiRuby::Paragraph.new(
38
+ text: "Press 'q' to quit."
39
+ )
40
+
41
+ # Layout
42
+ layout = RatatuiRuby::Layout.new(
43
+ direction: :vertical,
44
+ constraints: [
45
+ RatatuiRuby::Constraint.length(10), # Uniform Padding
46
+ RatatuiRuby::Constraint.length(10), # Directional Padding
47
+ RatatuiRuby::Constraint.min(0)
48
+ ],
49
+ children: [para1, para2, para3]
50
+ )
51
+
52
+ tui.draw(layout)
53
+
54
+ event = tui.poll_event
55
+ break if event == "q" || event == :ctrl_c
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ if __FILE__ == $0
62
+ BlockPaddingApp.new.run
63
+ end
@@ -0,0 +1,7 @@
1
+ class BlockPaddingApp
2
+ # @public
3
+ def self.new: () -> BlockPaddingApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,31 @@
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 TestBlockPadding < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = BlockPaddingApp.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, "Uniform Padding (2)"
26
+ assert_includes content, "This text is padded by 2 on all sides."
27
+ assert_includes content, "Directional Padding [Left: 4, Right: 0, Top: 2, Bottom: 0]"
28
+ assert_includes content, "Left: 4, Top: 2."
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
4
+ require "ratatui_ruby"
5
+
6
+ # Initialize the terminal
7
+ class BlockTitlesApp
8
+ def run
9
+ RatatuiRuby.run do |tui|
10
+ loop do
11
+ # Create a layout with multiple blocks demonstrating titles
12
+ blocks = [
13
+ tui.block(
14
+ titles: [
15
+ { content: "Top Left", alignment: :left, position: :top },
16
+ { content: "Top Right", alignment: :right, position: :top }
17
+ ],
18
+ borders: [:all],
19
+ border_color: "cyan"
20
+ ),
21
+ tui.block(
22
+ titles: [
23
+ { content: "Bottom Left", alignment: :left, position: :bottom },
24
+ { content: "Bottom Center", alignment: :center, position: :bottom },
25
+ { content: "Bottom Right", alignment: :right, position: :bottom }
26
+ ],
27
+ borders: [:all],
28
+ border_color: "magenta"
29
+ ),
30
+ tui.block(
31
+ titles: [
32
+ "Simple String Title (Top Left Default)",
33
+ { content: "Mixed Title", alignment: :center, position: :bottom }
34
+ ],
35
+ borders: [:all],
36
+ border_color: "green"
37
+ )
38
+ ]
39
+
40
+ layout = tui.layout(
41
+ direction: :vertical,
42
+ constraints: [
43
+ tui.constraint(:length, 10),
44
+ tui.constraint(:length, 10),
45
+ tui.constraint(:length, 10)
46
+ ],
47
+ children: blocks
48
+ )
49
+
50
+ tui.draw(layout)
51
+
52
+ event = tui.poll_event
53
+ break if event == "q" || event == :ctrl_c
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ if __FILE__ == $0
60
+ BlockTitlesApp.new.run
61
+ end
@@ -0,0 +1,7 @@
1
+ class BlockTitlesApp
2
+ # @public
3
+ def self.new: () -> BlockTitlesApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,34 @@
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 TestBlockTitles < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = BlockTitlesApp.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, "Top Left"
26
+ assert_includes content, "Top Right"
27
+ assert_includes content, "Bottom Left"
28
+ assert_includes content, "Bottom Center"
29
+ assert_includes content, "Bottom Right"
30
+ assert_includes content, "Simple String Title (Top Left Default)"
31
+ assert_includes content, "Mixed Title"
32
+ end
33
+ end
34
+ end