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,379 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ use crate::errors::type_error_with_context;
5
+ use crate::rendering::render_node;
6
+ use magnus::{prelude::*, Error, Symbol, Value};
7
+ use ratatui::{
8
+ buffer::Buffer,
9
+ layout::{Constraint, Direction, Flex, Layout, Rect},
10
+ };
11
+
12
+ pub fn render(buffer: &mut Buffer, area: Rect, node: Value) -> Result<(), Error> {
13
+ let ruby = magnus::Ruby::get().unwrap();
14
+ let direction_sym: Symbol = node.funcall("direction", ())?;
15
+ let children_val: Value = node.funcall("children", ())?;
16
+ let children_array = magnus::RArray::from_value(children_val).ok_or_else(|| {
17
+ type_error_with_context(&ruby, "expected array for children", children_val)
18
+ })?;
19
+
20
+ let constraints_val: Value = node.funcall("constraints", ())?;
21
+ let constraints_array = magnus::RArray::from_value(constraints_val);
22
+
23
+ let flex_sym: Symbol = node.funcall("flex", ())?;
24
+
25
+ let direction = if direction_sym.to_string() == "vertical" {
26
+ Direction::Vertical
27
+ } else {
28
+ Direction::Horizontal
29
+ };
30
+
31
+ let flex = match flex_sym.to_string().as_str() {
32
+ "start" => Flex::Start,
33
+ "center" => Flex::Center,
34
+ "end" => Flex::End,
35
+ "space_between" => Flex::SpaceBetween,
36
+ "space_around" => Flex::SpaceAround,
37
+ "space_evenly" => Flex::SpaceEvenly,
38
+ _ => Flex::Legacy,
39
+ };
40
+
41
+ let len = children_array.len();
42
+ if len > 0 {
43
+ let mut ratatui_constraints = Vec::new();
44
+
45
+ if let Some(arr) = constraints_array {
46
+ for i in 0..arr.len() {
47
+ let index = isize::try_from(i)
48
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
49
+ let constraint_obj: Value = arr.entry(index)?;
50
+ if let Ok(constraint) = parse_constraint(constraint_obj) {
51
+ ratatui_constraints.push(constraint);
52
+ }
53
+ }
54
+ }
55
+
56
+ // If constraints don't match children, adjust or default
57
+ if ratatui_constraints.len() != len {
58
+ ratatui_constraints = (0..len)
59
+ .map(|_| {
60
+ Constraint::Percentage(100 / u16::try_from(len).unwrap_or(u16::MAX).max(1))
61
+ })
62
+ .collect();
63
+ }
64
+
65
+ let chunks = Layout::default()
66
+ .direction(direction)
67
+ .flex(flex)
68
+ .constraints(ratatui_constraints)
69
+ .split(area);
70
+
71
+ for i in 0..len {
72
+ let index = isize::try_from(i)
73
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
74
+ let child: Value = children_array.entry(index)?;
75
+ if let Err(e) = render_node(buffer, chunks[i], child) {
76
+ eprintln!("Error rendering child {i}: {e:?}");
77
+ }
78
+ }
79
+ }
80
+ Ok(())
81
+ }
82
+
83
+ pub fn parse_constraint(value: Value) -> Result<Constraint, Error> {
84
+ let type_sym: Symbol = value.funcall("type", ())?;
85
+ let value_obj: Value = value.funcall("value", ())?;
86
+
87
+ match type_sym.to_string().as_str() {
88
+ "length" => {
89
+ let val = u16::try_convert(value_obj)?;
90
+ Ok(Constraint::Length(val))
91
+ }
92
+ "percentage" => {
93
+ let val = u16::try_convert(value_obj)?;
94
+ Ok(Constraint::Percentage(val))
95
+ }
96
+ "min" => {
97
+ let val = u16::try_convert(value_obj)?;
98
+ Ok(Constraint::Min(val))
99
+ }
100
+ "max" => {
101
+ let val = u16::try_convert(value_obj)?;
102
+ Ok(Constraint::Max(val))
103
+ }
104
+ "fill" => {
105
+ let val = u16::try_convert(value_obj)?;
106
+ Ok(Constraint::Fill(val))
107
+ }
108
+ "ratio" => {
109
+ if let Some(arr) = magnus::RArray::from_value(value_obj) {
110
+ if arr.len() == 2 {
111
+ let n = u32::try_convert(arr.entry(0)?)?;
112
+ let d = u32::try_convert(arr.entry(1)?)?;
113
+ return Ok(Constraint::Ratio(n, d));
114
+ }
115
+ }
116
+ // Fallback or error for invalid ratio?
117
+ // For now, let's treat it as Min(0) or similar, or error.
118
+ // But to match previous behavior (which ignored invalid), we just return 0 length or something?
119
+ // Check previous logic: it ignored it (`_ => {}`).
120
+ // But we need to return a Constraint. Use Length(0) as safe fallback if unmatched?
121
+ // Actually, let's error if strictly required, but existing logic pushed nothing if mismatched.
122
+ // If we push nothing, we can't return a Constraint.
123
+ // Let's assume input is valid for now or return a default.
124
+ Ok(Constraint::Length(0))
125
+ }
126
+ _ => Ok(Constraint::Length(0)), // Default fallback
127
+ }
128
+ }
129
+
130
+ /// Splits an area into multiple rectangles based on constraints.
131
+ /// This is a pure calculation helper for hit testing.
132
+ ///
133
+ /// # Arguments
134
+ /// * `area` - A Ruby Hash or Rect with :x, :y, :width, :height keys
135
+ /// * `direction` - Symbol :vertical or :horizontal
136
+ /// * `constraints` - Array of Constraint objects
137
+ /// * `flex` - Symbol for flex mode
138
+ ///
139
+ /// # Returns
140
+ /// An array of Ruby Hashes representing Rect objects
141
+ pub fn split_layout(
142
+ area: Value,
143
+ direction: Symbol,
144
+ constraints: magnus::RArray,
145
+ flex: Symbol,
146
+ ) -> Result<magnus::RArray, Error> {
147
+ let ruby = magnus::Ruby::get().unwrap();
148
+
149
+ // Parse area from Hash or Rect-like object
150
+ let x: u16 = area.funcall("x", ())?;
151
+ let y: u16 = area.funcall("y", ())?;
152
+ let width: u16 = area.funcall("width", ())?;
153
+ let height: u16 = area.funcall("height", ())?;
154
+ let rect = Rect::new(x, y, width, height);
155
+
156
+ // Parse direction
157
+ let dir = if direction.to_string() == "vertical" {
158
+ Direction::Vertical
159
+ } else {
160
+ Direction::Horizontal
161
+ };
162
+
163
+ // Parse flex
164
+ let flex_mode = match flex.to_string().as_str() {
165
+ "start" => Flex::Start,
166
+ "center" => Flex::Center,
167
+ "end" => Flex::End,
168
+ "space_between" => Flex::SpaceBetween,
169
+ "space_around" => Flex::SpaceAround,
170
+ "space_evenly" => Flex::SpaceEvenly,
171
+ _ => Flex::Legacy,
172
+ };
173
+
174
+ // Parse constraints
175
+ let mut ratatui_constraints = Vec::new();
176
+ for i in 0..constraints.len() {
177
+ let index = isize::try_from(i)
178
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
179
+ let constraint_obj: Value = constraints.entry(index)?;
180
+ if let Ok(constraint) = parse_constraint(constraint_obj) {
181
+ ratatui_constraints.push(constraint);
182
+ }
183
+ }
184
+
185
+ // Compute layout
186
+ let chunks = Layout::default()
187
+ .direction(dir)
188
+ .flex(flex_mode)
189
+ .constraints(ratatui_constraints)
190
+ .split(rect);
191
+
192
+ // Convert to Ruby array of Hashes
193
+ let result = ruby.ary_new_capa(chunks.len());
194
+ for chunk in chunks.iter() {
195
+ let hash = ruby.hash_new();
196
+ hash.aset(ruby.sym_new("x"), chunk.x)?;
197
+ hash.aset(ruby.sym_new("y"), chunk.y)?;
198
+ hash.aset(ruby.sym_new("width"), chunk.width)?;
199
+ hash.aset(ruby.sym_new("height"), chunk.height)?;
200
+ result.push(hash)?;
201
+ }
202
+
203
+ Ok(result)
204
+ }
205
+
206
+ /// Splits an area into multiple rectangles, returning both segments and spacers.
207
+ /// This is the Ratatui `split_with_spacers` equivalent.
208
+ ///
209
+ /// # Arguments
210
+ /// * `area` - A Ruby Hash or Rect with :x, :y, :width, :height keys
211
+ /// * `direction` - Symbol :vertical or :horizontal
212
+ /// * `constraints` - Array of Constraint objects
213
+ /// * `flex` - Symbol for flex mode
214
+ ///
215
+ /// # Returns
216
+ /// An array containing two arrays: [segments, spacers], each containing Ruby Hashes representing Rect objects
217
+ pub fn split_with_spacers_layout(
218
+ area: Value,
219
+ direction: Symbol,
220
+ constraints: magnus::RArray,
221
+ flex: Symbol,
222
+ ) -> Result<magnus::RArray, Error> {
223
+ let ruby = magnus::Ruby::get().unwrap();
224
+
225
+ // Parse area from Hash or Rect-like object
226
+ let x: u16 = area.funcall("x", ())?;
227
+ let y: u16 = area.funcall("y", ())?;
228
+ let width: u16 = area.funcall("width", ())?;
229
+ let height: u16 = area.funcall("height", ())?;
230
+ let rect = Rect::new(x, y, width, height);
231
+
232
+ // Parse direction
233
+ let dir = if direction.to_string() == "vertical" {
234
+ Direction::Vertical
235
+ } else {
236
+ Direction::Horizontal
237
+ };
238
+
239
+ // Parse flex
240
+ let flex_mode = match flex.to_string().as_str() {
241
+ "start" => Flex::Start,
242
+ "center" => Flex::Center,
243
+ "end" => Flex::End,
244
+ "space_between" => Flex::SpaceBetween,
245
+ "space_around" => Flex::SpaceAround,
246
+ "space_evenly" => Flex::SpaceEvenly,
247
+ _ => Flex::Legacy,
248
+ };
249
+
250
+ // Parse constraints
251
+ let mut ratatui_constraints = Vec::new();
252
+ for i in 0..constraints.len() {
253
+ let index = isize::try_from(i)
254
+ .map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
255
+ let constraint_obj: Value = constraints.entry(index)?;
256
+ if let Ok(constraint) = parse_constraint(constraint_obj) {
257
+ ratatui_constraints.push(constraint);
258
+ }
259
+ }
260
+
261
+ // Compute layout with spacers
262
+ let (segments, spacers) = Layout::default()
263
+ .direction(dir)
264
+ .flex(flex_mode)
265
+ .constraints(ratatui_constraints)
266
+ .split_with_spacers(rect);
267
+
268
+ // Helper to convert Rc<[Rect]> to Ruby array
269
+ let rects_to_ruby_array = |rects: &[Rect]| -> Result<magnus::RArray, Error> {
270
+ let arr = ruby.ary_new_capa(rects.len());
271
+ for chunk in rects {
272
+ let hash = ruby.hash_new();
273
+ hash.aset(ruby.sym_new("x"), chunk.x)?;
274
+ hash.aset(ruby.sym_new("y"), chunk.y)?;
275
+ hash.aset(ruby.sym_new("width"), chunk.width)?;
276
+ hash.aset(ruby.sym_new("height"), chunk.height)?;
277
+ arr.push(hash)?;
278
+ }
279
+ Ok(arr)
280
+ };
281
+
282
+ let segments_arr = rects_to_ruby_array(&segments)?;
283
+ let spacers_arr = rects_to_ruby_array(&spacers)?;
284
+
285
+ // Return [segments, spacers]
286
+ let result = ruby.ary_new_capa(2);
287
+ result.push(segments_arr)?;
288
+ result.push(spacers_arr)?;
289
+
290
+ Ok(result)
291
+ }
292
+ #[cfg(test)]
293
+ mod tests {
294
+ use ratatui::layout::{Constraint, Direction, Flex, Layout, Rect};
295
+
296
+ #[test]
297
+ fn test_layout_logic() {
298
+ let area = Rect::new(0, 0, 100, 100);
299
+ let chunks = Layout::default()
300
+ .direction(Direction::Vertical)
301
+ .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
302
+ .split(area);
303
+ assert_eq!(chunks.len(), 2);
304
+ assert_eq!(chunks[0].height, 50);
305
+ }
306
+
307
+ #[test]
308
+ fn test_fill_constraint() {
309
+ let area = Rect::new(0, 0, 100, 10);
310
+ let chunks = Layout::default()
311
+ .direction(Direction::Horizontal)
312
+ .constraints([Constraint::Fill(1), Constraint::Fill(3)])
313
+ .split(area);
314
+ assert_eq!(chunks.len(), 2);
315
+ assert_eq!(chunks[0].width, 25);
316
+ assert_eq!(chunks[1].width, 75);
317
+ }
318
+
319
+ #[test]
320
+ fn test_flex_space_between() {
321
+ let area = Rect::new(0, 0, 100, 10);
322
+ let chunks = Layout::default()
323
+ .direction(Direction::Horizontal)
324
+ .flex(Flex::SpaceBetween)
325
+ .constraints([
326
+ Constraint::Length(10),
327
+ Constraint::Length(10),
328
+ Constraint::Length(10),
329
+ ])
330
+ .split(area);
331
+ assert_eq!(chunks.len(), 3);
332
+ assert_eq!(chunks[0].x, 0);
333
+ assert_eq!(chunks[1].x, 45);
334
+ assert_eq!(chunks[2].x, 90);
335
+ }
336
+
337
+ #[test]
338
+ fn test_flex_space_evenly() {
339
+ let area = Rect::new(0, 0, 100, 10);
340
+ let chunks = Layout::default()
341
+ .direction(Direction::Horizontal)
342
+ .flex(Flex::SpaceEvenly)
343
+ .constraints([
344
+ Constraint::Length(10),
345
+ Constraint::Length(10),
346
+ Constraint::Length(10),
347
+ ])
348
+ .split(area);
349
+ assert_eq!(chunks.len(), 3);
350
+ assert_eq!(chunks[0].x, 18);
351
+ assert_eq!(chunks[1].x, 45);
352
+ assert_eq!(chunks[2].x, 73);
353
+ }
354
+
355
+ #[test]
356
+ fn test_flex_center() {
357
+ let area = Rect::new(0, 0, 100, 10);
358
+ let chunks = Layout::default()
359
+ .direction(Direction::Horizontal)
360
+ .flex(Flex::Center)
361
+ .constraints([Constraint::Length(20)])
362
+ .split(area);
363
+ assert_eq!(chunks.len(), 1);
364
+ assert_eq!(chunks[0].x, 40);
365
+ assert_eq!(chunks[0].width, 20);
366
+ }
367
+
368
+ #[test]
369
+ fn test_max_constraint() {
370
+ let area = Rect::new(0, 0, 100, 10);
371
+ let chunks = Layout::default()
372
+ .direction(Direction::Horizontal)
373
+ .constraints([Constraint::Max(30), Constraint::Fill(1)])
374
+ .split(area);
375
+ assert_eq!(chunks.len(), 2);
376
+ assert_eq!(chunks[0].width, 30);
377
+ assert_eq!(chunks[1].width, 70);
378
+ }
379
+ }
@@ -0,0 +1,100 @@
1
+ // SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+
4
+ use crate::style::{parse_block, parse_style};
5
+ use crate::text::parse_span;
6
+ use bumpalo::Bump;
7
+ use magnus::{prelude::*, Error, Value};
8
+ use ratatui::buffer::Buffer;
9
+ use ratatui::{layout::Rect, widgets::LineGauge, widgets::Widget};
10
+
11
+ pub fn render(buffer: &mut Buffer, area: Rect, node: Value) -> Result<(), Error> {
12
+ let bump = Bump::new();
13
+ let ratio: f64 = node.funcall("ratio", ())?;
14
+ let label_val: Value = node.funcall("label", ())?;
15
+ let style_val: Value = node.funcall("style", ())?;
16
+ let filled_style_val: Value = node.funcall("filled_style", ())?;
17
+ let unfilled_style_val: Value = node.funcall("unfilled_style", ())?;
18
+ let block_val: Value = node.funcall("block", ())?;
19
+ let filled_symbol_val: String = node.funcall("filled_symbol", ())?;
20
+ let unfilled_symbol_val: String = node.funcall("unfilled_symbol", ())?;
21
+
22
+ let mut gauge = LineGauge::default()
23
+ .ratio(ratio)
24
+ .filled_symbol(&filled_symbol_val)
25
+ .unfilled_symbol(&unfilled_symbol_val);
26
+
27
+ if !label_val.is_nil() {
28
+ // Try to parse as a Span (rich text)
29
+ if let Ok(span) = parse_span(label_val) {
30
+ gauge = gauge.label(span);
31
+ } else {
32
+ // Fallback to string
33
+ let label_str: String = label_val.funcall("to_s", ())?;
34
+ gauge = gauge.label(label_str);
35
+ }
36
+ }
37
+
38
+ if !style_val.is_nil() {
39
+ gauge = gauge.style(parse_style(style_val)?);
40
+ }
41
+
42
+ if !filled_style_val.is_nil() {
43
+ let parsed_style = parse_style(filled_style_val)?;
44
+ gauge = gauge.filled_style(parsed_style);
45
+ }
46
+
47
+ if !unfilled_style_val.is_nil() {
48
+ let parsed_style = parse_style(unfilled_style_val)?;
49
+ gauge = gauge.unfilled_style(parsed_style);
50
+ }
51
+
52
+ if !block_val.is_nil() {
53
+ gauge = gauge.block(parse_block(block_val, &bump)?);
54
+ }
55
+
56
+ gauge.render(area, buffer);
57
+ Ok(())
58
+ }
59
+
60
+ #[cfg(test)]
61
+ mod tests {
62
+ use super::*;
63
+ use ratatui::buffer::Buffer;
64
+ use ratatui::widgets::{LineGauge, Widget};
65
+
66
+ #[test]
67
+ fn test_line_gauge_rendering() {
68
+ let gauge = LineGauge::default().ratio(0.5).label("50%");
69
+ let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
70
+ gauge.render(Rect::new(0, 0, 20, 1), &mut buf);
71
+ // LineGauge renders filled and unfilled characters
72
+ assert!(buf.content().iter().any(|c| c.symbol() != " "));
73
+ // Should contain label
74
+ assert!(buf.content().iter().any(|c| c.symbol() == "5"));
75
+ assert!(buf.content().iter().any(|c| c.symbol() == "%"));
76
+ }
77
+
78
+ #[test]
79
+ fn test_line_gauge_zero_ratio() {
80
+ let gauge = LineGauge::default().ratio(0.0);
81
+ let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
82
+ gauge.render(Rect::new(0, 0, 10, 1), &mut buf);
83
+ // At zero ratio, should mostly show unfilled character
84
+ let unfilled_count = buf
85
+ .content()
86
+ .iter()
87
+ .filter(|c| c.symbol() == "░" || c.symbol() == " ")
88
+ .count();
89
+ assert!(unfilled_count > 0);
90
+ }
91
+
92
+ #[test]
93
+ fn test_line_gauge_full_ratio() {
94
+ let gauge = LineGauge::default().ratio(1.0).label("100%");
95
+ let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
96
+ gauge.render(Rect::new(0, 0, 20, 1), &mut buf);
97
+ // At full ratio, should show some non-space characters (filled or partial)
98
+ assert!(buf.content().iter().any(|c| c.symbol() != " "));
99
+ }
100
+ }