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,216 @@
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 BoxDemoApp
10
+ def initialize
11
+ # Border Types (ratatui native styles)
12
+ @border_types = [
13
+ { name: "Plain", type: :plain },
14
+ { name: "Rounded", type: :rounded },
15
+ { name: "Double", type: :double },
16
+ { name: "Thick", type: :thick },
17
+ { name: "Quadrant Inside", type: :quadrant_inside },
18
+ { name: "Quadrant Outside", type: :quadrant_outside }
19
+ ]
20
+ @border_type_index = 0
21
+
22
+ # Custom Border Sets
23
+ # NOTE: We define these ONCE in initialize for efficiency.
24
+ @border_sets = [
25
+ { name: "None", set: nil },
26
+ { name: "Digits (Short)", set: {
27
+ tl: "1", tr: "2", bl: "3", br: "4",
28
+ vl: "5", vr: "6", ht: "7", hb: "8"
29
+ }},
30
+ { name: "Letters (Long)", set: {
31
+ top_left: "A", top_right: "B", bottom_left: "C", bottom_right: "D",
32
+ vertical_left: "E", vertical_right: "F", horizontal_top: "G", horizontal_bottom: "H"
33
+ }}
34
+ ]
35
+ @border_set_index = 0
36
+
37
+ @colors = [
38
+ { name: "Green", color: "green" },
39
+ { name: "Red", color: "red" },
40
+ { name: "Blue", color: "blue" },
41
+ { name: "Yellow", color: "yellow" },
42
+ { name: "Magenta", color: "magenta" }
43
+ ]
44
+ @color_index = 0
45
+
46
+ @title_alignments = [
47
+ { name: "Left", alignment: :left },
48
+ { name: "Center", alignment: :center },
49
+ { name: "Right", alignment: :right }
50
+ ]
51
+ @title_alignment_index = 0
52
+
53
+ @styles = [
54
+ { name: "Default", style: nil },
55
+ { name: "Blue on White", style: { fg: "blue", bg: "white", modifiers: [:bold] } }
56
+ ]
57
+ @style_index = 0
58
+
59
+ @title_styles = [
60
+ { name: "Default", style: nil },
61
+ { name: "Yellow Bold Underlined", style: { fg: "yellow", modifiers: [:bold, :underlined] } }
62
+ ]
63
+ @title_style_index = 0
64
+
65
+ @border_styles = [
66
+ { name: "Default (no border style)", style: nil },
67
+ { name: "Bold Red", style: { fg: "red", modifiers: [:bold] } },
68
+ { name: "Cyan Italic", style: { fg: "cyan", modifiers: [:italic] } },
69
+ { name: "Magenta Dim", style: { fg: "magenta", modifiers: [:dim] } }
70
+ ]
71
+ @border_style_index = 0
72
+
73
+ @hotkey_style = RatatuiRuby::Style.new(modifiers: [:bold, :underlined])
74
+ end
75
+
76
+ def run
77
+ RatatuiRuby.run do
78
+ loop do
79
+ render
80
+ break if handle_input == :quit
81
+ end
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def render
88
+ # Get current values
89
+ border_type_config = @border_types[@border_type_index]
90
+ border_set_config = @border_sets[@border_set_index]
91
+
92
+ color_config = @colors[@color_index]
93
+ title_alignment_config = @title_alignments[@title_alignment_index]
94
+ style_config = @styles[@style_index]
95
+ title_style_config = @title_styles[@title_style_index]
96
+ border_style_config = @border_styles[@border_style_index]
97
+
98
+ # 1. State/View
99
+ # Use border_style if provided, otherwise fall back to border_color
100
+ effective_border_style = border_style_config[:style]
101
+ effective_border_color = effective_border_style ? nil : (style_config[:style] ? nil : color_config[:color])
102
+
103
+ # Show overridden status if border_set is active
104
+ type_display = border_type_config[:name]
105
+ if border_set_config[:set]
106
+ type_display += " (Overridden)"
107
+ end
108
+
109
+ block = RatatuiRuby::Block.new(
110
+ title: "Box Demo",
111
+ title_alignment: title_alignment_config[:alignment],
112
+ title_style: title_style_config[:style],
113
+ borders: [:all],
114
+ border_color: effective_border_color,
115
+ border_style: effective_border_style,
116
+ border_type: border_type_config[:type],
117
+ border_set: border_set_config[:set],
118
+ style: style_config[:style]
119
+ )
120
+
121
+ # Main content
122
+ main_panel = RatatuiRuby::Paragraph.new(
123
+ text: "Arrow Keys: Change Color\n\nCurrent Color: #{color_config[:name]}",
124
+ block: block,
125
+ fg: style_config[:style] ? nil : color_config[:color],
126
+ style: style_config[:style],
127
+ alignment: :center
128
+ )
129
+
130
+ # Bottom control panel
131
+ control_panel = RatatuiRuby::Block.new(
132
+ title: "Controls",
133
+ borders: [:all],
134
+ children: [
135
+ RatatuiRuby::Paragraph.new(
136
+ text: [
137
+ # Line 1: Main Controls
138
+ RatatuiRuby::Text::Line.new(spans: [
139
+ RatatuiRuby::Text::Span.new(content: "↑↓←→", style: @hotkey_style),
140
+ RatatuiRuby::Text::Span.new(content: ": Color (#{color_config[:name]}) "),
141
+ RatatuiRuby::Text::Span.new(content: "q", style: @hotkey_style),
142
+ RatatuiRuby::Text::Span.new(content: ": Quit")
143
+ ]),
144
+ # Line 2: Borders
145
+ RatatuiRuby::Text::Line.new(spans: [
146
+ RatatuiRuby::Text::Span.new(content: "space", style: @hotkey_style),
147
+ RatatuiRuby::Text::Span.new(content: ": Border Type (#{type_display}) "),
148
+ RatatuiRuby::Text::Span.new(content: "c", style: @hotkey_style),
149
+ RatatuiRuby::Text::Span.new(content: ": Border Set (#{border_set_config[:name]})")
150
+ ]),
151
+ # Line 3: Styles
152
+ RatatuiRuby::Text::Line.new(spans: [
153
+ RatatuiRuby::Text::Span.new(content: "s", style: @hotkey_style),
154
+ RatatuiRuby::Text::Span.new(content: ": Style (#{style_config[:name]}) "),
155
+ RatatuiRuby::Text::Span.new(content: "b", style: @hotkey_style),
156
+ RatatuiRuby::Text::Span.new(content: ": Border Style (#{border_style_config[:name]})")
157
+ ]),
158
+ # Line 4: Title
159
+ RatatuiRuby::Text::Line.new(spans: [
160
+ RatatuiRuby::Text::Span.new(content: "enter", style: @hotkey_style),
161
+ RatatuiRuby::Text::Span.new(content: ": Align Title (#{title_alignment_config[:name]}) "),
162
+ RatatuiRuby::Text::Span.new(content: "t", style: @hotkey_style),
163
+ RatatuiRuby::Text::Span.new(content: ": Title Style (#{title_style_config[:name]})")
164
+ ])
165
+ ]
166
+ )
167
+ ]
168
+ )
169
+
170
+ # Vertical Layout
171
+ layout = RatatuiRuby::Layout.new(
172
+ direction: :vertical,
173
+ constraints: [
174
+ RatatuiRuby::Constraint.fill(1),
175
+ RatatuiRuby::Constraint.length(6),
176
+ ],
177
+ children: [main_panel, control_panel]
178
+ )
179
+
180
+ # 2. Render
181
+ RatatuiRuby.draw(layout)
182
+ end
183
+
184
+ def handle_input
185
+ # 3. Events
186
+ case RatatuiRuby.poll_event
187
+ in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
188
+ :quit
189
+ in type: :key, code: "up"
190
+ @color_index = (@color_index - 1) % @colors.size
191
+ in type: :key, code: "down"
192
+ @color_index = (@color_index + 1) % @colors.size
193
+ in type: :key, code: "left"
194
+ @color_index = (@color_index - 1) % @colors.size
195
+ in type: :key, code: "right"
196
+ @color_index = (@color_index + 1) % @colors.size
197
+ in type: :key, code: " "
198
+ @border_type_index = (@border_type_index + 1) % @border_types.size
199
+ in type: :key, code: "c"
200
+ @border_set_index = (@border_set_index + 1) % @border_sets.size
201
+ in type: :key, code: "enter"
202
+ @title_alignment_index = (@title_alignment_index + 1) % @title_alignments.size
203
+ in type: :key, code: "s"
204
+ @style_index = (@style_index + 1) % @styles.size
205
+ in type: :key, code: "t"
206
+ @title_style_index = (@title_style_index + 1) % @title_styles.size
207
+ in type: :key, code: "b"
208
+ @border_style_index = (@border_style_index + 1) % @border_styles.size
209
+ else
210
+ nil
211
+ end
212
+ end
213
+ end
214
+
215
+ BoxDemoApp.new.run if __FILE__ == $0
216
+
@@ -0,0 +1,7 @@
1
+ class BoxDemoApp
2
+ # @public
3
+ def self.new: () -> BoxDemoApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,88 @@
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 TestBoxDemo < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = BoxDemoApp.new
17
+ end
18
+
19
+ def test_render_initial_state
20
+ with_test_terminal do
21
+ inject_key(:q)
22
+ @app.run
23
+
24
+ assert buffer_content.any? { |line| line.include?("Box Demo") }
25
+ assert buffer_content.any? { |line| line.include?("Controls") }
26
+ assert buffer_content.any? { |line| line.include?("Green") }
27
+ end
28
+ end
29
+
30
+ def test_color_cycling_changes_color
31
+ with_test_terminal do
32
+ # Pressing up should cycle colors backwards
33
+ inject_keys(:up, :q)
34
+ @app.run
35
+
36
+ # At least the Controls section should be rendered
37
+ assert buffer_content.any? { |line| line.include?("Controls") }
38
+ end
39
+ end
40
+
41
+ def test_border_type_cycling
42
+ with_test_terminal do
43
+ inject_keys(" ", :q)
44
+ @app.run
45
+
46
+ assert buffer_content.any? { |line| line.include?("Rounded") }
47
+ end
48
+ end
49
+
50
+ def test_title_alignment_cycling
51
+ with_test_terminal do
52
+ inject_keys(:enter, :q)
53
+ @app.run
54
+
55
+ assert buffer_content.any? { |line| line.include?("Center") }
56
+ end
57
+ end
58
+
59
+ def test_content_style_changes
60
+ with_test_terminal do
61
+ inject_keys(:s, :q)
62
+ @app.run
63
+
64
+ # At least the Controls section should be rendered
65
+ assert buffer_content.any? { |line| line.include?("Controls") }
66
+ end
67
+ end
68
+
69
+ def test_title_style_changes
70
+ with_test_terminal do
71
+ inject_keys(:t, :q)
72
+ @app.run
73
+
74
+ # At least the Controls section should be rendered
75
+ assert buffer_content.any? { |line| line.include?("Controls") }
76
+ end
77
+ end
78
+
79
+ def test_border_style_cycling
80
+ with_test_terminal do
81
+ inject_keys(:b, :q)
82
+ @app.run
83
+
84
+ # At least the Controls section should be rendered
85
+ assert buffer_content.any? { |line| line.include?("Controls") }
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,101 @@
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 application for the Calendar widget.
10
+ class CalendarDemoApp
11
+ def run
12
+ RatatuiRuby.run do
13
+ show_header = true
14
+ show_weekdays = true
15
+ show_surrounding = false
16
+ show_events = true
17
+
18
+ loop do
19
+ now = Time.now
20
+ surrounding_style = if show_surrounding
21
+ RatatuiRuby::Style.new(fg: "gray", modifiers: [:dim])
22
+ else
23
+ nil
24
+ end
25
+
26
+ events_map = if show_events
27
+ {
28
+ now => RatatuiRuby::Style.new(fg: "green", modifiers: [:bold]),
29
+ (now + 86400 * 2) => RatatuiRuby::Style.new(fg: "red", modifiers: [:underlined]),
30
+ (now - 86400 * 5) => RatatuiRuby::Style.new(fg: "blue", bg: "white"),
31
+ }
32
+ else
33
+ {}
34
+ end
35
+
36
+ calendar = RatatuiRuby::Calendar.new(
37
+ year: now.year,
38
+ month: now.month,
39
+ events: events_map,
40
+ header_style: RatatuiRuby::Style.new(fg: "yellow", modifiers: [:bold]),
41
+ show_month_header: show_header,
42
+ show_weekdays_header: show_weekdays,
43
+ show_surrounding: surrounding_style,
44
+ block: RatatuiRuby::Block.new(borders: [:top, :left, :right])
45
+ )
46
+
47
+ controls_text = [
48
+ RatatuiRuby::Text::Line.new(
49
+ spans: [
50
+ RatatuiRuby::Text::Span.new(content: " h/w/s/e", style: RatatuiRuby::Style.new(modifiers: [:bold])),
51
+ RatatuiRuby::Text::Span.new(content: ": Toggle Header/Weekdays/Surrounding/Events "),
52
+ RatatuiRuby::Text::Span.new(content: "q", style: RatatuiRuby::Style.new(modifiers: [:bold])),
53
+ RatatuiRuby::Text::Span.new(content: ": Quit"),
54
+ ]
55
+ ),
56
+ RatatuiRuby::Text::Line.new(
57
+ spans: [
58
+ RatatuiRuby::Text::Span.new(content: " Events: ", style: RatatuiRuby::Style.new(modifiers: [:bold])),
59
+ RatatuiRuby::Text::Span.new(content: "Today (Green), +2d (Red), -5d (Blue) (#{show_events ? "On" : "Off"})"),
60
+ ]
61
+ )
62
+ ]
63
+ controls = RatatuiRuby::Paragraph.new(
64
+ text: controls_text,
65
+ block: RatatuiRuby::Block.new(title: " Controls ", borders: [:all])
66
+ )
67
+
68
+ RatatuiRuby.draw(
69
+ RatatuiRuby::Layout.new(
70
+ direction: :vertical,
71
+ constraints: [
72
+ RatatuiRuby::Constraint.min(0),
73
+ RatatuiRuby::Constraint.length(4),
74
+ ],
75
+ children: [calendar, controls]
76
+ )
77
+ )
78
+
79
+ event = RatatuiRuby.poll_event
80
+ case event
81
+ in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
82
+ break
83
+ in type: :key, code: "h"
84
+ show_header = !show_header
85
+ in type: :key, code: "w"
86
+ show_weekdays = !show_weekdays
87
+ in type: :key, code: "s"
88
+ show_surrounding = !show_surrounding
89
+ in type: :key, code: "e"
90
+ show_events = !show_events
91
+ else
92
+ nil
93
+ end
94
+
95
+ sleep 0.05
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ CalendarDemoApp.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,7 @@
1
+ class CalendarDemoApp
2
+ # @public
3
+ def self.new: () -> CalendarDemoApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,108 @@
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 "minitest/autorun"
8
+ require "ratatui_ruby"
9
+ require "ratatui_ruby/test_helper"
10
+ require_relative "app"
11
+
12
+ class TestCalendarDemoApp < Minitest::Test
13
+ include RatatuiRuby::TestHelper
14
+
15
+ def setup
16
+ @app = CalendarDemoApp.new
17
+ end
18
+
19
+ def test_demo_renders
20
+ with_test_terminal do
21
+ inject_keys(:down, :down, :q)
22
+
23
+ @app.run
24
+
25
+ content = buffer_content
26
+ rendered_text = content.join("\n")
27
+
28
+ # Verify the bottom controls are present
29
+ assert_match(/h\/w\/s\/e: Toggle Header\/Weekdays\/Surrounding\/Events/, rendered_text)
30
+ assert_match(/q: Quit/, rendered_text)
31
+
32
+ # Verify the calendar content is present
33
+ assert_match(/#{Time.now.year}/, rendered_text)
34
+
35
+ # Verify layout structure (calendar taking up most space, controls at bottom)
36
+ # The controls are on the last line (index 23 for 24 lines) if the terminal is 24 lines high
37
+ # but standard terminal might be different. with_test_terminal defaults to 80x24.
38
+ # assert_match(/q: Quit/, content[23]) if content[23]
39
+ end
40
+ end
41
+
42
+ def test_toggle_weekdays_header
43
+ with_test_terminal do
44
+ inject_keys("w", :q)
45
+
46
+ @app.run
47
+
48
+ content = buffer_content
49
+ rendered_text = content.join("\n")
50
+
51
+ # The app should render successfully with weekdays toggled
52
+ assert_match(/#{Time.now.year}/, rendered_text)
53
+ end
54
+ end
55
+
56
+ def test_toggle_header
57
+ with_test_terminal do
58
+ inject_keys("h", :q)
59
+
60
+ @app.run
61
+
62
+ content = buffer_content
63
+ rendered_text = content.join("\n")
64
+
65
+ # The app should render successfully with header toggled (hidden)
66
+ refute_match(/#{Time.now.year}/, rendered_text)
67
+
68
+ # Inject h again to show it (h -> off, h -> on)
69
+ inject_keys("h", "h", :q)
70
+ @app.run
71
+ content = buffer_content
72
+ rendered_text = content.join("\n")
73
+ assert_match(/#{Time.now.year}/, rendered_text)
74
+ end
75
+ end
76
+
77
+ def test_toggle_surrounding
78
+ with_test_terminal do
79
+ inject_keys("s", :q)
80
+
81
+ @app.run
82
+
83
+ content = buffer_content
84
+ rendered_text = content.join("\n")
85
+
86
+ # The app should render successfully with surrounding toggled
87
+ assert_match(/#{Time.now.year}/, rendered_text)
88
+ end
89
+ end
90
+
91
+ def test_events_render
92
+ with_test_terminal do
93
+ inject_keys("e", :q)
94
+
95
+ @app.run
96
+
97
+ content = buffer_content
98
+ # We just check that the app runs without crashing and renders the calendar.
99
+ # Verifying specific color codes in the buffer content is harder without
100
+ # exposing the underlying buffer cells' style.
101
+ # But we can at least assert the content is still correct.
102
+ assert_match(/#{Time.now.year}/, content.join("\n"))
103
+
104
+ # Verify the legend is present and shows Off status after toggle
105
+ assert_match(/Events: .* \(Off\)/, content.join("\n"))
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
4
+ require "ratatui_ruby"
5
+
6
+ # A custom widget that fills its area with a checkered pattern using Cell objects.
7
+ class CheckeredBackground
8
+ def render(area)
9
+ cell = RatatuiRuby::Cell.new(char: "░", fg: :dark_gray)
10
+ commands = []
11
+ area.height.times do |y|
12
+ area.width.times do |x|
13
+ # Checkerboard logic
14
+ if (x + y).even?
15
+ # Use a dim cell for the background pattern
16
+ commands << RatatuiRuby::Draw.cell(area.x + x, area.y + y, cell)
17
+ end
18
+ end
19
+ end
20
+ commands
21
+ end
22
+ end
23
+
24
+ class CellDemoApp
25
+ def main
26
+ RatatuiRuby.run do |tui|
27
+ # Define some reusable cells for our table
28
+ ok_cell = RatatuiRuby::Cell.new(char: "OK", fg: :green)
29
+ fail_cell = RatatuiRuby::Cell.new(char: "FAIL", fg: :red, modifiers: ["bold"])
30
+ pending_cell = RatatuiRuby::Cell.new(char: "...", fg: :yellow, modifiers: ["dim"])
31
+
32
+ # A mix of Strings and Cells in rows
33
+ rows = [
34
+ ["Database", ok_cell],
35
+ ["Cache", ok_cell],
36
+ ["Worker", fail_cell],
37
+ ["Analytics", pending_cell],
38
+ ["Web Server", RatatuiRuby::Cell.new(char: "RESTARTING", fg: :blue, modifiers: ["rapid_blink"])]
39
+ ]
40
+
41
+ table = RatatuiRuby::Table.new(
42
+ header: ["Service", RatatuiRuby::Cell.new(char: "Status", modifiers: ["underlined"])],
43
+ rows: rows,
44
+ widths: [
45
+ RatatuiRuby::Constraint.percentage(70),
46
+ RatatuiRuby::Constraint.percentage(30)
47
+ ],
48
+ block: RatatuiRuby::Block.new(title: "System Status", borders: :all),
49
+ column_spacing: 1
50
+ )
51
+
52
+ # Main loop
53
+ loop do
54
+ # Create a layout that holds both widgets
55
+ # We use a vertical layout:
56
+ # Top: Custom CheckeredBackground with specific height
57
+ # Bottom: Table using remaining space
58
+
59
+ # Note: CheckeredBackground renders to the area implicitly if passed as a child.
60
+ # However, to overlay the paragraph, we might need a more complex structure or
61
+ # wrapper. RatatuiRuby::Layout handles non-overlapping children.
62
+ # To get the "Overlay" effect from my previous code (Paragraph over Background),
63
+ # we would need to composite them. For now, let's just show them stacked or
64
+ # using 'Overlay' widget if it exists.
65
+
66
+ # Checking schema: Overlay exists? Yes: require_relative "ratatui_ruby/schema/overlay" in lib/ratatui_ruby.rb
67
+
68
+ layout = RatatuiRuby::Layout.new(
69
+ direction: :vertical,
70
+ constraints: [
71
+ RatatuiRuby::Constraint.length(10), # Top section
72
+ RatatuiRuby::Constraint.min(0) # Bottom section
73
+ ],
74
+ children: [
75
+ # Top Child: An Overlay of Paragraph on top of CheckeredBackground
76
+ RatatuiRuby::Overlay.new(
77
+ layers: [
78
+ CheckeredBackground.new,
79
+ RatatuiRuby::Center.new(
80
+ width_percent: 50,
81
+ height_percent: 50,
82
+ child: RatatuiRuby::Paragraph.new(
83
+ text: "Custom Widget Demo\n(CheckeredBackground)",
84
+ alignment: :center,
85
+ block: RatatuiRuby::Block.new(borders: :all, title: "Overlay")
86
+ )
87
+ )
88
+ ]
89
+ ),
90
+ # Bottom Child: The Table
91
+ table
92
+ ]
93
+ )
94
+
95
+ tui.draw(layout)
96
+
97
+ event = RatatuiRuby.poll_event
98
+ if event.is_a?(RatatuiRuby::Event::Key)
99
+ break if event.code == "q" || (event.code == "c" && event.modifiers.include?("ctrl"))
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ if __FILE__ == $0
107
+ CellDemoApp.new.main
108
+ end
@@ -0,0 +1,7 @@
1
+ class CellDemoApp
2
+ # @public
3
+ def self.new: () -> CellDemoApp
4
+
5
+ # @public
6
+ def run: () -> void
7
+ end
@@ -0,0 +1,36 @@
1
+ require "minitest/autorun"
2
+ require "ratatui_ruby"
3
+ require "ratatui_ruby/test_helper"
4
+ require_relative "app"
5
+
6
+ class TestCellDemoApp < Minitest::Test
7
+ include RatatuiRuby::TestHelper
8
+
9
+ include RatatuiRuby::TestHelper
10
+
11
+ def test_cell_demo_output
12
+ # Run the demo in a test terminal
13
+ # We'll inject a 'q' key to exit the loop immediately after the first draw
14
+ with_test_terminal(timeout: 5) do
15
+ inject_key("q")
16
+ CellDemoApp.new.main
17
+
18
+ # Verify Custom Widget Output
19
+ # Check for "░" character from CheckeredBackground
20
+ # It should be visible around the centered widget
21
+ assert_includes buffer_content.join("\n"), "░"
22
+
23
+ # Verify Table Output
24
+ # Content strings
25
+ assert_includes buffer_content.join("\n"), "System Status"
26
+ assert_includes buffer_content.join("\n"), "Database"
27
+ assert_includes buffer_content.join("\n"), "Worker"
28
+
29
+ # We can't easily grep for "FAIL" with color in simple string content check,
30
+ # but we can check the text presence.
31
+ assert_includes buffer_content.join("\n"), "FAIL"
32
+ assert_includes buffer_content.join("\n"), "OK"
33
+ assert_includes buffer_content.join("\n"), "RESTARTING"
34
+ end
35
+ end
36
+ end