ratatui_ruby 1.4.0-x86_64-linux

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 (292) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +15 -0
  3. data/LICENSES/AGPL-3.0-or-later.txt +661 -0
  4. data/LICENSES/CC-BY-SA-4.0.txt +427 -0
  5. data/LICENSES/CC0-1.0.txt +121 -0
  6. data/LICENSES/LGPL-3.0-or-later.txt +304 -0
  7. data/LICENSES/MIT-0.txt +16 -0
  8. data/LICENSES/MIT.txt +21 -0
  9. data/REUSE.toml +42 -0
  10. data/exe/.gitkeep +0 -0
  11. data/ext/ratatui_ruby/.cargo/config.toml +13 -0
  12. data/ext/ratatui_ruby/.gitignore +4 -0
  13. data/ext/ratatui_ruby/Cargo.lock +1737 -0
  14. data/ext/ratatui_ruby/Cargo.toml +24 -0
  15. data/ext/ratatui_ruby/clippy.toml +7 -0
  16. data/ext/ratatui_ruby/extconf.rb +21 -0
  17. data/ext/ratatui_ruby/src/color.rs +82 -0
  18. data/ext/ratatui_ruby/src/errors.rs +28 -0
  19. data/ext/ratatui_ruby/src/events.rs +700 -0
  20. data/ext/ratatui_ruby/src/frame.rs +241 -0
  21. data/ext/ratatui_ruby/src/lib.rs +343 -0
  22. data/ext/ratatui_ruby/src/lib_header.rs +11 -0
  23. data/ext/ratatui_ruby/src/rendering.rs +158 -0
  24. data/ext/ratatui_ruby/src/string_width.rs +101 -0
  25. data/ext/ratatui_ruby/src/style.rs +469 -0
  26. data/ext/ratatui_ruby/src/terminal/capabilities.rs +46 -0
  27. data/ext/ratatui_ruby/src/terminal/init.rs +233 -0
  28. data/ext/ratatui_ruby/src/terminal/mod.rs +42 -0
  29. data/ext/ratatui_ruby/src/terminal/mutations.rs +158 -0
  30. data/ext/ratatui_ruby/src/terminal/queries.rs +231 -0
  31. data/ext/ratatui_ruby/src/terminal/query.rs +400 -0
  32. data/ext/ratatui_ruby/src/terminal/storage.rs +109 -0
  33. data/ext/ratatui_ruby/src/terminal/wrapper.rs +16 -0
  34. data/ext/ratatui_ruby/src/text.rs +225 -0
  35. data/ext/ratatui_ruby/src/widgets/barchart.rs +169 -0
  36. data/ext/ratatui_ruby/src/widgets/block.rs +41 -0
  37. data/ext/ratatui_ruby/src/widgets/calendar.rs +84 -0
  38. data/ext/ratatui_ruby/src/widgets/canvas.rs +183 -0
  39. data/ext/ratatui_ruby/src/widgets/center.rs +79 -0
  40. data/ext/ratatui_ruby/src/widgets/chart.rs +222 -0
  41. data/ext/ratatui_ruby/src/widgets/clear.rs +39 -0
  42. data/ext/ratatui_ruby/src/widgets/cursor.rs +32 -0
  43. data/ext/ratatui_ruby/src/widgets/gauge.rs +65 -0
  44. data/ext/ratatui_ruby/src/widgets/layout.rs +379 -0
  45. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +100 -0
  46. data/ext/ratatui_ruby/src/widgets/list.rs +378 -0
  47. data/ext/ratatui_ruby/src/widgets/list_state.rs +173 -0
  48. data/ext/ratatui_ruby/src/widgets/mod.rs +26 -0
  49. data/ext/ratatui_ruby/src/widgets/overlay.rs +24 -0
  50. data/ext/ratatui_ruby/src/widgets/paragraph.rs +87 -0
  51. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
  52. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +55 -0
  53. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +214 -0
  54. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
  55. data/ext/ratatui_ruby/src/widgets/sparkline.rs +127 -0
  56. data/ext/ratatui_ruby/src/widgets/table.rs +415 -0
  57. data/ext/ratatui_ruby/src/widgets/table_state.rs +203 -0
  58. data/ext/ratatui_ruby/src/widgets/tabs.rs +194 -0
  59. data/lib/ratatui_ruby/backend/window_size.rb +50 -0
  60. data/lib/ratatui_ruby/backend.rb +59 -0
  61. data/lib/ratatui_ruby/buffer/cell.rb +212 -0
  62. data/lib/ratatui_ruby/buffer.rb +149 -0
  63. data/lib/ratatui_ruby/cell.rb +208 -0
  64. data/lib/ratatui_ruby/debug.rb +215 -0
  65. data/lib/ratatui_ruby/draw.rb +63 -0
  66. data/lib/ratatui_ruby/event/focus_gained.rb +125 -0
  67. data/lib/ratatui_ruby/event/focus_lost.rb +127 -0
  68. data/lib/ratatui_ruby/event/key/character.rb +53 -0
  69. data/lib/ratatui_ruby/event/key/dwim.rb +301 -0
  70. data/lib/ratatui_ruby/event/key/media.rb +46 -0
  71. data/lib/ratatui_ruby/event/key/modifier.rb +107 -0
  72. data/lib/ratatui_ruby/event/key/navigation.rb +72 -0
  73. data/lib/ratatui_ruby/event/key/system.rb +47 -0
  74. data/lib/ratatui_ruby/event/key.rb +479 -0
  75. data/lib/ratatui_ruby/event/mouse.rb +291 -0
  76. data/lib/ratatui_ruby/event/none.rb +53 -0
  77. data/lib/ratatui_ruby/event/paste.rb +130 -0
  78. data/lib/ratatui_ruby/event/resize.rb +221 -0
  79. data/lib/ratatui_ruby/event/sync.rb +52 -0
  80. data/lib/ratatui_ruby/event.rb +163 -0
  81. data/lib/ratatui_ruby/frame.rb +257 -0
  82. data/lib/ratatui_ruby/labs/a11y.rb +182 -0
  83. data/lib/ratatui_ruby/labs/frame_a11y_capture.rb +50 -0
  84. data/lib/ratatui_ruby/labs.rb +47 -0
  85. data/lib/ratatui_ruby/layout/alignment.rb +91 -0
  86. data/lib/ratatui_ruby/layout/constraint.rb +337 -0
  87. data/lib/ratatui_ruby/layout/layout.rb +258 -0
  88. data/lib/ratatui_ruby/layout/position.rb +81 -0
  89. data/lib/ratatui_ruby/layout/rect.rb +733 -0
  90. data/lib/ratatui_ruby/layout/size.rb +62 -0
  91. data/lib/ratatui_ruby/layout.rb +29 -0
  92. data/lib/ratatui_ruby/list_state.rb +201 -0
  93. data/lib/ratatui_ruby/output_guard.rb +171 -0
  94. data/lib/ratatui_ruby/ratatui_ruby.so +0 -0
  95. data/lib/ratatui_ruby/scrollbar_state.rb +122 -0
  96. data/lib/ratatui_ruby/style/color.rb +149 -0
  97. data/lib/ratatui_ruby/style/style.rb +147 -0
  98. data/lib/ratatui_ruby/style.rb +19 -0
  99. data/lib/ratatui_ruby/symbols.rb +435 -0
  100. data/lib/ratatui_ruby/synthetic_events.rb +106 -0
  101. data/lib/ratatui_ruby/table_state.rb +251 -0
  102. data/lib/ratatui_ruby/terminal/capabilities.rb +316 -0
  103. data/lib/ratatui_ruby/terminal/viewport.rb +80 -0
  104. data/lib/ratatui_ruby/terminal.rb +66 -0
  105. data/lib/ratatui_ruby/terminal_lifecycle.rb +303 -0
  106. data/lib/ratatui_ruby/terminal_lifecycle.rb.bak +197 -0
  107. data/lib/ratatui_ruby/test_helper/event_injection.rb +241 -0
  108. data/lib/ratatui_ruby/test_helper/global_state.rb +111 -0
  109. data/lib/ratatui_ruby/test_helper/snapshot.rb +568 -0
  110. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.ansi +24 -0
  111. data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.txt +24 -0
  112. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.ansi +5 -0
  113. data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.txt +5 -0
  114. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.ansi +24 -0
  115. data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.txt +24 -0
  116. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.ansi +12 -0
  117. data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.txt +12 -0
  118. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.ansi +12 -0
  119. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.txt +12 -0
  120. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.ansi +12 -0
  121. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.txt +12 -0
  122. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.ansi +12 -0
  123. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.txt +12 -0
  124. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.ansi +12 -0
  125. data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.txt +12 -0
  126. data/lib/ratatui_ruby/test_helper/snapshots/my_snapshot.txt +1 -0
  127. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.ansi +10 -0
  128. data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.txt +10 -0
  129. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.ansi +10 -0
  130. data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.txt +10 -0
  131. data/lib/ratatui_ruby/test_helper/style_assertions.rb +449 -0
  132. data/lib/ratatui_ruby/test_helper/subprocess_timeout.rb +35 -0
  133. data/lib/ratatui_ruby/test_helper/terminal.rb +187 -0
  134. data/lib/ratatui_ruby/test_helper/test_doubles.rb +86 -0
  135. data/lib/ratatui_ruby/test_helper.rb +115 -0
  136. data/lib/ratatui_ruby/text/line.rb +245 -0
  137. data/lib/ratatui_ruby/text/span.rb +158 -0
  138. data/lib/ratatui_ruby/text.rb +99 -0
  139. data/lib/ratatui_ruby/tui/buffer_factories.rb +22 -0
  140. data/lib/ratatui_ruby/tui/canvas_factories.rb +149 -0
  141. data/lib/ratatui_ruby/tui/core.rb +67 -0
  142. data/lib/ratatui_ruby/tui/layout_factories.rb +153 -0
  143. data/lib/ratatui_ruby/tui/state_factories.rb +77 -0
  144. data/lib/ratatui_ruby/tui/style_factories.rb +22 -0
  145. data/lib/ratatui_ruby/tui/text_factories.rb +86 -0
  146. data/lib/ratatui_ruby/tui/widget_factories.rb +272 -0
  147. data/lib/ratatui_ruby/tui.rb +106 -0
  148. data/lib/ratatui_ruby/version.rb +12 -0
  149. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +51 -0
  150. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +29 -0
  151. data/lib/ratatui_ruby/widgets/bar_chart.rb +308 -0
  152. data/lib/ratatui_ruby/widgets/block.rb +266 -0
  153. data/lib/ratatui_ruby/widgets/calendar.rb +88 -0
  154. data/lib/ratatui_ruby/widgets/canvas.rb +297 -0
  155. data/lib/ratatui_ruby/widgets/cell.rb +59 -0
  156. data/lib/ratatui_ruby/widgets/center.rb +71 -0
  157. data/lib/ratatui_ruby/widgets/chart.rb +172 -0
  158. data/lib/ratatui_ruby/widgets/clear.rb +66 -0
  159. data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
  160. data/lib/ratatui_ruby/widgets/cursor.rb +54 -0
  161. data/lib/ratatui_ruby/widgets/gauge.rb +146 -0
  162. data/lib/ratatui_ruby/widgets/line_gauge.rb +158 -0
  163. data/lib/ratatui_ruby/widgets/list.rb +252 -0
  164. data/lib/ratatui_ruby/widgets/list_item.rb +55 -0
  165. data/lib/ratatui_ruby/widgets/overlay.rb +55 -0
  166. data/lib/ratatui_ruby/widgets/paragraph.rb +113 -0
  167. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +35 -0
  168. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +40 -0
  169. data/lib/ratatui_ruby/widgets/row.rb +123 -0
  170. data/lib/ratatui_ruby/widgets/scrollbar.rb +147 -0
  171. data/lib/ratatui_ruby/widgets/shape/label.rb +80 -0
  172. data/lib/ratatui_ruby/widgets/sparkline.rb +153 -0
  173. data/lib/ratatui_ruby/widgets/table.rb +213 -0
  174. data/lib/ratatui_ruby/widgets/tabs.rb +91 -0
  175. data/lib/ratatui_ruby/widgets.rb +43 -0
  176. data/lib/ratatui_ruby.rb +555 -0
  177. data/sig/examples/app_all_events/app.rbs +11 -0
  178. data/sig/examples/app_all_events/model/app_model.rbs +23 -0
  179. data/sig/examples/app_all_events/model/event_entry.rbs +23 -0
  180. data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
  181. data/sig/examples/app_all_events/view/app_view.rbs +8 -0
  182. data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
  183. data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
  184. data/sig/examples/app_all_events/view/live_view.rbs +6 -0
  185. data/sig/examples/app_all_events/view/log_view.rbs +6 -0
  186. data/sig/examples/app_all_events/view.rbs +14 -0
  187. data/sig/examples/app_cli_rich_moments/app.rbs +12 -0
  188. data/sig/examples/app_color_picker/app.rbs +17 -0
  189. data/sig/examples/app_external_editor/app.rbs +12 -0
  190. data/sig/examples/app_login_form/app.rbs +11 -0
  191. data/sig/examples/app_stateful_interaction/app.rbs +39 -0
  192. data/sig/examples/verify_quickstart_dsl/app.rbs +17 -0
  193. data/sig/examples/verify_quickstart_lifecycle/app.rbs +17 -0
  194. data/sig/examples/verify_readme_usage/app.rbs +17 -0
  195. data/sig/examples/widget_block_demo/app.rbs +38 -0
  196. data/sig/examples/widget_box_demo/app.rbs +17 -0
  197. data/sig/examples/widget_calendar_demo/app.rbs +17 -0
  198. data/sig/examples/widget_cell_demo/app.rbs +17 -0
  199. data/sig/examples/widget_chart_demo/app.rbs +17 -0
  200. data/sig/examples/widget_gauge_demo/app.rbs +17 -0
  201. data/sig/examples/widget_layout_split/app.rbs +16 -0
  202. data/sig/examples/widget_line_gauge_demo/app.rbs +17 -0
  203. data/sig/examples/widget_list_demo/app.rbs +17 -0
  204. data/sig/examples/widget_map_demo/app.rbs +17 -0
  205. data/sig/examples/widget_popup_demo/app.rbs +17 -0
  206. data/sig/examples/widget_ratatui_logo_demo/app.rbs +17 -0
  207. data/sig/examples/widget_ratatui_mascot_demo/app.rbs +17 -0
  208. data/sig/examples/widget_rect/app.rbs +18 -0
  209. data/sig/examples/widget_render/app.rbs +16 -0
  210. data/sig/examples/widget_rich_text/app.rbs +17 -0
  211. data/sig/examples/widget_scroll_text/app.rbs +17 -0
  212. data/sig/examples/widget_scrollbar_demo/app.rbs +17 -0
  213. data/sig/examples/widget_sparkline_demo/app.rbs +16 -0
  214. data/sig/examples/widget_style_colors/app.rbs +20 -0
  215. data/sig/examples/widget_table_demo/app.rbs +17 -0
  216. data/sig/examples/widget_text_width/app.rbs +16 -0
  217. data/sig/generated/event_key_predicates.rbs +1348 -0
  218. data/sig/manifest.yaml +5 -0
  219. data/sig/patches/data.rbs +26 -0
  220. data/sig/patches/debugger__.rbs +8 -0
  221. data/sig/ratatui_ruby/backend/window_size.rbs +17 -0
  222. data/sig/ratatui_ruby/backend.rbs +12 -0
  223. data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
  224. data/sig/ratatui_ruby/buffer.rbs +18 -0
  225. data/sig/ratatui_ruby/cell.rbs +44 -0
  226. data/sig/ratatui_ruby/clear.rbs +18 -0
  227. data/sig/ratatui_ruby/constraint.rbs +26 -0
  228. data/sig/ratatui_ruby/debug.rbs +45 -0
  229. data/sig/ratatui_ruby/draw.rbs +30 -0
  230. data/sig/ratatui_ruby/event.rbs +249 -0
  231. data/sig/ratatui_ruby/frame.rbs +23 -0
  232. data/sig/ratatui_ruby/interfaces.rbs +25 -0
  233. data/sig/ratatui_ruby/labs.rbs +90 -0
  234. data/sig/ratatui_ruby/layout/alignment.rbs +26 -0
  235. data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
  236. data/sig/ratatui_ruby/layout/layout.rbs +45 -0
  237. data/sig/ratatui_ruby/layout/position.rbs +18 -0
  238. data/sig/ratatui_ruby/layout/rect.rbs +64 -0
  239. data/sig/ratatui_ruby/layout/size.rbs +18 -0
  240. data/sig/ratatui_ruby/list_state.rbs +23 -0
  241. data/sig/ratatui_ruby/output_guard.rbs +23 -0
  242. data/sig/ratatui_ruby/ratatui_ruby.rbs +113 -0
  243. data/sig/ratatui_ruby/rect.rbs +17 -0
  244. data/sig/ratatui_ruby/scrollbar_state.rbs +24 -0
  245. data/sig/ratatui_ruby/session.rbs +93 -0
  246. data/sig/ratatui_ruby/style/color.rbs +22 -0
  247. data/sig/ratatui_ruby/style/style.rbs +29 -0
  248. data/sig/ratatui_ruby/symbols.rbs +141 -0
  249. data/sig/ratatui_ruby/synthetic_events.rbs +24 -0
  250. data/sig/ratatui_ruby/table_state.rbs +27 -0
  251. data/sig/ratatui_ruby/terminal/capabilities.rbs +38 -0
  252. data/sig/ratatui_ruby/terminal/viewport.rbs +33 -0
  253. data/sig/ratatui_ruby/terminal_lifecycle.rbs +39 -0
  254. data/sig/ratatui_ruby/test_helper/event_injection.rbs +22 -0
  255. data/sig/ratatui_ruby/test_helper/snapshot.rbs +37 -0
  256. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +77 -0
  257. data/sig/ratatui_ruby/test_helper/terminal.rbs +20 -0
  258. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +32 -0
  259. data/sig/ratatui_ruby/test_helper.rbs +18 -0
  260. data/sig/ratatui_ruby/text/line.rbs +27 -0
  261. data/sig/ratatui_ruby/text/span.rbs +23 -0
  262. data/sig/ratatui_ruby/text.rbs +12 -0
  263. data/sig/ratatui_ruby/tui/buffer_factories.rbs +16 -0
  264. data/sig/ratatui_ruby/tui/canvas_factories.rbs +38 -0
  265. data/sig/ratatui_ruby/tui/core.rbs +23 -0
  266. data/sig/ratatui_ruby/tui/layout_factories.rbs +39 -0
  267. data/sig/ratatui_ruby/tui/state_factories.rbs +23 -0
  268. data/sig/ratatui_ruby/tui/style_factories.rbs +18 -0
  269. data/sig/ratatui_ruby/tui/text_factories.rbs +23 -0
  270. data/sig/ratatui_ruby/tui/widget_factories.rbs +138 -0
  271. data/sig/ratatui_ruby/tui.rbs +25 -0
  272. data/sig/ratatui_ruby/version.rbs +12 -0
  273. data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
  274. data/sig/ratatui_ruby/widgets/block.rbs +51 -0
  275. data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
  276. data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
  277. data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
  278. data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
  279. data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
  280. data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
  281. data/sig/ratatui_ruby/widgets/list.rbs +63 -0
  282. data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
  283. data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
  284. data/sig/ratatui_ruby/widgets/row.rbs +43 -0
  285. data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
  286. data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
  287. data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
  288. data/sig/ratatui_ruby/widgets/table.rbs +78 -0
  289. data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
  290. data/sig/ratatui_ruby/widgets.rbs +16 -0
  291. data/vendor/goodcop/base.yml +1047 -0
  292. metadata +729 -0
@@ -0,0 +1,258 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: LGPL-3.0-or-later
6
+ #++
7
+
8
+ module RatatuiRuby
9
+ module Layout
10
+ # Divides an area into smaller chunks.
11
+ #
12
+ # Terminal screens vary in size. Hardcoded positions break when the window resizes. You need a way to organize space dynamically.
13
+ #
14
+ # This class manages geometry. It splits a given area into multiple sections based on a list of constraints.
15
+ #
16
+ # 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.
17
+ #
18
+ # {rdoc-image:/doc/images/widget_layout_split.png}[link:/examples/widget_layout_split/app_rb.html]
19
+ #
20
+ # === Example
21
+ #
22
+ # Run the interactive demo from the terminal:
23
+ #
24
+ # ruby examples/widget_layout_split/app.rb
25
+ class Layout < Data.define(:direction, :constraints, :children, :flex, :margin, :spacing)
26
+ ##
27
+ # :attr_reader: direction
28
+ # Direction of the split.
29
+ #
30
+ # Either <tt>:vertical</tt> (top to bottom) or <tt>:horizontal</tt> (left to right).
31
+ #
32
+ # layout.direction # => :vertical
33
+
34
+ ##
35
+ # :attr_reader: constraints
36
+ # Array of rules defining section sizes.
37
+ #
38
+ # See RatatuiRuby::Layout::Constraint.
39
+
40
+ ##
41
+ # :attr_reader: children
42
+ # Widgets to render in each section (optional).
43
+ #
44
+ # If provided, `children[i]` is rendered into the area defined by `constraints[i]`.
45
+
46
+ ##
47
+ # :attr_reader: flex
48
+ # Strategy for distributing extra space.
49
+ #
50
+ # One of <tt>:legacy</tt>, <tt>:start</tt>, <tt>:center</tt>, <tt>:end</tt>, <tt>:space_between</tt>, <tt>:space_around</tt>.
51
+
52
+ FLEX_MODES = %i[legacy start center end space_between space_around space_evenly].freeze # :nodoc:
53
+
54
+ ##
55
+ # Direction: split vertically (top to bottom).
56
+ DIRECTION_VERTICAL = :vertical
57
+ ##
58
+ # Direction: split horizontally (left to right).
59
+ DIRECTION_HORIZONTAL = :horizontal
60
+
61
+ ##
62
+ # Flex: use legacy sizing (default).
63
+ FLEX_LEGACY = :legacy
64
+ ##
65
+ # Flex: align to start.
66
+ FLEX_START = :start
67
+ ##
68
+ # Flex: center alignment.
69
+ FLEX_CENTER = :center
70
+ ##
71
+ # Flex: align to end.
72
+ FLEX_END = :end
73
+ ##
74
+ # Flex: space between elements.
75
+ FLEX_SPACE_BETWEEN = :space_between
76
+ ##
77
+ # Flex: space around elements.
78
+ FLEX_SPACE_AROUND = :space_around
79
+ ##
80
+ # Flex: space evenly between elements.
81
+ FLEX_SPACE_EVENLY = :space_evenly
82
+
83
+ ##
84
+ # :attr_reader: margin
85
+ # Margin around the layout area.
86
+ #
87
+ # Either a single <tt>Integer</tt> for uniform margin on all sides, or a
88
+ # <tt>Hash</tt> with <tt>:horizontal</tt> and <tt>:vertical</tt> keys.
89
+ #
90
+ # layout.margin # => 2
91
+
92
+ ##
93
+ # :attr_reader: spacing
94
+ # Gap between segments (in cells).
95
+ #
96
+ # A positive integer that specifies the number of cells between each segment.
97
+ #
98
+ # layout.spacing # => 1
99
+
100
+ # Creates a new Layout.
101
+ #
102
+ # [direction]
103
+ # <tt>:vertical</tt> or <tt>:horizontal</tt> (default: <tt>:vertical</tt>).
104
+ # [constraints]
105
+ # list of Constraint objects.
106
+ # [children]
107
+ # List of widgets to render (optional).
108
+ # [flex]
109
+ # Flex mode for spacing (default: <tt>:legacy</tt>).
110
+ # [margin]
111
+ # Edge margin in cells (default: <tt>0</tt>).
112
+ # [spacing]
113
+ # Gap between segments in cells (default: <tt>0</tt>).
114
+ def initialize(direction: :vertical, constraints: [], children: [], flex: :legacy, margin: 0, spacing: 0)
115
+ super
116
+ end
117
+
118
+ # Splits an area into multiple rectangles.
119
+ #
120
+ # This is a pure calculation helper for hit testing. It computes where
121
+ # widgets *would* be placed without actually rendering them.
122
+ #
123
+ #--
124
+ # SPDX-SnippetBegin
125
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
126
+ # SPDX-License-Identifier: MIT-0
127
+ #++
128
+ # rects = Layout::Layout.split(
129
+ # area,
130
+ # direction: :horizontal,
131
+ # constraints: [Layout::Constraint.percentage(50), Layout::Constraint.percentage(50)]
132
+ # )
133
+ # left, right = rects
134
+ #
135
+ #--
136
+ # SPDX-SnippetEnd
137
+ #++
138
+ # [area]
139
+ # The area to split. Can be a <tt>Rect</tt> or a <tt>Hash</tt> containing <tt>:x</tt>, <tt>:y</tt>, <tt>:width</tt>, and <tt>:height</tt>.
140
+ # [direction]
141
+ # <tt>:vertical</tt> or <tt>:horizontal</tt> (default: <tt>:vertical</tt>).
142
+ # [constraints]
143
+ # Array of <tt>Constraint</tt> objects defining section sizes.
144
+ # [flex]
145
+ #--
146
+ # SPDX-SnippetBegin
147
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
148
+ # SPDX-License-Identifier: MIT-0
149
+ #++
150
+ # Flex mode for spacing (default: <tt>:legacy</tt>).
151
+ #
152
+ #--
153
+ # SPDX-SnippetEnd
154
+ #++
155
+ # Returns an Array of <tt>Rect</tt> objects.
156
+ def self.split(area, direction: :vertical, constraints:, flex: :legacy)
157
+ # Coerce area to Rect for type safety (supports duck typing via _RectLike interface)
158
+ rect = case area
159
+ when Rect
160
+ area
161
+ when Hash
162
+ Rect.new(
163
+ x: Integer(area.fetch(:x, 0)),
164
+ y: Integer(area.fetch(:y, 0)),
165
+ width: Integer(area.fetch(:width, 0)),
166
+ height: Integer(area.fetch(:height, 0))
167
+ )
168
+ else
169
+ # Duck typing: accept any object responding to x, y, width, height
170
+ if area.respond_to?(:x) && area.respond_to?(:y) && area.respond_to?(:width) && area.respond_to?(:height)
171
+ # @type var rect_like: _RectLike
172
+ rect_like = area
173
+ Rect.new(x: rect_like.x, y: rect_like.y, width: rect_like.width, height: rect_like.height)
174
+ else
175
+ raise ArgumentError, "area must be a Rect, Hash, or respond to x/y/width/height, got #{area.class}"
176
+ end
177
+ end
178
+ raw_rects = _split(rect, direction, constraints, flex)
179
+ raw_rects.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
180
+ end
181
+
182
+ # Splits an area into multiple rectangles, returning both segments and spacers.
183
+ #
184
+ # Layout splitting returns only the content areas. But some designs need to
185
+ # render content in the gaps (dividers, separators, decorations).
186
+ #
187
+ # This method returns both the segments (content areas) and the spacers
188
+ # (gaps between segments) as separate arrays. The spacers are the Rects
189
+ # that represent the spacing between each segment.
190
+ #
191
+ # Use it to render custom separators or to calculate layout with spacing.
192
+ #
193
+ # [area]
194
+ # The area to split. Can be a <tt>Rect</tt> or a <tt>Hash</tt> containing <tt>:x</tt>, <tt>:y</tt>, <tt>:width</tt>, and <tt>:height</tt>.
195
+ # [direction]
196
+ # <tt>:vertical</tt> or <tt>:horizontal</tt> (default: <tt>:vertical</tt>).
197
+ # [constraints]
198
+ # Array of <tt>Constraint</tt> objects defining section sizes.
199
+ # [flex]
200
+ #--
201
+ # SPDX-SnippetBegin
202
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
203
+ # SPDX-License-Identifier: MIT-0
204
+ #++
205
+ # Flex mode for spacing (default: <tt>:legacy</tt>).
206
+ #
207
+ #--
208
+ # SPDX-SnippetEnd
209
+ #++
210
+ # Returns an Array of two Arrays: <tt>[segments, spacers]</tt>, each containing <tt>Rect</tt> objects.
211
+ #
212
+ # === Example
213
+ #
214
+ #--
215
+ # SPDX-SnippetBegin
216
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
217
+ # SPDX-License-Identifier: MIT-0
218
+ #++
219
+ # area = Rect.new(x: 0, y: 0, width: 100, height: 10)
220
+ # segments, spacers = Layout.split_with_spacers(
221
+ # area,
222
+ # direction: :horizontal,
223
+ # constraints: [Constraint.length(40), Constraint.length(40)],
224
+ # flex: :space_around
225
+ # )
226
+ # # segments: 2 Rects for content
227
+ # # spacers: Rects for gaps between/around segments
228
+ #--
229
+ # SPDX-SnippetEnd
230
+ #++
231
+ def self.split_with_spacers(area, direction: :vertical, constraints:, flex: :legacy)
232
+ # Coerce area to Rect for type safety
233
+ rect = case area
234
+ when Rect
235
+ area
236
+ when Hash
237
+ Rect.new(
238
+ x: Integer(area.fetch(:x, 0)),
239
+ y: Integer(area.fetch(:y, 0)),
240
+ width: Integer(area.fetch(:width, 0)),
241
+ height: Integer(area.fetch(:height, 0))
242
+ )
243
+ else
244
+ if area.respond_to?(:x) && area.respond_to?(:y) && area.respond_to?(:width) && area.respond_to?(:height)
245
+ rect_like = area
246
+ Rect.new(x: rect_like.x, y: rect_like.y, width: rect_like.width, height: rect_like.height)
247
+ else
248
+ raise ArgumentError, "area must be a Rect, Hash, or respond to x/y/width/height, got #{area.class}"
249
+ end
250
+ end
251
+ raw_segments, raw_spacers = _split_with_spacers(rect, direction, constraints, flex)
252
+ segments = raw_segments.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
253
+ spacers = raw_spacers.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
254
+ [segments, spacers]
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
+ # SPDX-License-Identifier: LGPL-3.0-or-later
6
+ #++
7
+
8
+ module RatatuiRuby
9
+ module Layout
10
+ # A position in terminal coordinates.
11
+ #
12
+ # Layout code passes x/y pairs between functions. Bundling them
13
+ # into separate variables is verbose and prone to ordering mistakes.
14
+ #
15
+ # This class wraps column and row into a single immutable object.
16
+ # Pass it around, destructure it, or convert from a Rect.
17
+ #
18
+ # Use it for cursor positioning, mouse coordinates, or anywhere
19
+ # you need to represent a single point on the terminal grid.
20
+ #
21
+ # === Example
22
+ #
23
+ #--
24
+ # SPDX-SnippetBegin
25
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
26
+ # SPDX-License-Identifier: MIT-0
27
+ #++
28
+ # pos = Layout::Position.new(x: 10, y: 5)
29
+ # puts "Cursor at column #{pos.x}, row #{pos.y}"
30
+ #
31
+ # # Extract from a Rect
32
+ # rect = Layout::Rect.new(x: 10, y: 5, width: 80, height: 24)
33
+ # pos = rect.as_position # => Position(x: 10, y: 5)
34
+ #--
35
+ # SPDX-SnippetEnd
36
+ #++
37
+ class Position < Data.define(:x, :y)
38
+ ##
39
+ # :attr_reader: x
40
+ # Column index (0-indexed from left edge).
41
+
42
+ ##
43
+ # :attr_reader: y
44
+ # Row index (0-indexed from top edge).
45
+
46
+ # Creates a new Position.
47
+ #
48
+ # [x] Column index (Integer, coerced via +Integer()+).
49
+ # [y] Row index (Integer, coerced via +Integer()+).
50
+ def initialize(x: 0, y: 0)
51
+ super(x: Integer(x), y: Integer(y))
52
+ end
53
+
54
+ # Enables array destructuring for convenient coordinate extraction.
55
+ #
56
+ # Returns:: Array of [x, y] coordinates.
57
+ #
58
+ # === Example
59
+ #
60
+ #--
61
+ # SPDX-SnippetBegin
62
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
63
+ # SPDX-License-Identifier: MIT-0
64
+ #++
65
+ # pos = Position.new(x: 10, y: 5)
66
+ # x, y = pos # Uses deconstruct
67
+ # puts "Column: #{x}, Row: #{y}"
68
+ #--
69
+ # SPDX-SnippetEnd
70
+ #++
71
+ def deconstruct
72
+ [x, y]
73
+ end
74
+
75
+ # Alias for implicit array conversion.
76
+ #
77
+ # Enables `x, y = position` syntax by making Position respond to +to_ary+.
78
+ alias to_ary deconstruct
79
+ end
80
+ end
81
+ end