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,62 @@
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
+ # Generic dimensions as width and height.
11
+ #
12
+ # Layout calculations need sizes. Passing width and height
13
+ # as separate arguments is verbose and easy to swap by mistake.
14
+ #
15
+ # This class bundles dimensions into a single immutable object.
16
+ # Extract it from a Rect or create it directly for sizing operations.
17
+ #
18
+ # Following upstream Ratatui's design, the same Size type represents
19
+ # both character-grid dimensions (columns × rows) and pixel dimensions.
20
+ # The semantic meaning depends on the source:
21
+ #
22
+ # - From <tt>Terminal.size</tt> or <tt>WindowSize#columns_rows</tt>: columns and rows
23
+ # - From <tt>WindowSize#pixels</tt>: pixel width and height
24
+ #
25
+ # Use it for terminal dimensions, widget sizing constraints,
26
+ # or anywhere you need width/height without position.
27
+ #
28
+ # === Example
29
+ #
30
+ #--
31
+ # SPDX-SnippetBegin
32
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
33
+ # SPDX-License-Identifier: MIT-0
34
+ #++
35
+ # size = Layout::Size.new(width: 80, height: 24)
36
+ # puts "Terminal is #{size.width} columns by #{size.height} rows"
37
+ #
38
+ # # Extract from a Rect
39
+ # rect = Layout::Rect.new(x: 10, y: 5, width: 80, height: 24)
40
+ # size = rect.as_size # => Size(width: 80, height: 24)
41
+ #--
42
+ # SPDX-SnippetEnd
43
+ #++
44
+ class Size < Data.define(:width, :height)
45
+ ##
46
+ # :attr_reader: width
47
+ # Width dimension (columns for character grids, pixels for pixel sizes).
48
+
49
+ ##
50
+ # :attr_reader: height
51
+ # Height dimension (rows for character grids, pixels for pixel sizes).
52
+
53
+ # Creates a new Size.
54
+ #
55
+ # [width] Width in columns (Integer, coerced via +Integer()+).
56
+ # [height] Height in rows (Integer, coerced via +Integer()+).
57
+ def initialize(width: 0, height: 0)
58
+ super(width: Integer(width), height: Integer(height))
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,29 @@
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
+ # Layout primitives for geometry and space distribution.
10
+ #
11
+ # This module mirrors +ratatui::layout+ and contains:
12
+ # - {Rect} — Rectangle geometry
13
+ # - {Position} — Terminal coordinates
14
+ # - {Size} — Terminal dimensions
15
+ # - {Constraint} — Sizing rules
16
+ # - {Layout} — Space distribution
17
+ # - {HorizontalAlignment} — Horizontal alignment constants
18
+ # - {VerticalAlignment} — Vertical alignment constants
19
+ # - {Alignment} — Alias for HorizontalAlignment
20
+ module Layout
21
+ end
22
+ end
23
+
24
+ require_relative "layout/alignment"
25
+ require_relative "layout/rect"
26
+ require_relative "layout/position"
27
+ require_relative "layout/size"
28
+ require_relative "layout/constraint"
29
+ require_relative "layout/layout"
@@ -0,0 +1,201 @@
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
+ # Mutable state object for List widgets.
10
+ #
11
+ # When using {Frame#render_stateful_widget}, the State object is the
12
+ # *single source of truth* for selection and scroll offset. Widget
13
+ # properties (+selected_index+, +offset+) are *ignored* in stateful mode.
14
+ #
15
+ # State objects persist across frames, allowing you to:
16
+ # - Track selection without manual index management
17
+ # - Read back the scroll offset calculated by Ratatui
18
+ # - Implement mouse click-to-row hit testing
19
+ #
20
+ # == Thread/Ractor Safety
21
+ #
22
+ # ListState is *not* Ractor-shareable. It contains mutable internal state.
23
+ # Store it in instance variables, not in immutable Models.
24
+ #
25
+ # == Example
26
+ #
27
+ #--
28
+ # SPDX-SnippetBegin
29
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
30
+ # SPDX-License-Identifier: MIT-0
31
+ #++
32
+ # @list_state = RatatuiRuby::ListState.new
33
+ # @list_state.select(2) # Select third item
34
+ #
35
+ # RatatuiRuby.draw do |frame|
36
+ # list = RatatuiRuby::Widgets::List.new(items: ["A", "B", "C", "D", "E"])
37
+ # frame.render_stateful_widget(list, frame.area, @list_state)
38
+ # end
39
+ #
40
+ # puts @list_state.offset # Scroll position after render
41
+ #
42
+ #--
43
+ # SPDX-SnippetEnd
44
+ #++
45
+ class ListState
46
+ ##
47
+ # :method: new
48
+ # :call-seq: new(selected = nil) -> ListState
49
+ #
50
+ # Creates a new ListState with optional initial selection.
51
+ #
52
+ # (Native method implemented in Rust)
53
+
54
+ ##
55
+ # :method: select
56
+ # :call-seq: select(index) -> nil
57
+ #
58
+ # Sets the selected index. Pass +nil+ to deselect.
59
+ #
60
+ # (Native method implemented in Rust)
61
+
62
+ ##
63
+ # :method: selected
64
+ # :call-seq: selected() -> Integer or nil
65
+ #
66
+ # Returns the currently selected index, or +nil+ if nothing is selected.
67
+ #
68
+ # (Native method implemented in Rust)
69
+
70
+ ##
71
+ # :method: offset
72
+ # :call-seq: offset() -> Integer
73
+ #
74
+ # Returns the current scroll offset.
75
+ #
76
+ # This is the critical read-back method. After +render_stateful_widget+,
77
+ # this returns the scroll position calculated by Ratatui to keep the
78
+ # selection visible.
79
+ #
80
+ # (Native method implemented in Rust)
81
+
82
+ ##
83
+ # :method: scroll_down_by
84
+ # :call-seq: scroll_down_by(n) -> nil
85
+ #
86
+ # Scrolls down by +n+ items.
87
+ #
88
+ # (Native method implemented in Rust)
89
+
90
+ ##
91
+ # :method: scroll_up_by
92
+ # :call-seq: scroll_up_by(n) -> nil
93
+ #
94
+ # Scrolls up by +n+ items.
95
+ #
96
+ # (Native method implemented in Rust)
97
+
98
+ ##
99
+ # :method: select_next
100
+ # :call-seq: select_next() -> nil
101
+ #
102
+ # Moves selection to the next item. Selects first item if nothing selected.
103
+ #
104
+ # === Optimistic Indexing
105
+ #
106
+ # Increments the index immediately, even past list bounds. The renderer
107
+ # clamps to valid range on draw. Reading <tt>selected</tt> between this
108
+ # call and render may return an out-of-bounds value.
109
+ #
110
+ # Matches upstream Ratatui behavior. See
111
+ # {ListState#select_next}[https://docs.rs/ratatui/0.30/ratatui/widgets/struct.ListState.html#method.select_next].
112
+ #
113
+ # To detect actual selection changes, check bounds first:
114
+ #
115
+ #--
116
+ # SPDX-SnippetBegin
117
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
118
+ # SPDX-License-Identifier: MIT-0
119
+ #++
120
+ # max_index = items.size - 1
121
+ # return if (state.selected || 0) >= max_index
122
+ # state.select_next
123
+ #
124
+ #--
125
+ # SPDX-SnippetEnd
126
+ #++
127
+ # (Native method implemented in Rust)
128
+
129
+ ##
130
+ # :method: select_previous
131
+ # :call-seq: select_previous() -> nil
132
+ #
133
+ # Moves selection to the previous item. Selects last item if nothing selected.
134
+ #
135
+ # === Optimistic Indexing
136
+ #
137
+ # At index 0, does nothing. With no selection, sets index to maximum value;
138
+ # the renderer clamps to actual last item on draw.
139
+ #
140
+ # To detect actual selection changes, check bounds first:
141
+ #
142
+ #--
143
+ # SPDX-SnippetBegin
144
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
145
+ # SPDX-License-Identifier: MIT-0
146
+ #++
147
+ # return if (state.selected || 0) <= 0
148
+ # state.select_previous
149
+ #
150
+ #--
151
+ # SPDX-SnippetEnd
152
+ #++
153
+ # (Native method implemented in Rust)
154
+
155
+ ##
156
+ # :method: select_first
157
+ # :call-seq: select_first() -> nil
158
+ #
159
+ # Jumps selection to the first item (index 0).
160
+ #
161
+ # To detect actual selection changes:
162
+ #
163
+ #--
164
+ # SPDX-SnippetBegin
165
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
166
+ # SPDX-License-Identifier: MIT-0
167
+ #++
168
+ # return if (state.selected || 0) == 0
169
+ # state.select_first
170
+ #
171
+ #--
172
+ # SPDX-SnippetEnd
173
+ #++
174
+ # (Native method implemented in Rust)
175
+
176
+ ##
177
+ # :method: select_last
178
+ # :call-seq: select_last() -> nil
179
+ #
180
+ # Jumps selection to the last item.
181
+ #
182
+ # === Optimistic Indexing
183
+ #
184
+ # Sets index to maximum possible value. The renderer clamps to actual last
185
+ # item on draw. To get or check the real last index, track item count:
186
+ #
187
+ #--
188
+ # SPDX-SnippetBegin
189
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
190
+ # SPDX-License-Identifier: MIT-0
191
+ #++
192
+ # max_index = items.size - 1
193
+ # return if (state.selected || 0) == max_index
194
+ # state.select(max_index)
195
+ #
196
+ #--
197
+ # SPDX-SnippetEnd
198
+ #++
199
+ # (Native method implemented in Rust)
200
+ end
201
+ end
@@ -0,0 +1,171 @@
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
+ ##
10
+ # Output protection for TUI sessions and batch/CLI mode.
11
+ #
12
+ # This module provides mechanisms to prevent accidental output to stdout/stderr
13
+ # during TUI sessions and to support headless (batch/CLI) mode for applications
14
+ # that can run with or without a TUI.
15
+ #
16
+ # @see guard_io
17
+ # @see headless!
18
+ module OutputGuard
19
+ # A null IO object that swallows all output.
20
+ #
21
+ # Used by {guard_io} to temporarily replace $stdout and $stderr.
22
+ # Implements method_missing to accept any IO method and discard output.
23
+ #
24
+ # Returns self for method chaining (e.g., puts.flush).
25
+ class NullIO
26
+ # Accepts any method call and returns self, discarding all output.
27
+ def method_missing(name, *args, &block)
28
+ self
29
+ end
30
+
31
+ # Reports that all methods are supported.
32
+ def respond_to_missing?(name, include_private = false)
33
+ true
34
+ end
35
+ end
36
+
37
+ ##
38
+ # Whether headless (batch/pipeline/CLI) mode is enabled.
39
+ #
40
+ # When headless mode is active:
41
+ # - {guard_io} becomes a silent no-op (output is not swallowed)
42
+ # - {init_terminal} and {run} raise {Error::Invariant}
43
+ #
44
+ # Use this when your app has a `--no-tui` or `--batch` flag and you want
45
+ # the same code to work in both TUI and non-TUI modes.
46
+ #
47
+ # @see headless!
48
+ def is_headless?
49
+ @headless_mode
50
+ end
51
+
52
+ ##
53
+ # Enables headless (batch/CLI) mode.
54
+ #
55
+ # Call this at app startup when running in batch/CLI mode (e.g., `--no-tui`).
56
+ # This tells RatatuiRuby that you intentionally don't want a TUI session.
57
+ #
58
+ # When headless mode is active:
59
+ # - {guard_io} becomes a silent no-op (output flows normally)
60
+ # - {init_terminal} and {run} raise {Error::Invariant}
61
+ #
62
+ # Headless mode and TUI sessions are mutually exclusive. Calling this
63
+ # while a TUI session is active raises {Error::Invariant}.
64
+ #
65
+ # === Why there is no exit_headless!
66
+ #
67
+ # Headless mode is a startup-time decision for the entire app run.
68
+ # If you need to temporarily exit TUI mode for user interaction
69
+ # (like lazygit does when editing a commit message), use
70
+ # {restore_terminal} and {init_terminal} instead:
71
+ #
72
+ #--
73
+ # SPDX-SnippetBegin
74
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
75
+ # SPDX-License-Identifier: MIT-0
76
+ #++
77
+ # RatatuiRuby.restore_terminal
78
+ # puts "Press enter to continue..."
79
+ # gets
80
+ # RatatuiRuby.init_terminal
81
+ #
82
+ #--
83
+ # SPDX-SnippetEnd
84
+ #++
85
+ # === Example
86
+ #
87
+ #--
88
+ # SPDX-SnippetBegin
89
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
90
+ # SPDX-License-Identifier: MIT-0
91
+ #++
92
+ # if ARGV.include?("--no-tui")
93
+ # RatatuiRuby.headless!
94
+ # process_batch_work # guard_io calls are silent no-ops
95
+ # else
96
+ # RatatuiRuby.run do |tui| # This branch only runs in TUI mode
97
+ # # ... TUI code ...
98
+ # end
99
+ # end
100
+ #
101
+ #--
102
+ # SPDX-SnippetEnd
103
+ #++
104
+ # Note: Calling {run} or {init_terminal} after {headless!} raises
105
+ # {Error::Invariant}. The block is never executed.
106
+ #
107
+ # @raise [Error::Invariant] if a TUI session is already active
108
+ # @see is_headless?
109
+ # @see restore_terminal
110
+ def headless!
111
+ if @tui_session_active
112
+ raise Error::Invariant, "Cannot enable headless mode: TUI session already active"
113
+ end
114
+ @headless_mode = true
115
+ end
116
+
117
+ ##
118
+ # Guards a block from stdout/stderr output.
119
+ #
120
+ # During a TUI session, writes to $stdout or $stderr corrupt the display.
121
+ # Wrap code that might produce output (e.g., chatty gems) in this block.
122
+ #
123
+ # This temporarily replaces $stdout and $stderr with a {NullIO} object
124
+ # that discards all output. The original streams are restored when the
125
+ # block exits, even if an exception occurs.
126
+ #
127
+ # === Behavior by mode
128
+ #
129
+ # - **TUI session active**: Output is swallowed (guarded)
130
+ # - **Headless mode**: Silent no-op (output flows normally)
131
+ # - **Neither**: Warns and yields (catches potential mistakes)
132
+ #
133
+ # === Example
134
+ #
135
+ #--
136
+ # SPDX-SnippetBegin
137
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
138
+ # SPDX-License-Identifier: MIT-0
139
+ #++
140
+ # RatatuiRuby.run do |tui|
141
+ # RatatuiRuby.guard_io do
142
+ # SomeChattyGem.do_something # Any puts/warn calls are swallowed
143
+ # end
144
+ # end
145
+ #
146
+ #--
147
+ # SPDX-SnippetEnd
148
+ #++
149
+ # @see headless!
150
+ def guard_io
151
+ # TUI active: guard the output
152
+ if terminal_active?
153
+ $stdout = NullIO.new
154
+ $stderr = NullIO.new
155
+ begin
156
+ return yield
157
+ ensure
158
+ $stdout = Object::STDOUT
159
+ $stderr = Object::STDERR
160
+ end
161
+ end
162
+
163
+ # Headless mode: silent no-op
164
+ return yield if is_headless?
165
+
166
+ # Neither: warn about potential mistake
167
+ warn "guard_io called outside TUI session. If this is intentional (batch/CLI mode), call RatatuiRuby.headless! at startup to silence this warning."
168
+ yield
169
+ end
170
+ end
171
+ end
Binary file
@@ -0,0 +1,122 @@
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
+ # Mutable state object for Scrollbar widgets.
10
+ #
11
+ # When using {Frame#render_stateful_widget}, the State object is the
12
+ # *single source of truth* for position and content length. Widget
13
+ # properties (+position+, +content_length+) are *ignored* in stateful mode.
14
+ #
15
+ # == Example
16
+ #
17
+ #--
18
+ # SPDX-SnippetBegin
19
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
20
+ # SPDX-License-Identifier: MIT-0
21
+ #++
22
+ # @scrollbar_state = RatatuiRuby::ScrollbarState.new(100)
23
+ # @scrollbar_state.position = 25
24
+ #
25
+ # RatatuiRuby.draw do |frame|
26
+ # scrollbar = RatatuiRuby::Scrollbar.new(orientation: :vertical_right)
27
+ # frame.render_stateful_widget(scrollbar, frame.area, @scrollbar_state)
28
+ # end
29
+ #
30
+ #--
31
+ # SPDX-SnippetEnd
32
+ #++
33
+ class ScrollbarState
34
+ ##
35
+ # :method: new
36
+ # :call-seq: new(content_length) -> ScrollbarState
37
+ #
38
+ # Creates a new ScrollbarState with the given content length.
39
+ #
40
+ # (Native method implemented in Rust)
41
+
42
+ ##
43
+ # :method: position
44
+ # :call-seq: position() -> Integer
45
+ #
46
+ # Returns the current scroll position.
47
+ #
48
+ # (Native method implemented in Rust)
49
+
50
+ ##
51
+ # :method: position=
52
+ # :call-seq: position=(value) -> Integer
53
+ #
54
+ # Sets the current scroll position.
55
+ #
56
+ # (Native method implemented in Rust)
57
+
58
+ ##
59
+ # :method: content_length
60
+ # :call-seq: content_length() -> Integer
61
+ #
62
+ # Returns the total content length.
63
+ #
64
+ # (Native method implemented in Rust)
65
+
66
+ ##
67
+ # :method: content_length=
68
+ # :call-seq: content_length=(value) -> Integer
69
+ #
70
+ # Sets the total content length.
71
+ #
72
+ # (Native method implemented in Rust)
73
+
74
+ ##
75
+ # :method: viewport_content_length
76
+ # :call-seq: viewport_content_length() -> Integer
77
+ #
78
+ # Returns the viewport content length.
79
+ #
80
+ # (Native method implemented in Rust)
81
+
82
+ ##
83
+ # :method: viewport_content_length=
84
+ # :call-seq: viewport_content_length=(value) -> Integer
85
+ #
86
+ # Sets the viewport content length.
87
+ #
88
+ # (Native method implemented in Rust)
89
+
90
+ ##
91
+ # :method: first
92
+ # :call-seq: first() -> nil
93
+ #
94
+ # Scrolls to the first position.
95
+ #
96
+ # (Native method implemented in Rust)
97
+
98
+ ##
99
+ # :method: last
100
+ # :call-seq: last() -> nil
101
+ #
102
+ # Scrolls to the last position.
103
+ #
104
+ # (Native method implemented in Rust)
105
+
106
+ ##
107
+ # :method: next
108
+ # :call-seq: next() -> nil
109
+ #
110
+ # Scrolls to the next position.
111
+ #
112
+ # (Native method implemented in Rust)
113
+
114
+ ##
115
+ # :method: prev
116
+ # :call-seq: prev() -> nil
117
+ #
118
+ # Scrolls to the previous position.
119
+ #
120
+ # (Native method implemented in Rust)
121
+ end
122
+ end