ratatui_ruby 0.9.1 → 0.10.1

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 +113 -0
  8. data/README.md +17 -0
  9. data/REUSE.toml +5 -0
  10. data/Rakefile +1 -1
  11. data/Steepfile +49 -0
  12. data/doc/concepts/debugging.md +401 -0
  13. data/doc/getting_started/quickstart.md +8 -3
  14. data/doc/images/app_all_events.png +0 -0
  15. data/doc/images/app_color_picker.png +0 -0
  16. data/doc/images/app_debugging_showcase.gif +0 -0
  17. data/doc/images/app_debugging_showcase.png +0 -0
  18. data/doc/images/app_login_form.png +0 -0
  19. data/doc/images/app_stateful_interaction.png +0 -0
  20. data/doc/images/verify_quickstart_dsl.png +0 -0
  21. data/doc/images/verify_quickstart_layout.png +0 -0
  22. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  23. data/doc/images/verify_readme_usage.png +0 -0
  24. data/doc/images/widget_barchart.png +0 -0
  25. data/doc/images/widget_block.png +0 -0
  26. data/doc/images/widget_box.png +0 -0
  27. data/doc/images/widget_calendar.png +0 -0
  28. data/doc/images/widget_canvas.png +0 -0
  29. data/doc/images/widget_cell.png +0 -0
  30. data/doc/images/widget_center.png +0 -0
  31. data/doc/images/widget_chart.png +0 -0
  32. data/doc/images/widget_gauge.png +0 -0
  33. data/doc/images/widget_layout_split.png +0 -0
  34. data/doc/images/widget_line_gauge.png +0 -0
  35. data/doc/images/widget_list.png +0 -0
  36. data/doc/images/widget_map.png +0 -0
  37. data/doc/images/widget_overlay.png +0 -0
  38. data/doc/images/widget_popup.png +0 -0
  39. data/doc/images/widget_ratatui_logo.png +0 -0
  40. data/doc/images/widget_ratatui_mascot.png +0 -0
  41. data/doc/images/widget_rect.png +0 -0
  42. data/doc/images/widget_render.png +0 -0
  43. data/doc/images/widget_rich_text.png +0 -0
  44. data/doc/images/widget_scroll_text.png +0 -0
  45. data/doc/images/widget_scrollbar.png +0 -0
  46. data/doc/images/widget_sparkline.png +0 -0
  47. data/doc/images/widget_style_colors.png +0 -0
  48. data/doc/images/widget_table.png +0 -0
  49. data/doc/images/widget_tabs.png +0 -0
  50. data/doc/images/widget_text_width.png +0 -0
  51. data/doc/troubleshooting/async.md +4 -0
  52. data/examples/app_debugging_showcase/README.md +119 -0
  53. data/examples/app_debugging_showcase/app.rb +318 -0
  54. data/examples/widget_canvas/app.rb +19 -14
  55. data/examples/widget_gauge/app.rb +18 -3
  56. data/examples/widget_layout_split/app.rb +10 -4
  57. data/examples/widget_list/app.rb +22 -6
  58. data/examples/widget_rect/app.rb +7 -6
  59. data/examples/widget_rich_text/app.rb +62 -37
  60. data/examples/widget_style_colors/app.rb +26 -47
  61. data/examples/widget_table/app.rb +28 -5
  62. data/examples/widget_text_width/app.rb +6 -4
  63. data/ext/ratatui_ruby/Cargo.lock +48 -1
  64. data/ext/ratatui_ruby/Cargo.toml +6 -2
  65. data/ext/ratatui_ruby/src/color.rs +82 -0
  66. data/ext/ratatui_ruby/src/errors.rs +28 -0
  67. data/ext/ratatui_ruby/src/events.rs +15 -14
  68. data/ext/ratatui_ruby/src/lib.rs +56 -0
  69. data/ext/ratatui_ruby/src/rendering.rs +3 -1
  70. data/ext/ratatui_ruby/src/style.rs +48 -21
  71. data/ext/ratatui_ruby/src/terminal.rs +40 -9
  72. data/ext/ratatui_ruby/src/text.rs +21 -9
  73. data/ext/ratatui_ruby/src/widgets/chart.rs +2 -1
  74. data/ext/ratatui_ruby/src/widgets/layout.rs +90 -2
  75. data/ext/ratatui_ruby/src/widgets/list.rs +6 -5
  76. data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
  77. data/ext/ratatui_ruby/src/widgets/table.rs +7 -6
  78. data/ext/ratatui_ruby/src/widgets/table_state.rs +55 -0
  79. data/ext/ratatui_ruby/src/widgets/tabs.rs +3 -2
  80. data/lib/ratatui_ruby/buffer/cell.rb +25 -15
  81. data/lib/ratatui_ruby/buffer.rb +134 -2
  82. data/lib/ratatui_ruby/cell.rb +13 -5
  83. data/lib/ratatui_ruby/debug.rb +215 -0
  84. data/lib/ratatui_ruby/event/key.rb +3 -2
  85. data/lib/ratatui_ruby/event.rb +1 -1
  86. data/lib/ratatui_ruby/layout/constraint.rb +49 -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 +1 -1
  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 +6 -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 +42 -11
  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 +84 -5
  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 +80 -63
  208. data/doc/contributors/v1.0.0_blockers.md +0 -870
  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 -30
  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,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
+ module Style
10
+ # Color constructors for creating RGB color values.
11
+ #
12
+ # Styles accept colors in multiple formats: symbols (<tt>:red</tt>),
13
+ # indexed integers (0-255), or hex strings (<tt>"#FF0000"</tt>).
14
+ # Converting from other color representations manually is tedious.
15
+ #
16
+ # This module provides factory methods matching Ratatui's Color API.
17
+ # Convert from hex integers, HSL, or other formats in a single call.
18
+ #
19
+ # Use these constructors when you have color data in non-standard formats.
20
+ #
21
+ # === Example
22
+ #
23
+ #--
24
+ # SPDX-SnippetBegin
25
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
26
+ # SPDX-License-Identifier: MIT-0
27
+ #++
28
+ # # From a hex integer (common in design tools)
29
+ # red = Style::Color.from_u32(0xFF0000)
30
+ # style = Style::Style.new(fg: red)
31
+ #
32
+ # # From HSL (common in color pickers)
33
+ # blue = Style::Color.from_hsl(240, 100, 50)
34
+ # style = Style::Style.new(bg: blue)
35
+ #--
36
+ # SPDX-SnippetEnd
37
+ #++
38
+ module Color
39
+ class << self
40
+ # Creates a color from a 32-bit unsigned integer.
41
+ #
42
+ # Design tools and APIs often represent colors as hex integers.
43
+ # Manually extracting RGB components and formatting is error-prone.
44
+ #
45
+ # This method parses the integer and returns a hex string
46
+ # ready for use with Style.
47
+ #
48
+ # === Example
49
+ #
50
+ #--
51
+ # SPDX-SnippetBegin
52
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
53
+ # SPDX-License-Identifier: MIT-0
54
+ #++
55
+ # Color.from_u32(0xFF0000) # => "#ff0000" (red)
56
+ # Color.from_u32(0x00FF00) # => "#00ff00" (green)
57
+ # Color.from_u32(0x0000FF) # => "#0000ff" (blue)
58
+ #--
59
+ # SPDX-SnippetEnd
60
+ #++
61
+ #
62
+ # [value] Integer in <tt>0xRRGGBB</tt> format.
63
+ #
64
+ # Returns a hex string like <tt>"#rrggbb"</tt>.
65
+ def from_u32(value)
66
+ RatatuiRuby._color_from_u32(value)
67
+ end
68
+
69
+ # Creates a color from HSL (Hue, Saturation, Lightness) values.
70
+ #
71
+ # Color pickers often use HSL because it matches human perception
72
+ # better than RGB. Converting HSL to RGB manually requires math.
73
+ #
74
+ # This method handles the conversion by delegating to Ratatui's
75
+ # palette integration.
76
+ #
77
+ # Note: This implementation uses degrees (0-360) for hue and
78
+ # percentages (0-100) for saturation and lightness, matching
79
+ # common UI conventions.
80
+ #
81
+ # === Example
82
+ #
83
+ #--
84
+ # SPDX-SnippetBegin
85
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
86
+ # SPDX-License-Identifier: MIT-0
87
+ #++
88
+ # Color.from_hsl(0, 100, 50) # => "#ff0000" (red)
89
+ # Color.from_hsl(120, 100, 50) # => "#00ff00" (green)
90
+ # Color.from_hsl(240, 100, 50) # => "#0000ff" (blue)
91
+ # Color.from_hsl(0, 0, 50) # => "#808080" (gray)
92
+ #--
93
+ # SPDX-SnippetEnd
94
+ #++
95
+ #
96
+ # [h] Hue in degrees (0-360). Values wrap automatically.
97
+ # [s] Saturation as percentage (0-100).
98
+ # [l] Lightness as percentage (0-100).
99
+ #
100
+ # Returns a hex string like <tt>"#rrggbb"</tt>.
101
+ def from_hsl(h, s, l)
102
+ RatatuiRuby._color_from_hsl(h.to_f, s.to_f, l.to_f)
103
+ end
104
+
105
+ # Ruby-idiomatic aliases (TIMTOWTDI)
106
+ alias hex from_u32
107
+ alias hsl from_hsl
108
+
109
+ # Creates a color from HSLuv (Human-friendly Hue, Saturation, Lightness) values.
110
+ #
111
+ # HSLuv is a perceptually uniform color space. Unlike standard HSL,
112
+ # colors at the same lightness appear equally bright regardless of hue.
113
+ # This makes it ideal for generating color palettes with consistent
114
+ # perceived brightness.
115
+ #
116
+ # This method delegates to Ratatui's palette integration for the
117
+ # complex HSLuv to RGB conversion.
118
+ #
119
+ # Note: Ratatui uses the range [-180, 180] for hue and [0, 100] for
120
+ # saturation and lightness. This implementation matches those conventions.
121
+ #
122
+ # === Example
123
+ #
124
+ #--
125
+ # SPDX-SnippetBegin
126
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
127
+ # SPDX-License-Identifier: MIT-0
128
+ #++
129
+ # Color.from_hsluv(12.18, 100, 53.2) # => "#ff0000" (bright red)
130
+ # Color.from_hsluv(-94.13, 100, 32.3) # => "#0000ff" (bright blue)
131
+ # Color.from_hsluv(0, 0, 50) # => gray
132
+ #--
133
+ # SPDX-SnippetEnd
134
+ #++
135
+ #
136
+ # [h] Hue in degrees (-180 to 360). Values wrap automatically.
137
+ # [s] Saturation as percentage (0-100). Values are clamped.
138
+ # [l] Lightness as percentage (0-100). Values are clamped.
139
+ #
140
+ # Returns a hex string like <tt>"#rrggbb"</tt>.
141
+ def from_hsluv(h, s, l)
142
+ RatatuiRuby._color_from_hsluv(h.to_f, s.to_f, l.to_f)
143
+ end
144
+
145
+ alias hsluv from_hsluv
146
+ end
147
+ end
148
+ end
149
+ end
@@ -60,7 +60,7 @@ module RatatuiRuby
60
60
  # ==== String
61
61
  # Represents a specific RGB color using a Hex code (<tt>"#RRGGBB"</tt>).
62
62
  # Requires a terminal emulator with "True Color" (24-bit color) support.
63
- class Style < Data.define(:fg, :bg, :modifiers)
63
+ class Style < Data.define(:fg, :bg, :underline_color, :modifiers, :remove_modifiers)
64
64
  ##
65
65
  # :attr_reader: fg
66
66
  # Foreground color.
@@ -73,19 +73,36 @@ module RatatuiRuby
73
73
  #
74
74
  # Symbol (<tt>:black</tt>), Hex String (<tt>"#000000"</tt>), or Integer (0-255).
75
75
 
76
+ ##
77
+ # :attr_reader: underline_color
78
+ # Color of the underline.
79
+ #
80
+ # Symbol (<tt>:red</tt>), Hex String (<tt>"#ff0000"</tt>), or Integer (0-255).
81
+ # Distinct from foreground color. Terminals supporting this feature render
82
+ # the underline in this color while text remains in the foreground color.
83
+
76
84
  ##
77
85
  # :attr_reader: modifiers
78
- # Text effects.
86
+ # Text effects to add.
79
87
  #
80
88
  # Array of symbols: <tt>:bold</tt>, <tt>:dim</tt>, <tt>:italic</tt>, <tt>:underlined</tt>,
81
89
  # <tt>:slow_blink</tt>, <tt>:rapid_blink</tt>, <tt>:reversed</tt>, <tt>:hidden</tt>, <tt>:crossed_out</tt>.
82
90
 
91
+ ##
92
+ # :attr_reader: remove_modifiers
93
+ # Text effects to remove.
94
+ #
95
+ # Array of symbols. When this style is applied, these modifiers are removed
96
+ # from any inherited/patched styles. Corresponds to Ratatui's sub_modifier.
97
+
83
98
  # Creates a new Style.
84
99
  #
85
100
  # [fg] Color (Symbol/String/Integer).
86
101
  # [bg] Color (Symbol/String/Integer).
87
- # [modifiers] Array of Symbols.
88
- def initialize(fg: nil, bg: nil, modifiers: [])
102
+ # [underline_color] Color for underline (Symbol/String/Integer).
103
+ # [modifiers] Array of Symbols to add.
104
+ # [remove_modifiers] Array of Symbols to remove (Ratatui: sub_modifier).
105
+ def initialize(fg: nil, bg: nil, underline_color: nil, modifiers: [], remove_modifiers: [])
89
106
  super
90
107
  end
91
108
 
@@ -95,6 +112,36 @@ module RatatuiRuby
95
112
  def self.default
96
113
  new
97
114
  end
115
+
116
+ # Creates a new Style (convenience alias for {#initialize}).
117
+ #
118
+ # Constructor keyword arguments require typing out the full <tt>Style.new</tt> form.
119
+ # This gets verbose in tight layout code or one-liners.
120
+ #
121
+ # <tt>Style.with</tt> reads more naturally and enables method chaining.
122
+ # It shows intent: "use this style with these properties."
123
+ #
124
+ # Use it for inline styling where conciseness matters.
125
+ #
126
+ # === Examples
127
+ #
128
+ #--
129
+ # SPDX-SnippetBegin
130
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
131
+ # SPDX-License-Identifier: MIT-0
132
+ #++
133
+ # Style.with(fg: :red, bg: :black, modifiers: [:bold])
134
+ # Style.with(fg: :white, modifiers: [:underlined], underline_color: :red)
135
+ # Style.with(modifiers: [:bold], remove_modifiers: [:italic]) # Add bold, remove italic
136
+ # paragraph = Paragraph.new(text: "Alert!", style: Style.with(fg: :red))
137
+ #--
138
+ # SPDX-SnippetEnd
139
+ #++
140
+ #
141
+ # @return [Style]
142
+ def self.with(fg: nil, bg: nil, underline_color: nil, modifiers: [], remove_modifiers: [])
143
+ new(fg:, bg:, underline_color:, modifiers:, remove_modifiers:)
144
+ end
98
145
  end
99
146
  end
100
147
  end
@@ -10,8 +10,10 @@ module RatatuiRuby
10
10
  #
11
11
  # This module mirrors +ratatui::style+ and contains:
12
12
  # - {Style} — Colors and modifiers
13
+ # - {Color} — Color constructors
13
14
  module Style
14
15
  end
15
16
  end
16
17
 
17
18
  require_relative "style/style"
19
+ require_relative "style/color"
@@ -0,0 +1,435 @@
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
+ # Symbol constants from Ratatui's symbols module.
10
+ #
11
+ # Ratatui provides various character sets for rendering UI elements.
12
+ # Hardcoding these characters is error-prone and makes code less readable.
13
+ #
14
+ # This module exposes those constants for Ruby apps to use directly.
15
+ # Use them for gradients, fills, or custom widget styling.
16
+ module Symbols
17
+ # Shade characters for creating gradient or density effects.
18
+ #
19
+ # Terminal UIs often need to show density levels or create visual gradients.
20
+ # Memorizing Unicode block characters is tedious and error-prone.
21
+ #
22
+ # These constants provide named access to the standard shade characters.
23
+ # Use them to fill areas with varying visual densities.
24
+ #
25
+ # === Examples
26
+ #
27
+ #--
28
+ # SPDX-SnippetBegin
29
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
30
+ # SPDX-License-Identifier: MIT-0
31
+ #++
32
+ # # Create a density gradient
33
+ # gradient = [Shade::EMPTY, Shade::LIGHT, Shade::MEDIUM, Shade::DARK, Shade::FULL]
34
+ #
35
+ # # Use in a progress indicator
36
+ # filled = Shade::FULL * progress
37
+ # empty = Shade::LIGHT * (total - progress)
38
+ #--
39
+ # SPDX-SnippetEnd
40
+ #++
41
+ module Shade
42
+ # Empty space - 0% density.
43
+ EMPTY = " "
44
+
45
+ # Light shading - approximately 25% density.
46
+ LIGHT = "░"
47
+
48
+ # Medium shading - approximately 50% density.
49
+ MEDIUM = "▒"
50
+
51
+ # Dark shading - approximately 75% density.
52
+ DARK = "▓"
53
+
54
+ # Full block - 100% density.
55
+ FULL = "█"
56
+ end
57
+
58
+ # Box-drawing characters for borders and lines.
59
+ #
60
+ # Terminal UIs need consistent box-drawing characters for borders and dividers.
61
+ # Memorizing Unicode box-drawing characters is tedious and error-prone.
62
+ #
63
+ # This module exposes both individual characters and predefined sets.
64
+ # Use the sets with widgets that accept a line_set parameter, or use
65
+ # individual characters for custom drawing.
66
+ #
67
+ # === Examples
68
+ #
69
+ #--
70
+ # SPDX-SnippetBegin
71
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
72
+ # SPDX-License-Identifier: MIT-0
73
+ #++
74
+ # # Use a predefined set for drawing boxes
75
+ # line_set = Symbols::Line::ROUNDED
76
+ #
77
+ # # Draw a simple frame
78
+ # top = "#{line_set[:top_left]}#{line_set[:horizontal] * 10}#{line_set[:top_right]}"
79
+ # mid = "#{line_set[:vertical]}#{' ' * 10}#{line_set[:vertical]}"
80
+ # bot = "#{line_set[:bottom_left]}#{line_set[:horizontal] * 10}#{line_set[:bottom_right]}"
81
+ #
82
+ # # Use individual characters for custom drawing
83
+ # corner = Symbols::Line::ROUNDED_TOP_LEFT # => "╭"
84
+ #--
85
+ # SPDX-SnippetEnd
86
+ #++
87
+ module Line
88
+ # Standard vertical line.
89
+ VERTICAL = "│"
90
+ # Double vertical line.
91
+ DOUBLE_VERTICAL = "║"
92
+ # Thick (heavy) vertical line.
93
+ THICK_VERTICAL = "┃"
94
+
95
+ # Standard horizontal line.
96
+ HORIZONTAL = "─"
97
+ # Double horizontal line.
98
+ DOUBLE_HORIZONTAL = "═"
99
+ # Thick (heavy) horizontal line.
100
+ THICK_HORIZONTAL = "━"
101
+
102
+ # Standard top-right corner.
103
+ TOP_RIGHT = "┐"
104
+ # Rounded top-right corner.
105
+ ROUNDED_TOP_RIGHT = "╮"
106
+ # Double top-right corner.
107
+ DOUBLE_TOP_RIGHT = "╗"
108
+ # Thick top-right corner.
109
+ THICK_TOP_RIGHT = "┓"
110
+
111
+ # Standard top-left corner.
112
+ TOP_LEFT = "┌"
113
+ # Rounded top-left corner.
114
+ ROUNDED_TOP_LEFT = "╭"
115
+ # Double top-left corner.
116
+ DOUBLE_TOP_LEFT = "╔"
117
+ # Thick top-left corner.
118
+ THICK_TOP_LEFT = "┏"
119
+
120
+ # Standard bottom-right corner.
121
+ BOTTOM_RIGHT = "┘"
122
+ # Rounded bottom-right corner.
123
+ ROUNDED_BOTTOM_RIGHT = "╯"
124
+ # Double bottom-right corner.
125
+ DOUBLE_BOTTOM_RIGHT = "╝"
126
+ # Thick bottom-right corner.
127
+ THICK_BOTTOM_RIGHT = "┛"
128
+
129
+ # Standard bottom-left corner.
130
+ BOTTOM_LEFT = "└"
131
+ # Rounded bottom-left corner.
132
+ ROUNDED_BOTTOM_LEFT = "╰"
133
+ # Double bottom-left corner.
134
+ DOUBLE_BOTTOM_LEFT = "╚"
135
+ # Thick bottom-left corner.
136
+ THICK_BOTTOM_LEFT = "┗"
137
+
138
+ # Standard T-junction pointing left.
139
+ VERTICAL_LEFT = "┤"
140
+ # Double T-junction pointing left.
141
+ DOUBLE_VERTICAL_LEFT = "╣"
142
+ # Thick T-junction pointing left.
143
+ THICK_VERTICAL_LEFT = "┫"
144
+
145
+ # Standard T-junction pointing right.
146
+ VERTICAL_RIGHT = "├"
147
+ # Double T-junction pointing right.
148
+ DOUBLE_VERTICAL_RIGHT = "╠"
149
+ # Thick T-junction pointing right.
150
+ THICK_VERTICAL_RIGHT = "┣"
151
+
152
+ # Standard T-junction pointing down.
153
+ HORIZONTAL_DOWN = "┬"
154
+ # Double T-junction pointing down.
155
+ DOUBLE_HORIZONTAL_DOWN = "╦"
156
+ # Thick T-junction pointing down.
157
+ THICK_HORIZONTAL_DOWN = "┳"
158
+
159
+ # Standard T-junction pointing up.
160
+ HORIZONTAL_UP = "┴"
161
+ # Double T-junction pointing up.
162
+ DOUBLE_HORIZONTAL_UP = "╩"
163
+ # Thick T-junction pointing up.
164
+ THICK_HORIZONTAL_UP = "┻"
165
+
166
+ # Standard cross (4-way intersection).
167
+ CROSS = "┼"
168
+ # Double cross (4-way intersection).
169
+ DOUBLE_CROSS = "╬"
170
+ # Thick cross (4-way intersection).
171
+ THICK_CROSS = "╋"
172
+
173
+ # Standard box-drawing set with straight corners.
174
+ NORMAL = {
175
+ vertical: VERTICAL,
176
+ horizontal: HORIZONTAL,
177
+ top_right: TOP_RIGHT,
178
+ top_left: TOP_LEFT,
179
+ bottom_right: BOTTOM_RIGHT,
180
+ bottom_left: BOTTOM_LEFT,
181
+ vertical_left: VERTICAL_LEFT,
182
+ vertical_right: VERTICAL_RIGHT,
183
+ horizontal_down: HORIZONTAL_DOWN,
184
+ horizontal_up: HORIZONTAL_UP,
185
+ cross: CROSS,
186
+ }.freeze
187
+
188
+ # Box-drawing set with rounded corners.
189
+ ROUNDED = {
190
+ vertical: VERTICAL,
191
+ horizontal: HORIZONTAL,
192
+ top_right: ROUNDED_TOP_RIGHT,
193
+ top_left: ROUNDED_TOP_LEFT,
194
+ bottom_right: ROUNDED_BOTTOM_RIGHT,
195
+ bottom_left: ROUNDED_BOTTOM_LEFT,
196
+ vertical_left: VERTICAL_LEFT,
197
+ vertical_right: VERTICAL_RIGHT,
198
+ horizontal_down: HORIZONTAL_DOWN,
199
+ horizontal_up: HORIZONTAL_UP,
200
+ cross: CROSS,
201
+ }.freeze
202
+
203
+ # Double-line box-drawing set.
204
+ DOUBLE = {
205
+ vertical: DOUBLE_VERTICAL,
206
+ horizontal: DOUBLE_HORIZONTAL,
207
+ top_right: DOUBLE_TOP_RIGHT,
208
+ top_left: DOUBLE_TOP_LEFT,
209
+ bottom_right: DOUBLE_BOTTOM_RIGHT,
210
+ bottom_left: DOUBLE_BOTTOM_LEFT,
211
+ vertical_left: DOUBLE_VERTICAL_LEFT,
212
+ vertical_right: DOUBLE_VERTICAL_RIGHT,
213
+ horizontal_down: DOUBLE_HORIZONTAL_DOWN,
214
+ horizontal_up: DOUBLE_HORIZONTAL_UP,
215
+ cross: DOUBLE_CROSS,
216
+ }.freeze
217
+
218
+ # Thick (heavy) box-drawing set.
219
+ THICK = {
220
+ vertical: THICK_VERTICAL,
221
+ horizontal: THICK_HORIZONTAL,
222
+ top_right: THICK_TOP_RIGHT,
223
+ top_left: THICK_TOP_LEFT,
224
+ bottom_right: THICK_BOTTOM_RIGHT,
225
+ bottom_left: THICK_BOTTOM_LEFT,
226
+ vertical_left: THICK_VERTICAL_LEFT,
227
+ vertical_right: THICK_VERTICAL_RIGHT,
228
+ horizontal_down: THICK_HORIZONTAL_DOWN,
229
+ horizontal_up: THICK_HORIZONTAL_UP,
230
+ cross: THICK_CROSS,
231
+ }.freeze
232
+ end
233
+
234
+ # Vertical bar characters for sparklines and bar charts.
235
+ #
236
+ # Sparklines and vertical bar charts need characters that show partial fill.
237
+ # Memorizing Unicode lower block characters is tedious and error-prone.
238
+ #
239
+ # This module exposes both individual characters and predefined sets.
240
+ # Use the sets with Sparkline widget, or use individual characters for
241
+ # custom visualizations.
242
+ #
243
+ # === Examples
244
+ #
245
+ #--
246
+ # SPDX-SnippetBegin
247
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
248
+ # SPDX-License-Identifier: MIT-0
249
+ #++
250
+ # # Use NINE_LEVELS for high-resolution sparklines (default)
251
+ # sparkline = tui.sparkline(data: [1, 2, 3], bar_set: Symbols::Bar::NINE_LEVELS)
252
+ #
253
+ # # Use THREE_LEVELS for simpler rendering
254
+ # sparkline = tui.sparkline(data: [1, 2, 3], bar_set: Symbols::Bar::THREE_LEVELS)
255
+ #--
256
+ # SPDX-SnippetEnd
257
+ #++
258
+ module Bar
259
+ # Full height bar (8/8).
260
+ FULL = "█"
261
+ # 7/8 height bar.
262
+ SEVEN_EIGHTHS = "▇"
263
+ # 3/4 height bar (6/8).
264
+ THREE_QUARTERS = "▆"
265
+ # 5/8 height bar.
266
+ FIVE_EIGHTHS = "▅"
267
+ # Half height bar (4/8).
268
+ HALF = "▄"
269
+ # 3/8 height bar.
270
+ THREE_EIGHTHS = "▃"
271
+ # 1/4 height bar (2/8).
272
+ ONE_QUARTER = "▂"
273
+ # 1/8 height bar.
274
+ ONE_EIGHTH = "▁"
275
+
276
+ # High-resolution bar set with 9 distinct levels.
277
+ NINE_LEVELS = {
278
+ full: FULL,
279
+ seven_eighths: SEVEN_EIGHTHS,
280
+ three_quarters: THREE_QUARTERS,
281
+ five_eighths: FIVE_EIGHTHS,
282
+ half: HALF,
283
+ three_eighths: THREE_EIGHTHS,
284
+ one_quarter: ONE_QUARTER,
285
+ one_eighth: ONE_EIGHTH,
286
+ empty: " ",
287
+ }.freeze
288
+
289
+ # Low-resolution bar set with 3 levels (full, half, empty).
290
+ THREE_LEVELS = {
291
+ full: FULL,
292
+ seven_eighths: FULL, # collapsed to full
293
+ three_quarters: HALF, # collapsed to half
294
+ five_eighths: HALF, # collapsed to half
295
+ half: HALF,
296
+ three_eighths: HALF, # collapsed to half
297
+ one_quarter: HALF, # collapsed to half
298
+ one_eighth: " ", # collapsed to empty
299
+ empty: " ",
300
+ }.freeze
301
+ end
302
+
303
+ # Horizontal block characters for gauges and progress indicators.
304
+ #
305
+ # Progress bars and gauges need characters that show partial fill from left to right.
306
+ # Memorizing Unicode left block characters is tedious and error-prone.
307
+ #
308
+ # This module exposes both individual characters and predefined sets.
309
+ # Use the sets with Gauge widget, or use individual characters for
310
+ # custom progress indicators.
311
+ #
312
+ # Note: Block uses LEFT block characters (fill from left) while Bar uses
313
+ # LOWER block characters (fill from bottom). They look similar but are different.
314
+ #
315
+ # === Examples
316
+ #
317
+ #--
318
+ # SPDX-SnippetBegin
319
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
320
+ # SPDX-License-Identifier: MIT-0
321
+ #++
322
+ # # Use NINE_LEVELS for high-resolution gauges (default)
323
+ # gauge = tui.gauge(percent: 50, block_set: Symbols::Block::NINE_LEVELS)
324
+ #
325
+ # # Use THREE_LEVELS for simpler rendering
326
+ # gauge = tui.gauge(percent: 50, block_set: Symbols::Block::THREE_LEVELS)
327
+ #--
328
+ # SPDX-SnippetEnd
329
+ #++
330
+ module Block
331
+ # Full width block (8/8).
332
+ FULL = "█"
333
+ # 7/8 width block.
334
+ SEVEN_EIGHTHS = "▉"
335
+ # 3/4 width block (6/8).
336
+ THREE_QUARTERS = "▊"
337
+ # 5/8 width block.
338
+ FIVE_EIGHTHS = "▋"
339
+ # Half width block (4/8).
340
+ HALF = "▌"
341
+ # 3/8 width block.
342
+ THREE_EIGHTHS = "▍"
343
+ # 1/4 width block (2/8).
344
+ ONE_QUARTER = "▎"
345
+ # 1/8 width block.
346
+ ONE_EIGHTH = "▏"
347
+
348
+ # High-resolution block set with 9 distinct levels.
349
+ NINE_LEVELS = {
350
+ full: FULL,
351
+ seven_eighths: SEVEN_EIGHTHS,
352
+ three_quarters: THREE_QUARTERS,
353
+ five_eighths: FIVE_EIGHTHS,
354
+ half: HALF,
355
+ three_eighths: THREE_EIGHTHS,
356
+ one_quarter: ONE_QUARTER,
357
+ one_eighth: ONE_EIGHTH,
358
+ empty: " ",
359
+ }.freeze
360
+
361
+ # Low-resolution block set with 3 levels (full, half, empty).
362
+ THREE_LEVELS = {
363
+ full: FULL,
364
+ seven_eighths: FULL, # collapsed to full
365
+ three_quarters: HALF, # collapsed to half
366
+ five_eighths: HALF, # collapsed to half
367
+ half: HALF,
368
+ three_eighths: HALF, # collapsed to half
369
+ one_quarter: HALF, # collapsed to half
370
+ one_eighth: " ", # collapsed to empty
371
+ empty: " ",
372
+ }.freeze
373
+ end
374
+
375
+ # Scrollbar symbol sets for the Scrollbar widget.
376
+ #
377
+ # Scrollbars need consistent visual elements: a track, a thumb, and arrow indicators.
378
+ # Memorizing Unicode scroll characters is tedious and error-prone.
379
+ #
380
+ # This module exposes predefined sets with different visual styles.
381
+ # Use them with the Scrollbar widget to customize its appearance.
382
+ #
383
+ # Note: Uses <tt>begin_char</tt> and <tt>end_char</tt> instead of Rust's
384
+ # <tt>begin</tt>/<tt>end</tt> to avoid Ruby reserved word conflicts.
385
+ #
386
+ # === Examples
387
+ #
388
+ #--
389
+ # SPDX-SnippetBegin
390
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
391
+ # SPDX-License-Identifier: MIT-0
392
+ #++
393
+ # # Use DOUBLE_VERTICAL for bold vertical scrollbars (default)
394
+ # scrollbar = tui.scrollbar(symbols: Symbols::Scrollbar::DOUBLE_VERTICAL)
395
+ #
396
+ # # Use VERTICAL for lighter appearance
397
+ # scrollbar = tui.scrollbar(symbols: Symbols::Scrollbar::VERTICAL)
398
+ #--
399
+ # SPDX-SnippetEnd
400
+ #++
401
+ module Scrollbar
402
+ # Double-line vertical scrollbar with triangle arrows.
403
+ DOUBLE_VERTICAL = {
404
+ track: Line::DOUBLE_VERTICAL,
405
+ thumb: Block::FULL,
406
+ begin_char: "▲",
407
+ end_char: "▼",
408
+ }.freeze
409
+
410
+ # Double-line horizontal scrollbar with triangle arrows.
411
+ DOUBLE_HORIZONTAL = {
412
+ track: Line::DOUBLE_HORIZONTAL,
413
+ thumb: Block::FULL,
414
+ begin_char: "◄",
415
+ end_char: "►",
416
+ }.freeze
417
+
418
+ # Single-line vertical scrollbar with arrow characters.
419
+ VERTICAL = {
420
+ track: Line::VERTICAL,
421
+ thumb: Block::FULL,
422
+ begin_char: "↑",
423
+ end_char: "↓",
424
+ }.freeze
425
+
426
+ # Single-line horizontal scrollbar with arrow characters.
427
+ HORIZONTAL = {
428
+ track: Line::HORIZONTAL,
429
+ thumb: Block::FULL,
430
+ begin_char: "←",
431
+ end_char: "→",
432
+ }.freeze
433
+ end
434
+ end
435
+ end
@@ -46,7 +46,7 @@ module RatatuiRuby
46
46
  # SPDX-SnippetEnd
47
47
  #++
48
48
  module SyntheticEvents
49
- @queue = []
49
+ @queue = [] #: Array[Event]
50
50
  @mutex = Mutex.new
51
51
 
52
52
  class << self