ratatui_ruby 0.5.0 → 0.6.0
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/.builds/ruby-3.2.yml +1 -1
- data/.builds/ruby-3.3.yml +1 -1
- data/.builds/ruby-3.4.yml +1 -1
- data/.builds/ruby-4.0.0.yml +1 -1
- data/AGENTS.md +6 -0
- data/CHANGELOG.md +44 -7
- data/README.md +11 -4
- data/REUSE.toml +2 -7
- data/doc/application_architecture.md +84 -10
- data/doc/application_testing.md +75 -29
- data/doc/contributors/design/ruby_frontend.md +39 -3
- data/doc/contributors/design/rust_backend.md +1 -0
- data/doc/contributors/developing_examples.md +129 -44
- data/doc/contributors/examples_audit/p1_high.md +21 -0
- data/doc/contributors/examples_audit/p2_moderate.md +81 -0
- data/doc/contributors/examples_audit.md +41 -0
- data/doc/event_handling.md +11 -3
- data/doc/images/app_all_events.png +0 -0
- data/doc/images/app_color_picker.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_demo.png +0 -0
- data/doc/images/widget_block_demo.png +0 -0
- data/doc/images/widget_canvas_demo.png +0 -0
- data/doc/images/widget_cell_demo.png +0 -0
- data/doc/images/widget_center_demo.png +0 -0
- data/doc/images/widget_chart_demo.png +0 -0
- data/doc/images/widget_list_demo.png +0 -0
- data/doc/images/widget_overlay_demo.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_sparkline_demo.png +0 -0
- data/doc/images/widget_table_demo.png +0 -0
- data/doc/images/widget_tabs_demo.png +0 -0
- data/doc/images/widget_text_width.png +0 -0
- data/doc/quickstart.md +69 -76
- data/doc/terminal_limitations.md +92 -0
- data/examples/app_all_events/README.md +45 -27
- data/examples/app_all_events/app.rb +38 -35
- data/examples/app_all_events/model/app_model.rb +157 -0
- data/examples/app_all_events/model/event_entry.rb +17 -0
- data/examples/app_all_events/model/msg.rb +37 -0
- data/examples/app_all_events/update.rb +73 -0
- data/examples/app_all_events/view/app_view.rb +8 -8
- data/examples/app_all_events/view/controls_view.rb +8 -6
- data/examples/app_all_events/view/counts_view.rb +12 -8
- data/examples/app_all_events/view/live_view.rb +8 -7
- data/examples/app_all_events/view/log_view.rb +10 -15
- data/examples/app_color_picker/README.md +84 -44
- data/examples/app_color_picker/app.rb +24 -62
- data/examples/app_color_picker/controls.rb +90 -0
- data/examples/app_color_picker/copy_dialog.rb +45 -49
- data/examples/app_color_picker/export_pane.rb +126 -0
- data/examples/app_color_picker/input.rb +99 -67
- data/examples/app_color_picker/main_container.rb +178 -0
- data/examples/app_color_picker/palette.rb +55 -26
- data/examples/app_login_form/README.md +47 -0
- data/examples/app_login_form/app.rb +2 -3
- data/examples/app_stateful_interaction/README.md +31 -0
- data/examples/app_stateful_interaction/app.rb +272 -0
- data/examples/timeout_demo.rb +43 -0
- data/examples/verify_quickstart_dsl/README.md +48 -0
- data/examples/verify_quickstart_dsl/app.rb +2 -0
- data/examples/verify_quickstart_layout/README.md +71 -0
- data/examples/verify_quickstart_layout/app.rb +2 -0
- data/examples/verify_quickstart_lifecycle/README.md +56 -0
- data/examples/verify_quickstart_lifecycle/app.rb +8 -2
- data/examples/verify_readme_usage/README.md +43 -0
- data/examples/verify_readme_usage/app.rb +8 -2
- data/examples/widget_barchart_demo/README.md +49 -0
- data/examples/widget_barchart_demo/app.rb +5 -5
- data/examples/widget_block_demo/README.md +34 -0
- data/examples/widget_block_demo/app.rb +256 -0
- data/examples/widget_box_demo/README.md +45 -0
- data/examples/widget_calendar_demo/README.md +39 -0
- data/examples/widget_canvas_demo/README.md +27 -0
- data/examples/widget_canvas_demo/app.rb +123 -0
- data/examples/widget_cell_demo/README.md +36 -0
- data/examples/widget_cell_demo/app.rb +31 -24
- data/examples/widget_center_demo/README.md +29 -0
- data/examples/widget_center_demo/app.rb +116 -0
- data/examples/widget_chart_demo/README.md +41 -0
- data/examples/widget_chart_demo/app.rb +7 -2
- data/examples/widget_gauge_demo/README.md +41 -0
- data/examples/widget_layout_split/README.md +44 -0
- data/examples/widget_line_gauge_demo/README.md +41 -0
- data/examples/widget_list_demo/README.md +49 -0
- data/examples/widget_list_demo/app.rb +91 -107
- data/examples/widget_map_demo/README.md +39 -0
- data/examples/{app_map_demo → widget_map_demo}/app.rb +2 -2
- data/examples/widget_overlay_demo/app.rb +248 -0
- data/examples/widget_popup_demo/README.md +36 -0
- data/examples/widget_ratatui_logo_demo/README.md +34 -0
- data/examples/widget_ratatui_mascot_demo/README.md +34 -0
- data/examples/widget_rect/README.md +38 -0
- data/examples/widget_render/README.md +37 -0
- data/examples/widget_rich_text/README.md +35 -0
- data/examples/widget_rich_text/app.rb +62 -33
- data/examples/widget_scroll_text/README.md +37 -0
- data/examples/widget_scroll_text/app.rb +0 -1
- data/examples/widget_scrollbar_demo/README.md +37 -0
- data/examples/widget_sparkline_demo/README.md +42 -0
- data/examples/widget_sparkline_demo/app.rb +4 -3
- data/examples/widget_style_colors/README.md +34 -0
- data/examples/widget_table_demo/README.md +48 -0
- data/examples/{app_table_select → widget_table_demo}/app.rb +46 -8
- data/examples/widget_tabs_demo/README.md +41 -0
- data/examples/widget_tabs_demo/app.rb +15 -1
- data/examples/widget_text_width/README.md +35 -0
- data/examples/widget_text_width/app.rb +106 -0
- data/exe/.gitkeep +0 -0
- data/ext/ratatui_ruby/Cargo.lock +11 -4
- data/ext/ratatui_ruby/Cargo.toml +2 -1
- data/ext/ratatui_ruby/src/events.rs +238 -26
- data/ext/ratatui_ruby/src/frame.rs +113 -1
- data/ext/ratatui_ruby/src/lib.rs +34 -4
- data/ext/ratatui_ruby/src/string_width.rs +101 -0
- data/ext/ratatui_ruby/src/terminal.rs +39 -15
- data/ext/ratatui_ruby/src/text.rs +1 -1
- data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
- data/ext/ratatui_ruby/src/widgets/gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +9 -2
- data/ext/ratatui_ruby/src/widgets/list.rs +179 -3
- data/ext/ratatui_ruby/src/widgets/list_state.rs +137 -0
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +93 -1
- data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/table.rs +113 -1
- data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
- data/lib/ratatui_ruby/cell.rb +4 -4
- data/lib/ratatui_ruby/event/key/character.rb +35 -0
- data/lib/ratatui_ruby/event/key/media.rb +44 -0
- data/lib/ratatui_ruby/event/key/modifier.rb +95 -0
- data/lib/ratatui_ruby/event/key/navigation.rb +55 -0
- data/lib/ratatui_ruby/event/key/system.rb +45 -0
- data/lib/ratatui_ruby/event/key.rb +111 -51
- data/lib/ratatui_ruby/event/mouse.rb +3 -3
- data/lib/ratatui_ruby/event/paste.rb +1 -1
- data/lib/ratatui_ruby/frame.rb +96 -0
- data/lib/ratatui_ruby/list_state.rb +88 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/schema/cursor.rb +5 -0
- data/lib/ratatui_ruby/schema/gauge.rb +3 -1
- data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/list.rb +25 -4
- data/lib/ratatui_ruby/schema/list_item.rb +41 -0
- data/lib/ratatui_ruby/schema/rect.rb +43 -0
- data/lib/ratatui_ruby/schema/style.rb +24 -4
- data/lib/ratatui_ruby/schema/table.rb +21 -3
- data/lib/ratatui_ruby/schema/text.rb +69 -1
- data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
- data/lib/ratatui_ruby/session/autodoc.rb +65 -0
- data/lib/ratatui_ruby/session.rb +22 -7
- data/lib/ratatui_ruby/table_state.rb +90 -0
- data/lib/ratatui_ruby/test_helper/event_injection.rb +169 -0
- data/lib/ratatui_ruby/test_helper/snapshot.rb +390 -0
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +351 -0
- data/lib/ratatui_ruby/test_helper/terminal.rb +127 -0
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +68 -0
- data/lib/ratatui_ruby/test_helper.rb +65 -358
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +42 -19
- data/sig/examples/app_stateful_interaction/app.rbs +33 -0
- data/sig/examples/widget_block_demo/app.rbs +32 -0
- data/sig/examples/{app_map_demo → widget_map_demo}/app.rbs +2 -2
- data/sig/examples/{app_table_select → widget_table_demo}/app.rbs +2 -2
- data/sig/examples/{widget_table_flex → widget_text_width}/app.rbs +2 -3
- data/sig/ratatui_ruby/event.rbs +11 -1
- data/sig/ratatui_ruby/frame.rbs +2 -0
- data/sig/ratatui_ruby/list_state.rbs +13 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -2
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +3 -3
- data/sig/ratatui_ruby/schema/gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/line_gauge.rbs +2 -2
- data/sig/ratatui_ruby/schema/list.rbs +4 -2
- data/sig/ratatui_ruby/schema/list_item.rbs +10 -0
- data/sig/ratatui_ruby/schema/rect.rbs +3 -0
- data/sig/ratatui_ruby/schema/style.rbs +3 -3
- data/sig/ratatui_ruby/schema/table.rbs +3 -1
- data/sig/ratatui_ruby/schema/text.rbs +8 -6
- data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
- data/sig/ratatui_ruby/session.rbs +13 -0
- data/sig/ratatui_ruby/table_state.rbs +15 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +16 -0
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +12 -0
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +64 -0
- data/sig/ratatui_ruby/test_helper/terminal.rbs +14 -0
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +22 -0
- data/sig/ratatui_ruby/test_helper.rbs +5 -4
- data/tasks/autodoc/examples.rb +79 -0
- data/tasks/autodoc/inventory.rb +9 -7
- data/tasks/autodoc.rake +11 -5
- data/tasks/bump/changelog.rb +3 -3
- data/tasks/bump/links.rb +67 -0
- data/tasks/sourcehut.rake +61 -21
- data/tasks/terminal_preview/app_screenshot.rb +13 -3
- data/tasks/terminal_preview/saved_screenshot.rb +4 -3
- metadata +111 -37
- data/doc/images/app_table_select.png +0 -0
- data/doc/images/widget_block_padding.png +0 -0
- data/doc/images/widget_block_titles.png +0 -0
- data/doc/images/widget_list_styles.png +0 -0
- data/examples/app_all_events/model/events.rb +0 -180
- data/examples/app_all_events/model/highlight.rb +0 -57
- data/examples/app_all_events/test/snapshots/after_focus_lost.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_focus_regained.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_horizontal_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_a.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_key_ctrl_x.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_mouse_drag.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_multiple_events.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_paste.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_right_click.txt +0 -24
- data/examples/app_all_events/test/snapshots/after_vertical_resize.txt +0 -24
- data/examples/app_all_events/test/snapshots/initial_state.txt +0 -24
- data/examples/app_all_events/view_state.rb +0 -42
- data/examples/app_color_picker/scene.rb +0 -201
- data/examples/widget_block_padding/app.rb +0 -67
- data/examples/widget_block_titles/app.rb +0 -69
- data/examples/widget_list_styles/app.rb +0 -141
- data/examples/widget_table_flex/app.rb +0 -95
- data/sig/examples/widget_block_padding/app.rbs +0 -11
- data/sig/examples/widget_block_titles/app.rbs +0 -11
- data/sig/examples/widget_list_styles/app.rbs +0 -11
- data/tasks/bump/comparison_links.rb +0 -41
- /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
-
|
|
6
|
-
require_relative "input"
|
|
7
|
-
require_relative "palette"
|
|
8
|
-
require_relative "clipboard"
|
|
9
|
-
require_relative "copy_dialog"
|
|
10
|
-
|
|
11
|
-
# Orchestrates layout and rendering of the color picker UI.
|
|
12
|
-
#
|
|
13
|
-
# Building a complete color picker UI involves layout calculation, widget
|
|
14
|
-
# composition, and coordinate tracking for hit testing. Keeping this logic
|
|
15
|
-
# scattered across the main app makes the app harder to read and test.
|
|
16
|
-
#
|
|
17
|
-
# This object owns the layout logic. It orchestrates all sections. It calculates
|
|
18
|
-
# and caches rects for hit testing.
|
|
19
|
-
#
|
|
20
|
-
# Use it to encapsulate complex UI composition.
|
|
21
|
-
#
|
|
22
|
-
# === Example
|
|
23
|
-
#
|
|
24
|
-
# scene = Scene.new(tui)
|
|
25
|
-
# scene.render(frame, input:, palette:, clipboard:, dialog:)
|
|
26
|
-
#
|
|
27
|
-
# # For hit testing:
|
|
28
|
-
# rect = scene.export_rect
|
|
29
|
-
# if rect.contains?(x, y)
|
|
30
|
-
# # Handle click
|
|
31
|
-
# end
|
|
32
|
-
class Scene
|
|
33
|
-
def initialize(tui)
|
|
34
|
-
@tui = tui
|
|
35
|
-
@export_area_rect = nil
|
|
36
|
-
@hotkey_style = @tui.style(modifiers: [:bold, :underlined])
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Renders the complete UI given all model objects.
|
|
40
|
-
#
|
|
41
|
-
# Calculates layout once per frame. Renders input, palette, export, controls,
|
|
42
|
-
# and dialog sections. Caches the export area rect for hit testing.
|
|
43
|
-
#
|
|
44
|
-
# [frame] Frame object from RatatuiRuby.draw block
|
|
45
|
-
# [input] Input object for text input display
|
|
46
|
-
# [palette] Palette object for color display
|
|
47
|
-
# [clipboard] Clipboard object for feedback message
|
|
48
|
-
# [dialog] CopyDialog object for confirmation dialog
|
|
49
|
-
#
|
|
50
|
-
# === Example
|
|
51
|
-
#
|
|
52
|
-
# scene.render(frame, input: @input, palette: @palette, clipboard: @clipboard, dialog: @dialog)
|
|
53
|
-
def render(frame, input:, palette:, clipboard:, dialog:)
|
|
54
|
-
input_area, rest = @tui.layout_split(
|
|
55
|
-
frame.area,
|
|
56
|
-
direction: :vertical,
|
|
57
|
-
constraints: [
|
|
58
|
-
@tui.constraint_length(3),
|
|
59
|
-
@tui.constraint_fill(1),
|
|
60
|
-
]
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
color_area, control_area = @tui.layout_split(
|
|
64
|
-
rest,
|
|
65
|
-
direction: :vertical,
|
|
66
|
-
constraints: [
|
|
67
|
-
@tui.constraint_length(14),
|
|
68
|
-
@tui.constraint_fill(1),
|
|
69
|
-
]
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
harmony_area, @export_area_rect = @tui.layout_split(
|
|
73
|
-
color_area,
|
|
74
|
-
direction: :vertical,
|
|
75
|
-
constraints: [
|
|
76
|
-
@tui.constraint_length(7),
|
|
77
|
-
@tui.constraint_fill(1),
|
|
78
|
-
]
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
frame.render_widget(input.render(@tui), input_area)
|
|
82
|
-
frame.render_widget(build_palette_section(palette, harmony_area), harmony_area)
|
|
83
|
-
frame.render_widget(build_export_section(palette), @export_area_rect)
|
|
84
|
-
frame.render_widget(build_controls_section(clipboard), control_area)
|
|
85
|
-
|
|
86
|
-
if dialog.active?
|
|
87
|
-
dialog_center = calculate_center_area(frame.area, 40, 8)
|
|
88
|
-
frame.render_widget(@tui.clear, frame.area)
|
|
89
|
-
frame.render_widget(dialog.render(@tui, dialog_center), dialog_center)
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# The cached rectangle of the export formats section, used for hit testing.
|
|
94
|
-
#
|
|
95
|
-
# Populated during #render. Use this to detect clicks on the export section.
|
|
96
|
-
#
|
|
97
|
-
# === Example
|
|
98
|
-
#
|
|
99
|
-
# scene.render(frame, ...)
|
|
100
|
-
# if scene.export_rect.contains?(x, y)
|
|
101
|
-
# # Click on export section
|
|
102
|
-
# end
|
|
103
|
-
def export_rect
|
|
104
|
-
@export_area_rect
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
private def build_palette_section(palette, _harmony_area)
|
|
108
|
-
if palette.main.nil?
|
|
109
|
-
@tui.paragraph(text: "No color selected")
|
|
110
|
-
else
|
|
111
|
-
blocks = palette.as_blocks(@tui)
|
|
112
|
-
@tui.layout(
|
|
113
|
-
direction: :horizontal,
|
|
114
|
-
constraints: Array.new(blocks.size) { @tui.constraint_fill(1) },
|
|
115
|
-
children: blocks
|
|
116
|
-
)
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
private def build_export_section(palette)
|
|
121
|
-
if palette.main.nil?
|
|
122
|
-
@tui.block(
|
|
123
|
-
title: "Export Formats",
|
|
124
|
-
borders: [:all],
|
|
125
|
-
children: [
|
|
126
|
-
@tui.paragraph(
|
|
127
|
-
text: @tui.text_line(spans: [
|
|
128
|
-
@tui.text_span(content: "Enter a color to see formats"),
|
|
129
|
-
])
|
|
130
|
-
),
|
|
131
|
-
]
|
|
132
|
-
)
|
|
133
|
-
else
|
|
134
|
-
color = palette.main
|
|
135
|
-
hex = color.hex
|
|
136
|
-
rgb = color.rgb
|
|
137
|
-
hsl = color.hsl_string
|
|
138
|
-
text_color = color.contrasting_text_color
|
|
139
|
-
bg_style = @tui.style(bg: hex, fg: text_color)
|
|
140
|
-
|
|
141
|
-
@tui.block(
|
|
142
|
-
title: "Export Formats",
|
|
143
|
-
borders: [:all],
|
|
144
|
-
style: bg_style,
|
|
145
|
-
children: [
|
|
146
|
-
@tui.paragraph(
|
|
147
|
-
text: [
|
|
148
|
-
@tui.text_line(spans: [
|
|
149
|
-
@tui.text_span(content: "HEX: ", style: bg_style),
|
|
150
|
-
@tui.text_span(content: hex, style: @tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
151
|
-
]),
|
|
152
|
-
@tui.text_line(spans: [
|
|
153
|
-
@tui.text_span(content: "RGB: ", style: bg_style),
|
|
154
|
-
@tui.text_span(content: rgb, style: @tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
155
|
-
]),
|
|
156
|
-
@tui.text_line(spans: [
|
|
157
|
-
@tui.text_span(content: "HSL: ", style: bg_style),
|
|
158
|
-
@tui.text_span(content: hsl, style: @tui.style(bg: hex, fg: text_color, modifiers: [:underlined])),
|
|
159
|
-
]),
|
|
160
|
-
]
|
|
161
|
-
),
|
|
162
|
-
]
|
|
163
|
-
)
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
private def build_controls_section(clipboard)
|
|
168
|
-
control_lines = [
|
|
169
|
-
@tui.text_line(spans: [
|
|
170
|
-
@tui.text_span(content: "a-z/0-9", style: @hotkey_style),
|
|
171
|
-
@tui.text_span(content: ": Type "),
|
|
172
|
-
@tui.text_span(content: "enter", style: @hotkey_style),
|
|
173
|
-
@tui.text_span(content: ": Parse "),
|
|
174
|
-
@tui.text_span(content: "bksp", style: @hotkey_style),
|
|
175
|
-
@tui.text_span(content: ": Erase "),
|
|
176
|
-
@tui.text_span(content: "esc", style: @hotkey_style),
|
|
177
|
-
@tui.text_span(content: ": Quit"),
|
|
178
|
-
]),
|
|
179
|
-
]
|
|
180
|
-
|
|
181
|
-
unless clipboard.message.empty?
|
|
182
|
-
control_lines << @tui.text_line(spans: [
|
|
183
|
-
@tui.text_span(content: clipboard.message, style: @tui.style(fg: :green, modifiers: [:bold])),
|
|
184
|
-
])
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
@tui.block(
|
|
188
|
-
title: "Controls",
|
|
189
|
-
borders: [:all],
|
|
190
|
-
children: [
|
|
191
|
-
@tui.paragraph(text: control_lines),
|
|
192
|
-
]
|
|
193
|
-
)
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
private def calculate_center_area(parent_area, width, height)
|
|
197
|
-
x = (parent_area.width - width) / 2
|
|
198
|
-
y = (parent_area.height - height) / 2
|
|
199
|
-
@tui.rect(x:, y:, width:, height:)
|
|
200
|
-
end
|
|
201
|
-
end
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
-
|
|
6
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
7
|
-
require "ratatui_ruby"
|
|
8
|
-
|
|
9
|
-
# A demo showing Block padding capabilities
|
|
10
|
-
class WidgetBlockPadding
|
|
11
|
-
def run
|
|
12
|
-
RatatuiRuby.run do |tui|
|
|
13
|
-
loop do
|
|
14
|
-
# 1. Uniform Padding
|
|
15
|
-
block1 = RatatuiRuby::Block.new(
|
|
16
|
-
title: "Uniform Padding (2)",
|
|
17
|
-
borders: [:all],
|
|
18
|
-
padding: 2
|
|
19
|
-
)
|
|
20
|
-
para1 = RatatuiRuby::Paragraph.new(
|
|
21
|
-
text: "This text is padded by 2 on all sides.\nNotice the space between the border and this text.",
|
|
22
|
-
block: block1
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
# 2. Directional Padding
|
|
26
|
-
block2 = RatatuiRuby::Block.new(
|
|
27
|
-
title: "Directional Padding [Left: 4, Right: 0, Top: 2, Bottom: 0]",
|
|
28
|
-
borders: [:all],
|
|
29
|
-
padding: [4, 0, 2, 0]
|
|
30
|
-
)
|
|
31
|
-
para2 = RatatuiRuby::Paragraph.new(
|
|
32
|
-
text: "This text has different padding per side.\nLeft: 4, Top: 2.",
|
|
33
|
-
block: block2
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
# Instructions
|
|
37
|
-
para3 = RatatuiRuby::Paragraph.new(
|
|
38
|
-
text: "Press 'q' to quit."
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
tui.draw do |frame|
|
|
42
|
-
# Layout
|
|
43
|
-
areas = RatatuiRuby::Layout.split(
|
|
44
|
-
frame.area,
|
|
45
|
-
direction: :vertical,
|
|
46
|
-
constraints: [
|
|
47
|
-
RatatuiRuby::Constraint.length(10), # Uniform Padding
|
|
48
|
-
RatatuiRuby::Constraint.length(10), # Directional Padding
|
|
49
|
-
RatatuiRuby::Constraint.min(0),
|
|
50
|
-
]
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
frame.render_widget(para1, areas[0])
|
|
54
|
-
frame.render_widget(para2, areas[1])
|
|
55
|
-
frame.render_widget(para3, areas[2])
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
event = tui.poll_event
|
|
59
|
-
break if event == "q" || event == :ctrl_c
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
if __FILE__ == $0
|
|
66
|
-
WidgetBlockPadding.new.run
|
|
67
|
-
end
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
-
#
|
|
3
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
-
|
|
5
|
-
# frozen_string_literal: true
|
|
6
|
-
|
|
7
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
8
|
-
require "ratatui_ruby"
|
|
9
|
-
|
|
10
|
-
# Initialize the terminal
|
|
11
|
-
class WidgetBlockTitles
|
|
12
|
-
def run
|
|
13
|
-
RatatuiRuby.run do |tui|
|
|
14
|
-
loop do
|
|
15
|
-
# Create a layout with multiple blocks demonstrating titles
|
|
16
|
-
blocks = [
|
|
17
|
-
tui.block(
|
|
18
|
-
titles: [
|
|
19
|
-
{ content: "Top Left", alignment: :left, position: :top },
|
|
20
|
-
{ content: "Top Right", alignment: :right, position: :top },
|
|
21
|
-
],
|
|
22
|
-
borders: [:all],
|
|
23
|
-
border_color: "cyan"
|
|
24
|
-
),
|
|
25
|
-
tui.block(
|
|
26
|
-
titles: [
|
|
27
|
-
{ content: "Bottom Left", alignment: :left, position: :bottom },
|
|
28
|
-
{ content: "Bottom Center", alignment: :center, position: :bottom },
|
|
29
|
-
{ content: "Bottom Right", alignment: :right, position: :bottom },
|
|
30
|
-
],
|
|
31
|
-
borders: [:all],
|
|
32
|
-
border_color: "magenta"
|
|
33
|
-
),
|
|
34
|
-
tui.block(
|
|
35
|
-
titles: [
|
|
36
|
-
"Simple String Title (Top Left Default)",
|
|
37
|
-
{ content: "Mixed Title", alignment: :center, position: :bottom },
|
|
38
|
-
],
|
|
39
|
-
borders: [:all],
|
|
40
|
-
border_color: "green"
|
|
41
|
-
),
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
tui.draw do |frame|
|
|
45
|
-
layout = tui.layout_split(
|
|
46
|
-
frame.area,
|
|
47
|
-
direction: :vertical,
|
|
48
|
-
constraints: [
|
|
49
|
-
tui.constraint(:length, 10),
|
|
50
|
-
tui.constraint(:length, 10),
|
|
51
|
-
tui.constraint(:length, 10),
|
|
52
|
-
]
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
layout.each_with_index do |area, i|
|
|
56
|
-
frame.render_widget(blocks[i], area) if blocks[i]
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
event = tui.poll_event
|
|
61
|
-
break if event == "q" || event == :ctrl_c
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
if __FILE__ == $0
|
|
68
|
-
WidgetBlockTitles.new.run
|
|
69
|
-
end
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
-
|
|
6
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
7
|
-
require "ratatui_ruby"
|
|
8
|
-
|
|
9
|
-
# Styled List Example
|
|
10
|
-
# Demonstrates advanced styling options for the List widget.
|
|
11
|
-
class WidgetListStyles
|
|
12
|
-
attr_reader :selected_index, :highlight_spacing
|
|
13
|
-
|
|
14
|
-
def initialize
|
|
15
|
-
@items = ["Item 1", "Item 2", "Item 3"]
|
|
16
|
-
@selected_index = nil
|
|
17
|
-
|
|
18
|
-
@directions = [
|
|
19
|
-
{ name: "Top to Bottom", direction: :top_to_bottom },
|
|
20
|
-
{ name: "Bottom to Top", direction: :bottom_to_top },
|
|
21
|
-
]
|
|
22
|
-
@direction_index = 0
|
|
23
|
-
|
|
24
|
-
@highlight_spacings = [
|
|
25
|
-
{ name: "When Selected", spacing: :when_selected },
|
|
26
|
-
{ name: "Always", spacing: :always },
|
|
27
|
-
{ name: "Never", spacing: :never },
|
|
28
|
-
]
|
|
29
|
-
@highlight_spacing_index = 0
|
|
30
|
-
|
|
31
|
-
@repeat_modes = [
|
|
32
|
-
{ name: "Off", repeat: false },
|
|
33
|
-
{ name: "On", repeat: true },
|
|
34
|
-
]
|
|
35
|
-
@repeat_index = 0
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def run
|
|
39
|
-
RatatuiRuby.run do |tui|
|
|
40
|
-
@tui = tui
|
|
41
|
-
@hotkey_style = @tui.style(modifiers: [:bold, :underlined])
|
|
42
|
-
|
|
43
|
-
loop do
|
|
44
|
-
render
|
|
45
|
-
break if handle_input == :quit
|
|
46
|
-
sleep 0.05
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
private def render
|
|
52
|
-
selection_label = @selected_index.nil? ? "none" : @selected_index.to_s
|
|
53
|
-
direction_config = @directions[@direction_index]
|
|
54
|
-
spacing_config = @highlight_spacings[@highlight_spacing_index]
|
|
55
|
-
repeat_config = @repeat_modes[@repeat_index]
|
|
56
|
-
|
|
57
|
-
@tui.draw do |frame|
|
|
58
|
-
# Split into main content and control panel
|
|
59
|
-
main_area, control_area = @tui.layout_split(
|
|
60
|
-
frame.area,
|
|
61
|
-
direction: :vertical,
|
|
62
|
-
constraints: [
|
|
63
|
-
@tui.constraint_fill(1),
|
|
64
|
-
@tui.constraint_length(5),
|
|
65
|
-
]
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
# Render list
|
|
69
|
-
main_list = @tui.list(
|
|
70
|
-
items: @items,
|
|
71
|
-
selected_index: @selected_index,
|
|
72
|
-
style: @tui.style(fg: :white, bg: :black),
|
|
73
|
-
highlight_style: @tui.style(fg: :blue, bg: :white, modifiers: [:bold]),
|
|
74
|
-
highlight_symbol: ">> ",
|
|
75
|
-
repeat_highlight_symbol: repeat_config[:repeat],
|
|
76
|
-
highlight_spacing: spacing_config[:spacing],
|
|
77
|
-
direction: direction_config[:direction],
|
|
78
|
-
block: @tui.block(
|
|
79
|
-
title: "Items",
|
|
80
|
-
borders: [:all]
|
|
81
|
-
)
|
|
82
|
-
)
|
|
83
|
-
frame.render_widget(main_list, main_area)
|
|
84
|
-
|
|
85
|
-
# Render control panel
|
|
86
|
-
control_panel = @tui.block(
|
|
87
|
-
title: "Controls",
|
|
88
|
-
borders: [:all],
|
|
89
|
-
children: [
|
|
90
|
-
@tui.paragraph(
|
|
91
|
-
text: [
|
|
92
|
-
@tui.text_line(spans: [
|
|
93
|
-
@tui.text_span(content: "↑/↓", style: @hotkey_style),
|
|
94
|
-
@tui.text_span(content: ": Select (#{selection_label}) "),
|
|
95
|
-
@tui.text_span(content: "x", style: @hotkey_style),
|
|
96
|
-
@tui.text_span(content: ": Toggle Selection "),
|
|
97
|
-
@tui.text_span(content: "q", style: @hotkey_style),
|
|
98
|
-
@tui.text_span(content: ": Quit"),
|
|
99
|
-
]),
|
|
100
|
-
@tui.text_line(spans: [
|
|
101
|
-
@tui.text_span(content: "d", style: @hotkey_style),
|
|
102
|
-
@tui.text_span(content: ": Direction (#{direction_config[:name]}) "),
|
|
103
|
-
@tui.text_span(content: "s", style: @hotkey_style),
|
|
104
|
-
@tui.text_span(content: ": Spacing (#{spacing_config[:name]})"),
|
|
105
|
-
]),
|
|
106
|
-
@tui.text_line(spans: [
|
|
107
|
-
@tui.text_span(content: "r", style: @hotkey_style),
|
|
108
|
-
@tui.text_span(content: ": Repeat Symbol (#{repeat_config[:name]})"),
|
|
109
|
-
]),
|
|
110
|
-
]
|
|
111
|
-
),
|
|
112
|
-
]
|
|
113
|
-
)
|
|
114
|
-
frame.render_widget(control_panel, control_area)
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
private def handle_input
|
|
119
|
-
case @tui.poll_event
|
|
120
|
-
in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
|
|
121
|
-
:quit
|
|
122
|
-
in type: :key, code: "up"
|
|
123
|
-
@selected_index = (@selected_index || 0) - 1
|
|
124
|
-
@selected_index = @items.size - 1 if @selected_index.negative?
|
|
125
|
-
in type: :key, code: "down"
|
|
126
|
-
@selected_index = ((@selected_index || -1) + 1) % @items.size
|
|
127
|
-
in type: :key, code: "d"
|
|
128
|
-
@direction_index = (@direction_index + 1) % @directions.size
|
|
129
|
-
in type: :key, code: "s"
|
|
130
|
-
@highlight_spacing_index = (@highlight_spacing_index + 1) % @highlight_spacings.size
|
|
131
|
-
in type: :key, code: "r"
|
|
132
|
-
@repeat_index = (@repeat_index + 1) % @repeat_modes.size
|
|
133
|
-
in type: :key, code: "x"
|
|
134
|
-
@selected_index = @selected_index.nil? ? 0 : nil
|
|
135
|
-
else
|
|
136
|
-
nil
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
WidgetListStyles.new.run if __FILE__ == $PROGRAM_NAME
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
-
|
|
6
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
7
|
-
require "ratatui_ruby"
|
|
8
|
-
|
|
9
|
-
# Demonstrates structured data layout with flexible column distribution.
|
|
10
|
-
#
|
|
11
|
-
# Data is often multidimensional. You need to show relationships between fields (Name, Age, ID). Aligning columns manually in a monospaced environment is painful and error-prone.
|
|
12
|
-
#
|
|
13
|
-
# This demo showcases the <tt>Table</tt> widget's flex modes. It renders multiple tables demonstrating how data is distributed across the available space using <tt>:legacy</tt>, <tt>:space_between</tt>, and <tt>:space_around</tt> settings.
|
|
14
|
-
#
|
|
15
|
-
# Use it to understand how to build perfectly aligned grids for database records, logs, or file lists.
|
|
16
|
-
#
|
|
17
|
-
# === Example
|
|
18
|
-
#
|
|
19
|
-
# Run the demo from the terminal:
|
|
20
|
-
#
|
|
21
|
-
# ruby examples/widget_table_flex/app.rb
|
|
22
|
-
#
|
|
23
|
-
# rdoc-image:/doc/images/widget_table_flex.png
|
|
24
|
-
class WidgetTableFlex
|
|
25
|
-
def run
|
|
26
|
-
RatatuiRuby.run do |tui|
|
|
27
|
-
loop do
|
|
28
|
-
render(tui)
|
|
29
|
-
break if handle_input(tui) == :quit
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def render(tui)
|
|
35
|
-
tui.draw do |frame|
|
|
36
|
-
chunks = tui.layout_split(
|
|
37
|
-
frame.area,
|
|
38
|
-
direction: :vertical,
|
|
39
|
-
constraints: [
|
|
40
|
-
tui.constraint_length(3),
|
|
41
|
-
tui.constraint_fill(1),
|
|
42
|
-
tui.constraint_fill(1),
|
|
43
|
-
tui.constraint_fill(1),
|
|
44
|
-
]
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
frame.render_widget(
|
|
48
|
-
tui.paragraph(
|
|
49
|
-
text: "Table Flex Layout (press 'q' to quit)",
|
|
50
|
-
block: tui.block(title: "Header", borders: [:all])
|
|
51
|
-
),
|
|
52
|
-
chunks[0]
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
frame.render_widget(
|
|
56
|
-
tui.table(
|
|
57
|
-
header: ["Legacy (Default)", "Table"],
|
|
58
|
-
rows: [["Item 1", "Item 2"], ["Item 3", "Item 4"]],
|
|
59
|
-
widths: [tui.constraint_length(20), tui.constraint_length(20)],
|
|
60
|
-
block: tui.block(title: "Flex: :legacy (Default)", borders: [:all])
|
|
61
|
-
),
|
|
62
|
-
chunks[1]
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
frame.render_widget(
|
|
66
|
-
tui.table(
|
|
67
|
-
header: ["Space", "Between"],
|
|
68
|
-
rows: [["A", "B"], ["C", "D"]],
|
|
69
|
-
widths: [tui.constraint_length(20), tui.constraint_length(20)],
|
|
70
|
-
block: tui.block(title: "Flex: :space_between", borders: [:all]),
|
|
71
|
-
flex: :space_between
|
|
72
|
-
),
|
|
73
|
-
chunks[2]
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
frame.render_widget(
|
|
77
|
-
tui.table(
|
|
78
|
-
header: ["Space", "Around"],
|
|
79
|
-
rows: [["E", "F"], ["G", "H"]],
|
|
80
|
-
widths: [tui.constraint_length(20), tui.constraint_length(20)],
|
|
81
|
-
block: tui.block(title: "Flex: :space_around", borders: [:all]),
|
|
82
|
-
flex: :space_around
|
|
83
|
-
),
|
|
84
|
-
chunks[3]
|
|
85
|
-
)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def handle_input(tui)
|
|
90
|
-
event = tui.poll_event
|
|
91
|
-
:quit if event == "q" || event == :ctrl_c
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
WidgetTableFlex.new.run if __FILE__ == $0
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
-
|
|
6
|
-
# ComparisonLinks manages the git comparison links at the bottom of the changelog.
|
|
7
|
-
class ComparisonLinks
|
|
8
|
-
PATTERN = /^(\[Unreleased\]: .*)$/m
|
|
9
|
-
|
|
10
|
-
# Extracts the comparison links from the given content.
|
|
11
|
-
def self.parse(content)
|
|
12
|
-
match = content.match(PATTERN)
|
|
13
|
-
new(match[1].strip) if match
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Creates a new ComparisonLinks from the given links text.
|
|
17
|
-
def initialize(links)
|
|
18
|
-
@links = links.dup
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# Updates the comparison links for the new version.
|
|
22
|
-
def update(new_version)
|
|
23
|
-
pattern = %r{^\[Unreleased\]: (.*?/compare/)v(.*)\.\.\.HEAD$}
|
|
24
|
-
match = @links.match(pattern)
|
|
25
|
-
return unless match
|
|
26
|
-
|
|
27
|
-
base_url = match[1]
|
|
28
|
-
prev_version = match[2]
|
|
29
|
-
|
|
30
|
-
new_unreleased = "[Unreleased]: #{base_url}v#{new_version}...HEAD"
|
|
31
|
-
new_version_link = "[#{new_version}]: #{base_url}v#{prev_version}...v#{new_version}"
|
|
32
|
-
|
|
33
|
-
@links.sub!(pattern, "#{new_unreleased}\n#{new_version_link}")
|
|
34
|
-
nil
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Returns the current state of the links as a string.
|
|
38
|
-
def to_s
|
|
39
|
-
@links
|
|
40
|
-
end
|
|
41
|
-
end
|
|
File without changes
|