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,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)