ratatui_ruby 1.0.0 → 1.0.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.
- checksums.yaml +4 -4
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/lib/ratatui_ruby/version.rb +1 -1
- metadata +1 -232
- data/.builds/ruby-3.2.yml +0 -54
- data/.builds/ruby-3.3.yml +0 -54
- data/.builds/ruby-3.4.yml +0 -54
- data/.builds/ruby-4.0.0.yml +0 -54
- data/.pre-commit-config.yaml +0 -16
- data/.rubocop.yml +0 -10
- data/AGENTS.md +0 -146
- data/CHANGELOG.md +0 -710
- data/README.md +0 -187
- data/README.rdoc +0 -302
- data/Rakefile +0 -11
- data/Steepfile +0 -49
- data/doc/concepts/application_architecture.md +0 -321
- data/doc/concepts/application_testing.md +0 -193
- data/doc/concepts/async.md +0 -190
- data/doc/concepts/custom_widgets.md +0 -247
- data/doc/concepts/debugging.md +0 -401
- data/doc/concepts/event_handling.md +0 -162
- data/doc/concepts/interactive_design.md +0 -146
- data/doc/contributors/auditing/parity.md +0 -239
- data/doc/contributors/design/ruby_frontend.md +0 -420
- data/doc/contributors/design/rust_backend.md +0 -422
- data/doc/contributors/design.md +0 -11
- data/doc/contributors/developing_examples.md +0 -400
- data/doc/contributors/documentation_style.md +0 -121
- data/doc/contributors/index.md +0 -21
- data/doc/contributors/todo/align/api_completeness_audit-finished.md +0 -375
- data/doc/contributors/todo/align/api_completeness_audit-unfinished.md +0 -206
- data/doc/contributors/todo/align/terminal.md +0 -647
- data/doc/contributors/todo/future_work.md +0 -169
- data/doc/contributors/upstream_requests/tab_rects.md +0 -173
- data/doc/contributors/upstream_requests/title_rects.md +0 -132
- data/doc/custom.css +0 -22
- data/doc/getting_started/quickstart.md +0 -291
- data/doc/getting_started/why.md +0 -93
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_cli_rich_moments.gif +0 -0
- data/doc/images/app_color_picker.png +0 -0
- data/doc/images/app_debugging_showcase.gif +0 -0
- data/doc/images/app_debugging_showcase.png +0 -0
- data/doc/images/app_login_form.png +0 -0
- data/doc/images/app_stateful_interaction.png +0 -0
- data/doc/images/verify_quickstart_dsl.png +0 -0
- data/doc/images/verify_quickstart_layout.png +0 -0
- data/doc/images/verify_quickstart_lifecycle.png +0 -0
- data/doc/images/verify_readme_usage.png +0 -0
- data/doc/images/widget_barchart.png +0 -0
- data/doc/images/widget_block.png +0 -0
- data/doc/images/widget_box.png +0 -0
- data/doc/images/widget_calendar.png +0 -0
- data/doc/images/widget_canvas.png +0 -0
- data/doc/images/widget_cell.png +0 -0
- data/doc/images/widget_center.png +0 -0
- data/doc/images/widget_chart.png +0 -0
- data/doc/images/widget_gauge.png +0 -0
- data/doc/images/widget_layout_split.png +0 -0
- data/doc/images/widget_line_gauge.png +0 -0
- data/doc/images/widget_list.png +0 -0
- data/doc/images/widget_map.png +0 -0
- data/doc/images/widget_overlay.png +0 -0
- data/doc/images/widget_popup.png +0 -0
- data/doc/images/widget_ratatui_logo.png +0 -0
- data/doc/images/widget_ratatui_mascot.png +0 -0
- data/doc/images/widget_rect.png +0 -0
- data/doc/images/widget_render.png +0 -0
- data/doc/images/widget_rich_text.png +0 -0
- data/doc/images/widget_scroll_text.png +0 -0
- data/doc/images/widget_scrollbar.png +0 -0
- data/doc/images/widget_sparkline.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/images/widget_table.png +0 -0
- data/doc/images/widget_tabs.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/index.md +0 -39
- data/doc/troubleshooting/async.md +0 -4
- data/doc/troubleshooting/terminal_limitations.md +0 -131
- data/doc/troubleshooting/tui_output.md +0 -197
- data/examples/app_all_events/README.md +0 -114
- data/examples/app_all_events/app.rb +0 -98
- data/examples/app_all_events/model/app_model.rb +0 -159
- data/examples/app_all_events/model/event_color_cycle.rb +0 -43
- data/examples/app_all_events/model/event_entry.rb +0 -94
- data/examples/app_all_events/model/msg.rb +0 -39
- data/examples/app_all_events/model/timestamp.rb +0 -56
- data/examples/app_all_events/update.rb +0 -75
- data/examples/app_all_events/view/app_view.rb +0 -80
- data/examples/app_all_events/view/controls_view.rb +0 -54
- data/examples/app_all_events/view/counts_view.rb +0 -61
- data/examples/app_all_events/view/live_view.rb +0 -72
- data/examples/app_all_events/view/log_view.rb +0 -57
- data/examples/app_all_events/view.rb +0 -9
- data/examples/app_cli_rich_moments/README.md +0 -81
- data/examples/app_cli_rich_moments/app.rb +0 -189
- data/examples/app_color_picker/README.md +0 -156
- data/examples/app_color_picker/app.rb +0 -76
- data/examples/app_color_picker/clipboard.rb +0 -86
- data/examples/app_color_picker/color.rb +0 -193
- data/examples/app_color_picker/controls.rb +0 -92
- data/examples/app_color_picker/copy_dialog.rb +0 -168
- data/examples/app_color_picker/export_pane.rb +0 -128
- data/examples/app_color_picker/harmony.rb +0 -58
- data/examples/app_color_picker/input.rb +0 -176
- data/examples/app_color_picker/main_container.rb +0 -180
- data/examples/app_color_picker/palette.rb +0 -111
- data/examples/app_debugging_showcase/README.md +0 -119
- data/examples/app_debugging_showcase/app.rb +0 -318
- data/examples/app_login_form/README.md +0 -58
- data/examples/app_login_form/app.rb +0 -109
- data/examples/app_stateful_interaction/README.md +0 -35
- data/examples/app_stateful_interaction/app.rb +0 -328
- data/examples/timeout_demo.rb +0 -45
- data/examples/verify_quickstart_dsl/README.md +0 -55
- data/examples/verify_quickstart_dsl/app.rb +0 -49
- data/examples/verify_quickstart_layout/README.md +0 -77
- data/examples/verify_quickstart_layout/app.rb +0 -73
- data/examples/verify_quickstart_lifecycle/README.md +0 -68
- data/examples/verify_quickstart_lifecycle/app.rb +0 -62
- data/examples/verify_readme_usage/README.md +0 -49
- data/examples/verify_readme_usage/app.rb +0 -42
- data/examples/verify_website_managed/README.md +0 -48
- data/examples/verify_website_managed/app.rb +0 -36
- data/examples/verify_website_menu/README.md +0 -60
- data/examples/verify_website_menu/app.rb +0 -84
- data/examples/verify_website_spinner/README.md +0 -44
- data/examples/verify_website_spinner/app.rb +0 -34
- data/examples/widget_barchart/README.md +0 -58
- data/examples/widget_barchart/app.rb +0 -240
- data/examples/widget_block/README.md +0 -44
- data/examples/widget_block/app.rb +0 -258
- data/examples/widget_box/README.md +0 -54
- data/examples/widget_box/app.rb +0 -255
- data/examples/widget_calendar/README.md +0 -48
- data/examples/widget_calendar/app.rb +0 -115
- data/examples/widget_canvas/README.md +0 -31
- data/examples/widget_canvas/app.rb +0 -130
- data/examples/widget_cell/README.md +0 -45
- data/examples/widget_cell/app.rb +0 -112
- data/examples/widget_center/README.md +0 -33
- data/examples/widget_center/app.rb +0 -118
- data/examples/widget_chart/README.md +0 -50
- data/examples/widget_chart/app.rb +0 -220
- data/examples/widget_gauge/README.md +0 -50
- data/examples/widget_gauge/app.rb +0 -229
- data/examples/widget_layout_split/README.md +0 -53
- data/examples/widget_layout_split/app.rb +0 -260
- data/examples/widget_line_gauge/README.md +0 -50
- data/examples/widget_line_gauge/app.rb +0 -219
- data/examples/widget_list/README.md +0 -58
- data/examples/widget_list/app.rb +0 -384
- data/examples/widget_map/README.md +0 -48
- data/examples/widget_map/app.rb +0 -95
- data/examples/widget_overlay/README.md +0 -45
- data/examples/widget_overlay/app.rb +0 -250
- data/examples/widget_popup/README.md +0 -45
- data/examples/widget_popup/app.rb +0 -106
- data/examples/widget_ratatui_logo/README.md +0 -43
- data/examples/widget_ratatui_logo/app.rb +0 -104
- data/examples/widget_ratatui_mascot/README.md +0 -43
- data/examples/widget_ratatui_mascot/app.rb +0 -95
- data/examples/widget_rect/README.md +0 -53
- data/examples/widget_rect/app.rb +0 -222
- data/examples/widget_render/README.md +0 -46
- data/examples/widget_render/app.rb +0 -186
- data/examples/widget_render/app.rbs +0 -41
- data/examples/widget_rich_text/README.md +0 -44
- data/examples/widget_rich_text/app.rb +0 -193
- data/examples/widget_scroll_text/README.md +0 -46
- data/examples/widget_scroll_text/app.rb +0 -109
- data/examples/widget_scrollbar/README.md +0 -46
- data/examples/widget_scrollbar/app.rb +0 -155
- data/examples/widget_sparkline/README.md +0 -51
- data/examples/widget_sparkline/app.rb +0 -277
- data/examples/widget_style_colors/README.md +0 -43
- data/examples/widget_style_colors/app.rb +0 -83
- data/examples/widget_table/README.md +0 -57
- data/examples/widget_table/app.rb +0 -279
- data/examples/widget_tabs/README.md +0 -50
- data/examples/widget_tabs/app.rb +0 -183
- data/examples/widget_text_width/README.md +0 -44
- data/examples/widget_text_width/app.rb +0 -117
- data/migrate_to_buffer.rb +0 -145
- data/mise.toml +0 -8
- data/tasks/autodoc/examples.rb +0 -87
- data/tasks/autodoc/member.rb +0 -58
- data/tasks/autodoc/name.rb +0 -21
- data/tasks/autodoc.rake +0 -21
- data/tasks/bump/cargo_lockfile.rb +0 -21
- data/tasks/bump/changelog.rb +0 -47
- data/tasks/bump/header.rb +0 -32
- data/tasks/bump/history.rb +0 -32
- data/tasks/bump/links.rb +0 -69
- data/tasks/bump/manifest.rb +0 -33
- data/tasks/bump/ruby_gem.rb +0 -49
- data/tasks/bump/sem_ver.rb +0 -40
- data/tasks/bump/unreleased_section.rb +0 -56
- data/tasks/bump.rake +0 -51
- data/tasks/doc.rake +0 -887
- data/tasks/example_viewer.html.erb +0 -172
- data/tasks/extension.rake +0 -14
- data/tasks/license/headers_md.rb +0 -223
- data/tasks/license/headers_rb.rb +0 -210
- data/tasks/license/license_utils.rb +0 -130
- data/tasks/license/snippets_md.rb +0 -315
- data/tasks/license/snippets_rdoc.rb +0 -150
- data/tasks/license.rake +0 -91
- data/tasks/lint.rake +0 -170
- data/tasks/rdoc_config.rb +0 -29
- data/tasks/resources/build.yml.erb +0 -60
- data/tasks/resources/index.html.erb +0 -141
- data/tasks/resources/rubies.yml +0 -7
- data/tasks/sourcehut.rake +0 -110
- data/tasks/steep.rake +0 -11
- data/tasks/terminal_preview/app_screenshot.rb +0 -45
- data/tasks/terminal_preview/crash_report.rb +0 -54
- data/tasks/terminal_preview/example_app.rb +0 -27
- data/tasks/terminal_preview/launcher_script.rb +0 -48
- data/tasks/terminal_preview/preview_collection.rb +0 -60
- data/tasks/terminal_preview/preview_timing.rb +0 -24
- data/tasks/terminal_preview/safety_confirmation.rb +0 -58
- data/tasks/terminal_preview/saved_screenshot.rb +0 -56
- data/tasks/terminal_preview/system_appearance.rb +0 -13
- data/tasks/terminal_preview/terminal_window.rb +0 -138
- data/tasks/terminal_preview/window_id.rb +0 -16
- data/tasks/terminal_preview.rake +0 -30
- data/tasks/test.rake +0 -33
- data/tasks/website/index_page.rb +0 -30
- data/tasks/website/version.rb +0 -127
- data/tasks/website/version_menu.rb +0 -68
- data/tasks/website/versioned_documentation.rb +0 -83
- data/tasks/website/website.rb +0 -53
- data/tasks/website.rake +0 -28
|
@@ -1,92 +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 display-only component showing keyboard shortcuts and clipboard feedback.
|
|
9
|
-
#
|
|
10
|
-
# Users need to know what keys are available. They also need feedback when
|
|
11
|
-
# they copy a color. This component renders the controls section.
|
|
12
|
-
#
|
|
13
|
-
# === Component Contract
|
|
14
|
-
#
|
|
15
|
-
# - `render(tui, frame, area, clipboard:)`: Draws the controls; stores `area`
|
|
16
|
-
# - `handle_event(event) -> nil`: Display-only, always returns nil
|
|
17
|
-
# - `tick`: Delegates to clipboard for time-based feedback updates
|
|
18
|
-
#
|
|
19
|
-
# === Example
|
|
20
|
-
#
|
|
21
|
-
# controls = Controls.new
|
|
22
|
-
# controls.render(tui, frame, area, clipboard: clipboard)
|
|
23
|
-
# controls.tick(clipboard)
|
|
24
|
-
class Controls
|
|
25
|
-
def initialize
|
|
26
|
-
@area = nil
|
|
27
|
-
@hotkey_style = nil
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# The cached render area.
|
|
31
|
-
attr_reader :area
|
|
32
|
-
|
|
33
|
-
# Renders the controls section into the given area.
|
|
34
|
-
#
|
|
35
|
-
# Shows keyboard shortcuts and clipboard feedback message if one is active.
|
|
36
|
-
#
|
|
37
|
-
# [tui] Session or TUI factory object
|
|
38
|
-
# [frame] Frame object from RatatuiRuby.draw block
|
|
39
|
-
# [area] Rect area to draw into
|
|
40
|
-
# [clipboard] Clipboard object for feedback message
|
|
41
|
-
#
|
|
42
|
-
# === Example
|
|
43
|
-
#
|
|
44
|
-
# controls.render(tui, frame, control_area, clipboard: clipboard)
|
|
45
|
-
def render(tui, frame, area, clipboard:)
|
|
46
|
-
@area = area
|
|
47
|
-
@hotkey_style ||= tui.style(modifiers: [:bold, :underlined])
|
|
48
|
-
widget = build_widget(tui, clipboard)
|
|
49
|
-
frame.render_widget(widget, area)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Display-only component; always returns nil.
|
|
53
|
-
def handle_event(_event)
|
|
54
|
-
nil
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Delegates tick to the clipboard for time-based updates.
|
|
58
|
-
#
|
|
59
|
-
# [clipboard] Clipboard object to tick
|
|
60
|
-
def tick(clipboard)
|
|
61
|
-
clipboard.tick
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
private def build_widget(tui, clipboard)
|
|
65
|
-
control_lines = [
|
|
66
|
-
tui.text_line(spans: [
|
|
67
|
-
tui.text_span(content: "a-z/0-9", style: @hotkey_style),
|
|
68
|
-
tui.text_span(content: ": Type "),
|
|
69
|
-
tui.text_span(content: "enter", style: @hotkey_style),
|
|
70
|
-
tui.text_span(content: ": Parse "),
|
|
71
|
-
tui.text_span(content: "bksp", style: @hotkey_style),
|
|
72
|
-
tui.text_span(content: ": Erase "),
|
|
73
|
-
tui.text_span(content: "esc", style: @hotkey_style),
|
|
74
|
-
tui.text_span(content: ": Quit"),
|
|
75
|
-
]),
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
unless clipboard.message.empty?
|
|
79
|
-
control_lines << tui.text_line(spans: [
|
|
80
|
-
tui.text_span(content: clipboard.message, style: tui.style(fg: :green, modifiers: [:bold])),
|
|
81
|
-
])
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
tui.block(
|
|
85
|
-
title: "Controls",
|
|
86
|
-
borders: [:all],
|
|
87
|
-
children: [
|
|
88
|
-
tui.paragraph(text: control_lines),
|
|
89
|
-
]
|
|
90
|
-
)
|
|
91
|
-
end
|
|
92
|
-
end
|
|
@@ -1,168 +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 "clipboard"
|
|
9
|
-
|
|
10
|
-
# A self-contained modal dialog component for copying text to the clipboard.
|
|
11
|
-
#
|
|
12
|
-
# Users click on content they want to copy. The app needs to confirm: "Are you
|
|
13
|
-
# sure?" This component owns dialog state, renders itself, and handles keyboard
|
|
14
|
-
# input.
|
|
15
|
-
#
|
|
16
|
-
# === Component Contract
|
|
17
|
-
#
|
|
18
|
-
# - `render(tui, frame, area)`: Draws the dialog; stores `area`
|
|
19
|
-
# - `handle_event(event) -> Symbol | nil`: Returns `:consumed` when handled
|
|
20
|
-
# - `open(text)`: Opens the dialog with the text to copy
|
|
21
|
-
# - `close`: Closes the dialog
|
|
22
|
-
# - `active?`: True if the dialog is visible
|
|
23
|
-
#
|
|
24
|
-
# === Example
|
|
25
|
-
#
|
|
26
|
-
# dialog = CopyDialog.new(clipboard)
|
|
27
|
-
# dialog.open("#FF0000")
|
|
28
|
-
#
|
|
29
|
-
# result = dialog.handle_event(event)
|
|
30
|
-
# # result == :consumed when dialog handled the event
|
|
31
|
-
#
|
|
32
|
-
# dialog.render(tui, frame, center_area)
|
|
33
|
-
class CopyDialog
|
|
34
|
-
def initialize(clipboard)
|
|
35
|
-
@clipboard = clipboard
|
|
36
|
-
@text = ""
|
|
37
|
-
@selected = :yes
|
|
38
|
-
@active = false
|
|
39
|
-
@area = nil
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# The cached render area.
|
|
43
|
-
attr_reader :area
|
|
44
|
-
|
|
45
|
-
# Opens the dialog with text to copy.
|
|
46
|
-
#
|
|
47
|
-
# Initializes selection to <tt>:yes</tt> and sets active to true.
|
|
48
|
-
#
|
|
49
|
-
# [text] String text to show and copy
|
|
50
|
-
#
|
|
51
|
-
# === Example
|
|
52
|
-
#
|
|
53
|
-
# dialog.open("#FF0000")
|
|
54
|
-
# dialog.active? # => true
|
|
55
|
-
def open(text)
|
|
56
|
-
@text = text
|
|
57
|
-
@selected = :yes
|
|
58
|
-
@active = true
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Closes the dialog and deactivates it.
|
|
62
|
-
def close
|
|
63
|
-
@active = false
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# True if the dialog is currently open and visible.
|
|
67
|
-
def active?
|
|
68
|
-
@active
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Renders the dialog into the given area.
|
|
72
|
-
#
|
|
73
|
-
# Shows the text to copy, Yes/No buttons with current selection highlighted,
|
|
74
|
-
# and keyboard instructions.
|
|
75
|
-
#
|
|
76
|
-
# [tui] Session or TUI factory object
|
|
77
|
-
# [frame] Frame object from RatatuiRuby.draw block
|
|
78
|
-
# [area] Rect area to draw into
|
|
79
|
-
#
|
|
80
|
-
# === Example
|
|
81
|
-
#
|
|
82
|
-
# dialog.render(tui, frame, center_area)
|
|
83
|
-
def render(tui, frame, area)
|
|
84
|
-
@area = area
|
|
85
|
-
widget = build_widget(tui)
|
|
86
|
-
frame.render_widget(widget, area)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Processes a keyboard event and updates selection or closes the dialog.
|
|
90
|
-
#
|
|
91
|
-
# Returns:
|
|
92
|
-
# - `:consumed` when the event was handled
|
|
93
|
-
# - `nil` when the event was ignored or dialog is inactive
|
|
94
|
-
#
|
|
95
|
-
# [event] Event from RatatuiRuby.poll_event
|
|
96
|
-
#
|
|
97
|
-
# === Example
|
|
98
|
-
#
|
|
99
|
-
# result = dialog.handle_event(event)
|
|
100
|
-
def handle_event(event)
|
|
101
|
-
return nil unless @active
|
|
102
|
-
|
|
103
|
-
case event
|
|
104
|
-
in { type: :key, code: "left" } | { type: :key, code: "h" }
|
|
105
|
-
@selected = :yes
|
|
106
|
-
:consumed
|
|
107
|
-
in { type: :key, code: "right" } | { type: :key, code: "l" }
|
|
108
|
-
@selected = :no
|
|
109
|
-
:consumed
|
|
110
|
-
in { type: :key, code: "enter" }
|
|
111
|
-
if @selected == :yes
|
|
112
|
-
@clipboard.copy(@text)
|
|
113
|
-
end
|
|
114
|
-
@active = false
|
|
115
|
-
:consumed
|
|
116
|
-
in { type: :key, code: "y" }
|
|
117
|
-
@clipboard.copy(@text)
|
|
118
|
-
@active = false
|
|
119
|
-
:consumed
|
|
120
|
-
in { type: :key, code: "n" }
|
|
121
|
-
@active = false
|
|
122
|
-
:consumed
|
|
123
|
-
else
|
|
124
|
-
nil
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
private def build_widget(tui)
|
|
129
|
-
yes_style = if @selected == :yes
|
|
130
|
-
tui.style(bg: :cyan, fg: :black, modifiers: [:bold])
|
|
131
|
-
else
|
|
132
|
-
tui.style(fg: :gray)
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
no_style = if @selected == :no
|
|
136
|
-
tui.style(bg: :cyan, fg: :black, modifiers: [:bold])
|
|
137
|
-
else
|
|
138
|
-
tui.style(fg: :gray)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
tui.block(
|
|
142
|
-
title: "Copy to Clipboard",
|
|
143
|
-
borders: [:all],
|
|
144
|
-
border_type: :rounded,
|
|
145
|
-
style: tui.style(bg: :black, fg: :white),
|
|
146
|
-
children: [
|
|
147
|
-
tui.paragraph(
|
|
148
|
-
text: [
|
|
149
|
-
tui.text_line(spans: [
|
|
150
|
-
tui.text_span(content: "Copy #{@text}?", style: tui.style(fg: :white)),
|
|
151
|
-
]),
|
|
152
|
-
tui.text_line(spans: []),
|
|
153
|
-
tui.text_line(spans: [
|
|
154
|
-
tui.text_span(content: "[", style: tui.style(fg: :white)),
|
|
155
|
-
tui.text_span(content: "Yes", style: yes_style),
|
|
156
|
-
tui.text_span(content: "] [", style: tui.style(fg: :white)),
|
|
157
|
-
tui.text_span(content: "No", style: no_style),
|
|
158
|
-
tui.text_span(content: "]", style: tui.style(fg: :white)),
|
|
159
|
-
]),
|
|
160
|
-
tui.text_line(spans: [
|
|
161
|
-
tui.text_span(content: "Use ←/→ or h/l to select, Enter to confirm", style: tui.style(fg: :gray, modifiers: [:italic])),
|
|
162
|
-
]),
|
|
163
|
-
]
|
|
164
|
-
),
|
|
165
|
-
]
|
|
166
|
-
)
|
|
167
|
-
end
|
|
168
|
-
end
|
|
@@ -1,128 +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 self-contained component displaying export formats for a color.
|
|
9
|
-
#
|
|
10
|
-
# Users need to copy color values in different formats (HEX, RGB, HSL).
|
|
11
|
-
# This component renders the export section and detects clicks on itself.
|
|
12
|
-
#
|
|
13
|
-
# === Component Contract
|
|
14
|
-
#
|
|
15
|
-
# - `render(tui, frame, area, palette:)`: Draws the export formats; stores `area` for hit testing
|
|
16
|
-
# - `handle_event(event) -> Symbol | nil`: Returns `:copy_requested` when clicked
|
|
17
|
-
#
|
|
18
|
-
# === Example
|
|
19
|
-
#
|
|
20
|
-
# export_pane = ExportPane.new
|
|
21
|
-
# export_pane.render(tui, frame, area, palette: palette)
|
|
22
|
-
#
|
|
23
|
-
# result = export_pane.handle_event(event)
|
|
24
|
-
# if result == :copy_requested && palette.main
|
|
25
|
-
# dialog.open(palette.main.hex)
|
|
26
|
-
# end
|
|
27
|
-
class ExportPane
|
|
28
|
-
def initialize
|
|
29
|
-
@area = nil
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# The cached render area, for hit testing.
|
|
33
|
-
attr_reader :area
|
|
34
|
-
|
|
35
|
-
# Renders the export formats section into the given area.
|
|
36
|
-
#
|
|
37
|
-
# Shows HEX, RGB, and HSL values for the current color. If no color is set,
|
|
38
|
-
# displays a placeholder message.
|
|
39
|
-
#
|
|
40
|
-
# [tui] Session or TUI factory object
|
|
41
|
-
# [frame] Frame object from RatatuiRuby.draw block
|
|
42
|
-
# [area] Rect area to draw into
|
|
43
|
-
# [palette] Palette object containing the color to display
|
|
44
|
-
#
|
|
45
|
-
# === Example
|
|
46
|
-
#
|
|
47
|
-
# export_pane.render(tui, frame, export_area, palette: palette)
|
|
48
|
-
def render(tui, frame, area, palette:)
|
|
49
|
-
@area = area
|
|
50
|
-
widget = build_widget(tui, palette)
|
|
51
|
-
frame.render_widget(widget, area)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Processes a mouse event and returns a signal if clicked.
|
|
55
|
-
#
|
|
56
|
-
# Returns:
|
|
57
|
-
# - `:copy_requested` when the pane is clicked (caller should open copy dialog)
|
|
58
|
-
# - `nil` when the event was ignored or outside the area
|
|
59
|
-
#
|
|
60
|
-
# [event] Event from RatatuiRuby.poll_event
|
|
61
|
-
#
|
|
62
|
-
# === Example
|
|
63
|
-
#
|
|
64
|
-
# result = export_pane.handle_event(event)
|
|
65
|
-
# if result == :copy_requested
|
|
66
|
-
# dialog.open(palette.main.hex)
|
|
67
|
-
# end
|
|
68
|
-
def handle_event(event)
|
|
69
|
-
case event
|
|
70
|
-
in { type: :mouse, kind: "down", button: "left", x:, y: }
|
|
71
|
-
if @area&.contains?(x, y)
|
|
72
|
-
:copy_requested
|
|
73
|
-
end
|
|
74
|
-
else
|
|
75
|
-
nil
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
private def build_widget(tui, palette)
|
|
80
|
-
if palette.main.nil?
|
|
81
|
-
tui.block(
|
|
82
|
-
title: "Export Formats",
|
|
83
|
-
borders: [:all],
|
|
84
|
-
children: [
|
|
85
|
-
tui.paragraph(
|
|
86
|
-
text: tui.text_line(spans: [
|
|
87
|
-
tui.text_span(content: "Enter a color to see formats"),
|
|
88
|
-
])
|
|
89
|
-
),
|
|
90
|
-
]
|
|
91
|
-
)
|
|
92
|
-
else
|
|
93
|
-
build_color_widget(tui, palette.main)
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
private def build_color_widget(tui, color)
|
|
98
|
-
hex = color.hex
|
|
99
|
-
rgb = color.rgb
|
|
100
|
-
hsl = color.hsl_string
|
|
101
|
-
text_color = color.contrasting_text_color
|
|
102
|
-
bg_style = tui.style(bg: hex, fg: text_color)
|
|
103
|
-
|
|
104
|
-
tui.block(
|
|
105
|
-
title: "Export Formats",
|
|
106
|
-
borders: [:all],
|
|
107
|
-
style: bg_style,
|
|
108
|
-
children: [
|
|
109
|
-
tui.paragraph(
|
|
110
|
-
text: [
|
|
111
|
-
tui.text_line(spans: [
|
|
112
|
-
tui.text_span(content: "HEX: ", style: bg_style),
|
|
113
|
-
tui.text_span(content: hex, style: tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
114
|
-
]),
|
|
115
|
-
tui.text_line(spans: [
|
|
116
|
-
tui.text_span(content: "RGB: ", style: bg_style),
|
|
117
|
-
tui.text_span(content: rgb, style: tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
118
|
-
]),
|
|
119
|
-
tui.text_line(spans: [
|
|
120
|
-
tui.text_span(content: "HSL: ", style: bg_style),
|
|
121
|
-
tui.text_span(content: hsl, style: tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
122
|
-
]),
|
|
123
|
-
]
|
|
124
|
-
),
|
|
125
|
-
]
|
|
126
|
-
)
|
|
127
|
-
end
|
|
128
|
-
end
|
|
@@ -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
|