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
@@ -4,19 +4,101 @@
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
6
  module RatatuiRuby
7
- # A widget that splits an area into multiple sections based on constraints.
8
- #
9
- # [direction] The direction of the layout (:vertical or :horizontal).
10
- # [constraints] An array of Constraint objects defining the size of each section.
11
- # [children] An array of widgets to render within each section.
12
- class Layout < Data.define(:direction, :constraints, :children)
13
- # Creates a new Layout.
7
+ # Divides an area into smaller chunks.
14
8
  #
15
- # [direction] The direction of the layout (:vertical or :horizontal).
16
- # [constraints] An array of Constraint objects defining the size of each section.
17
- # [children] An array of widgets to render within each section.
18
- def initialize(direction: :vertical, constraints: [], children: [])
19
- super
9
+ # Terminal screens vary in size. Hardcoded positions break when the window resizes. You need a way to organize space dynamically.
10
+ #
11
+ # This class manages geometry. It splits a given area into multiple sections based on a list of constraints.
12
+ #
13
+ # Use layouts to build responsive grids. Stack sections vertically for a sidebar-main structure. Partition them horizontally for headers and footers. Let the layout engine do the math.
14
+ #
15
+ # === Examples
16
+ #
17
+ # # A simple vertical split (Sidebar / Main)
18
+ # Layout.new(
19
+ # direction: :horizontal,
20
+ # constraints: [
21
+ # Constraint.length(20), # Sidebar
22
+ # Constraint.min(0) # Main content
23
+ # ]
24
+ # )
25
+ #
26
+ # # Flex layout (Centering a block)
27
+ # Layout.new(
28
+ # direction: :vertical,
29
+ # flex: :center,
30
+ # constraints: [Constraint.length(10)], # A 10-row block centered vertically
31
+ # children: [modal_block]
32
+ # )
33
+ class Layout < Data.define(:direction, :constraints, :children, :flex)
34
+ ##
35
+ # :attr_reader: direction
36
+ # Direction of the split.
37
+ #
38
+ # Either <tt>:vertical</tt> (top to bottom) or <tt>:horizontal</tt> (left to right).
39
+ #
40
+ # layout.direction # => :vertical
41
+
42
+ ##
43
+ # :attr_reader: constraints
44
+ # Array of rules defining section sizes.
45
+ #
46
+ # See RatatuiRuby::Constraint.
47
+
48
+ ##
49
+ # :attr_reader: children
50
+ # Widgets to render in each section (optional).
51
+ #
52
+ # If provided, `children[i]` is rendered into the area defined by `constraints[i]`.
53
+
54
+ ##
55
+ # :attr_reader: flex
56
+ # Strategy for distributing extra space.
57
+ #
58
+ # One of <tt>:legacy</tt>, <tt>:start</tt>, <tt>:center</tt>, <tt>:end</tt>, <tt>:space_between</tt>, <tt>:space_around</tt>.
59
+
60
+ # :nodoc:
61
+ FLEX_MODES = %i[legacy start center end space_between space_around space_evenly].freeze
62
+
63
+ # Creates a new Layout.
64
+ #
65
+ # [direction]
66
+ # <tt>:vertical</tt> or <tt>:horizontal</tt> (default: <tt>:vertical</tt>).
67
+ # [constraints]
68
+ # list of Constraint objects.
69
+ # [children]
70
+ # List of widgets to render (optional).
71
+ # [flex]
72
+ # Flex mode for spacing (default: <tt>:legacy</tt>).
73
+ def initialize(direction: :vertical, constraints: [], children: [], flex: :legacy)
74
+ super
75
+ end
76
+
77
+ # Splits an area into multiple rectangles.
78
+ #
79
+ # This is a pure calculation helper for hit testing. It computes where
80
+ # widgets *would* be placed without actually rendering them.
81
+ #
82
+ # rects = Layout.split(
83
+ # area,
84
+ # direction: :horizontal,
85
+ # constraints: [Constraint.percentage(50), Constraint.percentage(50)]
86
+ # )
87
+ # left, right = rects
88
+ #
89
+ # [area]
90
+ # The area to split (a Rect or any object responding to x, y, width, height).
91
+ # [direction]
92
+ # <tt>:vertical</tt> or <tt>:horizontal</tt> (default: <tt>:vertical</tt>).
93
+ # [constraints]
94
+ # Array of Constraint objects defining section sizes.
95
+ # [flex]
96
+ # Flex mode for spacing (default: <tt>:legacy</tt>).
97
+ #
98
+ # Returns an Array of Rect objects.
99
+ def self.split(area, direction: :vertical, constraints:, flex: :legacy)
100
+ raw_rects = _split(area, direction, constraints, flex)
101
+ raw_rects.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
102
+ end
20
103
  end
21
- end
22
104
  end
@@ -0,0 +1,78 @@
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
+ module RatatuiRuby
7
+ # Displays a compact, single-line progress bar.
8
+ #
9
+ # Screen space is precious. Standard block gauges are bulky and consume multiple rows.
10
+ #
11
+ # This widget compresses the feedback. It draws a progress bar using line characters, fitting perfectly into tight layouts or lists.
12
+ #
13
+ # Use it when you need to show status without stealing focus or space.
14
+ #
15
+ # === Examples
16
+ #
17
+ # LineGauge.new(
18
+ # ratio: 0.4,
19
+ # filled_style: Style.new(fg: :blue),
20
+ # unfilled_symbol: "-"
21
+ # )
22
+ class LineGauge < Data.define(:ratio, :label, :style, :filled_style, :unfilled_style, :block, :filled_symbol, :unfilled_symbol)
23
+ ##
24
+ # :attr_reader: ratio
25
+ # Progress ratio from 0.0 to 1.0.
26
+
27
+ ##
28
+ # :attr_reader: label
29
+ # Optional label.
30
+
31
+ ##
32
+ # :attr_reader: style
33
+ # Base style applied to the entire gauge.
34
+
35
+ ##
36
+ # :attr_reader: filled_style
37
+ # Style for the completed portion.
38
+
39
+ ##
40
+ # :attr_reader: unfilled_style
41
+ # Style for the remainder.
42
+
43
+ ##
44
+ # :attr_reader: block
45
+ # Optional wrapping block.
46
+
47
+ ##
48
+ # :attr_reader: filled_symbol
49
+ # Character for filled segments.
50
+
51
+ ##
52
+ # :attr_reader: unfilled_symbol
53
+ # Character for empty segments.
54
+
55
+ # Creates a new LineGauge.
56
+ #
57
+ # [ratio] Float (0.0 - 1.0).
58
+ # [label] String (optional).
59
+ # [style] Style (optional, base style for the gauge).
60
+ # [filled_style] Style.
61
+ # [unfilled_style] Style.
62
+ # [block] Block.
63
+ # [filled_symbol] String (default: <tt>"█"</tt>).
64
+ # [unfilled_symbol] String (default: <tt>"░"</tt>).
65
+ def initialize(ratio: 0.0, label: nil, style: nil, filled_style: nil, unfilled_style: nil, block: nil, filled_symbol: "█", unfilled_symbol: "░")
66
+ super(
67
+ ratio: Float(ratio),
68
+ label: label,
69
+ style: style,
70
+ filled_style: filled_style,
71
+ unfilled_style: unfilled_style,
72
+ block: block,
73
+ filled_symbol: filled_symbol,
74
+ unfilled_symbol: unfilled_symbol
75
+ )
76
+ end
77
+ end
78
+ end
@@ -4,25 +4,99 @@
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
6
  module RatatuiRuby
7
- # A widget that displays a list of selectable items.
8
- #
9
- # [items] An array of strings to display in the list.
10
- # [selected_index] The index of the currently selected item, or nil if none.
11
- # [style] The base style for all items.
12
- # [highlight_style] The style for the selected item.
13
- # [highlight_symbol] The symbol to display in front of the selected item.
14
- # [block] An optional Block widget to wrap the list.
15
- class List < Data.define(:items, :selected_index, :style, :highlight_style, :highlight_symbol, :block)
16
- # Creates a new List.
7
+ # Displays a selectable list of items.
17
8
  #
18
- # [items] An array of strings to display in the list.
19
- # [selected_index] The index of the currently selected item, or nil if none.
20
- # [style] The base style for all items.
21
- # [highlight_style] The style for the selected item.
22
- # [highlight_symbol] The symbol to display in front of the selected item.
23
- # [block] An optional Block widget to wrap the list.
24
- def initialize(items: [], selected_index: nil, style: nil, highlight_style: nil, highlight_symbol: "> ", block: nil)
25
- super
9
+ # Users need to choose from options. Menus, file explorers, and selectors are everywhere.
10
+ # Implementing navigation, highlighting, and scrolling state from scratch is tedious.
11
+ #
12
+ # This widget manages the list. It renders the items. It highlights the selection. It handles the scrolling window.
13
+ #
14
+ # Use it to build main menus, navigation sidebars, or logs.
15
+ #
16
+ # === Examples
17
+ #
18
+ # # Basic List
19
+ # List.new(items: ["Item 1", "Item 2"])
20
+ #
21
+ # # Navigation Menu
22
+ # List.new(
23
+ # items: ["New Game", "Load Game", "Options", "Quit"],
24
+ # selected_index: 0,
25
+ # highlight_style: Style.new(bg: :blue),
26
+ # highlight_symbol: ">> "
27
+ # )
28
+ class List < Data.define(:items, :selected_index, :style, :highlight_style, :highlight_symbol, :repeat_highlight_symbol, :highlight_spacing, :direction, :scroll_padding, :block)
29
+ ##
30
+ # :attr_reader: items
31
+ # The items to display (Array of Strings).
32
+
33
+ ##
34
+ # :attr_reader: selected_index
35
+ # Index of the active selection (Integer or nil).
36
+
37
+ ##
38
+ # :attr_reader: style
39
+ # Base style for unselected items.
40
+
41
+ ##
42
+ # :attr_reader: highlight_style
43
+ # Style for the selected item.
44
+
45
+ ##
46
+ # :attr_reader: highlight_symbol
47
+ # Symbol drawn before the selected item.
48
+
49
+ ##
50
+ # :attr_reader: repeat_highlight_symbol
51
+ # Whether to repeat the highlight symbol for each line of the selected item.
52
+
53
+ ##
54
+ # :attr_reader: highlight_spacing
55
+ # When to show the highlight symbol column.
56
+ #
57
+ # <tt>:always</tt>, <tt>:when_selected</tt>, or <tt>:never</tt>.
58
+
59
+ ##
60
+ # :attr_reader: direction
61
+ # Render direction.
62
+ #
63
+ # <tt>:top_to_bottom</tt> or <tt>:bottom_to_top</tt>.
64
+
65
+ ##
66
+ # :attr_reader: scroll_padding
67
+ # Number of items to keep visible above/below the selected item when scrolling (Integer or nil).
68
+
69
+ ##
70
+ # :attr_reader: block
71
+ # Optional wrapping block.
72
+
73
+ # Creates a new List.
74
+ #
75
+ # Integer parameters accept any object responding to +to_int+ or +to_i+ (duck-typed).
76
+ #
77
+ # [items] Array of Strings.
78
+ # [selected_index] Numeric (nullable, coerced to Integer).
79
+ # [style] Style object.
80
+ # [highlight_style] Style object.
81
+ # [highlight_symbol] String (default: <tt>"> "</tt>).
82
+ # [repeat_highlight_symbol] Boolean (default: <tt>false</tt>).
83
+ # [highlight_spacing] Symbol (default: <tt>:when_selected</tt>).
84
+ # [direction] Symbol (default: <tt>:top_to_bottom</tt>).
85
+ # [scroll_padding] Numeric (nullable, coerced to Integer, default: <tt>nil</tt>).
86
+ # [block] Block (optional).
87
+ def initialize(items: [], selected_index: nil, style: nil, highlight_style: nil, highlight_symbol: "> ", repeat_highlight_symbol: false, highlight_spacing: :when_selected, direction: :top_to_bottom, scroll_padding: nil, block: nil)
88
+ super(
89
+ items: items,
90
+ selected_index: selected_index.nil? ? nil : Integer(selected_index),
91
+ style: style,
92
+ highlight_style: highlight_style,
93
+ highlight_symbol: highlight_symbol,
94
+ repeat_highlight_symbol: repeat_highlight_symbol,
95
+ highlight_spacing: highlight_spacing,
96
+ direction: direction,
97
+ scroll_padding: scroll_padding.nil? ? nil : Integer(scroll_padding),
98
+ block: block
99
+ )
100
+ end
26
101
  end
27
- end
28
102
  end
@@ -4,12 +4,38 @@
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
6
  module RatatuiRuby
7
- # Renders children on top of each other (Painter's Algorithm).
8
- #
9
- # [layers] Array of Widgets.
10
- class Overlay < Data.define(:layers)
11
- # Creates a new Overlay.
12
- #
13
- # [layers] Array of Widgets.
14
- end
7
+ # Stacks widgets on top of each other.
8
+ #
9
+ # Terminal interfaces are 2D grids, but complex UIs require depth. You need to float modals over text,
10
+ # or place a status bar on top of a map.
11
+ #
12
+ # This widget manages the Z-axis. It renders a list of widgets sequentially into the same area.
13
+ # Later widgets draw over earlier ones (Painter's Algorithm).
14
+ #
15
+ # Use overlays to compose complex scenes. Combine backgrounds, main content, and floating elements.
16
+ #
17
+ # === Examples
18
+ #
19
+ # Overlay.new(
20
+ # layers: [
21
+ # BackgroundMap.new,
22
+ # StatusBar.new, # Draws over map
23
+ # ModalDialog.new # Draws over everything
24
+ # ]
25
+ # )
26
+ class Overlay < Data.define(:layers)
27
+ ##
28
+ # :attr_reader: layers
29
+ # The stack of widgets to render.
30
+ #
31
+ # Rendered from index 0 to N. Index N is the top-most layer.
32
+
33
+ # Creates a new Overlay.
34
+ #
35
+ # [layers]
36
+ # Array of widgets.
37
+ def initialize(layers: [])
38
+ super
39
+ end
40
+ end
15
41
  end
@@ -4,37 +4,94 @@
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
6
  module RatatuiRuby
7
- # A widget that displays a block of text.
8
- #
9
- # [text] the text to display.
10
- # [style] the style to apply (Style object).
11
- # [block] an optional Block widget to wrap the paragraph.
12
- # [scroll] scroll offset as (y, x) array matching ratatui convention.
13
- class Paragraph < Data.define(:text, :style, :block, :wrap, :align, :scroll)
14
- # Creates a new Paragraph.
7
+ # Displays a block of text.
15
8
  #
16
- # [text] the text to display.
17
- # [style] the style to apply.
18
- # [block] the block to wrap the paragraph.
19
- # [wrap] whether to wrap text at width.
20
- # [align] alignment (:left, :center, :right).
21
- # [scroll] scroll offset as (y, x) array (default: [0, 0]).
22
- def initialize(text:, style: Style.default, block: nil, wrap: false, align: :left, scroll: [0, 0])
23
- super
24
- end
9
+ # Raw strings are insufficient for UIs. They overflow constraints. They don't respect alignment (left, center, right).
10
+ #
11
+ # This widget creates a smart text container. It wraps content to fit the area. It aligns text as requested. It supports scrolling.
12
+ #
13
+ # Use it for everything from simple labels to complex, multi-paragraph documents.
14
+ #
15
+ # === Examples
16
+ #
17
+ # # Basic Text
18
+ # Paragraph.new(text: "Hello, World!")
19
+ #
20
+ # # Styled container with wrapping
21
+ # Paragraph.new(
22
+ # text: "This is a long line that will wrap automatically.",
23
+ # style: Style.new(fg: :green),
24
+ # wrap: true,
25
+ # block: Block.new(title: "Output", borders: [:all])
26
+ # )
27
+ #
28
+ # # Scrolling mechanism
29
+ # Paragraph.new(text: large_text, scroll: [scroll_y, 0])
30
+ class Paragraph < Data.define(:text, :style, :block, :wrap, :alignment, :scroll)
31
+ ##
32
+ # :attr_reader: text
33
+ # The content to display.
34
+
35
+ ##
36
+ # :attr_reader: style
37
+ # Base style for the text.
38
+
39
+ ##
40
+ # :attr_reader: block
41
+ # Optional wrapping block.
42
+
43
+ ##
44
+ # :attr_reader: wrap
45
+ # Whether to wrap text at the edge of the container (Boolean).
46
+
47
+ ##
48
+ # :attr_reader: alignment
49
+ # Text alignment.
50
+ #
51
+ # <tt>:left</tt>, <tt>:center</tt>, or <tt>:right</tt>.
52
+
53
+ ##
54
+ # :attr_reader: scroll
55
+ # Scroll offset [y, x].
56
+
57
+ # Creates a new Paragraph.
58
+ #
59
+ # [text] String or Text::Line array.
60
+ # [style] Style object.
61
+ # [block] Block object.
62
+ # [wrap] Boolean (default: false).
63
+ # [alignment] Symbol (default: <tt>:left</tt>).
64
+ # [scroll] Array of [y, x] integers (duck-typed via +to_int+).
65
+ def initialize(text:, style: Style.default, block: nil, wrap: false, alignment: :left, scroll: [0, 0])
66
+ super(
67
+ text: text,
68
+ style: style,
69
+ block: block,
70
+ wrap: wrap,
71
+ alignment: alignment,
72
+ scroll: [Integer(scroll[0]), Integer(scroll[1])]
73
+ )
74
+ end
75
+
76
+ # Legacy constructor support.
77
+ def self.new(text:, style: nil, fg: nil, bg: nil, block: nil, wrap: false, alignment: :left, scroll: [0, 0])
78
+ style ||= Style.new(fg:, bg:)
79
+ coerced_scroll = [Integer(scroll[0]), Integer(scroll[1])]
80
+ super(text:, style:, block:, wrap:, alignment:, scroll: coerced_scroll)
81
+ end
82
+
83
+ # Returns the number of lines the paragraph would take up if rendered with the given width.
84
+ #
85
+ # [width] Integer (max width).
86
+ def line_count(width)
87
+ RatatuiRuby.warn_experimental_feature("Paragraph#line_count")
88
+ RatatuiRuby._paragraph_line_count(self, Integer(width))
89
+ end
25
90
 
26
- # Support for legacy fg/bg arguments.
27
- # [text] the text to display.
28
- # [style] the style to apply.
29
- # [fg] legacy foreground color.
30
- # [bg] legacy background color.
31
- # [block] the block to wrap the paragraph.
32
- # [wrap] whether to wrap text at width.
33
- # [align] alignment (:left, :center, :right).
34
- # [scroll] scroll offset as (y, x) array (default: [0, 0]).
35
- def self.new(text:, style: nil, fg: nil, bg: nil, block: nil, wrap: false, align: :left, scroll: [0, 0])
36
- style ||= Style.new(fg:, bg:)
37
- super(text:, style:, block:, wrap:, align:, scroll:)
91
+ # Returns the minimum width needed to not wrap any text.
92
+ def line_width
93
+ RatatuiRuby.warn_experimental_feature("Paragraph#line_width")
94
+ RatatuiRuby._paragraph_line_width(self)
95
+ end
38
96
  end
39
- end
40
97
  end
@@ -0,0 +1,25 @@
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
+ module RatatuiRuby
7
+ # Displays the Ratatui logo.
8
+ #
9
+ # A simple widget that renders the Ratatui logo. Useful for demos and about screens.
10
+ #
11
+ # === Examples
12
+ #
13
+ # RatatuiLogo.new
14
+ #
15
+ class RatatuiLogo < Data.define
16
+ ##
17
+ # :method: new
18
+ # :call-seq: new -> RatatuiLogo
19
+ #
20
+ # Creates a new RatatuiLogo.
21
+ def initialize
22
+ super
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
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
+ module RatatuiRuby
7
+ # Displays the Ratatui mascot.
8
+ #
9
+ # A widget that renders the Ratatui mascot (a rat).
10
+ #
11
+ # === Examples
12
+ #
13
+ # RatatuiMascot.new(
14
+ # block: Block.new(title: "Mascot")
15
+ # )
16
+ #
17
+ class RatatuiMascot < Data.define(:block)
18
+ ##
19
+ # :method: new
20
+ # :call-seq: new(block: nil) -> RatatuiMascot
21
+ #
22
+ # Creates a new RatatuiMascot.
23
+ #
24
+ # @param block [Block, nil] A block to wrap the widget in.
25
+ def initialize(block: nil)
26
+ super
27
+ end
28
+ end
29
+ end
@@ -4,21 +4,70 @@
4
4
  # SPDX-License-Identifier: AGPL-3.0-or-later
5
5
 
6
6
  module RatatuiRuby
7
- # A rectangle in the terminal grid.
8
- #
9
- # [x] The x-coordinate of the top-left corner.
10
- # [y] The y-coordinate of the top-left corner.
11
- # [width] The width of the rectangle.
12
- # [height] The height of the rectangle.
13
- class Rect < Data.define(:x, :y, :width, :height)
14
- # Creates a new Rect.
7
+ # Defines a rectangular area in the terminal grid.
15
8
  #
16
- # [x] The x-coordinate of the top-left corner.
17
- # [y] The y-coordinate of the top-left corner.
18
- # [width] The width of the rectangle.
19
- # [height] The height of the rectangle.
20
- def initialize(x: 0, y: 0, width: 0, height: 0)
21
- super
9
+ # Geometry management involves passing groups of four integers (`x, y, width, height`) repeatedly.
10
+ # This is verbose and prone to parameter mismatch errors.
11
+ #
12
+ # This class encapsulates the geometry. It provides a standard primitive for passing area definitions
13
+ # between layout engines and rendering functions.
14
+ #
15
+ # Use it when manual positioning is required or when querying layout results.
16
+ #
17
+ # === Examples
18
+ #
19
+ # area = Rect.new(x: 0, y: 0, width: 80, height: 24)
20
+ # puts area.width # => 80
21
+ class Rect < Data.define(:x, :y, :width, :height)
22
+ ##
23
+ # :attr_reader: x
24
+ # X coordinate (column) of the top-left corner (Integer, coerced via +to_int+ or +to_i+).
25
+
26
+ ##
27
+ # :attr_reader: y
28
+ # Y coordinate (row) of the top-left corner (Integer, coerced via +to_int+ or +to_i+).
29
+
30
+ ##
31
+ # :attr_reader: width
32
+ # Width in characters (Integer, coerced via +to_int+ or +to_i+).
33
+
34
+ ##
35
+ # :attr_reader: height
36
+ # Height in characters (Integer, coerced via +to_int+ or +to_i+).
37
+
38
+ # Creates a new Rect.
39
+ #
40
+ # All parameters accept any object responding to +to_int+ or +to_i+ (duck-typed).
41
+ #
42
+ # [x] Column index (Numeric).
43
+ # [y] Row index (Numeric).
44
+ # [width] Width in columns (Numeric).
45
+ # [height] Height in rows (Numeric).
46
+ def initialize(x: 0, y: 0, width: 0, height: 0)
47
+ super(
48
+ x: Integer(x),
49
+ y: Integer(y),
50
+ width: Integer(width),
51
+ height: Integer(height)
52
+ )
53
+ end
54
+
55
+ # Tests whether a point is inside this rectangle.
56
+ #
57
+ # Essential for hit testing mouse clicks against layout regions.
58
+ #
59
+ # area = Rect.new(x: 10, y: 5, width: 20, height: 10)
60
+ # area.contains?(15, 8) # => true
61
+ # area.contains?(5, 8) # => false
62
+ #
63
+ # [px]
64
+ # X coordinate to test (column).
65
+ # [py]
66
+ # Y coordinate to test (row).
67
+ #
68
+ # Returns true if the point (px, py) is within the rectangle bounds.
69
+ def contains?(px, py)
70
+ px >= x && px < x + width && py >= y && py < y + height
71
+ end
22
72
  end
23
- end
24
73
  end