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,291 @@
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 Event
10
+ # Reports a mouse interaction.
11
+ #
12
+ # Modern terminals support rich pointer input, but the protocols are complex and varied.
13
+ # Handling clicks, drags, and scrolls requires robust parsing.
14
+ #
15
+ # This event simplifies the complexity. It tells you exactly *what* happened (+kind+),
16
+ # *where* it happened (+x+, +y+), and *which* button was involved.
17
+ #
18
+ # Use this to build interactive UIs. Implement click handlers, draggable sliders, or
19
+ # scrollable viewports with confidence.
20
+ #
21
+ # === Example
22
+ #
23
+ #--
24
+ # SPDX-SnippetBegin
25
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
26
+ # SPDX-License-Identifier: MIT-0
27
+ #++
28
+ # if event.mouse? && event.down? && event.button == "left"
29
+ # puts "Left click at #{event.x}, #{event.y}"
30
+ # end
31
+ #--
32
+ # SPDX-SnippetEnd
33
+ #++
34
+ class Mouse < Event
35
+ # The kind of event (<tt>"down"</tt>, <tt>"up"</tt>, <tt>"drag"</tt>, <tt>"moved"</tt>, <tt>"scroll_up"</tt>, <tt>"scroll_down"</tt>).
36
+ #
37
+ # puts event.kind # => "down"
38
+ attr_reader :kind
39
+ # X coordinate (column).
40
+ #
41
+ # puts event.x # => 10
42
+ attr_reader :x
43
+ # Y coordinate (row).
44
+ #
45
+ # puts event.y # => 5
46
+ attr_reader :y
47
+ # The button pressed (<tt>"left"</tt>, <tt>"right"</tt>, <tt>"middle"</tt>, <tt>"none"</tt>).
48
+ #
49
+ #--
50
+ # SPDX-SnippetBegin
51
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
52
+ # SPDX-License-Identifier: MIT-0
53
+ #++
54
+ # puts event.button # => "left"
55
+ #
56
+ #--
57
+ # SPDX-SnippetEnd
58
+ #++
59
+ # Can be <tt>nil</tt>, which is treated as <tt>"none"</tt>.
60
+ attr_reader :button
61
+ # List of active modifiers.
62
+ #
63
+ # puts event.modifiers # => ["ctrl"]
64
+ attr_reader :modifiers
65
+
66
+ # Returns true for Mouse events.
67
+ #
68
+ #--
69
+ # SPDX-SnippetBegin
70
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
71
+ # SPDX-License-Identifier: MIT-0
72
+ #++
73
+ # event.mouse? # => true
74
+ # event.key? # => false
75
+ # event.resize? # => false
76
+ #--
77
+ # SPDX-SnippetEnd
78
+ #++
79
+ def mouse?
80
+ true
81
+ end
82
+
83
+ # Creates a new Mouse event.
84
+ #
85
+ # [kind]
86
+ # Event kind (String).
87
+ # [x]
88
+ # X coordinate (Integer).
89
+ # [y]
90
+ # Y coordinate (Integer).
91
+ # [button]
92
+ # Button name (String or <tt>nil</tt>).
93
+ # [modifiers]
94
+ # List of modifiers (Array<String>).
95
+ def initialize(kind:, x:, y:, button:, modifiers: [])
96
+ @kind = kind.freeze
97
+ @x = x
98
+ @y = y
99
+ @button = (button || "none").freeze
100
+ @modifiers = modifiers.map(&:freeze).sort.freeze
101
+ end
102
+
103
+ # Returns true if mouse button was pressed down.
104
+ def down?
105
+ @kind == "down"
106
+ end
107
+
108
+ # Returns true if mouse button was released.
109
+ def up?
110
+ @kind == "up"
111
+ end
112
+
113
+ alias mouse_down? down?
114
+ alias mouse_up? up?
115
+
116
+ # Returns true if mouse is being dragged.
117
+ def drag?
118
+ @kind == "drag"
119
+ end
120
+
121
+ # Returns true if scroll wheel moved up.
122
+ def scroll_up?
123
+ @kind == "scroll_up"
124
+ end
125
+
126
+ # Returns true if scroll wheel moved down.
127
+ #
128
+ #--
129
+ # SPDX-SnippetBegin
130
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
131
+ # SPDX-License-Identifier: MIT-0
132
+ #++
133
+ # if event.scroll_down?
134
+ # scroll_offset += 1
135
+ # end
136
+ #--
137
+ # SPDX-SnippetEnd
138
+ #++
139
+ def scroll_down?
140
+ @kind == "scroll_down"
141
+ end
142
+
143
+ # Returns true if event involves the left mouse button.
144
+ def left?
145
+ @button == "left"
146
+ end
147
+
148
+ # Returns true if event involves the right mouse button.
149
+ def right?
150
+ @button == "right"
151
+ end
152
+
153
+ # Returns true if event involves the middle mouse button.
154
+ def middle?
155
+ @button == "middle"
156
+ end
157
+
158
+ alias left_button? left?
159
+ alias right_button? right?
160
+ alias middle_button? middle?
161
+
162
+ # Deconstructs the event for pattern matching.
163
+ #
164
+ #--
165
+ # SPDX-SnippetBegin
166
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
167
+ # SPDX-License-Identifier: MIT-0
168
+ #++
169
+ # case event
170
+ # in type: :mouse, kind: "down", x:, y:
171
+ # puts "Click at #{x}, #{y}"
172
+ # end
173
+ #--
174
+ # SPDX-SnippetEnd
175
+ #++
176
+ def deconstruct_keys(keys)
177
+ { type: :mouse, kind: @kind, x: @x, y: @y, button: @button, modifiers: @modifiers }
178
+ end
179
+
180
+ ##
181
+ # Converts the event to a Symbol representation.
182
+ #
183
+ # The format varies by event type:
184
+ #
185
+ # [Left Button]
186
+ # <tt>:mouse_left_down</tt>, <tt>:mouse_left_up</tt>, <tt>:mouse_left_drag</tt>
187
+ # [Right Button]
188
+ # <tt>:mouse_right_down</tt>, <tt>:mouse_right_up</tt>, <tt>:mouse_right_drag</tt>
189
+ # [Middle Button]
190
+ # <tt>:mouse_middle_down</tt>, <tt>:mouse_middle_up</tt>, <tt>:mouse_middle_drag</tt>
191
+ # [Scroll]
192
+ # <tt>:scroll_up</tt>, <tt>:scroll_down</tt>
193
+ # [Move]
194
+ #--
195
+ # SPDX-SnippetBegin
196
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
197
+ # SPDX-License-Identifier: MIT-0
198
+ #++
199
+ # <tt>:mouse_moved</tt>
200
+ #
201
+ #--
202
+ # SPDX-SnippetEnd
203
+ #++
204
+ # === Example
205
+ #
206
+ #--
207
+ # SPDX-SnippetBegin
208
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
209
+ # SPDX-License-Identifier: MIT-0
210
+ #++
211
+ # event = Event::Mouse.new(kind: "down", x: 10, y: 5, button: "left")
212
+ # event.to_sym # => :mouse_left_down
213
+ #
214
+ # scroll = Event::Mouse.new(kind: "scroll_up", x: 0, y: 0, button: "none")
215
+ # scroll.to_sym # => :scroll_up
216
+ #--
217
+ # SPDX-SnippetEnd
218
+ #++
219
+ def to_sym
220
+ if @kind.start_with?("scroll")
221
+ @kind.to_sym
222
+ elsif @button == "none"
223
+ :"mouse_#{@kind}"
224
+ else
225
+ :"mouse_#{@button}_#{@kind}"
226
+ end
227
+ end
228
+
229
+ ##
230
+ # Compares the event with another object.
231
+ #
232
+ # - If +other+ is a +Symbol+, compares against #to_sym.
233
+ # - If +other+ is a +Mouse+, compares as a value object.
234
+ # - Otherwise, returns +false+.
235
+ #
236
+ # === Example
237
+ #
238
+ #--
239
+ # SPDX-SnippetBegin
240
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
241
+ # SPDX-License-Identifier: MIT-0
242
+ #++
243
+ # if event == :mouse_left_down
244
+ # handle_click(event)
245
+ # end
246
+ #--
247
+ # SPDX-SnippetEnd
248
+ #++
249
+ def ==(other)
250
+ case other
251
+ when Symbol then to_sym == other
252
+ when Mouse then kind == other.kind && x == other.x && y == other.y && button == other.button && modifiers == other.modifiers
253
+ else false
254
+ end
255
+ end
256
+
257
+ alias wheel_up? scroll_up?
258
+ alias wheel_down? scroll_down?
259
+
260
+ # Returns true for any scroll event.
261
+ #
262
+ # event.scroll? # => true for scroll_up or scroll_down
263
+ def scroll?
264
+ scroll_up? || scroll_down?
265
+ end
266
+
267
+ alias primary? left?
268
+ alias secondary? right?
269
+ alias context_menu? right?
270
+ alias aux? middle?
271
+ alias auxiliary? aux?
272
+
273
+ # Returns true for mouse movement without button press.
274
+ #
275
+ # event.moved? # => true for moved (no button)
276
+ def moved?
277
+ @kind == "moved"
278
+ end
279
+
280
+ alias hover? moved?
281
+ alias hovering? moved?
282
+ alias move? moved?
283
+ alias dragging? drag?
284
+
285
+ alias release? up?
286
+ alias released? up?
287
+ alias press? down?
288
+ alias pressed? down?
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,53 @@
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 Event
10
+ # {Null object}[https://en.wikipedia.org/wiki/Null_object_pattern] for absent events.
11
+ #
12
+ # Event loops poll for input 60 times per second. Usually nothing is happening.
13
+ # If <tt>RatatuiRuby.poll_event</tt> returned <tt>nil</tt>, you would need
14
+ # nil-checks: <tt>event&.key?</tt>, <tt>next unless event</tt>.
15
+ #
16
+ # This class eliminates that friction. It responds to every predicate with
17
+ # <tt>false</tt>. Call <tt>none?</tt> to detect it explicitly. Pattern-match on
18
+ # <tt>type: :none</tt> for exhaustive dispatch.
19
+ #
20
+ # Use it to simplify your event loop. No guards. Optional `else` clauses.
21
+ #
22
+ # See Martin Fowler's {Special Case}[https://martinfowler.com/eaaCatalog/specialCase.html] pattern.
23
+ #
24
+ # === Predicate Example
25
+ #
26
+ #--
27
+ # SPDX-SnippetBegin
28
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
29
+ # SPDX-License-Identifier: MIT-0
30
+ #++
31
+ # event = RatatuiRuby.poll_event
32
+ # break if event.ctrl_c?
33
+ # redraw if event.none?
34
+ #
35
+ #--
36
+ # SPDX-SnippetEnd
37
+ #++
38
+ # === Pattern Matching Example
39
+ #
40
+ # redraw if RatatuiRuby.poll_event in type: :none
41
+ class None < Event
42
+ # Returns true for None events.
43
+ def none?
44
+ true
45
+ end
46
+
47
+ # Deconstructs the event for pattern matching.
48
+ def deconstruct_keys(keys)
49
+ { type: :none }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,130 @@
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 Event
10
+ # Encapsulates pasted text.
11
+ #
12
+ # Users frequently paste text into terminals. Without specific handling, a paste appears as
13
+ # a flood of rapid keystrokes, often triggering accidental commands or confusing the input state.
14
+ #
15
+ # This event makes pasting safe. It groups the entire inserted block into a single atomic action.
16
+ #
17
+ # Handle this event to support bulk text insertion cleanly. Insert the +content+ directly into
18
+ # your field or buffer without triggering per-character logic.
19
+ #
20
+ # === Examples
21
+ #
22
+ # Using predicates:
23
+ #--
24
+ # SPDX-SnippetBegin
25
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
26
+ # SPDX-License-Identifier: MIT-0
27
+ #++
28
+ # if event.paste?
29
+ # puts "Pasted: #{event.content}"
30
+ # end
31
+ #
32
+ #--
33
+ # SPDX-SnippetEnd
34
+ #++
35
+ # Using pattern matching:
36
+ #--
37
+ # SPDX-SnippetBegin
38
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
39
+ # SPDX-License-Identifier: MIT-0
40
+ #++
41
+ # case event
42
+ # in type: :paste, content:
43
+ # puts "Pasted: #{content}"
44
+ # end
45
+ #--
46
+ # SPDX-SnippetEnd
47
+ #++
48
+ class Paste < Event
49
+ # The pasted content.
50
+ #
51
+ # puts event.content # => "https://example.com"
52
+ attr_reader :content
53
+
54
+ # Returns true for Paste events.
55
+ #
56
+ #--
57
+ # SPDX-SnippetBegin
58
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
59
+ # SPDX-License-Identifier: MIT-0
60
+ #++
61
+ # event.paste? # => true
62
+ # event.key? # => false
63
+ # event.resize? # => false
64
+ #--
65
+ # SPDX-SnippetEnd
66
+ #++
67
+ def paste?
68
+ true
69
+ end
70
+ alias clipboard? paste?
71
+ alias pasteboard? paste?
72
+ alias pasted? paste?
73
+
74
+ # Creates a new Paste event.
75
+ #
76
+ # [content]
77
+ # Pasted text (String).
78
+ def initialize(content:)
79
+ @content = content.freeze
80
+ end
81
+
82
+ # Deconstructs the event for pattern matching.
83
+ #
84
+ #--
85
+ # SPDX-SnippetBegin
86
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
87
+ # SPDX-License-Identifier: MIT-0
88
+ #++
89
+ # case event
90
+ # in type: :paste, content:
91
+ # puts "User pasted: #{content}"
92
+ # end
93
+ #--
94
+ # SPDX-SnippetEnd
95
+ #++
96
+ def deconstruct_keys(keys)
97
+ { type: :paste, content: @content }
98
+ end
99
+
100
+ ##
101
+ # Compares this event with another for equality.
102
+ def ==(other)
103
+ return false unless other.is_a?(Paste)
104
+ content == other.content
105
+ end
106
+
107
+ # Returns true if the pasted content is empty.
108
+ def empty?
109
+ @content.empty?
110
+ end
111
+
112
+ # Returns true if the pasted content is empty or whitespace-only.
113
+ def blank?
114
+ @content.strip.empty?
115
+ end
116
+
117
+ # Returns true if the pasted content spans multiple lines.
118
+ def multiline?
119
+ @content.include?("\n")
120
+ end
121
+ alias multi_line? multiline?
122
+
123
+ # Returns true if the pasted content is a single line.
124
+ def single_line?
125
+ !multiline?
126
+ end
127
+ alias singleline? single_line?
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,221 @@
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 Event
10
+ # Signals a change in terminal dimensions.
11
+ #
12
+ # The terminal window is dynamic, not static. The user changes its dimensions at will,
13
+ # usually breaking a fixed layout.
14
+ #
15
+ # This event captures the new state. It delivers the updated +width+ and +height+
16
+ # immediately after the change.
17
+ #
18
+ # Use these dimensions to drive your layout logic. Recalculate constraints. Reallocate space.
19
+ # Fill the new canvas completely to maintain a responsive design.
20
+ #
21
+ # === Examples
22
+ #
23
+ # Using predicates:
24
+ #--
25
+ # SPDX-SnippetBegin
26
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
27
+ # SPDX-License-Identifier: MIT-0
28
+ #++
29
+ # if event.resize?
30
+ # puts "Resized to #{event.width}x#{event.height}"
31
+ # end
32
+ #
33
+ #--
34
+ # SPDX-SnippetEnd
35
+ #++
36
+ # Using pattern matching:
37
+ #--
38
+ # SPDX-SnippetBegin
39
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
40
+ # SPDX-License-Identifier: MIT-0
41
+ #++
42
+ # case event
43
+ # in type: :resize, width:, height:
44
+ # puts "Resized to #{width}x#{height}"
45
+ # end
46
+ #--
47
+ # SPDX-SnippetEnd
48
+ #++
49
+ class Resize < Event
50
+ # New terminal width in columns.
51
+ #
52
+ # puts event.width # => 80
53
+ attr_reader :width
54
+
55
+ # New terminal height in rows.
56
+ #
57
+ # puts event.height # => 24
58
+ attr_reader :height
59
+
60
+ # Returns true for Resize events.
61
+ #
62
+ #--
63
+ # SPDX-SnippetBegin
64
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
65
+ # SPDX-License-Identifier: MIT-0
66
+ #++
67
+ # event.resize? # => true
68
+ # event.key? # => false
69
+ # event.mouse? # => false
70
+ #--
71
+ # SPDX-SnippetEnd
72
+ #++
73
+ def resize?
74
+ true
75
+ end
76
+
77
+ # Creates a new Resize event.
78
+ #
79
+ # [width]
80
+ # New width (Integer).
81
+ # [height]
82
+ # New height (Integer).
83
+ def initialize(width:, height:)
84
+ @width = width
85
+ @height = height
86
+ end
87
+
88
+ # Deconstructs the event for pattern matching.
89
+ #
90
+ #--
91
+ # SPDX-SnippetBegin
92
+ # SPDX-FileCopyrightText: 2025 Kerrick Long
93
+ # SPDX-License-Identifier: MIT-0
94
+ #++
95
+ # case event
96
+ # in type: :resize, width:, height:
97
+ # puts "Resized to #{width}x#{height}"
98
+ # end
99
+ #--
100
+ # SPDX-SnippetEnd
101
+ #++
102
+ def deconstruct_keys(keys)
103
+ { type: :resize, width: @width, height: @height }
104
+ end
105
+
106
+ ##
107
+ # Converts the event to a Symbol representation.
108
+ #
109
+ # Always returns <tt>:resize</tt>.
110
+ #
111
+ # === Example
112
+ #
113
+ #--
114
+ # SPDX-SnippetBegin
115
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
116
+ # SPDX-License-Identifier: MIT-0
117
+ #++
118
+ # event = Event::Resize.new(width: 80, height: 24)
119
+ # event.to_sym # => :resize
120
+ #--
121
+ # SPDX-SnippetEnd
122
+ #++
123
+ def to_sym
124
+ :resize
125
+ end
126
+
127
+ ##
128
+ # Compares the event with another object.
129
+ #
130
+ # - If +other+ is a +Symbol+, compares against #to_sym.
131
+ # - If +other+ is a +Resize+, compares as a value object.
132
+ # - Otherwise, returns +false+.
133
+ #
134
+ # === Example
135
+ #
136
+ #--
137
+ # SPDX-SnippetBegin
138
+ # SPDX-FileCopyrightText: 2026 Kerrick Long
139
+ # SPDX-License-Identifier: MIT-0
140
+ #++
141
+ # if event == :resize
142
+ # handle_resize(event)
143
+ # end
144
+ #--
145
+ # SPDX-SnippetEnd
146
+ #++
147
+ def ==(other)
148
+ case other
149
+ when Symbol then to_sym == other
150
+ when Resize then width == other.width && height == other.height
151
+ else false
152
+ end
153
+ end
154
+
155
+ # Returns true. Unix <tt>SIGWINCH</tt> triggers terminal resize.
156
+ #
157
+ # event.sigwinch? # => true
158
+ def sigwinch?
159
+ true
160
+ end
161
+
162
+ alias winch? sigwinch?
163
+ alias sig_winch? sigwinch?
164
+
165
+ alias terminal_resize? resize?
166
+ alias window_resize? resize?
167
+ alias window_change? resize?
168
+ alias viewport_resize? resize?
169
+ alias viewport_change? resize?
170
+ alias size_change? resize?
171
+ alias resized? resize?
172
+
173
+ VT100_WIDTH = 80 # :nodoc:
174
+ VT100_HEIGHT = 24 # :nodoc:
175
+
176
+ # Returns true if width exceeds height.
177
+ #
178
+ # Event::Resize.new(width: 120, height: 24).landscape? # => true
179
+ def landscape?
180
+ @width > @height
181
+ end
182
+
183
+ # Returns true if height exceeds or equals width.
184
+ #
185
+ # Event::Resize.new(width: 40, height: 80).portrait? # => true
186
+ def portrait?
187
+ @height >= @width
188
+ end
189
+
190
+ # Returns true if dimensions are exactly 80x24.
191
+ #
192
+ # Event::Resize.new(width: 80, height: 24).vt100? # => true
193
+ def vt100?
194
+ @width == VT100_WIDTH && @height == VT100_HEIGHT
195
+ end
196
+
197
+ # Returns true if both dimensions meet or exceed 80x24.
198
+ #
199
+ # Event::Resize.new(width: 120, height: 40).at_least_vt100? # => true
200
+ def at_least_vt100?
201
+ @width >= VT100_WIDTH && @height >= VT100_HEIGHT
202
+ end
203
+
204
+ # Returns true if both dimensions exceed 80x24.
205
+ #
206
+ # Event::Resize.new(width: 81, height: 25).over_vt100? # => true
207
+ def over_vt100?
208
+ @width > VT100_WIDTH && @height > VT100_HEIGHT
209
+ end
210
+
211
+ # Returns true if either dimension falls below VT100 standard.
212
+ #
213
+ # Event::Resize.new(width: 60, height: 24).cramped? # => true
214
+ def cramped?
215
+ @width < VT100_WIDTH || @height < VT100_HEIGHT
216
+ end
217
+
218
+ alias constrained? cramped?
219
+ end
220
+ end
221
+ end