ratatui_ruby 1.3.0 → 1.3.3

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 (301) hide show
  1. checksums.yaml +4 -4
  2. data/ext/ratatui_ruby/Cargo.lock +2 -1
  3. data/ext/ratatui_ruby/Cargo.toml +2 -1
  4. data/ext/ratatui_ruby/src/color.rs +1 -1
  5. data/ext/ratatui_ruby/src/errors.rs +1 -1
  6. data/ext/ratatui_ruby/src/events.rs +158 -19
  7. data/ext/ratatui_ruby/src/frame.rs +1 -1
  8. data/ext/ratatui_ruby/src/lib.rs +1 -1
  9. data/ext/ratatui_ruby/src/lib_header.rs +1 -1
  10. data/ext/ratatui_ruby/src/rendering.rs +1 -1
  11. data/ext/ratatui_ruby/src/string_width.rs +1 -1
  12. data/ext/ratatui_ruby/src/style.rs +1 -1
  13. data/ext/ratatui_ruby/src/terminal/capabilities.rs +1 -1
  14. data/ext/ratatui_ruby/src/terminal/init.rs +1 -1
  15. data/ext/ratatui_ruby/src/terminal/mod.rs +1 -1
  16. data/ext/ratatui_ruby/src/terminal/mutations.rs +1 -1
  17. data/ext/ratatui_ruby/src/terminal/queries.rs +1 -1
  18. data/ext/ratatui_ruby/src/terminal/query.rs +1 -1
  19. data/ext/ratatui_ruby/src/terminal/storage.rs +1 -1
  20. data/ext/ratatui_ruby/src/terminal/wrapper.rs +1 -1
  21. data/ext/ratatui_ruby/src/text.rs +1 -1
  22. data/ext/ratatui_ruby/src/widgets/barchart.rs +1 -1
  23. data/ext/ratatui_ruby/src/widgets/block.rs +1 -1
  24. data/ext/ratatui_ruby/src/widgets/calendar.rs +1 -1
  25. data/ext/ratatui_ruby/src/widgets/canvas.rs +1 -1
  26. data/ext/ratatui_ruby/src/widgets/center.rs +1 -1
  27. data/ext/ratatui_ruby/src/widgets/chart.rs +1 -1
  28. data/ext/ratatui_ruby/src/widgets/clear.rs +1 -1
  29. data/ext/ratatui_ruby/src/widgets/cursor.rs +1 -1
  30. data/ext/ratatui_ruby/src/widgets/gauge.rs +1 -1
  31. data/ext/ratatui_ruby/src/widgets/layout.rs +1 -1
  32. data/ext/ratatui_ruby/src/widgets/line_gauge.rs +1 -1
  33. data/ext/ratatui_ruby/src/widgets/list.rs +1 -1
  34. data/ext/ratatui_ruby/src/widgets/list_state.rs +1 -1
  35. data/ext/ratatui_ruby/src/widgets/mod.rs +1 -1
  36. data/ext/ratatui_ruby/src/widgets/overlay.rs +1 -1
  37. data/ext/ratatui_ruby/src/widgets/paragraph.rs +1 -1
  38. data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +1 -1
  39. data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +1 -1
  40. data/ext/ratatui_ruby/src/widgets/scrollbar.rs +1 -1
  41. data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +1 -1
  42. data/ext/ratatui_ruby/src/widgets/sparkline.rs +1 -1
  43. data/ext/ratatui_ruby/src/widgets/table.rs +1 -1
  44. data/ext/ratatui_ruby/src/widgets/table_state.rs +1 -1
  45. data/ext/ratatui_ruby/src/widgets/tabs.rs +1 -1
  46. data/lib/ratatui_ruby/version.rb +1 -1
  47. metadata +1 -255
  48. data/.builds/ruby-3.2.yml +0 -54
  49. data/.builds/ruby-3.3.yml +0 -54
  50. data/.builds/ruby-3.4.yml +0 -54
  51. data/.builds/ruby-4.0.0.yml +0 -54
  52. data/.pre-commit-config.yaml +0 -16
  53. data/.rubocop.yml +0 -10
  54. data/AGENTS.md +0 -147
  55. data/CHANGELOG.md +0 -771
  56. data/README.md +0 -187
  57. data/README.rdoc +0 -302
  58. data/Rakefile +0 -11
  59. data/Steepfile +0 -50
  60. data/doc/concepts/application_architecture.md +0 -321
  61. data/doc/concepts/application_testing.md +0 -193
  62. data/doc/concepts/async.md +0 -190
  63. data/doc/concepts/custom_widgets.md +0 -247
  64. data/doc/concepts/debugging.md +0 -401
  65. data/doc/concepts/event_handling.md +0 -162
  66. data/doc/concepts/interactive_design.md +0 -146
  67. data/doc/contributors/auditing/parity.md +0 -239
  68. data/doc/contributors/design/ruby_frontend.md +0 -448
  69. data/doc/contributors/design/rust_backend.md +0 -434
  70. data/doc/contributors/design.md +0 -11
  71. data/doc/contributors/developing_examples.md +0 -400
  72. data/doc/contributors/documentation_style.md +0 -121
  73. data/doc/contributors/index.md +0 -21
  74. data/doc/contributors/releasing.md +0 -215
  75. data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -381
  76. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -200
  77. data/doc/contributors/todo/align/term.md +0 -351
  78. data/doc/contributors/todo/align/terminal.md +0 -647
  79. data/doc/contributors/todo/future_work.md +0 -169
  80. data/doc/contributors/upstream_requests/paragraph_span_rects.md +0 -259
  81. data/doc/contributors/upstream_requests/tab_rects.md +0 -173
  82. data/doc/contributors/upstream_requests/title_rects.md +0 -132
  83. data/doc/custom.css +0 -22
  84. data/doc/getting_started/quickstart.md +0 -291
  85. data/doc/getting_started/why.md +0 -93
  86. data/doc/images/app_all_events.png +0 -0
  87. data/doc/images/app_cli_rich_moments.gif +0 -0
  88. data/doc/images/app_color_picker.png +0 -0
  89. data/doc/images/app_debugging_showcase.gif +0 -0
  90. data/doc/images/app_debugging_showcase.png +0 -0
  91. data/doc/images/app_external_editor.gif +0 -0
  92. data/doc/images/app_login_form.png +0 -0
  93. data/doc/images/app_stateful_interaction.png +0 -0
  94. data/doc/images/verify_quickstart_dsl.png +0 -0
  95. data/doc/images/verify_quickstart_layout.png +0 -0
  96. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  97. data/doc/images/verify_readme_usage.png +0 -0
  98. data/doc/images/widget_barchart.png +0 -0
  99. data/doc/images/widget_block.png +0 -0
  100. data/doc/images/widget_box.png +0 -0
  101. data/doc/images/widget_calendar.png +0 -0
  102. data/doc/images/widget_canvas.png +0 -0
  103. data/doc/images/widget_cell.png +0 -0
  104. data/doc/images/widget_center.png +0 -0
  105. data/doc/images/widget_chart.png +0 -0
  106. data/doc/images/widget_gauge.png +0 -0
  107. data/doc/images/widget_layout_split.png +0 -0
  108. data/doc/images/widget_line_gauge.png +0 -0
  109. data/doc/images/widget_list.png +0 -0
  110. data/doc/images/widget_map.png +0 -0
  111. data/doc/images/widget_overlay.png +0 -0
  112. data/doc/images/widget_popup.png +0 -0
  113. data/doc/images/widget_ratatui_logo.png +0 -0
  114. data/doc/images/widget_ratatui_mascot.png +0 -0
  115. data/doc/images/widget_rect.png +0 -0
  116. data/doc/images/widget_render.png +0 -0
  117. data/doc/images/widget_rich_text.png +0 -0
  118. data/doc/images/widget_scroll_text.png +0 -0
  119. data/doc/images/widget_scrollbar.png +0 -0
  120. data/doc/images/widget_sparkline.png +0 -0
  121. data/doc/images/widget_style_colors.png +0 -0
  122. data/doc/images/widget_table.png +0 -0
  123. data/doc/images/widget_tabs.png +0 -0
  124. data/doc/images/widget_text_width.png +0 -0
  125. data/doc/index.md +0 -34
  126. data/doc/troubleshooting/async.md +0 -4
  127. data/doc/troubleshooting/terminal_limitations.md +0 -131
  128. data/doc/troubleshooting/tui_output.md +0 -197
  129. data/examples/app_all_events/README.md +0 -114
  130. data/examples/app_all_events/app.rb +0 -98
  131. data/examples/app_all_events/model/app_model.rb +0 -159
  132. data/examples/app_all_events/model/event_color_cycle.rb +0 -43
  133. data/examples/app_all_events/model/event_entry.rb +0 -94
  134. data/examples/app_all_events/model/msg.rb +0 -39
  135. data/examples/app_all_events/model/timestamp.rb +0 -56
  136. data/examples/app_all_events/update.rb +0 -75
  137. data/examples/app_all_events/view/app_view.rb +0 -80
  138. data/examples/app_all_events/view/controls_view.rb +0 -54
  139. data/examples/app_all_events/view/counts_view.rb +0 -61
  140. data/examples/app_all_events/view/live_view.rb +0 -72
  141. data/examples/app_all_events/view/log_view.rb +0 -57
  142. data/examples/app_all_events/view.rb +0 -9
  143. data/examples/app_cli_rich_moments/README.md +0 -81
  144. data/examples/app_cli_rich_moments/app.rb +0 -189
  145. data/examples/app_color_picker/README.md +0 -156
  146. data/examples/app_color_picker/app.rb +0 -76
  147. data/examples/app_color_picker/clipboard.rb +0 -86
  148. data/examples/app_color_picker/color.rb +0 -193
  149. data/examples/app_color_picker/controls.rb +0 -92
  150. data/examples/app_color_picker/copy_dialog.rb +0 -168
  151. data/examples/app_color_picker/export_pane.rb +0 -128
  152. data/examples/app_color_picker/harmony.rb +0 -58
  153. data/examples/app_color_picker/input.rb +0 -176
  154. data/examples/app_color_picker/main_container.rb +0 -180
  155. data/examples/app_color_picker/palette.rb +0 -111
  156. data/examples/app_debugging_showcase/README.md +0 -119
  157. data/examples/app_debugging_showcase/app.rb +0 -318
  158. data/examples/app_external_editor/README.md +0 -62
  159. data/examples/app_external_editor/app.rb +0 -344
  160. data/examples/app_login_form/README.md +0 -58
  161. data/examples/app_login_form/app.rb +0 -109
  162. data/examples/app_stateful_interaction/README.md +0 -35
  163. data/examples/app_stateful_interaction/app.rb +0 -328
  164. data/examples/timeout_demo.rb +0 -45
  165. data/examples/verify_quickstart_dsl/README.md +0 -55
  166. data/examples/verify_quickstart_dsl/app.rb +0 -49
  167. data/examples/verify_quickstart_layout/README.md +0 -77
  168. data/examples/verify_quickstart_layout/app.rb +0 -73
  169. data/examples/verify_quickstart_lifecycle/README.md +0 -68
  170. data/examples/verify_quickstart_lifecycle/app.rb +0 -62
  171. data/examples/verify_readme_usage/README.md +0 -49
  172. data/examples/verify_readme_usage/app.rb +0 -42
  173. data/examples/verify_website_managed/README.md +0 -48
  174. data/examples/verify_website_managed/app.rb +0 -36
  175. data/examples/verify_website_menu/README.md +0 -60
  176. data/examples/verify_website_menu/app.rb +0 -84
  177. data/examples/verify_website_spinner/README.md +0 -44
  178. data/examples/verify_website_spinner/app.rb +0 -34
  179. data/examples/widget_barchart/README.md +0 -58
  180. data/examples/widget_barchart/app.rb +0 -240
  181. data/examples/widget_block/README.md +0 -44
  182. data/examples/widget_block/app.rb +0 -258
  183. data/examples/widget_box/README.md +0 -54
  184. data/examples/widget_box/app.rb +0 -255
  185. data/examples/widget_calendar/README.md +0 -48
  186. data/examples/widget_calendar/app.rb +0 -115
  187. data/examples/widget_canvas/README.md +0 -31
  188. data/examples/widget_canvas/app.rb +0 -130
  189. data/examples/widget_cell/README.md +0 -45
  190. data/examples/widget_cell/app.rb +0 -112
  191. data/examples/widget_center/README.md +0 -33
  192. data/examples/widget_center/app.rb +0 -118
  193. data/examples/widget_chart/README.md +0 -50
  194. data/examples/widget_chart/app.rb +0 -220
  195. data/examples/widget_gauge/README.md +0 -50
  196. data/examples/widget_gauge/app.rb +0 -229
  197. data/examples/widget_layout_split/README.md +0 -53
  198. data/examples/widget_layout_split/app.rb +0 -260
  199. data/examples/widget_line_gauge/README.md +0 -50
  200. data/examples/widget_line_gauge/app.rb +0 -219
  201. data/examples/widget_list/README.md +0 -58
  202. data/examples/widget_list/app.rb +0 -382
  203. data/examples/widget_map/README.md +0 -48
  204. data/examples/widget_map/app.rb +0 -95
  205. data/examples/widget_overlay/README.md +0 -45
  206. data/examples/widget_overlay/app.rb +0 -250
  207. data/examples/widget_popup/README.md +0 -45
  208. data/examples/widget_popup/app.rb +0 -106
  209. data/examples/widget_ratatui_logo/README.md +0 -43
  210. data/examples/widget_ratatui_logo/app.rb +0 -104
  211. data/examples/widget_ratatui_mascot/README.md +0 -43
  212. data/examples/widget_ratatui_mascot/app.rb +0 -95
  213. data/examples/widget_rect/README.md +0 -53
  214. data/examples/widget_rect/app.rb +0 -222
  215. data/examples/widget_render/README.md +0 -46
  216. data/examples/widget_render/app.rb +0 -186
  217. data/examples/widget_render/app.rbs +0 -41
  218. data/examples/widget_rich_text/README.md +0 -44
  219. data/examples/widget_rich_text/app.rb +0 -193
  220. data/examples/widget_scroll_text/README.md +0 -46
  221. data/examples/widget_scroll_text/app.rb +0 -109
  222. data/examples/widget_scrollbar/README.md +0 -46
  223. data/examples/widget_scrollbar/app.rb +0 -155
  224. data/examples/widget_sparkline/README.md +0 -51
  225. data/examples/widget_sparkline/app.rb +0 -277
  226. data/examples/widget_style_colors/README.md +0 -43
  227. data/examples/widget_style_colors/app.rb +0 -83
  228. data/examples/widget_table/README.md +0 -57
  229. data/examples/widget_table/app.rb +0 -285
  230. data/examples/widget_tabs/README.md +0 -50
  231. data/examples/widget_tabs/app.rb +0 -183
  232. data/examples/widget_text_width/README.md +0 -44
  233. data/examples/widget_text_width/app.rb +0 -117
  234. data/migrate_to_buffer.rb +0 -145
  235. data/mise.toml +0 -8
  236. data/tasks/autodoc/examples.rb +0 -87
  237. data/tasks/autodoc/member.rb +0 -58
  238. data/tasks/autodoc/name.rb +0 -21
  239. data/tasks/autodoc.rake +0 -21
  240. data/tasks/bump/bump_workflow.rb +0 -49
  241. data/tasks/bump/cargo_lockfile.rb +0 -21
  242. data/tasks/bump/changelog.rb +0 -104
  243. data/tasks/bump/header.rb +0 -32
  244. data/tasks/bump/history.rb +0 -32
  245. data/tasks/bump/links.rb +0 -69
  246. data/tasks/bump/manifest.rb +0 -33
  247. data/tasks/bump/patch_release.rb +0 -19
  248. data/tasks/bump/release_branch.rb +0 -17
  249. data/tasks/bump/release_from_trunk.rb +0 -49
  250. data/tasks/bump/repository.rb +0 -54
  251. data/tasks/bump/ruby_gem.rb +0 -29
  252. data/tasks/bump/sem_ver.rb +0 -44
  253. data/tasks/bump/unreleased_section.rb +0 -73
  254. data/tasks/bump.rake +0 -61
  255. data/tasks/doc/documentation.rb +0 -59
  256. data/tasks/doc/link/file_url.rb +0 -30
  257. data/tasks/doc/link/relative_path.rb +0 -61
  258. data/tasks/doc/link/web_url.rb +0 -55
  259. data/tasks/doc/link.rb +0 -52
  260. data/tasks/doc/link_audit.rb +0 -116
  261. data/tasks/doc/problem.rb +0 -40
  262. data/tasks/doc/source_file.rb +0 -93
  263. data/tasks/doc.rake +0 -905
  264. data/tasks/example_viewer.html.erb +0 -172
  265. data/tasks/extension.rake +0 -14
  266. data/tasks/license/headers_md.rb +0 -223
  267. data/tasks/license/headers_rb.rb +0 -210
  268. data/tasks/license/license_utils.rb +0 -130
  269. data/tasks/license/snippets_md.rb +0 -315
  270. data/tasks/license/snippets_rdoc.rb +0 -150
  271. data/tasks/license.rake +0 -91
  272. data/tasks/lint.rake +0 -170
  273. data/tasks/rbs_predicates/predicate_catalog.rb +0 -52
  274. data/tasks/rbs_predicates/predicate_tests.rb +0 -124
  275. data/tasks/rbs_predicates/rbs_signature.rb +0 -63
  276. data/tasks/rbs_predicates.rake +0 -31
  277. data/tasks/rdoc_config.rb +0 -29
  278. data/tasks/resources/build.yml.erb +0 -60
  279. data/tasks/resources/index.html.erb +0 -141
  280. data/tasks/resources/rubies.yml +0 -7
  281. data/tasks/sourcehut.rake +0 -122
  282. data/tasks/steep.rake +0 -11
  283. data/tasks/terminal_preview/app_screenshot.rb +0 -45
  284. data/tasks/terminal_preview/crash_report.rb +0 -54
  285. data/tasks/terminal_preview/example_app.rb +0 -27
  286. data/tasks/terminal_preview/launcher_script.rb +0 -48
  287. data/tasks/terminal_preview/preview_collection.rb +0 -60
  288. data/tasks/terminal_preview/preview_timing.rb +0 -24
  289. data/tasks/terminal_preview/safety_confirmation.rb +0 -58
  290. data/tasks/terminal_preview/saved_screenshot.rb +0 -56
  291. data/tasks/terminal_preview/system_appearance.rb +0 -13
  292. data/tasks/terminal_preview/terminal_window.rb +0 -138
  293. data/tasks/terminal_preview/window_id.rb +0 -16
  294. data/tasks/terminal_preview.rake +0 -30
  295. data/tasks/test.rake +0 -36
  296. data/tasks/website/index_page.rb +0 -30
  297. data/tasks/website/version.rb +0 -122
  298. data/tasks/website/version_menu.rb +0 -68
  299. data/tasks/website/versioned_documentation.rb +0 -83
  300. data/tasks/website/website.rb +0 -53
  301. data/tasks/website.rake +0 -28
@@ -1,159 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
6
- #++
7
-
8
- require_relative "timestamp"
9
- require_relative "event_entry"
10
- require_relative "event_color_cycle"
11
-
12
- # Immutable application state for the Model-View-Update architecture.
13
- #
14
- # The Elm Architecture requires a single immutable Model. State changes return
15
- # a new Model instance. This consolidates all app state into one place.
16
- #
17
- # Use `AppModel.initial` to create the starting state, and `model.with(...)`
18
- # to create updated states.
19
- #
20
- # === Attributes
21
- #
22
- # [entries] Array of EventEntry objects (event log)
23
- # [focused] Boolean window focus state
24
- # [window_size] Array [width, height] of terminal dimensions
25
- # [lit_types] Hash mapping event types to Timestamp (for highlight expiry)
26
- # [none_count] Integer count of :none events (not logged)
27
- # [color_cycle_index] Integer index into EventColorCycle::COLORS
28
- #
29
- # === Example
30
- #
31
- # model = AppModel.initial
32
- # model.count(:key) #=> 0
33
- # model.focused #=> true
34
- class AppModel < Data.define(:entries, :focused, :window_size, :lit_types, :none_count, :color_cycle_index)
35
- # Highlight duration in milliseconds.
36
- HIGHLIGHT_DURATION_MS = 300
37
-
38
- # Creates the initial application state.
39
- #
40
- # === Example
41
- #
42
- # AppModel.initial #=> #<data AppModel entries=[] focused=true ...>
43
- def self.initial
44
- new(
45
- entries: [],
46
- focused: true,
47
- window_size: [80, 24],
48
- lit_types: {},
49
- none_count: 0,
50
- color_cycle_index: 0
51
- )
52
- end
53
-
54
- # Returns the count of events for a given type.
55
- #
56
- # [type] Symbol event type (:key, :mouse, :resize, :paste, :focus, :none)
57
- #
58
- # === Example
59
- #
60
- # model.count(:key) #=> 5
61
- def count(type)
62
- return none_count if type == :none
63
-
64
- entries.count { |e| e.matches_type?(type) }
65
- end
66
-
67
- # Returns counts grouped by subtype (kind or modifier status).
68
- #
69
- # [type] Symbol event type.
70
- #
71
- # === Example
72
- #
73
- # model.sub_counts(:mouse) #=> { "down" => 1, "up" => 2 }
74
- def sub_counts(type)
75
- return {} if type == :none
76
-
77
- matching = entries.select { |e| e.matches_type?(type) }
78
- defaults = {
79
- key: %w[standard function media system modifier],
80
- focus: %w[gained lost],
81
- mouse: %w[down up drag moved scroll_up scroll_down],
82
- }
83
-
84
- matching.each_with_object(defaults.fetch(type, []).to_h { |k| [k, 0] }) do |entry, counts|
85
- group = subtype_for(entry, type)
86
- counts[group] += 1 if group
87
- end
88
- end
89
-
90
- # Checks if an event type should be highlighted.
91
- #
92
- # [type] Symbol event type.
93
- #
94
- # === Example
95
- #
96
- # model.lit?(:key) #=> true
97
- def lit?(type)
98
- timestamp = lit_types[type]
99
- return false unless timestamp
100
-
101
- !timestamp.elapsed?(HIGHLIGHT_DURATION_MS)
102
- end
103
-
104
- # Returns the most recent entries up to the given limit.
105
- #
106
- # [max_entries] Integer maximum number of entries to return.
107
- #
108
- # === Example
109
- #
110
- # model.visible(10) #=> [#<EventEntry ...>, ...]
111
- def visible(max_entries)
112
- entries.last(max_entries)
113
- end
114
-
115
- # Checks if any events have been recorded.
116
- #
117
- # === Example
118
- #
119
- # model.empty? #=> true
120
- def empty?
121
- entries.empty?
122
- end
123
-
124
- # Returns the most recent live event data for a type.
125
- #
126
- # [type] Symbol event type.
127
- #
128
- # === Example
129
- #
130
- # model.live_event(:key) #=> { time: Time, description: "..." }
131
- def live_event(type)
132
- entry = entries.reverse.find { |e| e.live_type == type }
133
- return nil unless entry
134
-
135
- { time: Time.at(entry.timestamp.milliseconds / 1000.0), description: entry.description }
136
- end
137
-
138
- # Returns the next color in the cycle for a new event.
139
- #
140
- # === Example
141
- #
142
- # model.next_color #=> :cyan
143
- def next_color
144
- EventColorCycle::COLORS[color_cycle_index]
145
- end
146
-
147
- private def subtype_for(entry, type)
148
- case type
149
- when :key
150
- # Key events: group by category kind (standard/function/media/modifier/system)
151
- entry.event.kind.to_s if entry.event.respond_to?(:kind)
152
- when :mouse
153
- # Mouse events: group by event kind (down/up/drag/moved/scroll_up/scroll_down)
154
- entry.event.kind.to_s if entry.event.respond_to?(:kind)
155
- when :focus
156
- entry.type.to_s.sub("focus_", "")
157
- end
158
- end
159
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
6
- #++
7
-
8
- # Cycles through a set of colors for event logging.
9
- #
10
- # Sequential events in a log are hard to distinguish if they all look the same.
11
- # Manually assigning colors to every event type or entry is repetitive.
12
- #
13
- # This class automatically cycles through a predefined list of vibrant colors.
14
- #
15
- # Use it to give each event in a log a distinct visual identity.
16
- #
17
- # === Examples
18
- #
19
- # cycler = EventColorCycle.new
20
- # cycler.next_color #=> :cyan
21
- # cycler.next_color #=> :magenta
22
- # cycler.next_color #=> :yellow
23
- # cycler.next_color #=> :cyan
24
- class EventColorCycle
25
- # List of colors to cycle through.
26
- COLORS = %i[cyan magenta yellow].freeze
27
-
28
- # Creates a new EventColorCycle.
29
- def initialize
30
- @index = 0
31
- end
32
-
33
- # Returns the next color in the cycle.
34
- #
35
- # === Example
36
- #
37
- # cycler.next_color #=> :cyan
38
- def next_color
39
- color = COLORS[@index]
40
- @index = (@index + 1) % COLORS.length
41
- color
42
- end
43
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
6
- #++
7
-
8
- require_relative "timestamp"
9
- require "ratatui_ruby"
10
-
11
- # Stores details about a single event in the history log.
12
- #
13
- # Event logs need to store diverse data including types, keys, colors, and timestamps.
14
- # Managing loose hashes or arrays for event history is error-prone and hard to query.
15
- #
16
- # This class provides a structured data object for every recorded event.
17
- #
18
- # Use it to represent mouse clicks, key presses, or resize events in a log.
19
- #
20
- # === Examples
21
- #
22
- # # Typically created via Events.record
23
- # entry = EventEntry.create(key_event, :cyan, Timestamp.now)
24
- # puts entry.type #=> :key
25
- # puts entry.description #=> '#<RatatuiRuby::Event::Key ...>'
26
- class EventEntry < Data.define(:event, :color, :timestamp)
27
- # Creates a new EventEntry.
28
- #
29
- # [event] RatatuiRuby::Event object.
30
- # [color] Symbol color for the log display.
31
- # [timestamp] Timestamp of when the event occurred.
32
- def self.create(event, color, timestamp)
33
- new(
34
- event:,
35
- color:,
36
- timestamp:
37
- )
38
- end
39
-
40
- # Returns the event type.
41
- #
42
- # === Example
43
- #
44
- # entry.type #=> :key
45
- def type
46
- case event
47
- when RatatuiRuby::Event::Key then :key
48
- when RatatuiRuby::Event::Mouse then :mouse
49
- when RatatuiRuby::Event::Resize then :resize
50
- when RatatuiRuby::Event::Paste then :paste
51
- when RatatuiRuby::Event::FocusGained then :focus_gained
52
- when RatatuiRuby::Event::FocusLost then :focus_lost
53
- else :unknown
54
- end
55
- end
56
-
57
- # Returns the event description using inspect.
58
- #
59
- # === Example
60
- #
61
- # entry.description #=> '#<RatatuiRuby::Event::Key code="a" modifiers=[]>'
62
- def description
63
- event.inspect
64
- end
65
-
66
- # Checks if the entry matches the given type.
67
- #
68
- # [check_type] Symbol type to check against.
69
- #
70
- # === Example
71
- #
72
- # entry.matches_type?(:key) #=> true
73
- def matches_type?(check_type)
74
- return true if check_type == :focus && (type == :focus_gained || type == :focus_lost)
75
-
76
- type == check_type
77
- end
78
-
79
- # Returns the display type for live event grouping.
80
- #
81
- # Normalizes focus_gained and focus_lost to :focus.
82
- #
83
- # === Example
84
- #
85
- # entry.live_type #=> :focus
86
- def live_type
87
- case type
88
- when :focus_gained, :focus_lost
89
- :focus
90
- else
91
- type
92
- end
93
- end
94
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
6
- #++
7
-
8
- # Semantic message types for the Model-View-Update architecture.
9
- #
10
- # Raw events from the terminal are converted to semantic Msg types. This
11
- # decouples the Update function from the event system, making it easier
12
- # to test and reason about.
13
- #
14
- # === Example
15
- #
16
- # msg = Msg::Input.new(event: key_event)
17
- # msg = Msg::Quit.new
18
- module Msg
19
- # A keyboard, mouse, or paste event to record.
20
- Input = Data.define(:event)
21
-
22
- # A terminal resize event.
23
- #
24
- # [width] Integer new terminal width
25
- # [height] Integer new terminal height
26
- # [previous_size] Array [width, height] before resize
27
- Resize = Data.define(:width, :height, :previous_size)
28
-
29
- # A focus change event.
30
- #
31
- # [gained] Boolean true if focus was gained, false if lost
32
- Focus = Data.define(:gained)
33
-
34
- # A none/timeout event (no input received).
35
- NoneEvent = Data.define
36
-
37
- # A quit signal.
38
- Quit = Data.define
39
- end
@@ -1,56 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
6
- #++
7
-
8
- # Represents a high-resolution point in time.
9
- #
10
- # Comparing events and calculating durations requires consistent time measurement.
11
- # Standard Time objects are often too granular or complex for simple millisecond offsets.
12
- #
13
- # This class provides a millisecond-precision timestamp for event measurement.
14
- #
15
- # Use it to track event timing, calculate elapsed time, or trigger debouncing.
16
- #
17
- # === Examples
18
- #
19
- # timestamp = Timestamp.now
20
- # puts timestamp.milliseconds
21
- #
22
- # if timestamp.elapsed?(300)
23
- # puts "More than 300ms have passed."
24
- # end
25
- class Timestamp < Data.define(:milliseconds)
26
- # Returns a new Timestamp representing the current time.
27
- #
28
- # === Example
29
- #
30
- # Timestamp.now #=> #<struct Timestamp milliseconds=123456789>
31
- def self.now
32
- new(milliseconds: (Time.now.to_f * 1000).to_i)
33
- end
34
-
35
- # Checks if a duration has passed since this timestamp.
36
- #
37
- # [duration_ms] Integer duration in milliseconds.
38
- #
39
- # === Example
40
- #
41
- # timestamp = Timestamp.now
42
- # sleep(0.5)
43
- # timestamp.elapsed?(300) #=> true
44
- def elapsed?(duration_ms)
45
- Timestamp.now.milliseconds >= milliseconds + duration_ms
46
- end
47
-
48
- # Returns the current time in milliseconds.
49
- #
50
- # === Example
51
- #
52
- # Timestamp.current #=> 123456789
53
- def self.current
54
- now.milliseconds
55
- end
56
- end
@@ -1,75 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
6
- #++
7
-
8
- require_relative "model/app_model"
9
- require_relative "model/msg"
10
- require_relative "model/event_entry"
11
- require_relative "model/timestamp"
12
- require_relative "model/event_color_cycle"
13
-
14
- # Pure update function for the Model-View-Update architecture.
15
- #
16
- # Given a Msg and the current AppModel, returns the next AppModel.
17
- # This function is pure: it does not mutate arguments, draw to the screen,
18
- # or perform IO. It simply calculates the next state.
19
- #
20
- # === Example
21
- #
22
- # model = AppModel.initial
23
- # msg = Msg::Input.new(event: key_event)
24
- # new_model = Update.call(msg, model)
25
- module Update
26
- extend self
27
-
28
- # Processes a message and returns the next model.
29
- #
30
- # [msg] A Msg value object
31
- # [model] The current AppModel
32
- #
33
- # === Example
34
- #
35
- # Update.call(Msg::Quit.new, model) #=> model (unchanged)
36
- def call(msg, model)
37
- case msg
38
- in Msg::Quit
39
- model
40
- in Msg::NoneEvent
41
- model.with(none_count: model.none_count + 1)
42
- in Msg::Focus(gained:)
43
- event = gained ? RatatuiRuby::Event::FocusGained.new : RatatuiRuby::Event::FocusLost.new
44
- entry = create_entry(event, model)
45
- add_entry(model, entry, :focus).with(focused: gained)
46
- in Msg::Resize(width:, height:, previous_size: _)
47
- event = RatatuiRuby::Event::Resize.new(width:, height:)
48
- entry = create_entry(event, model)
49
- add_entry(model, entry, :resize).with(window_size: [width, height])
50
- in Msg::Input(event:)
51
- entry = create_entry(event, model)
52
- add_entry(model, entry, entry.live_type)
53
- else
54
- model
55
- end
56
- end
57
-
58
- # Creates an EventEntry with the next color and current timestamp.
59
- def create_entry(event, model)
60
- EventEntry.create(event, model.next_color, Timestamp.now)
61
- end
62
-
63
- # Adds an entry to the model, updates highlights, and advances the color cycle.
64
- def add_entry(model, entry, live_type)
65
- new_entries = model.entries + [entry]
66
- new_lit_types = model.lit_types.merge(live_type => Timestamp.now)
67
- new_color_index = (model.color_cycle_index + 1) % EventColorCycle::COLORS.length
68
-
69
- model.with(
70
- entries: new_entries,
71
- lit_types: new_lit_types,
72
- color_cycle_index: new_color_index
73
- )
74
- end
75
- end
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
6
- #++
7
-
8
- require_relative "../view"
9
- require_relative "counts_view"
10
- require_relative "live_view"
11
- require_relative "log_view"
12
- require_relative "controls_view"
13
-
14
- # Orchestrates the complete UI layout and sub-view composition.
15
- #
16
- # Complex applications need a structured way to divide the screen and delegate rendering.
17
- # Placing all layout logic in one monolithic method makes the code difficult to maintain.
18
- #
19
- # This class defines the screen layout using a series of split constraints and delegates to sub-views.
20
- #
21
- # Use it as the root view for the All Events example application.
22
- #
23
- # === Examples
24
- #
25
- # app_view = View::App.new
26
- # app_view.call(model, tui, frame, area)
27
- class View::App
28
- # Creates a new View::App and initializes sub-views.
29
- def initialize
30
- @counts_view = View::Counts.new
31
- @live_view = View::Live.new
32
- @log_view = View::Log.new
33
- @controls_view = View::Controls.new
34
- end
35
-
36
- # Renders the entire application UI to the given area.
37
- #
38
- # [model] AppModel containing all application data.
39
- # [tui] RatatuiRuby instance.
40
- # [frame] RatatuiRuby::Frame being rendered.
41
- # [area] RatatuiRuby::Layout::Rect defining the total available space.
42
- #
43
- # === Example
44
- #
45
- # app_view.call(model, tui, frame, area)
46
- def call(model, tui, frame, area)
47
- main_area, control_area = tui.layout_split(
48
- area,
49
- direction: :vertical,
50
- constraints: [
51
- tui.constraint_fill(1),
52
- tui.constraint_length(3),
53
- ]
54
- )
55
-
56
- counts_area, _margin_area, right_area = tui.layout_split(
57
- main_area,
58
- direction: :horizontal,
59
- constraints: [
60
- tui.constraint_length(20),
61
- tui.constraint_length(1),
62
- tui.constraint_fill(1),
63
- ]
64
- )
65
-
66
- live_area, log_area = tui.layout_split(
67
- right_area,
68
- direction: :vertical,
69
- constraints: [
70
- tui.constraint_length(9),
71
- tui.constraint_fill(1),
72
- ]
73
- )
74
-
75
- @counts_view.call(model, tui, frame, counts_area)
76
- @live_view.call(model, tui, frame, live_area)
77
- @log_view.call(model, tui, frame, log_area)
78
- @controls_view.call(model, tui, frame, control_area)
79
- end
80
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
6
- #++
7
-
8
- require_relative "../view"
9
-
10
- # Renders the keyboard controls and shortcuts panel.
11
- #
12
- # Users need to know how to interact with the application and exit.
13
- # Hardcoding control descriptions into the main layout makes the code hard to read.
14
- #
15
- # This component renders a formatted paragraph listing available global shortcuts.
16
- #
17
- # Use it to display help information in a sidebar or dedicated panel.
18
- #
19
- # === Examples
20
- #
21
- # controls = View::Controls.new
22
- # controls.call(model, tui, frame, area)
23
- class View::Controls
24
- # Renders the controls widget to the given area.
25
- #
26
- # [model] AppModel (unused, included for consistent interface).
27
- # [tui] RatatuiRuby instance.
28
- # [frame] RatatuiRuby::Frame being rendered.
29
- # [area] RatatuiRuby::Layout::Rect defining the widget's bounds.
30
- #
31
- # === Example
32
- #
33
- # controls.call(model, tui, frame, area)
34
- def call(_model, tui, frame, area)
35
- hotkey_style = tui.style(modifiers: [:bold, :underlined])
36
-
37
- widget = tui.paragraph(
38
- text: [
39
- tui.text_line(spans: [
40
- tui.text_span(content: "q", style: hotkey_style),
41
- tui.text_span(content: ": Quit "),
42
- tui.text_span(content: "Ctrl+C", style: hotkey_style),
43
- tui.text_span(content: ": Quit"),
44
- ]),
45
- ],
46
- block: tui.block(
47
- title: "Controls",
48
- borders: [:all],
49
- border_style: tui.style(fg: :white)
50
- )
51
- )
52
- frame.render_widget(widget, area)
53
- end
54
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- #--
4
- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
5
- # SPDX-License-Identifier: AGPL-3.0-or-later
6
- #++
7
-
8
- require_relative "../view"
9
-
10
- # Renders the event statistics dashboard.
11
- #
12
- # Developers auditing input need to see real-time counts of various event types.
13
- #
14
- # This component displays a list of event types with their total counts.
15
- #
16
- # Use it to build an interactive dashboard of application activity.
17
- class View::Counts
18
- # Renders the event counts widget to the given area.
19
- #
20
- # [model] AppModel containing event data.
21
- # [tui] RatatuiRuby instance.
22
- # [frame] RatatuiRuby::Frame being rendered.
23
- # [area] RatatuiRuby::Layout::Rect defining the widget's bounds.
24
- def call(model, tui, frame, area)
25
- dimmed_style = tui.style(fg: :dark_gray)
26
- lit_style = tui.style(fg: :green, modifiers: [:bold])
27
- border_color = model.focused ? :green : :gray
28
-
29
- count_lines = []
30
-
31
- AppAllEvents::EVENT_TYPES.each do |type|
32
- count = model.count(type)
33
- label = type.to_s.capitalize
34
- style = model.lit?(type) ? lit_style : nil
35
-
36
- count_lines << tui.text_line(spans: [
37
- tui.text_span(content: "#{label}: ", style:),
38
- tui.text_span(content: count.to_s, style: style || tui.style(fg: :yellow)),
39
- ])
40
-
41
- model.sub_counts(type).each do |sub_type, sub_count|
42
- sub_label = sub_type.to_s.capitalize
43
- count_lines << tui.text_line(spans: [
44
- tui.text_span(content: " #{sub_label}: ", style: dimmed_style),
45
- tui.text_span(content: sub_count.to_s, style: dimmed_style),
46
- ])
47
- end
48
- end
49
-
50
- widget = tui.paragraph(
51
- text: count_lines,
52
- scroll: [0, 0],
53
- block: tui.block(
54
- title: "Event Counts",
55
- borders: [:all],
56
- border_style: tui.style(fg: border_color)
57
- )
58
- )
59
- frame.render_widget(widget, area)
60
- end
61
- end