ratatui_ruby 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. checksums.yaml +4 -4
  2. data/.builds/ruby-3.2.yml +1 -1
  3. data/.builds/ruby-3.3.yml +1 -1
  4. data/.builds/ruby-3.4.yml +1 -1
  5. data/.builds/ruby-4.0.0.yml +1 -1
  6. data/AGENTS.md +2 -1
  7. data/CHANGELOG.md +122 -0
  8. data/REUSE.toml +5 -0
  9. data/Rakefile +1 -1
  10. data/Steepfile +49 -0
  11. data/doc/concepts/debugging.md +401 -0
  12. data/doc/getting_started/quickstart.md +8 -3
  13. data/doc/images/app_all_events.png +0 -0
  14. data/doc/images/app_color_picker.png +0 -0
  15. data/doc/images/app_debugging_showcase.gif +0 -0
  16. data/doc/images/app_debugging_showcase.png +0 -0
  17. data/doc/images/app_login_form.png +0 -0
  18. data/doc/images/app_stateful_interaction.png +0 -0
  19. data/doc/images/verify_quickstart_dsl.png +0 -0
  20. data/doc/images/verify_quickstart_layout.png +0 -0
  21. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  22. data/doc/images/verify_readme_usage.png +0 -0
  23. data/doc/images/widget_barchart.png +0 -0
  24. data/doc/images/widget_block.png +0 -0
  25. data/doc/images/widget_box.png +0 -0
  26. data/doc/images/widget_calendar.png +0 -0
  27. data/doc/images/widget_canvas.png +0 -0
  28. data/doc/images/widget_cell.png +0 -0
  29. data/doc/images/widget_center.png +0 -0
  30. data/doc/images/widget_chart.png +0 -0
  31. data/doc/images/widget_gauge.png +0 -0
  32. data/doc/images/widget_layout_split.png +0 -0
  33. data/doc/images/widget_line_gauge.png +0 -0
  34. data/doc/images/widget_list.png +0 -0
  35. data/doc/images/widget_map.png +0 -0
  36. data/doc/images/widget_overlay.png +0 -0
  37. data/doc/images/widget_popup.png +0 -0
  38. data/doc/images/widget_ratatui_logo.png +0 -0
  39. data/doc/images/widget_ratatui_mascot.png +0 -0
  40. data/doc/images/widget_rect.png +0 -0
  41. data/doc/images/widget_render.png +0 -0
  42. data/doc/images/widget_rich_text.png +0 -0
  43. data/doc/images/widget_scroll_text.png +0 -0
  44. data/doc/images/widget_scrollbar.png +0 -0
  45. data/doc/images/widget_sparkline.png +0 -0
  46. data/doc/images/widget_style_colors.png +0 -0
  47. data/doc/images/widget_table.png +0 -0
  48. data/doc/images/widget_tabs.png +0 -0
  49. data/doc/images/widget_text_width.png +0 -0
  50. data/doc/troubleshooting/async.md +4 -0
  51. data/examples/app_debugging_showcase/README.md +119 -0
  52. data/examples/app_debugging_showcase/app.rb +318 -0
  53. data/examples/widget_canvas/app.rb +19 -14
  54. data/examples/widget_gauge/app.rb +18 -3
  55. data/examples/widget_layout_split/app.rb +16 -4
  56. data/examples/widget_list/app.rb +22 -6
  57. data/examples/widget_rect/app.rb +7 -6
  58. data/examples/widget_rich_text/app.rb +62 -37
  59. data/examples/widget_style_colors/app.rb +26 -47
  60. data/examples/widget_table/app.rb +28 -5
  61. data/examples/widget_text_width/app.rb +6 -4
  62. data/ext/ratatui_ruby/Cargo.lock +48 -1
  63. data/ext/ratatui_ruby/Cargo.toml +6 -2
  64. data/ext/ratatui_ruby/src/color.rs +82 -0
  65. data/ext/ratatui_ruby/src/errors.rs +28 -0
  66. data/ext/ratatui_ruby/src/events.rs +16 -14
  67. data/ext/ratatui_ruby/src/lib.rs +56 -0
  68. data/ext/ratatui_ruby/src/rendering.rs +3 -1
  69. data/ext/ratatui_ruby/src/style.rs +48 -21
  70. data/ext/ratatui_ruby/src/terminal.rs +40 -9
  71. data/ext/ratatui_ruby/src/text.rs +21 -9
  72. data/ext/ratatui_ruby/src/widgets/chart.rs +2 -1
  73. data/ext/ratatui_ruby/src/widgets/layout.rs +90 -2
  74. data/ext/ratatui_ruby/src/widgets/list.rs +6 -5
  75. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  76. data/ext/ratatui_ruby/src/widgets/table.rs +7 -6
  77. data/ext/ratatui_ruby/src/widgets/table_state.rs +55 -0
  78. data/ext/ratatui_ruby/src/widgets/tabs.rs +3 -2
  79. data/lib/ratatui_ruby/buffer/cell.rb +25 -15
  80. data/lib/ratatui_ruby/buffer.rb +134 -2
  81. data/lib/ratatui_ruby/cell.rb +13 -5
  82. data/lib/ratatui_ruby/debug.rb +215 -0
  83. data/lib/ratatui_ruby/event/key.rb +3 -2
  84. data/lib/ratatui_ruby/event/sync.rb +52 -0
  85. data/lib/ratatui_ruby/event.rb +7 -1
  86. data/lib/ratatui_ruby/layout/constraint.rb +184 -0
  87. data/lib/ratatui_ruby/layout/layout.rb +119 -13
  88. data/lib/ratatui_ruby/layout/position.rb +55 -0
  89. data/lib/ratatui_ruby/layout/rect.rb +188 -0
  90. data/lib/ratatui_ruby/layout/size.rb +55 -0
  91. data/lib/ratatui_ruby/layout.rb +4 -0
  92. data/lib/ratatui_ruby/style/color.rb +149 -0
  93. data/lib/ratatui_ruby/style/style.rb +51 -4
  94. data/lib/ratatui_ruby/style.rb +2 -0
  95. data/lib/ratatui_ruby/symbols.rb +435 -0
  96. data/lib/ratatui_ruby/synthetic_events.rb +86 -0
  97. data/lib/ratatui_ruby/table_state.rb +51 -0
  98. data/lib/ratatui_ruby/terminal_lifecycle.rb +2 -1
  99. data/lib/ratatui_ruby/test_helper/event_injection.rb +34 -1
  100. data/lib/ratatui_ruby/test_helper.rb +9 -0
  101. data/lib/ratatui_ruby/text/line.rb +245 -0
  102. data/lib/ratatui_ruby/text/span.rb +158 -0
  103. data/lib/ratatui_ruby/text.rb +99 -0
  104. data/lib/ratatui_ruby/tui/canvas_factories.rb +103 -0
  105. data/lib/ratatui_ruby/tui/core.rb +13 -2
  106. data/lib/ratatui_ruby/tui/layout_factories.rb +50 -3
  107. data/lib/ratatui_ruby/tui/state_factories.rb +42 -0
  108. data/lib/ratatui_ruby/tui/text_factories.rb +40 -0
  109. data/lib/ratatui_ruby/tui/widget_factories.rb +135 -60
  110. data/lib/ratatui_ruby/tui.rb +22 -1
  111. data/lib/ratatui_ruby/version.rb +1 -1
  112. data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -0
  113. data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -0
  114. data/lib/ratatui_ruby/widgets/bar_chart.rb +30 -20
  115. data/lib/ratatui_ruby/widgets/block.rb +14 -6
  116. data/lib/ratatui_ruby/widgets/calendar.rb +2 -0
  117. data/lib/ratatui_ruby/widgets/canvas.rb +56 -0
  118. data/lib/ratatui_ruby/widgets/cell.rb +2 -0
  119. data/lib/ratatui_ruby/widgets/center.rb +2 -0
  120. data/lib/ratatui_ruby/widgets/chart.rb +6 -0
  121. data/lib/ratatui_ruby/widgets/clear.rb +2 -0
  122. data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
  123. data/lib/ratatui_ruby/widgets/cursor.rb +2 -0
  124. data/lib/ratatui_ruby/widgets/gauge.rb +61 -3
  125. data/lib/ratatui_ruby/widgets/line_gauge.rb +66 -4
  126. data/lib/ratatui_ruby/widgets/list.rb +87 -3
  127. data/lib/ratatui_ruby/widgets/list_item.rb +2 -0
  128. data/lib/ratatui_ruby/widgets/overlay.rb +2 -0
  129. data/lib/ratatui_ruby/widgets/paragraph.rb +4 -0
  130. data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -0
  131. data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -0
  132. data/lib/ratatui_ruby/widgets/row.rb +45 -0
  133. data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -0
  134. data/lib/ratatui_ruby/widgets/shape/label.rb +2 -0
  135. data/lib/ratatui_ruby/widgets/sparkline.rb +21 -13
  136. data/lib/ratatui_ruby/widgets/table.rb +13 -3
  137. data/lib/ratatui_ruby/widgets/tabs.rb +6 -4
  138. data/lib/ratatui_ruby/widgets.rb +1 -0
  139. data/lib/ratatui_ruby.rb +51 -16
  140. data/sig/examples/app_all_events/model/app_model.rbs +23 -0
  141. data/sig/examples/app_all_events/model/event_entry.rbs +15 -8
  142. data/sig/examples/app_all_events/model/timestamp.rbs +1 -1
  143. data/sig/examples/app_all_events/view.rbs +1 -1
  144. data/sig/examples/app_stateful_interaction/app.rbs +5 -5
  145. data/sig/examples/widget_block_demo/app.rbs +6 -6
  146. data/sig/manifest.yaml +5 -0
  147. data/sig/patches/data.rbs +26 -0
  148. data/sig/patches/debugger__.rbs +8 -0
  149. data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
  150. data/sig/ratatui_ruby/buffer.rbs +18 -0
  151. data/sig/ratatui_ruby/cell.rbs +44 -0
  152. data/sig/ratatui_ruby/clear.rbs +18 -0
  153. data/sig/ratatui_ruby/constraint.rbs +26 -0
  154. data/sig/ratatui_ruby/debug.rbs +45 -0
  155. data/sig/ratatui_ruby/draw.rbs +30 -0
  156. data/sig/ratatui_ruby/event.rbs +68 -8
  157. data/sig/ratatui_ruby/frame.rbs +4 -4
  158. data/sig/ratatui_ruby/interfaces.rbs +25 -0
  159. data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
  160. data/sig/ratatui_ruby/layout/layout.rbs +45 -0
  161. data/sig/ratatui_ruby/layout/position.rbs +18 -0
  162. data/sig/ratatui_ruby/layout/rect.rbs +64 -0
  163. data/sig/ratatui_ruby/layout/size.rbs +18 -0
  164. data/sig/ratatui_ruby/output_guard.rbs +23 -0
  165. data/sig/ratatui_ruby/ratatui_ruby.rbs +83 -4
  166. data/sig/ratatui_ruby/rect.rbs +17 -0
  167. data/sig/ratatui_ruby/style/color.rbs +22 -0
  168. data/sig/ratatui_ruby/style/style.rbs +29 -0
  169. data/sig/ratatui_ruby/symbols.rbs +141 -0
  170. data/sig/ratatui_ruby/synthetic_events.rbs +21 -0
  171. data/sig/ratatui_ruby/table_state.rbs +6 -0
  172. data/sig/ratatui_ruby/terminal_lifecycle.rbs +31 -0
  173. data/sig/ratatui_ruby/test_helper/event_injection.rbs +2 -2
  174. data/sig/ratatui_ruby/test_helper/snapshot.rbs +22 -3
  175. data/sig/ratatui_ruby/test_helper/style_assertions.rbs +8 -1
  176. data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -3
  177. data/sig/ratatui_ruby/text/line.rbs +27 -0
  178. data/sig/ratatui_ruby/text/span.rbs +23 -0
  179. data/sig/ratatui_ruby/text.rbs +12 -0
  180. data/sig/ratatui_ruby/tui/buffer_factories.rbs +1 -1
  181. data/sig/ratatui_ruby/tui/canvas_factories.rbs +23 -5
  182. data/sig/ratatui_ruby/tui/core.rbs +2 -2
  183. data/sig/ratatui_ruby/tui/layout_factories.rbs +16 -2
  184. data/sig/ratatui_ruby/tui/state_factories.rbs +8 -3
  185. data/sig/ratatui_ruby/tui/style_factories.rbs +3 -1
  186. data/sig/ratatui_ruby/tui/text_factories.rbs +7 -4
  187. data/sig/ratatui_ruby/tui/widget_factories.rbs +123 -30
  188. data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
  189. data/sig/ratatui_ruby/widgets/block.rbs +51 -0
  190. data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
  191. data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
  192. data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
  193. data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
  194. data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
  195. data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
  196. data/sig/ratatui_ruby/widgets/list.rbs +63 -0
  197. data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
  198. data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
  199. data/sig/ratatui_ruby/widgets/row.rbs +43 -0
  200. data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
  201. data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
  202. data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
  203. data/sig/ratatui_ruby/widgets/table.rbs +78 -0
  204. data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
  205. data/sig/ratatui_ruby/{schema/list_item.rbs → widgets.rbs} +4 -4
  206. data/tasks/steep.rake +11 -0
  207. metadata +82 -63
  208. data/doc/contributors/v1.0.0_blockers.md +0 -876
  209. data/doc/troubleshooting/debugging.md +0 -101
  210. data/lib/ratatui_ruby/schema/bar_chart/bar.rb +0 -47
  211. data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +0 -25
  212. data/lib/ratatui_ruby/schema/bar_chart.rb +0 -287
  213. data/lib/ratatui_ruby/schema/block.rb +0 -198
  214. data/lib/ratatui_ruby/schema/calendar.rb +0 -84
  215. data/lib/ratatui_ruby/schema/canvas.rb +0 -239
  216. data/lib/ratatui_ruby/schema/center.rb +0 -67
  217. data/lib/ratatui_ruby/schema/chart.rb +0 -159
  218. data/lib/ratatui_ruby/schema/clear.rb +0 -62
  219. data/lib/ratatui_ruby/schema/constraint.rb +0 -151
  220. data/lib/ratatui_ruby/schema/cursor.rb +0 -50
  221. data/lib/ratatui_ruby/schema/gauge.rb +0 -72
  222. data/lib/ratatui_ruby/schema/layout.rb +0 -122
  223. data/lib/ratatui_ruby/schema/line_gauge.rb +0 -80
  224. data/lib/ratatui_ruby/schema/list.rb +0 -135
  225. data/lib/ratatui_ruby/schema/list_item.rb +0 -51
  226. data/lib/ratatui_ruby/schema/overlay.rb +0 -51
  227. data/lib/ratatui_ruby/schema/paragraph.rb +0 -107
  228. data/lib/ratatui_ruby/schema/ratatui_logo.rb +0 -31
  229. data/lib/ratatui_ruby/schema/ratatui_mascot.rb +0 -36
  230. data/lib/ratatui_ruby/schema/rect.rb +0 -174
  231. data/lib/ratatui_ruby/schema/row.rb +0 -76
  232. data/lib/ratatui_ruby/schema/scrollbar.rb +0 -143
  233. data/lib/ratatui_ruby/schema/shape/label.rb +0 -76
  234. data/lib/ratatui_ruby/schema/sparkline.rb +0 -142
  235. data/lib/ratatui_ruby/schema/style.rb +0 -97
  236. data/lib/ratatui_ruby/schema/table.rb +0 -141
  237. data/lib/ratatui_ruby/schema/tabs.rb +0 -85
  238. data/lib/ratatui_ruby/schema/text.rb +0 -217
  239. data/sig/examples/app_all_events/model/events.rbs +0 -15
  240. data/sig/examples/app_all_events/view_state.rbs +0 -21
  241. data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +0 -22
  242. data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +0 -19
  243. data/sig/ratatui_ruby/schema/bar_chart.rbs +0 -38
  244. data/sig/ratatui_ruby/schema/block.rbs +0 -18
  245. data/sig/ratatui_ruby/schema/calendar.rbs +0 -23
  246. data/sig/ratatui_ruby/schema/canvas.rbs +0 -81
  247. data/sig/ratatui_ruby/schema/center.rbs +0 -17
  248. data/sig/ratatui_ruby/schema/chart.rbs +0 -39
  249. data/sig/ratatui_ruby/schema/constraint.rbs +0 -22
  250. data/sig/ratatui_ruby/schema/cursor.rbs +0 -16
  251. data/sig/ratatui_ruby/schema/draw.rbs +0 -33
  252. data/sig/ratatui_ruby/schema/gauge.rbs +0 -23
  253. data/sig/ratatui_ruby/schema/layout.rbs +0 -27
  254. data/sig/ratatui_ruby/schema/line_gauge.rbs +0 -24
  255. data/sig/ratatui_ruby/schema/list.rbs +0 -28
  256. data/sig/ratatui_ruby/schema/overlay.rbs +0 -15
  257. data/sig/ratatui_ruby/schema/paragraph.rbs +0 -20
  258. data/sig/ratatui_ruby/schema/ratatui_logo.rbs +0 -14
  259. data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +0 -17
  260. data/sig/ratatui_ruby/schema/rect.rbs +0 -48
  261. data/sig/ratatui_ruby/schema/row.rbs +0 -28
  262. data/sig/ratatui_ruby/schema/scrollbar.rbs +0 -42
  263. data/sig/ratatui_ruby/schema/sparkline.rbs +0 -22
  264. data/sig/ratatui_ruby/schema/style.rbs +0 -19
  265. data/sig/ratatui_ruby/schema/table.rbs +0 -32
  266. data/sig/ratatui_ruby/schema/tabs.rbs +0 -21
  267. data/sig/ratatui_ruby/schema/text.rbs +0 -31
  268. /data/lib/ratatui_ruby/{schema/draw.rb → draw.rb} +0 -0
@@ -0,0 +1,30 @@
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 Draw
10
+ class StringCmd < Data
11
+ attr_reader x: Integer
12
+ attr_reader y: Integer
13
+ attr_reader string: String
14
+ attr_reader style: style_input
15
+
16
+ def self.new: (x: Integer, y: Integer, string: String, style: style_input) -> StringCmd
17
+ end
18
+
19
+ class CellCmd < Data
20
+ attr_reader x: Integer
21
+ attr_reader y: Integer
22
+ attr_reader cell: Buffer::Cell
23
+
24
+ def self.new: (x: Integer, y: Integer, cell: Buffer::Cell) -> CellCmd
25
+ end
26
+
27
+ def self.string: (_ToI x, _ToI y, String string, ?style_input style) -> StringCmd
28
+ def self.cell: (_ToI x, _ToI y, Buffer::Cell cell) -> CellCmd
29
+ end
30
+ end
@@ -9,17 +9,70 @@
9
9
 
10
10
  module RatatuiRuby
11
11
  class Event
12
+ def none?: () -> bool
12
13
  def key?: () -> bool
13
14
  def mouse?: () -> bool
14
15
  def resize?: () -> bool
15
16
  def paste?: () -> bool
16
17
  def focus_gained?: () -> bool
17
18
  def focus_lost?: () -> bool
18
- def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, untyped]
19
+ def sync?: () -> bool
20
+ def method_missing: (Symbol, *untyped, **untyped) ?{ (?) -> untyped } -> untyped
21
+ def respond_to_missing?: (Symbol, *untyped) -> bool
22
+ def deconstruct_keys: (Array[Symbol]?) -> { }
19
23
 
20
24
  type key_kind = :standard | :function | :media | :modifier | :system
21
25
 
26
+ class Sync < Event
27
+ def sync?: () -> bool
28
+ def deconstruct_keys: (Array[Symbol]?) -> { type: :sync }
29
+ end
30
+
22
31
  class Key < Event
32
+ # Character handling module
33
+ module Character
34
+ def text?: () -> bool
35
+ def char: () -> String?
36
+ end
37
+
38
+ # Modifier key detection module
39
+ module Modifier
40
+ def ctrl?: () -> bool
41
+ def alt?: () -> bool
42
+ def shift?: () -> bool
43
+ def super?: () -> bool
44
+ def hyper?: () -> bool
45
+ def meta?: () -> bool
46
+ def modifier?: () -> bool
47
+ private def match_modifier_dwim?: (String key_name, Symbol key_sym) -> bool
48
+ end
49
+
50
+ # Media key handling module
51
+ module Media
52
+ def media?: () -> bool
53
+ private def match_media_dwim?: (String key_name) -> bool
54
+ end
55
+
56
+ # Navigation key handling module
57
+ module Navigation
58
+ def standard?: () -> bool
59
+ alias unmodified? standard?
60
+ private def match_navigation_dwim?: (String key_name, Symbol key_sym) -> bool
61
+ end
62
+
63
+ # System key handling module
64
+ module System
65
+ def system?: () -> bool
66
+ def function?: () -> bool
67
+ private def match_system_dwim?: (String key_name, Symbol key_sym) -> bool
68
+ end
69
+
70
+ include Character
71
+ include Modifier
72
+ include Media
73
+ include Navigation
74
+ include System
75
+
23
76
  attr_reader code: String
24
77
  attr_reader modifiers: Array[String]
25
78
  attr_reader kind: key_kind
@@ -33,14 +86,16 @@ module RatatuiRuby
33
86
  def alt?: () -> bool
34
87
  def shift?: () -> bool
35
88
  def text?: () -> bool
36
- def char: () -> String
89
+ def char: () -> String?
37
90
  def media?: () -> bool
38
91
  def system?: () -> bool
39
92
  def function?: () -> bool
40
93
  def modifier?: () -> bool
41
94
  def standard?: () -> bool
42
95
  def unmodified?: () -> bool
43
- def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, untyped]
96
+ def method_missing: (Symbol, *untyped, **untyped) ?{ (?) -> untyped } -> untyped
97
+ def respond_to_missing?: (Symbol, *untyped) -> bool
98
+ def deconstruct_keys: (Array[Symbol]?) -> { type: :key, code: String, modifiers: Array[String], kind: key_kind }
44
99
  end
45
100
 
46
101
  class Mouse < Event
@@ -56,7 +111,7 @@ module RatatuiRuby
56
111
  def drag?: () -> bool
57
112
  def scroll_up?: () -> bool
58
113
  def scroll_down?: () -> bool
59
- def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, untyped]
114
+ def deconstruct_keys: (Array[Symbol]?) -> { type: :mouse, kind: String, x: Integer, y: Integer, button: String, modifiers: Array[String] }
60
115
  end
61
116
 
62
117
  class Resize < Event
@@ -64,22 +119,27 @@ module RatatuiRuby
64
119
  attr_reader height: Integer
65
120
 
66
121
  def initialize: (width: Integer, height: Integer) -> void
67
- def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, untyped]
122
+ def deconstruct_keys: (Array[Symbol]?) -> { type: :resize, width: Integer, height: Integer }
68
123
  end
69
124
 
70
125
  class Paste < Event
71
126
  attr_reader content: String
72
127
 
73
128
  def initialize: (content: String) -> void
74
- def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, untyped]
129
+ def deconstruct_keys: (Array[Symbol]?) -> { type: :paste, content: String }
75
130
  end
76
131
 
77
132
  class FocusGained < Event
78
- def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, untyped]
133
+ def deconstruct_keys: (Array[Symbol]?) -> { type: :focus_gained }
79
134
  end
80
135
 
81
136
  class FocusLost < Event
82
- def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, untyped]
137
+ def deconstruct_keys: (Array[Symbol]?) -> { type: :focus_lost }
138
+ end
139
+
140
+ class None < Event
141
+ def none?: () -> bool
142
+ def deconstruct_keys: (Array[Symbol]?) -> { type: :none }
83
143
  end
84
144
  end
85
145
  end
@@ -11,13 +11,13 @@ module RatatuiRuby
11
11
  # Any Ruby object implementing this interface works as a widget.
12
12
  # Return an Array of Draw commands describing what to render.
13
13
  interface _CustomWidget
14
- def render: (Rect area) -> Array[Draw::StringCmd | Draw::CellCmd]
14
+ def render: (Layout::Rect area) -> Array[Draw::StringCmd | Draw::CellCmd]
15
15
  end
16
16
 
17
17
  class Frame
18
- def area: () -> Rect
19
- def render_widget: (_CustomWidget | widget widget, Rect area) -> nil
20
- def render_stateful_widget: (widget widget, Rect area, (ListState | TableState | ScrollbarState) state) -> nil
18
+ def area: () -> Layout::Rect
19
+ def render_widget: ((_CustomWidget | widget) widget, Layout::Rect area) -> nil
20
+ def render_stateful_widget: (widget widget, Layout::Rect area, (ListState | TableState | ScrollbarState) state) -> nil
21
21
  def set_cursor_position: (Integer x, Integer y) -> nil
22
22
  end
23
23
  end
@@ -0,0 +1,25 @@
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
+ # SPDX-License-Identifier: AGPL-3.0-or-later
9
+
10
+ # Duck-typing interfaces for RatatuiRuby.
11
+ #
12
+ # These interfaces enable type-safe duck typing: any object responding to
13
+ # the specified methods can be used, not just the canonical types.
14
+ module RatatuiRuby
15
+ # Interface for Rect-like objects.
16
+ #
17
+ # Any object responding to x, y, width, and height with Integer returns
18
+ # can be used where Layout::Rect is expected.
19
+ interface _RectLike
20
+ def x: () -> Integer
21
+ def y: () -> Integer
22
+ def width: () -> Integer
23
+ def height: () -> Integer
24
+ end
25
+ end
@@ -0,0 +1,39 @@
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
+ # SPDX-License-Identifier: AGPL-3.0-or-later
9
+
10
+ module RatatuiRuby
11
+ module Layout
12
+ class Constraint < Data
13
+ attr_reader type: Symbol
14
+ attr_reader value: (Integer | Array[Integer])
15
+ def self.length: (_ToI) -> Constraint
16
+ def self.percentage: (_ToI) -> Constraint
17
+ def self.min: (_ToI) -> Constraint
18
+ def self.max: (_ToI) -> Constraint
19
+ def self.fill: (?_ToI) -> Constraint
20
+ def self.ratio: (_ToI, _ToI) -> Constraint
21
+ def self.new: (type: Symbol, value: (Integer | Array[Integer])) -> Constraint
22
+ def initialize: (type: Symbol, value: (Integer | Array[Integer])) -> void
23
+
24
+ # Batch constructors
25
+ def self.from_lengths: (Enumerable[_ToI]) -> Array[Constraint]
26
+ def self.from_percentages: (Enumerable[_ToI]) -> Array[Constraint]
27
+ def self.from_mins: (Enumerable[_ToI]) -> Array[Constraint]
28
+ def self.from_maxes: (Enumerable[_ToI]) -> Array[Constraint]
29
+ def self.from_fills: (Enumerable[_ToI]) -> Array[Constraint]
30
+ def self.from_ratios: (Enumerable[[_ToI, _ToI]]) -> Array[Constraint]
31
+
32
+ # Instance methods
33
+ def apply: (_ToInt) -> Integer
34
+
35
+ # Ruby-idiomatic alias
36
+ alias call apply
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,45 @@
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
+ # SPDX-License-Identifier: AGPL-3.0-or-later
9
+
10
+ module RatatuiRuby
11
+ module Layout
12
+ type flex_mode = Symbol
13
+
14
+ class Layout < Data
15
+ FLEX_MODES: Array[Symbol]
16
+ DIRECTION_VERTICAL: Symbol
17
+ DIRECTION_HORIZONTAL: Symbol
18
+ FLEX_LEGACY: Symbol
19
+ FLEX_START: Symbol
20
+ FLEX_CENTER: Symbol
21
+ FLEX_END: Symbol
22
+ FLEX_SPACE_BETWEEN: Symbol
23
+ FLEX_SPACE_AROUND: Symbol
24
+ FLEX_SPACE_EVENLY: Symbol
25
+
26
+ attr_reader direction: Symbol
27
+ attr_reader constraints: Array[Constraint]
28
+ attr_reader children: Array[RatatuiRuby::widget]
29
+ attr_reader flex: flex_mode
30
+ attr_reader margin: (Integer | Hash[Symbol, Integer])
31
+ attr_reader spacing: Integer
32
+ def self.new: (?direction: Symbol, ?constraints: Array[Constraint], ?children: Array[RatatuiRuby::widget], ?flex: flex_mode, ?margin: (Integer | Hash[Symbol, Integer]), ?spacing: Integer) -> Layout
33
+ def initialize: (?direction: Symbol, ?constraints: Array[Constraint], ?children: Array[RatatuiRuby::widget], ?flex: flex_mode, ?margin: (Integer | Hash[Symbol, Integer]), ?spacing: Integer) -> void
34
+ def self.split: ((Rect | Hash[Symbol, Integer]) area, ?direction: Symbol, constraints: Array[Constraint], ?flex: flex_mode) -> Array[Rect]
35
+
36
+ # Splits an area into segments and spacers.
37
+ # Returns [segments, spacers] where each is an Array of Rects.
38
+ def self.split_with_spacers: ((Rect | Hash[Symbol, Integer]) area, ?direction: Symbol, constraints: Array[Constraint], ?flex: flex_mode) -> [Array[Rect], Array[Rect]]
39
+
40
+ private
41
+
42
+ def self._split: (Rect, Symbol, Array[Constraint], Symbol) -> Array[Hash[Symbol, Integer]]
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
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
+ class Position < Data
11
+ attr_reader x: Integer
12
+ attr_reader y: Integer
13
+
14
+ def self.new: (?x: _ToInt, ?y: _ToInt) -> instance
15
+ def initialize: (?x: _ToInt, ?y: _ToInt) -> void
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,64 @@
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
+ # SPDX-License-Identifier: AGPL-3.0-or-later
9
+
10
+ module RatatuiRuby
11
+ module Layout
12
+ class Rect < Data
13
+ attr_reader x: Integer
14
+ attr_reader y: Integer
15
+ attr_reader width: Integer
16
+ attr_reader height: Integer
17
+
18
+ def self.new: (?x: _ToI, ?y: _ToI, ?width: _ToI, ?height: _ToI) -> instance
19
+ def initialize: (?x: _ToI, ?y: _ToI, ?width: _ToI, ?height: _ToI) -> void
20
+
21
+ # Predicates
22
+ def contains?: (Integer px, Integer py) -> bool
23
+ def intersects?: (Rect other) -> bool
24
+ def empty?: () -> bool
25
+
26
+ # Edge accessors
27
+ def left: () -> Integer
28
+ def right: () -> Integer
29
+ def top: () -> Integer
30
+ def bottom: () -> Integer
31
+ def area: () -> Integer
32
+
33
+ # Geometry transformations
34
+ def intersection: (Rect other) -> Rect?
35
+ def union: (Rect other) -> Rect
36
+ def inner: (Integer margin) -> Rect
37
+ def outer: (Integer margin) -> Rect
38
+ def resize: (Size new_size) -> Rect
39
+ def offset: (Integer dx, Integer dy) -> Rect
40
+ def clamp: (Rect other) -> Rect
41
+
42
+ # Iterators
43
+ def rows: () -> Enumerator[Rect, void]
44
+ | () { (Rect) -> void } -> void
45
+ def columns: () -> Enumerator[Rect, void]
46
+ | () { (Rect) -> void } -> void
47
+ def positions: () -> Enumerator[[Integer, Integer], void]
48
+ | () { ([Integer, Integer]) -> void } -> void
49
+
50
+ # Conversions
51
+ def as_position: () -> Position
52
+ def as_size: () -> Size
53
+
54
+ # Centering
55
+ def centered_horizontally: (Constraint) -> Rect
56
+ def centered_vertically: (Constraint) -> Rect
57
+ def centered: (Constraint, Constraint) -> Rect
58
+
59
+ # Ruby-idiomatic aliases
60
+ alias position as_position
61
+ alias size as_size
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,18 @@
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
+ class Size < Data
11
+ attr_reader width: Integer
12
+ attr_reader height: Integer
13
+
14
+ def self.new: (?width: _ToInt, ?height: _ToInt) -> instance
15
+ def initialize: (?width: _ToInt, ?height: _ToInt) -> void
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
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
+ # SPDX-License-Identifier: AGPL-3.0-or-later
9
+
10
+ module RatatuiRuby
11
+ module OutputGuard
12
+ # Null IO object that discards all output
13
+ class NullIO
14
+ def method_missing: (Symbol name, *untyped args) ?{ (*untyped) -> untyped } -> NullIO
15
+ def respond_to_missing?: (Symbol name, ?bool include_private) -> true
16
+ end
17
+
18
+ def is_headless?: () -> bool
19
+ def headless!: () -> void
20
+ def guard_io: [T] () { () -> T } -> T
21
+ def terminal_active?: () -> bool
22
+ end
23
+ end
@@ -8,17 +8,96 @@
8
8
  # SPDX-License-Identifier: AGPL-3.0-or-later
9
9
 
10
10
  module RatatuiRuby
11
- type widget = Paragraph | Layout | List | Gauge | Table | Tabs | BarChart | Block | Sparkline
11
+ # Common type aliases for widget parameters
12
+ type alignment = :left | :center | :right
13
+ type border_side = :top | :bottom | :left | :right | :all
14
+ type border_type = :plain | :rounded | :double | :thick | :hidden | :quadrant_inside | :quadrant_outside
15
+ type title_position = :top | :bottom
16
+ type style_input = Style::Style | { ?fg: (Symbol | String | Integer), ?bg: (Symbol | String | Integer), ?modifiers: Array[Symbol] }
17
+ type title_entry = String | { content: (String | Text::Line), ?alignment: alignment, ?position: title_position, ?style: style_input }
18
+ type border_set_hash = { ?top_left: String, ?top_right: String, ?bottom_left: String, ?bottom_right: String, ?vertical_left: String, ?vertical_right: String, ?horizontal_top: String, ?horizontal_bottom: String }
19
+ type date_key = Date | [Integer, Integer, Integer]
20
+
21
+ type widget = Widgets::Paragraph | Layout::Layout | Widgets::List | Widgets::Gauge | Widgets::Table | Widgets::Tabs | Widgets::BarChart | Widgets::Block | Widgets::Sparkline
12
22
 
13
23
  interface _ToS
14
24
  def to_s: () -> String
15
25
  end
16
26
 
17
- def self.init_terminal: (focus_events: bool, bracketed_paste: bool) -> void
27
+ # Error classes
28
+ class Error < StandardError
29
+ class Terminal < Error
30
+ end
31
+ class Safety < Error
32
+ end
33
+ class Invariant < Error
34
+ end
35
+ end
36
+
37
+ # NullIO for headless mode
38
+
39
+
40
+ # Core module methods
41
+ def self.init_terminal: (?focus_events: bool, ?bracketed_paste: bool) -> void
42
+ def self.init_test_terminal: (Integer width, Integer height) -> void
18
43
  def self.restore_terminal: () -> void
19
- def self.draw: (widget tree) -> void
44
+ def self.terminal_active?: () -> bool
45
+ def self.run: (?focus_events: bool, ?bracketed_paste: bool) { (TUI) -> void } -> void
46
+ def self.draw: (?widget? tree) -> void
20
47
  | () { (Frame) -> void } -> void
48
+ # untyped: FFI boundary - Rust returns dynamic event hash with varying keys per event type
21
49
  def self._poll_event: (Float?) -> Hash[Symbol, untyped]?
22
50
  def self.poll_event: (?timeout: Float?) -> Event?
23
- def self.inject_test_event: (String, Hash[Symbol, untyped]) -> void
51
+ def self.inject_test_event: ("key", { code: String, ?modifiers: Array[String], ?kind: Event::key_kind }) -> void
52
+ | ("mouse", { kind: String, x: Integer, y: Integer, ?button: String, ?modifiers: Array[String] }) -> void
53
+ | ("resize", { width: Integer, height: Integer }) -> void
54
+ | ("paste", { content: String }) -> void
55
+ | ("focus_gained", { }) -> void
56
+ | ("focus_lost", { }) -> void
57
+ def self.get_cell_at: (Integer x, Integer y) -> Buffer::Cell
58
+ def self.experimental_warnings: () -> bool
59
+ def self.experimental_warnings=: (bool) -> bool
60
+ def self.warn_experimental_feature: (String feature_name) -> void
61
+
62
+ # Warning queue (private)
63
+ def self.queue_warning: (String message) -> void
64
+ def self.flush_warnings: () -> void
65
+
66
+ # Panic info (private, for deferred backtrace output)
67
+ def self._get_last_panic: () -> String?
68
+ def self.flush_panic_info: () -> void
69
+
70
+ # Draw internal
71
+ def self._draw: (widget) -> void
72
+ | () { (Frame) -> void } -> void
73
+
74
+ # Cell accessor internal (returns Hash with String keys from Rust FFI)
75
+ def self._get_cell_at: (Integer x, Integer y) -> Hash[String, untyped]
76
+
77
+ # Signal handling
78
+ def self.trap: (String | Symbol signal) { () -> void } -> void
79
+
80
+ # Paragraph helpers
81
+ def self._paragraph_line_count: (Widgets::Paragraph p, Integer width) -> Integer
82
+ def self._paragraph_line_width: (Widgets::Paragraph p) -> Integer
83
+
84
+ # Tabs/Text helpers
85
+ def self._tabs_width: (Widgets::Tabs t) -> Integer
86
+ def self._text_width: (String text) -> Integer
87
+
88
+ # Debug mode convenience method
89
+ def self.debug_mode!: () -> void
90
+
91
+ # Headless mode
92
+ def self.headless!: () -> void
93
+ def self.headless?: () -> bool
94
+
95
+ # Terminal area accessor (internal, returns Hash with String keys from Rust FFI)
96
+ def self._get_terminal_area: () -> Hash[String, Integer]
97
+
98
+ # Color conversion (internal, Rust FFI)
99
+ def self._color_from_u32: (Integer) -> String
100
+ def self._color_from_hsl: (Float, Float, Float) -> String
101
+ def self._color_from_hsluv: (Float, Float, Float) -> String
24
102
  end
103
+
@@ -0,0 +1,17 @@
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
+ # SPDX-License-Identifier: AGPL-3.0-or-later
9
+
10
+ # NOTE: RatatuiRuby::Rect is not defined at runtime in the main gem.
11
+ # This file exists only to satisfy the build system.
12
+ # The actual Rect class is Layout::Rect in sig/ratatui_ruby/schema/rect.rbs.
13
+ #
14
+ # The schema/ folder structure is pending migration per:
15
+ # doc/contributors/v1.0.0_blockers.md
16
+
17
+ # Empty placeholder - actual type is RatatuiRuby::Layout::Rect
@@ -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
+ module Style
10
+ module Color
11
+ def self.from_u32: (Integer) -> String
12
+ def self.from_hsl: (_ToF, _ToF, _ToF) -> String
13
+ def self.from_hsluv: (_ToF, _ToF, _ToF) -> String
14
+
15
+ # Ruby-idiomatic aliases
16
+ alias self.hex self.from_u32
17
+ alias self.hsl self.from_hsl
18
+ alias self.hsluv self.from_hsluv
19
+ end
20
+ end
21
+ end
22
+
@@ -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
+ # SPDX-License-Identifier: AGPL-3.0-or-later
9
+
10
+ module RatatuiRuby
11
+ # Style module at RatatuiRuby::Style
12
+ module Style
13
+ # Style class for RatatuiRuby::Style::Style
14
+ class Style < Data
15
+ type color = String | Symbol | Integer
16
+
17
+ attr_reader fg: color?
18
+ attr_reader bg: color?
19
+ attr_reader underline_color: color?
20
+ attr_reader modifiers: Array[Symbol]
21
+ attr_reader remove_modifiers: Array[Symbol]
22
+
23
+ def self.new: (?fg: color?, ?bg: color?, ?underline_color: color?, ?modifiers: Array[Symbol], ?remove_modifiers: Array[Symbol]) -> Style
24
+ def initialize: (?fg: color?, ?bg: color?, ?underline_color: color?, ?modifiers: Array[Symbol], ?remove_modifiers: Array[Symbol]) -> void
25
+ def self.default: () -> Style
26
+ def self.with: (?fg: color?, ?bg: color?, ?underline_color: color?, ?modifiers: Array[Symbol], ?remove_modifiers: Array[Symbol]) -> Style
27
+ end
28
+ end
29
+ end