ratatui_ruby 1.2.0 → 1.2.2

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 (260) 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/events.rs +157 -18
  5. data/lib/ratatui_ruby/version.rb +1 -1
  6. metadata +1 -255
  7. data/.builds/ruby-3.2.yml +0 -54
  8. data/.builds/ruby-3.3.yml +0 -54
  9. data/.builds/ruby-3.4.yml +0 -54
  10. data/.builds/ruby-4.0.0.yml +0 -54
  11. data/.pre-commit-config.yaml +0 -16
  12. data/.rubocop.yml +0 -10
  13. data/AGENTS.md +0 -147
  14. data/CHANGELOG.md +0 -751
  15. data/README.md +0 -187
  16. data/README.rdoc +0 -302
  17. data/Rakefile +0 -11
  18. data/Steepfile +0 -50
  19. data/doc/concepts/application_architecture.md +0 -321
  20. data/doc/concepts/application_testing.md +0 -193
  21. data/doc/concepts/async.md +0 -190
  22. data/doc/concepts/custom_widgets.md +0 -247
  23. data/doc/concepts/debugging.md +0 -401
  24. data/doc/concepts/event_handling.md +0 -162
  25. data/doc/concepts/interactive_design.md +0 -146
  26. data/doc/contributors/auditing/parity.md +0 -239
  27. data/doc/contributors/design/ruby_frontend.md +0 -448
  28. data/doc/contributors/design/rust_backend.md +0 -434
  29. data/doc/contributors/design.md +0 -11
  30. data/doc/contributors/developing_examples.md +0 -400
  31. data/doc/contributors/documentation_style.md +0 -121
  32. data/doc/contributors/index.md +0 -21
  33. data/doc/contributors/releasing.md +0 -215
  34. data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -381
  35. data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -200
  36. data/doc/contributors/todo/align/term.md +0 -351
  37. data/doc/contributors/todo/align/terminal.md +0 -647
  38. data/doc/contributors/todo/future_work.md +0 -169
  39. data/doc/contributors/upstream_requests/paragraph_span_rects.md +0 -259
  40. data/doc/contributors/upstream_requests/tab_rects.md +0 -173
  41. data/doc/contributors/upstream_requests/title_rects.md +0 -132
  42. data/doc/custom.css +0 -22
  43. data/doc/getting_started/quickstart.md +0 -291
  44. data/doc/getting_started/why.md +0 -93
  45. data/doc/images/app_all_events.png +0 -0
  46. data/doc/images/app_cli_rich_moments.gif +0 -0
  47. data/doc/images/app_color_picker.png +0 -0
  48. data/doc/images/app_debugging_showcase.gif +0 -0
  49. data/doc/images/app_debugging_showcase.png +0 -0
  50. data/doc/images/app_external_editor.gif +0 -0
  51. data/doc/images/app_login_form.png +0 -0
  52. data/doc/images/app_stateful_interaction.png +0 -0
  53. data/doc/images/verify_quickstart_dsl.png +0 -0
  54. data/doc/images/verify_quickstart_layout.png +0 -0
  55. data/doc/images/verify_quickstart_lifecycle.png +0 -0
  56. data/doc/images/verify_readme_usage.png +0 -0
  57. data/doc/images/widget_barchart.png +0 -0
  58. data/doc/images/widget_block.png +0 -0
  59. data/doc/images/widget_box.png +0 -0
  60. data/doc/images/widget_calendar.png +0 -0
  61. data/doc/images/widget_canvas.png +0 -0
  62. data/doc/images/widget_cell.png +0 -0
  63. data/doc/images/widget_center.png +0 -0
  64. data/doc/images/widget_chart.png +0 -0
  65. data/doc/images/widget_gauge.png +0 -0
  66. data/doc/images/widget_layout_split.png +0 -0
  67. data/doc/images/widget_line_gauge.png +0 -0
  68. data/doc/images/widget_list.png +0 -0
  69. data/doc/images/widget_map.png +0 -0
  70. data/doc/images/widget_overlay.png +0 -0
  71. data/doc/images/widget_popup.png +0 -0
  72. data/doc/images/widget_ratatui_logo.png +0 -0
  73. data/doc/images/widget_ratatui_mascot.png +0 -0
  74. data/doc/images/widget_rect.png +0 -0
  75. data/doc/images/widget_render.png +0 -0
  76. data/doc/images/widget_rich_text.png +0 -0
  77. data/doc/images/widget_scroll_text.png +0 -0
  78. data/doc/images/widget_scrollbar.png +0 -0
  79. data/doc/images/widget_sparkline.png +0 -0
  80. data/doc/images/widget_style_colors.png +0 -0
  81. data/doc/images/widget_table.png +0 -0
  82. data/doc/images/widget_tabs.png +0 -0
  83. data/doc/images/widget_text_width.png +0 -0
  84. data/doc/index.md +0 -34
  85. data/doc/troubleshooting/async.md +0 -4
  86. data/doc/troubleshooting/terminal_limitations.md +0 -131
  87. data/doc/troubleshooting/tui_output.md +0 -197
  88. data/examples/app_all_events/README.md +0 -114
  89. data/examples/app_all_events/app.rb +0 -98
  90. data/examples/app_all_events/model/app_model.rb +0 -159
  91. data/examples/app_all_events/model/event_color_cycle.rb +0 -43
  92. data/examples/app_all_events/model/event_entry.rb +0 -94
  93. data/examples/app_all_events/model/msg.rb +0 -39
  94. data/examples/app_all_events/model/timestamp.rb +0 -56
  95. data/examples/app_all_events/update.rb +0 -75
  96. data/examples/app_all_events/view/app_view.rb +0 -80
  97. data/examples/app_all_events/view/controls_view.rb +0 -54
  98. data/examples/app_all_events/view/counts_view.rb +0 -61
  99. data/examples/app_all_events/view/live_view.rb +0 -72
  100. data/examples/app_all_events/view/log_view.rb +0 -57
  101. data/examples/app_all_events/view.rb +0 -9
  102. data/examples/app_cli_rich_moments/README.md +0 -81
  103. data/examples/app_cli_rich_moments/app.rb +0 -189
  104. data/examples/app_color_picker/README.md +0 -156
  105. data/examples/app_color_picker/app.rb +0 -76
  106. data/examples/app_color_picker/clipboard.rb +0 -86
  107. data/examples/app_color_picker/color.rb +0 -193
  108. data/examples/app_color_picker/controls.rb +0 -92
  109. data/examples/app_color_picker/copy_dialog.rb +0 -168
  110. data/examples/app_color_picker/export_pane.rb +0 -128
  111. data/examples/app_color_picker/harmony.rb +0 -58
  112. data/examples/app_color_picker/input.rb +0 -176
  113. data/examples/app_color_picker/main_container.rb +0 -180
  114. data/examples/app_color_picker/palette.rb +0 -111
  115. data/examples/app_debugging_showcase/README.md +0 -119
  116. data/examples/app_debugging_showcase/app.rb +0 -318
  117. data/examples/app_external_editor/README.md +0 -62
  118. data/examples/app_external_editor/app.rb +0 -344
  119. data/examples/app_login_form/README.md +0 -58
  120. data/examples/app_login_form/app.rb +0 -109
  121. data/examples/app_stateful_interaction/README.md +0 -35
  122. data/examples/app_stateful_interaction/app.rb +0 -328
  123. data/examples/timeout_demo.rb +0 -45
  124. data/examples/verify_quickstart_dsl/README.md +0 -55
  125. data/examples/verify_quickstart_dsl/app.rb +0 -49
  126. data/examples/verify_quickstart_layout/README.md +0 -77
  127. data/examples/verify_quickstart_layout/app.rb +0 -73
  128. data/examples/verify_quickstart_lifecycle/README.md +0 -68
  129. data/examples/verify_quickstart_lifecycle/app.rb +0 -62
  130. data/examples/verify_readme_usage/README.md +0 -49
  131. data/examples/verify_readme_usage/app.rb +0 -42
  132. data/examples/verify_website_managed/README.md +0 -48
  133. data/examples/verify_website_managed/app.rb +0 -36
  134. data/examples/verify_website_menu/README.md +0 -60
  135. data/examples/verify_website_menu/app.rb +0 -84
  136. data/examples/verify_website_spinner/README.md +0 -44
  137. data/examples/verify_website_spinner/app.rb +0 -34
  138. data/examples/widget_barchart/README.md +0 -58
  139. data/examples/widget_barchart/app.rb +0 -240
  140. data/examples/widget_block/README.md +0 -44
  141. data/examples/widget_block/app.rb +0 -258
  142. data/examples/widget_box/README.md +0 -54
  143. data/examples/widget_box/app.rb +0 -255
  144. data/examples/widget_calendar/README.md +0 -48
  145. data/examples/widget_calendar/app.rb +0 -115
  146. data/examples/widget_canvas/README.md +0 -31
  147. data/examples/widget_canvas/app.rb +0 -130
  148. data/examples/widget_cell/README.md +0 -45
  149. data/examples/widget_cell/app.rb +0 -112
  150. data/examples/widget_center/README.md +0 -33
  151. data/examples/widget_center/app.rb +0 -118
  152. data/examples/widget_chart/README.md +0 -50
  153. data/examples/widget_chart/app.rb +0 -220
  154. data/examples/widget_gauge/README.md +0 -50
  155. data/examples/widget_gauge/app.rb +0 -229
  156. data/examples/widget_layout_split/README.md +0 -53
  157. data/examples/widget_layout_split/app.rb +0 -260
  158. data/examples/widget_line_gauge/README.md +0 -50
  159. data/examples/widget_line_gauge/app.rb +0 -219
  160. data/examples/widget_list/README.md +0 -58
  161. data/examples/widget_list/app.rb +0 -382
  162. data/examples/widget_map/README.md +0 -48
  163. data/examples/widget_map/app.rb +0 -95
  164. data/examples/widget_overlay/README.md +0 -45
  165. data/examples/widget_overlay/app.rb +0 -250
  166. data/examples/widget_popup/README.md +0 -45
  167. data/examples/widget_popup/app.rb +0 -106
  168. data/examples/widget_ratatui_logo/README.md +0 -43
  169. data/examples/widget_ratatui_logo/app.rb +0 -104
  170. data/examples/widget_ratatui_mascot/README.md +0 -43
  171. data/examples/widget_ratatui_mascot/app.rb +0 -95
  172. data/examples/widget_rect/README.md +0 -53
  173. data/examples/widget_rect/app.rb +0 -222
  174. data/examples/widget_render/README.md +0 -46
  175. data/examples/widget_render/app.rb +0 -186
  176. data/examples/widget_render/app.rbs +0 -41
  177. data/examples/widget_rich_text/README.md +0 -44
  178. data/examples/widget_rich_text/app.rb +0 -193
  179. data/examples/widget_scroll_text/README.md +0 -46
  180. data/examples/widget_scroll_text/app.rb +0 -109
  181. data/examples/widget_scrollbar/README.md +0 -46
  182. data/examples/widget_scrollbar/app.rb +0 -155
  183. data/examples/widget_sparkline/README.md +0 -51
  184. data/examples/widget_sparkline/app.rb +0 -277
  185. data/examples/widget_style_colors/README.md +0 -43
  186. data/examples/widget_style_colors/app.rb +0 -83
  187. data/examples/widget_table/README.md +0 -57
  188. data/examples/widget_table/app.rb +0 -285
  189. data/examples/widget_tabs/README.md +0 -50
  190. data/examples/widget_tabs/app.rb +0 -183
  191. data/examples/widget_text_width/README.md +0 -44
  192. data/examples/widget_text_width/app.rb +0 -117
  193. data/migrate_to_buffer.rb +0 -145
  194. data/mise.toml +0 -8
  195. data/tasks/autodoc/examples.rb +0 -87
  196. data/tasks/autodoc/member.rb +0 -58
  197. data/tasks/autodoc/name.rb +0 -21
  198. data/tasks/autodoc.rake +0 -21
  199. data/tasks/bump/bump_workflow.rb +0 -49
  200. data/tasks/bump/cargo_lockfile.rb +0 -21
  201. data/tasks/bump/changelog.rb +0 -104
  202. data/tasks/bump/header.rb +0 -32
  203. data/tasks/bump/history.rb +0 -32
  204. data/tasks/bump/links.rb +0 -69
  205. data/tasks/bump/manifest.rb +0 -33
  206. data/tasks/bump/patch_release.rb +0 -19
  207. data/tasks/bump/release_branch.rb +0 -17
  208. data/tasks/bump/release_from_trunk.rb +0 -49
  209. data/tasks/bump/repository.rb +0 -54
  210. data/tasks/bump/ruby_gem.rb +0 -29
  211. data/tasks/bump/sem_ver.rb +0 -44
  212. data/tasks/bump/unreleased_section.rb +0 -73
  213. data/tasks/bump.rake +0 -61
  214. data/tasks/doc/documentation.rb +0 -59
  215. data/tasks/doc/link/file_url.rb +0 -30
  216. data/tasks/doc/link/relative_path.rb +0 -61
  217. data/tasks/doc/link/web_url.rb +0 -55
  218. data/tasks/doc/link.rb +0 -52
  219. data/tasks/doc/link_audit.rb +0 -116
  220. data/tasks/doc/problem.rb +0 -40
  221. data/tasks/doc/source_file.rb +0 -93
  222. data/tasks/doc.rake +0 -905
  223. data/tasks/example_viewer.html.erb +0 -172
  224. data/tasks/extension.rake +0 -14
  225. data/tasks/license/headers_md.rb +0 -223
  226. data/tasks/license/headers_rb.rb +0 -210
  227. data/tasks/license/license_utils.rb +0 -130
  228. data/tasks/license/snippets_md.rb +0 -315
  229. data/tasks/license/snippets_rdoc.rb +0 -150
  230. data/tasks/license.rake +0 -91
  231. data/tasks/lint.rake +0 -170
  232. data/tasks/rbs_predicates/predicate_catalog.rb +0 -52
  233. data/tasks/rbs_predicates/predicate_tests.rb +0 -124
  234. data/tasks/rbs_predicates/rbs_signature.rb +0 -63
  235. data/tasks/rbs_predicates.rake +0 -31
  236. data/tasks/rdoc_config.rb +0 -29
  237. data/tasks/resources/build.yml.erb +0 -60
  238. data/tasks/resources/index.html.erb +0 -141
  239. data/tasks/resources/rubies.yml +0 -7
  240. data/tasks/sourcehut.rake +0 -122
  241. data/tasks/steep.rake +0 -11
  242. data/tasks/terminal_preview/app_screenshot.rb +0 -45
  243. data/tasks/terminal_preview/crash_report.rb +0 -54
  244. data/tasks/terminal_preview/example_app.rb +0 -27
  245. data/tasks/terminal_preview/launcher_script.rb +0 -48
  246. data/tasks/terminal_preview/preview_collection.rb +0 -60
  247. data/tasks/terminal_preview/preview_timing.rb +0 -24
  248. data/tasks/terminal_preview/safety_confirmation.rb +0 -58
  249. data/tasks/terminal_preview/saved_screenshot.rb +0 -56
  250. data/tasks/terminal_preview/system_appearance.rb +0 -13
  251. data/tasks/terminal_preview/terminal_window.rb +0 -138
  252. data/tasks/terminal_preview/window_id.rb +0 -16
  253. data/tasks/terminal_preview.rake +0 -30
  254. data/tasks/test.rake +0 -36
  255. data/tasks/website/index_page.rb +0 -30
  256. data/tasks/website/version.rb +0 -122
  257. data/tasks/website/version_menu.rb +0 -68
  258. data/tasks/website/versioned_documentation.rb +0 -83
  259. data/tasks/website/website.rb +0 -53
  260. 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