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,158 @@
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 Text
10
+ # A styled string fragment.
11
+ #
12
+ # Text is rarely uniform. You need to bold a keyword, colorize an error, or dim a timestamp.
13
+ #
14
+ # This class attaches style to content. It pairs a string with visual attributes.
15
+ #
16
+ # combine spans into a {Line} to create rich text.
17
+ #
18
+ # === Examples
19
+ #
20
+ # Text::Span.new(content: "Error", style: Style.new(fg: :red, modifiers: [:bold]))
21
+ class Span < Data.define(:content, :style)
22
+ ##
23
+ # :attr_reader: content
24
+ # The text content.
25
+
26
+ ##
27
+ # :attr_reader: style
28
+ # The style to apply.
29
+
30
+ # Creates a new Span.
31
+ #
32
+ # [content] String.
33
+ # [style] Style object (optional).
34
+ def initialize(content:, style: nil)
35
+ super
36
+ end
37
+
38
+ # Concise helper for styling.
39
+ #
40
+ # === Example
41
+ #
42
+ # Text::Span.styled("Bold", Style::Style.new(modifiers: [:bold]))
43
+ def self.styled(content, style = nil)
44
+ new(content:, style:)
45
+ end
46
+
47
+ # Returns the unicode display width of the content in terminal cells.
48
+ #
49
+ # CJK characters and emoji count as 2 cells. ASCII characters count as 1 cell.
50
+ # Use this to measure how much horizontal space a span will occupy.
51
+ #
52
+ # === Example
53
+ #
54
+ #--
55
+ # SPDX-SnippetBegin
56
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
57
+ # SPDX-License-Identifier: MIT-0
58
+ #++
59
+ # span = Text::Span.new(content: "Hello")
60
+ # span.width # => 5
61
+ #
62
+ # span = Text::Span.new(content: "你好") # Chinese characters
63
+ # span.width # => 4
64
+ #
65
+ #--
66
+ # SPDX-SnippetEnd
67
+ #++
68
+ # Returns: Integer.
69
+ def width
70
+ RatatuiRuby::Text.width(content.to_s)
71
+ end
72
+
73
+ # Creates a span with the default style.
74
+ #
75
+ # Use this factory method when you want unstyled text. It mirrors the Ratatui API.
76
+ #
77
+ # === Example
78
+ #
79
+ #--
80
+ # SPDX-SnippetBegin
81
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
82
+ # SPDX-License-Identifier: MIT-0
83
+ #++
84
+ # span = Text::Span.raw("test content")
85
+ # span.content # => "test content"
86
+ # span.style # => nil
87
+ #
88
+ #--
89
+ # SPDX-SnippetEnd
90
+ #++
91
+ # [content] String.
92
+ #
93
+ # Returns: Span.
94
+ def self.raw(content)
95
+ new(content:)
96
+ end
97
+
98
+ # Patches the style of the span, merging modifiers from the given style.
99
+ #
100
+ # Non-nil values from the patch style override the existing style. Use this when you want to
101
+ # layer styles without replacing the entire style. Colors in the patch take precedence over
102
+ # existing colors. Modifiers are combined.
103
+ #
104
+ # === Example
105
+ #
106
+ #--
107
+ # SPDX-SnippetBegin
108
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
109
+ # SPDX-License-Identifier: MIT-0
110
+ #++
111
+ # span = Text::Span.new(content: "test", style: Style::Style.new(fg: :green))
112
+ # patched = span.patch_style(Style::Style.new(bg: :yellow, modifiers: [:bold]))
113
+ # patched.style.fg # => :green (preserved)
114
+ # patched.style.bg # => :yellow (added)
115
+ #
116
+ #--
117
+ # SPDX-SnippetEnd
118
+ #++
119
+ # [patch] Style::Style to merge.
120
+ #
121
+ # Returns: Span.
122
+ def patch_style(patch)
123
+ return self if patch.nil?
124
+ return with(style: patch) if style.nil?
125
+
126
+ merged = Style::Style.new(
127
+ fg: patch.fg.nil? ? style.fg : patch.fg,
128
+ bg: patch.bg.nil? ? style.bg : patch.bg,
129
+ modifiers: patch.modifiers.empty? ? style.modifiers : (style.modifiers + patch.modifiers).uniq
130
+ )
131
+ with(style: merged)
132
+ end
133
+
134
+ # Resets the style of the span.
135
+ #
136
+ # Returns a new span with no style applied. Use this to strip all styling.
137
+ #
138
+ # === Example
139
+ #
140
+ #--
141
+ # SPDX-SnippetBegin
142
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
143
+ # SPDX-License-Identifier: MIT-0
144
+ #++
145
+ # span = Text::Span.new(content: "styled", style: Style::Style.new(fg: :red))
146
+ # reset = span.reset_style
147
+ # reset.style # => nil
148
+ #
149
+ #--
150
+ # SPDX-SnippetEnd
151
+ #++
152
+ # Returns: Span.
153
+ def reset_style
154
+ with(style: nil)
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,99 @@
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
+ # Namespace for rich text components (Span, Line) and text utilities.
10
+ # Distinct from canvas shapes and other Line usages.
11
+ #
12
+ # == Text Measurement
13
+ #
14
+ # The Text module provides a utility method for calculating the display width
15
+ # of strings in terminal cells. This accounts for unicode complexity:
16
+ #
17
+ # - ASCII characters: 1 cell each
18
+ # - CJK (Chinese, Japanese, Korean) characters: 2 cells each (full-width)
19
+ # - Emoji: typically 2 cells each (varies by terminal)
20
+ # - Combining marks: 0 cells (zero-width)
21
+ #
22
+ # This is essential for layout calculations in TUI applications, where you need to know
23
+ # how much space a string will occupy on the screen, not just its byte or character length.
24
+ #
25
+ # === Use Cases
26
+ #
27
+ # - Auto-sizing widgets (Button, Badge) that fit their content
28
+ # - Calculating padding or centering for text alignment
29
+ # - Building responsive layouts that adapt to content width
30
+ # - Measuring text for scrolling or truncation logic
31
+ #
32
+ # === Examples
33
+ #
34
+ #--
35
+ # SPDX-SnippetBegin
36
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
37
+ # SPDX-License-Identifier: MIT-0
38
+ #++
39
+ # # Simple ASCII text
40
+ # RatatuiRuby::Text.width("Hello") # => 5
41
+ #
42
+ # # With emoji
43
+ # RatatuiRuby::Text.width("Hello 👍") # => 8 (5 + space + 2-width emoji)
44
+ #
45
+ # # With CJK characters
46
+ # RatatuiRuby::Text.width("你好") # => 4 (each CJK char is 2 cells)
47
+ #
48
+ # # Mixed content
49
+ # RatatuiRuby::Text.width("Hi 你好 👍") # => 11 (2 + space + 4 + space + 2)
50
+ #--
51
+ # SPDX-SnippetEnd
52
+ #++
53
+ module Text
54
+ ##
55
+ # :method: width
56
+ # :call-seq: width(string) -> Integer
57
+ #
58
+ # Calculates the display width of a string in terminal cells.
59
+ #
60
+ # Layout demands precision. Terminals measure space in cells, not characters. An ASCII letter occupies one cell. A Chinese character occupies two. An emoji occupies two. Combining marks occupy zero.
61
+ #
62
+ # Measuring width manually is error-prone. You can count <tt>string.length</tt>, but that counts characters, not cells. A string with one emoji counts as 1 character but occupies 2 cells.
63
+ #
64
+ # This method returns the true display width. Use it to auto-size widgets, calculate padding, center text, or build responsive layouts.
65
+ #
66
+ # === Examples
67
+ #
68
+ #--
69
+ # SPDX-SnippetBegin
70
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
71
+ # SPDX-License-Identifier: MIT-0
72
+ #++
73
+ # RatatuiRuby::Text.width("Hello") # => 5 (5 ASCII chars × 1 cell)
74
+ #
75
+ # RatatuiRuby::Text.width("你好") # => 4 (2 CJK chars × 2 cells)
76
+ #
77
+ # RatatuiRuby::Text.width("Hello 👍") # => 8 (5 ASCII + 1 space + 1 emoji × 2)
78
+ #
79
+ # # In the Session DSL (easier)
80
+ # RatatuiRuby.run do |tui|
81
+ # width = tui.text_width("Hello 👍")
82
+ # end
83
+ #
84
+ #--
85
+ # SPDX-SnippetEnd
86
+ #++
87
+ # [string] String to measure (String or object convertible to String)
88
+ # Returns: Integer (number of terminal cells the string occupies)
89
+ # Raises: TypeError if the argument is not a String
90
+ #
91
+ # (Native method implemented in Rust)
92
+ def self.width(string)
93
+ RatatuiRuby._text_width(string)
94
+ end
95
+ end
96
+ end
97
+
98
+ require_relative "text/span"
99
+ require_relative "text/line"
@@ -0,0 +1,22 @@
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
+ class TUI
10
+ # Buffer inspection factory methods for Session.
11
+ #
12
+ # Provides convenient access to Buffer::Cell for testing
13
+ # and buffer inspection purposes.
14
+ module BufferFactories
15
+ # Creates a Buffer::Cell (for testing).
16
+ # @return [Buffer::Cell]
17
+ def cell(...)
18
+ Buffer::Cell.new(...)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,149 @@
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
+ class TUI
10
+ # Canvas shape factory methods for Session.
11
+ #
12
+ # Provides convenient access to Widgets::Shape::* classes
13
+ # for creating custom drawings on Canvas widgets.
14
+ module CanvasFactories
15
+ # Creates a map shape for Canvas.
16
+ # @return [Widgets::Shape::Map]
17
+ def shape_map(...)
18
+ Widgets::Shape::Map.new(...)
19
+ end
20
+
21
+ # Creates a line shape for Canvas.
22
+ # @return [Widgets::Shape::Line]
23
+ def shape_line(...)
24
+ Widgets::Shape::Line.new(...)
25
+ end
26
+
27
+ # Creates a point (single pixel) shape for Canvas.
28
+ # @return [Widgets::Shape::Point]
29
+ def shape_point(...)
30
+ Widgets::Shape::Point.new(...)
31
+ end
32
+
33
+ # Creates a circle shape for Canvas.
34
+ # @return [Widgets::Shape::Circle]
35
+ def shape_circle(...)
36
+ Widgets::Shape::Circle.new(...)
37
+ end
38
+
39
+ # Creates a rectangle shape for Canvas.
40
+ # @return [Widgets::Shape::Rectangle]
41
+ def shape_rectangle(...)
42
+ Widgets::Shape::Rectangle.new(...)
43
+ end
44
+
45
+ # =====================================
46
+ # Terse Shape Aliases (DWIM)
47
+ # =====================================
48
+
49
+ # Creates a circle shape (terse alias).
50
+ # @return [Widgets::Shape::Circle]
51
+ alias circle shape_circle
52
+
53
+ # Creates a point shape (terse alias).
54
+ # @return [Widgets::Shape::Point]
55
+ alias point shape_point
56
+
57
+ # NOTE: No terse 'rectangle' alias - would conflict with Layout::Rect concept.
58
+ # Use shape_rectangle() explicitly.
59
+
60
+ # Creates a map shape (terse alias).
61
+ # @return [Widgets::Shape::Map]
62
+ alias map shape_map
63
+
64
+ # Creates a label shape (terse alias).
65
+ #
66
+ # Note: shape_label is defined in WidgetFactories.
67
+ # @return [Widgets::Shape::Label]
68
+ def label(first = nil, **kwargs)
69
+ Widgets::Shape::Label.coerce_args(first, kwargs)
70
+ end
71
+
72
+ # =====================================
73
+ # Bidirectional Shape Aliases (*_shape)
74
+ # =====================================
75
+
76
+ # Creates a circle shape (bidirectional alias).
77
+ # @return [Widgets::Shape::Circle]
78
+ alias circle_shape shape_circle
79
+
80
+ # Creates a point shape (bidirectional alias).
81
+ # @return [Widgets::Shape::Point]
82
+ alias point_shape shape_point
83
+
84
+ # Creates a rectangle shape (bidirectional alias).
85
+ # Note: Terse 'rectangle' is intentionally excluded to avoid confusion with Layout::Rect,
86
+ # but 'rectangle_shape' is unambiguous.
87
+ # @return [Widgets::Shape::Rectangle]
88
+ alias rectangle_shape shape_rectangle
89
+
90
+ # Creates a map shape (bidirectional alias).
91
+ # @return [Widgets::Shape::Map]
92
+ alias map_shape shape_map
93
+
94
+ # Creates a label shape (bidirectional alias).
95
+ # @return [Widgets::Shape::Label]
96
+ alias label_shape label
97
+
98
+ # =====================================
99
+ # Shape Dispatcher (TIMTOWTDI)
100
+ # =====================================
101
+
102
+ # Creates a shape by type symbol.
103
+ #
104
+ # Hard-coding method names limits flexibility. Programmatic shape creation
105
+ # from user input or config files requires tedious case statements.
106
+ #
107
+ # This dispatcher routes shape creation through a single entry point.
108
+ # Pass the type as a symbol and the remaining parameters as kwargs.
109
+ #
110
+ # Use it for dynamic shape generation, config-driven UIs, or when you
111
+ # prefer explicit type specification over method names.
112
+ #
113
+ # Also available as: <tt>tui.circle</tt>, <tt>tui.shape_circle</tt>
114
+ #
115
+ # === Examples
116
+ #
117
+ #--
118
+ # SPDX-SnippetBegin
119
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
120
+ # SPDX-License-Identifier: MIT-0
121
+ #++
122
+ # # Direct dispatch
123
+ # tui.shape(:circle, x: 5.0, y: 5.0, radius: 2.5, color: :red)
124
+ #
125
+ # # Dynamic shape creation from config
126
+ # config = { type: :rectangle, x: 0.0, y: 0.0, width: 10.0, height: 10.0 }
127
+ # tui.shape(config[:type], **config.except(:type))
128
+ #--
129
+ # SPDX-SnippetEnd
130
+ #++
131
+ #
132
+ # @param type [Symbol] Shape type: :circle, :line, :point, :rectangle, :map, :label
133
+ # @return [Widgets::Shape::*]
134
+ def shape(type, **)
135
+ case type
136
+ when :circle then shape_circle(**)
137
+ when :line then shape_line(**)
138
+ when :point then shape_point(**)
139
+ when :rectangle then shape_rectangle(**)
140
+ when :map then shape_map(**)
141
+ when :label then label(**)
142
+ else
143
+ raise ArgumentError, "Unknown shape type: #{type.inspect}. " \
144
+ "Valid types: :circle, :line, :point, :rectangle, :map, :label"
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,67 @@
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
+ class TUI
10
+ # Core terminal methods delegated to RatatuiRuby module.
11
+ #
12
+ # These are the fundamental operations for the render loop:
13
+ # drawing UI, polling events, and inspecting the buffer.
14
+ module Core
15
+ # Draws the given UI node tree to the terminal.
16
+ # @see RatatuiRuby.draw
17
+ def draw(tree = nil, &block)
18
+ if tree && block
19
+ raise ArgumentError, "Cannot provide both a tree and a block to draw"
20
+ end
21
+ unless tree || block
22
+ raise ArgumentError, "Must provide either a tree or a block to draw"
23
+ end
24
+
25
+ if block
26
+ RatatuiRuby.draw(&block)
27
+ else
28
+ RatatuiRuby.draw(tree)
29
+ end
30
+ end
31
+
32
+ # Checks for user input.
33
+ # @see RatatuiRuby.poll_event
34
+ def poll_event(timeout: 0.016)
35
+ RatatuiRuby.poll_event(timeout:)
36
+ end
37
+
38
+ # Inspects the terminal buffer at specific coordinates.
39
+ # @see RatatuiRuby.get_cell_at
40
+ def get_cell_at(x, y)
41
+ RatatuiRuby.get_cell_at(x, y)
42
+ end
43
+
44
+ # Creates a Draw::CellCmd for placing a cell at coordinates.
45
+ # @return [Draw::CellCmd]
46
+ def draw_cell(x, y, cell)
47
+ Draw.cell(x, y, cell)
48
+ end
49
+
50
+ # Inserts content above an inline viewport.
51
+ # @see RatatuiRuby.insert_before
52
+ def insert_before(height, widget = nil, &)
53
+ RatatuiRuby.insert_before(height, widget, &)
54
+ end
55
+
56
+ # Gets the Rect of the entire terminal, regardless of viewport
57
+ def terminal_area
58
+ RatatuiRuby.terminal_area
59
+ end
60
+
61
+ # Gets the Rect of the viewport
62
+ def viewport_area
63
+ RatatuiRuby.viewport_area
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,153 @@
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
+ class TUI
10
+ # Layout factory methods for Session.
11
+ #
12
+ # Provides convenient access to Layout::Rect, Layout::Constraint,
13
+ # and Layout::Layout without fully qualifying the class names.
14
+ module LayoutFactories
15
+ # Creates a Layout::Rect.
16
+ # @return [Layout::Rect]
17
+ def rect(...)
18
+ Layout::Rect.new(...)
19
+ end
20
+
21
+ # Creates a Layout::Constraint, with optional type-based dispatch.
22
+ #
23
+ # When called with a type symbol as the first argument, dispatches to
24
+ # the appropriate constraint factory (TIMTOWTDI pattern).
25
+ #
26
+ # Also available as: <tt>tui.percent(50)</tt>, <tt>tui.flex(1)</tt>
27
+ #
28
+ # === Examples
29
+ #
30
+ #--
31
+ # SPDX-SnippetBegin
32
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
33
+ # SPDX-License-Identifier: MIT-0
34
+ #++
35
+ # tui.constraint(:length, 10) # => Constraint.length(10)
36
+ # tui.constraint(:percentage, 50) # => Constraint.percentage(50)
37
+ # tui.constraint(:min, 5) # => Constraint.min(5)
38
+ # tui.constraint(:fill, 2) # => Constraint.fill(2)
39
+ # tui.constraint(:ratio, 1, 3) # => Constraint.ratio(1, 3)
40
+ #--
41
+ # SPDX-SnippetEnd
42
+ #++
43
+ # @return [Layout::Constraint]
44
+ def constraint(type_or_arg = nil, arg1 = nil, arg2 = nil, **)
45
+ # Type-based dispatch when first arg is a symbol
46
+ if type_or_arg.is_a?(Symbol)
47
+ case type_or_arg
48
+ when :length, :fixed
49
+ constraint_length(arg1 || raise(ArgumentError, "#{type_or_arg} requires a value"))
50
+ when :percentage, :percent
51
+ constraint_percentage(arg1 || raise(ArgumentError, "#{type_or_arg} requires a value"))
52
+ when :min
53
+ constraint_min(arg1 || raise(ArgumentError, "min requires a value"))
54
+ when :max
55
+ constraint_max(arg1 || raise(ArgumentError, "max requires a value"))
56
+ when :fill, :flex, :fr
57
+ constraint_fill(arg1 || 1)
58
+ when :ratio, :aspect
59
+ n = arg1 || raise(ArgumentError, "ratio requires numerator")
60
+ d = arg2 || raise(ArgumentError, "ratio requires denominator")
61
+ constraint_ratio(n, d)
62
+ else
63
+ # Use to_s since type_or_arg must be a Symbol here
64
+ raise ArgumentError, "Unknown constraint type: :#{type_or_arg}. " \
65
+ "Valid types: :length, :percentage, :min, :max, :fill, :ratio"
66
+ end
67
+ elsif type_or_arg.nil? && arg1.nil?
68
+ Layout::Constraint.new(**)
69
+ else
70
+ Layout::Constraint.new(type_or_arg, arg1, arg2, **)
71
+ end
72
+ end
73
+
74
+ # Creates a Layout::Constraint.length.
75
+ # @return [Layout::Constraint]
76
+ def constraint_length(n)
77
+ Layout::Constraint.length(n)
78
+ end
79
+
80
+ # Creates a Layout::Constraint.percentage.
81
+ # @return [Layout::Constraint]
82
+ def constraint_percentage(n)
83
+ Layout::Constraint.percentage(n)
84
+ end
85
+
86
+ # Creates a Layout::Constraint.min.
87
+ # @return [Layout::Constraint]
88
+ def constraint_min(n)
89
+ Layout::Constraint.min(n)
90
+ end
91
+
92
+ # Creates a Layout::Constraint.max.
93
+ # @return [Layout::Constraint]
94
+ def constraint_max(n)
95
+ Layout::Constraint.max(n)
96
+ end
97
+
98
+ # Creates a Layout::Constraint.fill.
99
+ # @return [Layout::Constraint]
100
+ def constraint_fill(n = 1)
101
+ Layout::Constraint.fill(n)
102
+ end
103
+
104
+ # Creates a Layout::Constraint.ratio.
105
+ # @return [Layout::Constraint]
106
+ def constraint_ratio(numerator, denominator)
107
+ Layout::Constraint.ratio(numerator, denominator)
108
+ end
109
+
110
+ # Creates a Layout::Layout.
111
+ # @return [Layout::Layout]
112
+ def layout(...)
113
+ Layout::Layout.new(...)
114
+ end
115
+
116
+ # Splits an area using Layout::Layout.split.
117
+ # @return [Array<Layout::Rect>]
118
+ def layout_split(area, direction: :vertical, constraints:, flex: :legacy)
119
+ Layout::Layout.split(area, direction:, constraints:, flex:)
120
+ end
121
+
122
+ # Alias for {#layout_split} — shorter, more ergonomic.
123
+ # @return [Array<Layout::Rect>]
124
+ def split(area, direction: :vertical, constraints:, flex: :legacy)
125
+ layout_split(area, direction:, constraints:, flex:)
126
+ end
127
+
128
+ # Alias for {#constraint_length} — CSS-inspired "fixed" sizing.
129
+ # @return [Layout::Constraint]
130
+ def fixed(n)
131
+ constraint_length(n)
132
+ end
133
+
134
+ # Alias for {#constraint_percentage} — CSS-inspired percent sizing.
135
+ # @return [Layout::Constraint]
136
+ def percent(n)
137
+ constraint_percentage(n)
138
+ end
139
+
140
+ # Alias for {#constraint_fill} — CSS Flexbox-inspired flexible sizing.
141
+ # @return [Layout::Constraint]
142
+ def flex(n = 1)
143
+ constraint_fill(n)
144
+ end
145
+
146
+ # Alias for {#constraint_fill} — CSS Grid-inspired "fr" (fraction) unit.
147
+ # @return [Layout::Constraint]
148
+ def fr(n = 1)
149
+ constraint_fill(n)
150
+ end
151
+ end
152
+ end
153
+ end