ratatui_ruby 1.1.0 → 1.1.1

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