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,58 +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
- # A single color variant with label and styling information.
9
- #
10
- # Color palettes need to show individual colors with labels (Main, Shade, Tint,
11
- # Complement). Bundling a color's hex code, text color, and frame color together
12
- # is natural—they're always used as a set.
13
- #
14
- # This value object pairs a color with its metadata and rendering styles.
15
- #
16
- # Use it to represent colors in a palette or harmony.
17
- #
18
- # === Attributes
19
- #
20
- # [label] String label for this color variant
21
- # [hex] String hex color code
22
- # [text_color] Symbol (:white or :black) for readable text
23
- # [frame_color] String background color for the swatch frame
24
- #
25
- # === Example
26
- #
27
- # harmony = Harmony.new(
28
- # label: "Main",
29
- # hex: "#FF0000",
30
- # text_color: :white,
31
- # frame_color: "#000000"
32
- # )
33
- Harmony = Data.define(:label, :hex, :text_color, :frame_color) do
34
- # Renders a 4-line color swatch for display in a TUI Block.
35
- #
36
- # Produces a visual representation: a 7-character-wide box with the color
37
- # centered and the hex code below.
38
- #
39
- # [tui] Session or TUI factory object
40
- #
41
- # === Example
42
- #
43
- # harmony = Harmony.new(...)
44
- # lines = harmony.color_swatch_lines(tui)
45
- # # => [TextLine, TextLine, TextLine, TextLine]
46
- def color_swatch_lines(tui)
47
- [
48
- tui.text_line(spans: Array.new(7) { tui.text_span(content: " ", style: tui.style(bg: frame_color)) }),
49
- tui.text_line(spans: [
50
- *Array.new(3) { tui.text_span(content: " ", style: tui.style(bg: frame_color)) },
51
- tui.text_span(content: " ", style: tui.style(bg: hex, fg: text_color)),
52
- *Array.new(3) { tui.text_span(content: " ", style: tui.style(bg: frame_color)) },
53
- ]),
54
- tui.text_line(spans: Array.new(7) { tui.text_span(content: " ", style: tui.style(bg: frame_color)) }),
55
- tui.text_line(spans: [tui.text_span(content: hex, style: tui.style(fg: :white))]),
56
- ]
57
- end
58
- end
@@ -1,176 +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 "color"
9
-
10
- # A self-contained text input component for color entry.
11
- #
12
- # Users type color values. They make mistakes—typos, invalid formats. The app
13
- # needs to validate their input and show helpful error messages.
14
- #
15
- # This component encapsulates rendering, state, and event handling. It draws
16
- # itself into the provided area, caches that area for hit testing, and handles
17
- # keyboard events internally.
18
- #
19
- # === Component Contract
20
- #
21
- # - `render(tui, frame, area)`: Draws the input field; stores `area` for hit testing
22
- # - `handle_event(event) -> Symbol | nil`: Returns `:consumed`, `:submitted`, or `nil`
23
- #
24
- # === Example
25
- #
26
- # input = Input.new
27
- # input.render(tui, frame, area)
28
- #
29
- # result = input.handle_event(event)
30
- # case result
31
- # when :submitted
32
- # palette.update_color(input.parsed_color)
33
- # end
34
- class Input
35
- PRINTABLE_PATTERN = /[\w#,().\s%]/
36
-
37
- # Creates a new Input with an optional initial value.
38
- #
39
- # [initial_value] String initial color input (default: <tt>"#F96302"</tt>)
40
- def initialize(initial_value = "#F96302")
41
- @value = initial_value
42
- @error = ""
43
- @parsed_color = nil
44
- @area = nil
45
- end
46
-
47
- # Current input string.
48
- attr_reader :value
49
-
50
- # Error message from the last failed parse, or empty string.
51
- attr_reader :error
52
-
53
- # The last successfully parsed Color, or nil.
54
- attr_reader :parsed_color
55
-
56
- # The cached render area, for hit testing.
57
- attr_reader :area
58
-
59
- # Clears the current error message.
60
- def clear_error
61
- @error = ""
62
- end
63
-
64
- # Renders the input widget into the given area.
65
- #
66
- # Caches `area` for hit testing. Shows the current input value and positions
67
- # the terminal's blinking cursor at the end of the text using
68
- # `frame.set_cursor_position`. Displays the error message in red if set.
69
- #
70
- # [tui] Session or TUI factory object
71
- # [frame] Frame object from RatatuiRuby.draw block
72
- # [area] Rect area to draw into
73
- #
74
- # === Example
75
- #
76
- # input.render(tui, frame, input_area)
77
- def render(tui, frame, area)
78
- @area = area
79
- widget = build_widget(tui)
80
- frame.render_widget(widget, area)
81
-
82
- # Position real blinking cursor at end of input text
83
- cursor_x, cursor_y = cursor_position_in(area)
84
- frame.set_cursor_position(cursor_x, cursor_y)
85
- end
86
-
87
- # Processes a keyboard event and updates internal state.
88
- #
89
- # Returns:
90
- # - `:submitted` when Enter is pressed (caller should read `parsed_color`)
91
- # - `:consumed` when the event was handled (typing, backspace)
92
- # - `nil` when the event was ignored
93
- #
94
- # [event] Event from RatatuiRuby.poll_event
95
- #
96
- # === Example
97
- #
98
- # result = input.handle_event(event)
99
- # if result == :submitted
100
- # palette.update_color(input.parsed_color)
101
- # end
102
- def handle_event(event)
103
- case event
104
- in { type: :key, code: "enter" }
105
- parse
106
- :submitted
107
- in { type: :key, code: "backspace" }
108
- delete_char
109
- :consumed
110
- in { type: :paste, content: }
111
- set(content)
112
- parse
113
- :submitted
114
- in { type: :key, code: code }
115
- append_char(code)
116
- :consumed
117
- else
118
- nil
119
- end
120
- end
121
-
122
- private def append_char(char)
123
- @value += char if char.length == 1 && char.match?(PRINTABLE_PATTERN)
124
- end
125
-
126
- private def delete_char
127
- @value = @value[0...-1]
128
- end
129
-
130
- private def set(text)
131
- @value = text
132
- end
133
-
134
- private def parse
135
- color = Color.parse(@value)
136
- if color
137
- clear_error
138
- @parsed_color = color
139
- else
140
- @error = "Invalid color format. Try: #ff0000, rgb(255,0,0), red"
141
- @parsed_color = nil
142
- end
143
- end
144
-
145
- private def build_widget(tui)
146
- input_lines = [
147
- tui.text_line(spans: [
148
- tui.text_span(content: @value),
149
- ]),
150
- ]
151
-
152
- unless @error.empty?
153
- input_lines << tui.text_line(spans: [
154
- tui.text_span(content: @error, style: tui.style(fg: :red)),
155
- ])
156
- end
157
-
158
- tui.block(
159
- title: "Color Input",
160
- borders: [:all],
161
- children: [
162
- tui.paragraph(text: input_lines),
163
- ]
164
- )
165
- end
166
-
167
- # Calculates cursor position within the input area.
168
- #
169
- # Accounts for block border (1 cell) and current text length.
170
- private def cursor_position_in(area)
171
- # Border takes 1 cell on left, cursor goes after last character
172
- x = area.x + 1 + @value.length
173
- y = area.y + 1 # First line inside border
174
- [x, y]
175
- end
176
- end
@@ -1,180 +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 "input"
9
- require_relative "palette"
10
- require_relative "export_pane"
11
- require_relative "controls"
12
- require_relative "clipboard"
13
- require_relative "copy_dialog"
14
-
15
- # The root container that owns all child components and orchestrates the UI.
16
- #
17
- # Building a complete color picker UI involves layout calculation, widget
18
- # composition, event routing, and cross-component communication. The Container
19
- # pattern centralizes this orchestration while keeping components decoupled.
20
- #
21
- # This container:
22
- # - **Layout Phase**: Calculates Rects using tui.layout_split
23
- # - **Delegation Phase**: Calls child.render(tui, frame, area) for each component
24
- # - **Event Routing (Chain of Responsibility)**: Delegates events front-to-back
25
- # - **Mediator Pattern**: Manages cross-component communication via symbolic signals
26
- #
27
- # === Component Contract
28
- #
29
- # - `render(tui, frame, area)`: Lays out and renders all children
30
- # - `handle_event(event) -> Symbol | nil`: Routes events to children
31
- # - `tick`: Delegates lifecycle updates (clipboard timer)
32
- #
33
- # === Example
34
- #
35
- # container = MainContainer.new(tui)
36
- # container.render(tui, frame, frame.area)
37
- # result = container.handle_event(event)
38
- # container.tick
39
- class MainContainer
40
- def initialize(tui)
41
- @tui = tui
42
- @input = Input.new
43
- @palette = Palette.new(@input.parsed_color)
44
- @export_pane = ExportPane.new
45
- @controls = Controls.new
46
- @clipboard = Clipboard.new
47
- @dialog = CopyDialog.new(@clipboard)
48
-
49
- # Parse initial color
50
- initial_result = simulate_initial_parse
51
- @palette.update_color(initial_result) if initial_result
52
- end
53
-
54
- # Renders all child components into the given area.
55
- #
56
- # Calculates layout once per frame. Delegates rendering to each component.
57
- # Renders the dialog overlay last for z-ordering.
58
- #
59
- # [tui] Session or TUI factory object
60
- # [frame] Frame object from RatatuiRuby.draw block
61
- # [area] Rect area to draw into
62
- #
63
- # === Example
64
- #
65
- # tui.draw { |frame| container.render(tui, frame, frame.area) }
66
- def render(tui, frame, area)
67
- # Layout Phase: calculate all areas
68
- input_area, rest = tui.layout_split(
69
- area,
70
- direction: :vertical,
71
- constraints: [
72
- tui.constraint_length(3),
73
- tui.constraint_fill(1),
74
- ]
75
- )
76
-
77
- color_area, control_area = tui.layout_split(
78
- rest,
79
- direction: :vertical,
80
- constraints: [
81
- tui.constraint_length(14),
82
- tui.constraint_fill(1),
83
- ]
84
- )
85
-
86
- harmony_area, export_area = tui.layout_split(
87
- color_area,
88
- direction: :vertical,
89
- constraints: [
90
- tui.constraint_length(7),
91
- tui.constraint_fill(1),
92
- ]
93
- )
94
-
95
- # Delegation Phase: render each component
96
- @input.render(tui, frame, input_area)
97
- @palette.render(tui, frame, harmony_area)
98
- @export_pane.render(tui, frame, export_area, palette: @palette)
99
- @controls.render(tui, frame, control_area, clipboard: @clipboard)
100
-
101
- # Overlay Logic: dialog rendered last for z-ordering
102
- if @dialog.active?
103
- dialog_area = calculate_center_area(area, 40, 8)
104
- frame.render_widget(tui.clear, area)
105
- @dialog.render(tui, frame, dialog_area)
106
- end
107
- end
108
-
109
- # Routes events to child components in visual order (front-to-back).
110
- #
111
- # Implements Chain of Responsibility:
112
- # 1. If dialog is active, offer it the event first
113
- # 2. Then Input, ExportPane (which may trigger dialog)
114
- # 3. Mediator pattern: interprets symbolic signals for cross-component effects
115
- #
116
- # Returns:
117
- # - `:consumed` when any component handled the event
118
- # - `nil` when no component handled the event
119
- #
120
- # [event] Event from RatatuiRuby.poll_event
121
- #
122
- # === Example
123
- #
124
- # result = container.handle_event(event)
125
- def handle_event(event)
126
- # Clear input error when not in dialog mode
127
- @input.clear_error unless @dialog.active?
128
-
129
- # Front-to-back: dialog has priority when active
130
- if @dialog.active?
131
- result = @dialog.handle_event(event)
132
- return :consumed if result == :consumed
133
- end
134
-
135
- # Input component
136
- result = @input.handle_event(event)
137
- case result
138
- when :submitted
139
- # Mediator: sync Input -> Palette
140
- @palette.update_color(@input.parsed_color)
141
- return :consumed
142
- when :consumed
143
- return :consumed
144
- end
145
-
146
- # ExportPane: may request copy dialog
147
- result = @export_pane.handle_event(event)
148
- if result == :copy_requested && @palette.main
149
- @dialog.open(@palette.main.hex)
150
- return :consumed
151
- end
152
-
153
- # Palette and Controls are display-only
154
- nil
155
- end
156
-
157
- # Delegates lifecycle tick to time-sensitive components.
158
- #
159
- # Currently handles clipboard feedback timer.
160
- #
161
- # === Example
162
- #
163
- # container.tick
164
- def tick
165
- @controls.tick(@clipboard)
166
- end
167
-
168
- private def calculate_center_area(parent_area, width, height)
169
- x = (parent_area.width - width) / 2
170
- y = (parent_area.height - height) / 2
171
- @tui.rect(x:, y:, width:, height:)
172
- end
173
-
174
- # Simulates the initial parse that happens when the app starts.
175
- # Input is initialized with a default color, so we need to parse it.
176
- private def simulate_initial_parse
177
- require_relative "color"
178
- Color.parse(@input.value)
179
- end
180
- end
@@ -1,111 +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 "color"
9
-
10
- # A self-contained component displaying a color palette with harmonies.
11
- #
12
- # Color pickers need to show related colors: shades, tints, complements. This
13
- # component owns a primary color and renders its harmonies.
14
- #
15
- # === Component Contract
16
- #
17
- # - `render(tui, frame, area)`: Draws the harmony blocks; stores `area`
18
- # - `handle_event(event) -> nil`: Display-only, always returns nil
19
- # - `update_color(color)`: Updates the primary color (called by MainContainer)
20
- #
21
- # === Example
22
- #
23
- # palette = Palette.new
24
- # palette.update_color(Color.parse("#FF0000"))
25
- # palette.render(tui, frame, palette_area)
26
- class Palette
27
- def initialize(primary_color = nil)
28
- @primary = primary_color
29
- @area = nil
30
- end
31
-
32
- # The cached render area.
33
- attr_reader :area
34
-
35
- # The primary (main) color, or nil if no color is set.
36
- #
37
- # === Example
38
- #
39
- # palette.main.hex # => "#FF0000"
40
- def main
41
- @primary
42
- end
43
-
44
- # Updates the primary color.
45
- #
46
- # Called by the MainContainer when Input submits a new color.
47
- #
48
- # [color] Color object or nil
49
- def update_color(color)
50
- @primary = color
51
- end
52
-
53
- # All harmonies: main, shade, tint, complement, split 1, split 2, split-complement.
54
- #
55
- # Returns an empty array if no primary color is set.
56
- def all
57
- return [] if @primary.nil?
58
-
59
- @primary.harmonies
60
- end
61
-
62
- # Renders the palette into the given area.
63
- #
64
- # Shows all harmony blocks in a horizontal layout. If no color is set,
65
- # displays a placeholder message.
66
- #
67
- # [tui] Session or TUI factory object
68
- # [frame] Frame object from RatatuiRuby.draw block
69
- # [area] Rect area to draw into
70
- #
71
- # === Example
72
- #
73
- # palette.render(tui, frame, palette_area)
74
- def render(tui, frame, area)
75
- @area = area
76
- widget = build_widget(tui)
77
- frame.render_widget(widget, area)
78
- end
79
-
80
- # Display-only component; always returns nil.
81
- def handle_event(_event)
82
- nil
83
- end
84
-
85
- private def build_widget(tui)
86
- if @primary.nil?
87
- tui.paragraph(text: "No color selected")
88
- else
89
- blocks = as_blocks(tui)
90
- tui.layout(
91
- direction: :horizontal,
92
- constraints: Array.new(blocks.size) { tui.constraint_fill(1) },
93
- children: blocks
94
- )
95
- end
96
- end
97
-
98
- private def as_blocks(tui)
99
- return [] if @primary.nil?
100
-
101
- all.map do |harmony|
102
- tui.block(
103
- title: harmony.label,
104
- borders: [:all],
105
- children: [
106
- tui.paragraph(text: harmony.color_swatch_lines(tui)),
107
- ]
108
- )
109
- end
110
- end
111
- end
@@ -1,119 +0,0 @@
1
- <!--
2
- SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
3
- SPDX-License-Identifier: CC-BY-SA-4.0
4
- -->
5
-
6
- # Debugging Showcase
7
-
8
- [![Debugging Showcase](../../doc/images/app_debugging_showcase.gif)](app.rb)
9
-
10
- Interactive demonstration of RatatuiRuby's debugging features.
11
-
12
- For comprehensive documentation, see the [Debugging Guide](../../doc/concepts/debugging.md).
13
-
14
- ## What This Example Does
15
-
16
- This app lets you trigger each debugging feature with a hotkey. Test your setup before encountering a real bug.
17
-
18
- | Key | Action |
19
- |-----|--------|
20
- | `d` | Enable `debug_mode!` programmatically |
21
- | `p` | Trigger a Rust panic |
22
- | `t` | Trigger a Rust `TypeError` |
23
- | `b` | Refresh debug status |
24
- | `q` | Quit |
25
-
26
- ## Library Features Showcased
27
-
28
- Reading this code will teach you how to:
29
-
30
- * **Enable Remote Debugging**: Call `RatatuiRuby.debug_mode!` and receive the socket path
31
- * **Detect Debug State**: Query `Debug.enabled?`, `Debug.rust_backtrace_enabled?`, and `Debug.remote_debugging_mode`
32
- * **Trigger Test Panics**: Use `Debug.test_panic!` to verify Rust backtrace visibility
33
- * **See Improved Error Messages**: Bypass DWIM coercion to see `TypeError` messages with value context
34
-
35
- **Note:** Rust backtraces only appear for panics (`[p]`). Exceptions (`[t]`) show Ruby backtraces with improved error messages from Rust.
36
-
37
- ## Environment Variables
38
-
39
- The example behaves differently depending on which environment variables you set.
40
-
41
- ### No Environment Variables
42
-
43
- <!-- SPDX-SnippetBegin -->
44
- <!--
45
- SPDX-FileCopyrightText: 2026 Kerrick Long
46
- SPDX-License-Identifier: MIT-0
47
- -->
48
- ```bash
49
- ruby examples/app_debugging_showcase/app.rb
50
- ```
51
- <!-- SPDX-SnippetEnd -->
52
-
53
- The TUI starts normally. Rust backtraces are disabled. Press `[d]` to enable remote debugging mid-session.
54
-
55
- ### `RUST_BACKTRACE=1`
56
-
57
- <!-- SPDX-SnippetBegin -->
58
- <!--
59
- SPDX-FileCopyrightText: 2026 Kerrick Long
60
- SPDX-License-Identifier: MIT-0
61
- -->
62
- ```bash
63
- RUST_BACKTRACE=1 ruby examples/app_debugging_showcase/app.rb
64
- ```
65
- <!-- SPDX-SnippetEnd -->
66
-
67
- Enables Rust backtraces. Press `[p]` to see the full Rust stack trace with file names and line numbers.
68
-
69
- ### `RR_DEBUG=1`
70
-
71
- <!-- SPDX-SnippetBegin -->
72
- <!--
73
- SPDX-FileCopyrightText: 2026 Kerrick Long
74
- SPDX-License-Identifier: MIT-0
75
- -->
76
- ```bash
77
- RR_DEBUG=1 ruby examples/app_debugging_showcase/app.rb
78
- ```
79
- <!-- SPDX-SnippetEnd -->
80
-
81
- Full debug mode at startup. The debugger opens a socket and **waits for a connection** before the TUI starts. You'll see the socket path printed before the app runs.
82
-
83
- In another terminal:
84
-
85
- <!-- SPDX-SnippetBegin -->
86
- <!--
87
- SPDX-FileCopyrightText: 2026 Kerrick Long
88
- SPDX-License-Identifier: MIT-0
89
- -->
90
- ```bash
91
- rdbg --attach
92
- ```
93
- <!-- SPDX-SnippetEnd -->
94
-
95
- When you attach, you'll see "Stop by SIGURG" — that's normal! SIGURG is how the debug gem signals the app to pause. Type `continue` to resume.
96
-
97
- This uses the [ruby/debug](https://github.com/ruby/debug?tab=readme-ov-file#readme) gem for remote debugging.
98
-
99
- ### When to Use Which
100
-
101
- `RR_DEBUG=1` includes Rust backtraces automatically (it calls `enable_rust_backtrace!` internally).
102
-
103
- Use `RUST_BACKTRACE=1` alone when you want backtraces but **don't** want the debugger to stop and wait for a connection at startup.
104
-
105
- ## What Problems Does This Solve?
106
-
107
- ### "Is my Rust backtrace setup working?"
108
-
109
- Press `[p]`. If you see a stack trace with file names and line numbers, you're good. If not, you need `RUST_BACKTRACE=1`.
110
-
111
- ### "How do I attach a debugger to a TUI?"
112
-
113
- TUIs run in raw mode — you can't type into a REPL. Press `[d]` to enable remote debugging. The socket path appears in the UI. Run `rdbg --attach` from another terminal.
114
-
115
- ### "What does a Rust TypeError look like?"
116
-
117
- Press `[t]`. The error message shows the expected type and the actual value you passed (e.g., `expected array for rows, got 42`). Note: This shows Ruby's backtrace — only panics (`[p]`) show Rust backtraces.
118
-
119
- [Read the source code →](app.rb)