ratatui_ruby 0.9.0 → 0.10.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 +2 -1
- data/CHANGELOG.md +122 -0
- data/REUSE.toml +5 -0
- data/Rakefile +1 -1
- data/Steepfile +49 -0
- data/doc/concepts/debugging.md +401 -0
- data/doc/getting_started/quickstart.md +8 -3
- data/doc/images/app_all_events.png +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/troubleshooting/async.md +4 -0
- data/examples/app_debugging_showcase/README.md +119 -0
- data/examples/app_debugging_showcase/app.rb +318 -0
- data/examples/widget_canvas/app.rb +19 -14
- data/examples/widget_gauge/app.rb +18 -3
- data/examples/widget_layout_split/app.rb +16 -4
- data/examples/widget_list/app.rb +22 -6
- data/examples/widget_rect/app.rb +7 -6
- data/examples/widget_rich_text/app.rb +62 -37
- data/examples/widget_style_colors/app.rb +26 -47
- data/examples/widget_table/app.rb +28 -5
- data/examples/widget_text_width/app.rb +6 -4
- data/ext/ratatui_ruby/Cargo.lock +48 -1
- data/ext/ratatui_ruby/Cargo.toml +6 -2
- data/ext/ratatui_ruby/src/color.rs +82 -0
- data/ext/ratatui_ruby/src/errors.rs +28 -0
- data/ext/ratatui_ruby/src/events.rs +16 -14
- data/ext/ratatui_ruby/src/lib.rs +56 -0
- data/ext/ratatui_ruby/src/rendering.rs +3 -1
- data/ext/ratatui_ruby/src/style.rs +48 -21
- data/ext/ratatui_ruby/src/terminal.rs +40 -9
- data/ext/ratatui_ruby/src/text.rs +21 -9
- data/ext/ratatui_ruby/src/widgets/chart.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/layout.rs +90 -2
- data/ext/ratatui_ruby/src/widgets/list.rs +6 -5
- data/ext/ratatui_ruby/src/widgets/overlay.rs +2 -1
- data/ext/ratatui_ruby/src/widgets/table.rs +7 -6
- data/ext/ratatui_ruby/src/widgets/table_state.rs +55 -0
- data/ext/ratatui_ruby/src/widgets/tabs.rs +3 -2
- data/lib/ratatui_ruby/buffer/cell.rb +25 -15
- data/lib/ratatui_ruby/buffer.rb +134 -2
- data/lib/ratatui_ruby/cell.rb +13 -5
- data/lib/ratatui_ruby/debug.rb +215 -0
- data/lib/ratatui_ruby/event/key.rb +3 -2
- data/lib/ratatui_ruby/event/sync.rb +52 -0
- data/lib/ratatui_ruby/event.rb +7 -1
- data/lib/ratatui_ruby/layout/constraint.rb +184 -0
- data/lib/ratatui_ruby/layout/layout.rb +119 -13
- data/lib/ratatui_ruby/layout/position.rb +55 -0
- data/lib/ratatui_ruby/layout/rect.rb +188 -0
- data/lib/ratatui_ruby/layout/size.rb +55 -0
- data/lib/ratatui_ruby/layout.rb +4 -0
- data/lib/ratatui_ruby/style/color.rb +149 -0
- data/lib/ratatui_ruby/style/style.rb +51 -4
- data/lib/ratatui_ruby/style.rb +2 -0
- data/lib/ratatui_ruby/symbols.rb +435 -0
- data/lib/ratatui_ruby/synthetic_events.rb +86 -0
- data/lib/ratatui_ruby/table_state.rb +51 -0
- data/lib/ratatui_ruby/terminal_lifecycle.rb +2 -1
- data/lib/ratatui_ruby/test_helper/event_injection.rb +34 -1
- data/lib/ratatui_ruby/test_helper.rb +9 -0
- data/lib/ratatui_ruby/text/line.rb +245 -0
- data/lib/ratatui_ruby/text/span.rb +158 -0
- data/lib/ratatui_ruby/text.rb +99 -0
- data/lib/ratatui_ruby/tui/canvas_factories.rb +103 -0
- data/lib/ratatui_ruby/tui/core.rb +13 -2
- data/lib/ratatui_ruby/tui/layout_factories.rb +50 -3
- data/lib/ratatui_ruby/tui/state_factories.rb +42 -0
- data/lib/ratatui_ruby/tui/text_factories.rb +40 -0
- data/lib/ratatui_ruby/tui/widget_factories.rb +135 -60
- data/lib/ratatui_ruby/tui.rb +22 -1
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -0
- data/lib/ratatui_ruby/widgets/bar_chart.rb +30 -20
- data/lib/ratatui_ruby/widgets/block.rb +14 -6
- data/lib/ratatui_ruby/widgets/calendar.rb +2 -0
- data/lib/ratatui_ruby/widgets/canvas.rb +56 -0
- data/lib/ratatui_ruby/widgets/cell.rb +2 -0
- data/lib/ratatui_ruby/widgets/center.rb +2 -0
- data/lib/ratatui_ruby/widgets/chart.rb +6 -0
- data/lib/ratatui_ruby/widgets/clear.rb +2 -0
- data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
- data/lib/ratatui_ruby/widgets/cursor.rb +2 -0
- data/lib/ratatui_ruby/widgets/gauge.rb +61 -3
- data/lib/ratatui_ruby/widgets/line_gauge.rb +66 -4
- data/lib/ratatui_ruby/widgets/list.rb +87 -3
- data/lib/ratatui_ruby/widgets/list_item.rb +2 -0
- data/lib/ratatui_ruby/widgets/overlay.rb +2 -0
- data/lib/ratatui_ruby/widgets/paragraph.rb +4 -0
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -0
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -0
- data/lib/ratatui_ruby/widgets/row.rb +45 -0
- data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -0
- data/lib/ratatui_ruby/widgets/shape/label.rb +2 -0
- data/lib/ratatui_ruby/widgets/sparkline.rb +21 -13
- data/lib/ratatui_ruby/widgets/table.rb +13 -3
- data/lib/ratatui_ruby/widgets/tabs.rb +6 -4
- data/lib/ratatui_ruby/widgets.rb +1 -0
- data/lib/ratatui_ruby.rb +51 -16
- data/sig/examples/app_all_events/model/app_model.rbs +23 -0
- data/sig/examples/app_all_events/model/event_entry.rbs +15 -8
- data/sig/examples/app_all_events/model/timestamp.rbs +1 -1
- data/sig/examples/app_all_events/view.rbs +1 -1
- data/sig/examples/app_stateful_interaction/app.rbs +5 -5
- data/sig/examples/widget_block_demo/app.rbs +6 -6
- data/sig/manifest.yaml +5 -0
- data/sig/patches/data.rbs +26 -0
- data/sig/patches/debugger__.rbs +8 -0
- data/sig/ratatui_ruby/buffer/cell.rbs +46 -0
- data/sig/ratatui_ruby/buffer.rbs +18 -0
- data/sig/ratatui_ruby/cell.rbs +44 -0
- data/sig/ratatui_ruby/clear.rbs +18 -0
- data/sig/ratatui_ruby/constraint.rbs +26 -0
- data/sig/ratatui_ruby/debug.rbs +45 -0
- data/sig/ratatui_ruby/draw.rbs +30 -0
- data/sig/ratatui_ruby/event.rbs +68 -8
- data/sig/ratatui_ruby/frame.rbs +4 -4
- data/sig/ratatui_ruby/interfaces.rbs +25 -0
- data/sig/ratatui_ruby/layout/constraint.rbs +39 -0
- data/sig/ratatui_ruby/layout/layout.rbs +45 -0
- data/sig/ratatui_ruby/layout/position.rbs +18 -0
- data/sig/ratatui_ruby/layout/rect.rbs +64 -0
- data/sig/ratatui_ruby/layout/size.rbs +18 -0
- data/sig/ratatui_ruby/output_guard.rbs +23 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +83 -4
- data/sig/ratatui_ruby/rect.rbs +17 -0
- data/sig/ratatui_ruby/style/color.rbs +22 -0
- data/sig/ratatui_ruby/style/style.rbs +29 -0
- data/sig/ratatui_ruby/symbols.rbs +141 -0
- data/sig/ratatui_ruby/synthetic_events.rbs +21 -0
- data/sig/ratatui_ruby/table_state.rbs +6 -0
- data/sig/ratatui_ruby/terminal_lifecycle.rbs +31 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +2 -2
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +22 -3
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +8 -1
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -3
- data/sig/ratatui_ruby/text/line.rbs +27 -0
- data/sig/ratatui_ruby/text/span.rbs +23 -0
- data/sig/ratatui_ruby/text.rbs +12 -0
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +1 -1
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +23 -5
- data/sig/ratatui_ruby/tui/core.rbs +2 -2
- data/sig/ratatui_ruby/tui/layout_factories.rbs +16 -2
- data/sig/ratatui_ruby/tui/state_factories.rbs +8 -3
- data/sig/ratatui_ruby/tui/style_factories.rbs +3 -1
- data/sig/ratatui_ruby/tui/text_factories.rbs +7 -4
- data/sig/ratatui_ruby/tui/widget_factories.rbs +123 -30
- data/sig/ratatui_ruby/widgets/bar_chart.rbs +95 -0
- data/sig/ratatui_ruby/widgets/block.rbs +51 -0
- data/sig/ratatui_ruby/widgets/calendar.rbs +45 -0
- data/sig/ratatui_ruby/widgets/canvas.rbs +95 -0
- data/sig/ratatui_ruby/widgets/chart.rbs +91 -0
- data/sig/ratatui_ruby/widgets/coerceable_widget.rbs +26 -0
- data/sig/ratatui_ruby/widgets/gauge.rbs +44 -0
- data/sig/ratatui_ruby/widgets/line_gauge.rbs +48 -0
- data/sig/ratatui_ruby/widgets/list.rbs +63 -0
- data/sig/ratatui_ruby/widgets/misc.rbs +158 -0
- data/sig/ratatui_ruby/widgets/paragraph.rbs +45 -0
- data/sig/ratatui_ruby/widgets/row.rbs +43 -0
- data/sig/ratatui_ruby/widgets/scrollbar.rbs +53 -0
- data/sig/ratatui_ruby/widgets/shape/label.rbs +37 -0
- data/sig/ratatui_ruby/widgets/sparkline.rbs +45 -0
- data/sig/ratatui_ruby/widgets/table.rbs +78 -0
- data/sig/ratatui_ruby/widgets/tabs.rbs +44 -0
- data/sig/ratatui_ruby/{schema/list_item.rbs → widgets.rbs} +4 -4
- data/tasks/steep.rake +11 -0
- metadata +82 -63
- data/doc/contributors/v1.0.0_blockers.md +0 -876
- data/doc/troubleshooting/debugging.md +0 -101
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +0 -47
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +0 -25
- data/lib/ratatui_ruby/schema/bar_chart.rb +0 -287
- data/lib/ratatui_ruby/schema/block.rb +0 -198
- data/lib/ratatui_ruby/schema/calendar.rb +0 -84
- data/lib/ratatui_ruby/schema/canvas.rb +0 -239
- data/lib/ratatui_ruby/schema/center.rb +0 -67
- data/lib/ratatui_ruby/schema/chart.rb +0 -159
- data/lib/ratatui_ruby/schema/clear.rb +0 -62
- data/lib/ratatui_ruby/schema/constraint.rb +0 -151
- data/lib/ratatui_ruby/schema/cursor.rb +0 -50
- data/lib/ratatui_ruby/schema/gauge.rb +0 -72
- data/lib/ratatui_ruby/schema/layout.rb +0 -122
- data/lib/ratatui_ruby/schema/line_gauge.rb +0 -80
- data/lib/ratatui_ruby/schema/list.rb +0 -135
- data/lib/ratatui_ruby/schema/list_item.rb +0 -51
- data/lib/ratatui_ruby/schema/overlay.rb +0 -51
- data/lib/ratatui_ruby/schema/paragraph.rb +0 -107
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +0 -31
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +0 -36
- data/lib/ratatui_ruby/schema/rect.rb +0 -174
- data/lib/ratatui_ruby/schema/row.rb +0 -76
- data/lib/ratatui_ruby/schema/scrollbar.rb +0 -143
- data/lib/ratatui_ruby/schema/shape/label.rb +0 -76
- data/lib/ratatui_ruby/schema/sparkline.rb +0 -142
- data/lib/ratatui_ruby/schema/style.rb +0 -97
- data/lib/ratatui_ruby/schema/table.rb +0 -141
- data/lib/ratatui_ruby/schema/tabs.rb +0 -85
- data/lib/ratatui_ruby/schema/text.rb +0 -217
- data/sig/examples/app_all_events/model/events.rbs +0 -15
- data/sig/examples/app_all_events/view_state.rbs +0 -21
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +0 -22
- data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +0 -19
- data/sig/ratatui_ruby/schema/bar_chart.rbs +0 -38
- data/sig/ratatui_ruby/schema/block.rbs +0 -18
- data/sig/ratatui_ruby/schema/calendar.rbs +0 -23
- data/sig/ratatui_ruby/schema/canvas.rbs +0 -81
- data/sig/ratatui_ruby/schema/center.rbs +0 -17
- data/sig/ratatui_ruby/schema/chart.rbs +0 -39
- data/sig/ratatui_ruby/schema/constraint.rbs +0 -22
- data/sig/ratatui_ruby/schema/cursor.rbs +0 -16
- data/sig/ratatui_ruby/schema/draw.rbs +0 -33
- data/sig/ratatui_ruby/schema/gauge.rbs +0 -23
- data/sig/ratatui_ruby/schema/layout.rbs +0 -27
- data/sig/ratatui_ruby/schema/line_gauge.rbs +0 -24
- data/sig/ratatui_ruby/schema/list.rbs +0 -28
- data/sig/ratatui_ruby/schema/overlay.rbs +0 -15
- data/sig/ratatui_ruby/schema/paragraph.rbs +0 -20
- data/sig/ratatui_ruby/schema/ratatui_logo.rbs +0 -14
- data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +0 -17
- data/sig/ratatui_ruby/schema/rect.rbs +0 -48
- data/sig/ratatui_ruby/schema/row.rbs +0 -28
- data/sig/ratatui_ruby/schema/scrollbar.rbs +0 -42
- data/sig/ratatui_ruby/schema/sparkline.rbs +0 -22
- data/sig/ratatui_ruby/schema/style.rbs +0 -19
- data/sig/ratatui_ruby/schema/table.rbs +0 -32
- data/sig/ratatui_ruby/schema/tabs.rbs +0 -21
- data/sig/ratatui_ruby/schema/text.rbs +0 -31
- /data/lib/ratatui_ruby/{schema/draw.rb → draw.rb} +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
module RatatuiRuby
|
|
9
|
+
##
|
|
10
|
+
# Debug mode control for RatatuiRuby.
|
|
11
|
+
#
|
|
12
|
+
# TUI applications are hard to debug. Rust panics show cryptic stack traces.
|
|
13
|
+
# Ruby exceptions lack Rust context.
|
|
14
|
+
#
|
|
15
|
+
# This module controls debug visibility. Enable Rust backtraces only, or
|
|
16
|
+
# enable full debug mode for both Rust and Ruby-side features.
|
|
17
|
+
#
|
|
18
|
+
# == Activation Methods
|
|
19
|
+
#
|
|
20
|
+
# Three ways to enable debug features:
|
|
21
|
+
#
|
|
22
|
+
# [<tt>RUST_BACKTRACE=1</tt>] Rust backtraces only (no Ruby-side debug).
|
|
23
|
+
# [<tt>RR_DEBUG=1</tt>] Full debug mode (backtraces + Ruby features).
|
|
24
|
+
# [<tt>include RatatuiRuby::TestHelper</tt>] Auto-enables debug mode.
|
|
25
|
+
#
|
|
26
|
+
# === Example
|
|
27
|
+
#
|
|
28
|
+
#--
|
|
29
|
+
# SPDX-SnippetBegin
|
|
30
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
31
|
+
# SPDX-License-Identifier: MIT-0
|
|
32
|
+
#++
|
|
33
|
+
# # Programmatic activation
|
|
34
|
+
# RatatuiRuby::Debug.enable!
|
|
35
|
+
#
|
|
36
|
+
# # Or use the convenience alias
|
|
37
|
+
# RatatuiRuby.debug_mode!
|
|
38
|
+
#
|
|
39
|
+
#--
|
|
40
|
+
# SPDX-SnippetEnd
|
|
41
|
+
#++
|
|
42
|
+
module Debug
|
|
43
|
+
@rust_backtrace_enabled = false
|
|
44
|
+
@debug_mode_enabled = false
|
|
45
|
+
|
|
46
|
+
class << self
|
|
47
|
+
##
|
|
48
|
+
# Enables Rust backtraces only.
|
|
49
|
+
#
|
|
50
|
+
# Call this to get meaningful stack traces when Rust panics.
|
|
51
|
+
# Does not enable Ruby-side debug features.
|
|
52
|
+
#
|
|
53
|
+
# Safe to call multiple times; subsequent calls are no-ops.
|
|
54
|
+
def enable_rust_backtrace!
|
|
55
|
+
return if @rust_backtrace_enabled
|
|
56
|
+
|
|
57
|
+
@rust_backtrace_enabled = true
|
|
58
|
+
RatatuiRuby.__send__(:_enable_rust_backtrace)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Enables full debug mode.
|
|
63
|
+
#
|
|
64
|
+
# Activates Rust backtraces plus any Ruby-side debug features.
|
|
65
|
+
# Optionally enables remote debugging via the debug gem.
|
|
66
|
+
#
|
|
67
|
+
# Safe to call multiple times; subsequent calls are no-ops.
|
|
68
|
+
#
|
|
69
|
+
# [source] <tt>:env</tt> if called from RR_DEBUG env var,
|
|
70
|
+
#--
|
|
71
|
+
# SPDX-SnippetBegin
|
|
72
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
73
|
+
# SPDX-License-Identifier: MIT-0
|
|
74
|
+
#++
|
|
75
|
+
# <tt>:test</tt> from TestHelper (skips remote debugging),
|
|
76
|
+
# <tt>:programmatic</tt> otherwise.
|
|
77
|
+
#--
|
|
78
|
+
# SPDX-SnippetEnd
|
|
79
|
+
#++
|
|
80
|
+
def enable!(source: :programmatic)
|
|
81
|
+
return @socket_path if @debug_mode_enabled
|
|
82
|
+
|
|
83
|
+
@debug_mode_enabled = true
|
|
84
|
+
enable_rust_backtrace!
|
|
85
|
+
|
|
86
|
+
# Tests don't need remote debugging — it would cause hangs
|
|
87
|
+
return if source == :test
|
|
88
|
+
|
|
89
|
+
@remote_debugging_mode = (source == :env) ? :open : :open_nonstop
|
|
90
|
+
@socket_path = enable_remote_debugging!
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# rubocop:disable Lint/Debugger -- intentional debug gem integration
|
|
94
|
+
private def enable_remote_debugging!
|
|
95
|
+
# Suppress the "Debugger can attach via..." message that corrupts TUI displays
|
|
96
|
+
# Only suppress for programmatic activation; RR_DEBUG=1 users need to see it
|
|
97
|
+
old_log_level = ENV["RUBY_DEBUG_LOG_LEVEL"]
|
|
98
|
+
ENV["RUBY_DEBUG_LOG_LEVEL"] = "ERROR" if @remote_debugging_mode == :open_nonstop
|
|
99
|
+
|
|
100
|
+
case @remote_debugging_mode
|
|
101
|
+
when :open
|
|
102
|
+
# Stop at load so user can read socket path before TUI enters raw mode
|
|
103
|
+
ENV["RUBY_DEBUG_STOP_AT_LOAD"] = "1"
|
|
104
|
+
require "debug/open"
|
|
105
|
+
when :open_nonstop
|
|
106
|
+
require "debug/open_nonstop"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Restore log level after require (the require is what prints the message)
|
|
110
|
+
ENV["RUBY_DEBUG_LOG_LEVEL"] = old_log_level if @remote_debugging_mode == :open_nonstop
|
|
111
|
+
|
|
112
|
+
# Return the socket path so apps can display it
|
|
113
|
+
::DEBUGGER__.create_unix_domain_socket_name
|
|
114
|
+
rescue NameError
|
|
115
|
+
# Windows uses TCP/IP, not Unix sockets — DEBUGGER__ might not have this method
|
|
116
|
+
nil
|
|
117
|
+
# rubocop:enable Lint/Debugger
|
|
118
|
+
rescue LoadError
|
|
119
|
+
return unless @remote_debugging_mode == :open
|
|
120
|
+
|
|
121
|
+
raise LoadError,
|
|
122
|
+
"RR_DEBUG=1 requires the 'debug' gem for remote debugging. " \
|
|
123
|
+
"Add `gem 'debug'` to your Gemfile or install it with `gem install debug`."
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
# Returns whether full debug mode is enabled.
|
|
128
|
+
public def enabled?
|
|
129
|
+
@debug_mode_enabled
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
##
|
|
133
|
+
# Returns whether Rust backtraces are enabled.
|
|
134
|
+
public def rust_backtrace_enabled?
|
|
135
|
+
@rust_backtrace_enabled
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
##
|
|
139
|
+
# Returns the remote debugging mode for debug gem integration.
|
|
140
|
+
#
|
|
141
|
+
# TUI apps run in raw terminal mode, making interactive debugging
|
|
142
|
+
# impossible. The debug gem's remote debugging feature lets you
|
|
143
|
+
# attach from another terminal via UNIX socket.
|
|
144
|
+
#
|
|
145
|
+
# Returns one of:
|
|
146
|
+
# <tt>:open</tt> Stop at program start, wait for debugger attach.
|
|
147
|
+
# Activated when <tt>RR_DEBUG=1</tt> is set at startup.
|
|
148
|
+
# <tt>:open_nonstop</tt> Continue running, attach whenever ready.
|
|
149
|
+
# Activated when <tt>enable!</tt> is called programmatically.
|
|
150
|
+
# <tt>nil</tt> No remote debugging configured.
|
|
151
|
+
public def remote_debugging_mode
|
|
152
|
+
@remote_debugging_mode
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
##
|
|
156
|
+
# Triggers a Rust panic for backtrace verification.
|
|
157
|
+
#
|
|
158
|
+
# Debugging TUI apps is hard. Rust errors lack context. You want to
|
|
159
|
+
# confirm <tt>RUST_BACKTRACE=1</tt> actually shows stack traces before
|
|
160
|
+
# hitting a real bug.
|
|
161
|
+
#
|
|
162
|
+
# This method deliberately panics. The panic hook catches it and prints
|
|
163
|
+
# the Rust backtrace to stderr. If you see stack frames, your setup works.
|
|
164
|
+
#
|
|
165
|
+
# <b>WARNING</b>: Crashes your process. Use only for debugging.
|
|
166
|
+
#
|
|
167
|
+
# === Example
|
|
168
|
+
#
|
|
169
|
+
#--
|
|
170
|
+
# SPDX-SnippetBegin
|
|
171
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
172
|
+
# SPDX-License-Identifier: MIT-0
|
|
173
|
+
#++
|
|
174
|
+
# RUST_BACKTRACE=1 ruby -e 'require "ratatui_ruby"; RatatuiRuby::Debug.test_panic!'
|
|
175
|
+
#--
|
|
176
|
+
# SPDX-SnippetEnd
|
|
177
|
+
#++
|
|
178
|
+
public def test_panic!
|
|
179
|
+
RatatuiRuby.__send__(:_test_panic)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
##
|
|
183
|
+
# Temporarily suppresses Ruby-side debug mode checks.
|
|
184
|
+
#
|
|
185
|
+
# Rust backtraces remain enabled if previously activated; only
|
|
186
|
+
# Ruby-side features (like unknown-key errors) are suppressed
|
|
187
|
+
# within the block.
|
|
188
|
+
#
|
|
189
|
+
# === Example
|
|
190
|
+
#
|
|
191
|
+
#--
|
|
192
|
+
# SPDX-SnippetBegin
|
|
193
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
194
|
+
# SPDX-License-Identifier: MIT-0
|
|
195
|
+
#++
|
|
196
|
+
# RatatuiRuby::Debug.suppress_debug_mode do
|
|
197
|
+
# tui.table({ unknown_key: 1 }) # Does not raise
|
|
198
|
+
# end
|
|
199
|
+
#--
|
|
200
|
+
# SPDX-SnippetEnd
|
|
201
|
+
#++
|
|
202
|
+
public def suppress_debug_mode
|
|
203
|
+
old_value = @debug_mode_enabled
|
|
204
|
+
@debug_mode_enabled = false
|
|
205
|
+
yield
|
|
206
|
+
ensure
|
|
207
|
+
@debug_mode_enabled = old_value
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Auto-enable based on environment variables
|
|
214
|
+
RatatuiRuby::Debug.enable_rust_backtrace! if ENV["RUST_BACKTRACE"]
|
|
215
|
+
RatatuiRuby::Debug.enable!(source: :env) if ENV["RR_DEBUG"]
|
|
@@ -330,9 +330,10 @@ module RatatuiRuby
|
|
|
330
330
|
#--
|
|
331
331
|
# SPDX-SnippetEnd
|
|
332
332
|
#++
|
|
333
|
-
def method_missing(name, *args, &block)
|
|
333
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
334
334
|
if name.to_s.end_with?("?")
|
|
335
|
-
|
|
335
|
+
name_str = name.to_s
|
|
336
|
+
key_name = name_str.chop # Returns String, never nil for non-empty string
|
|
336
337
|
key_sym = key_name.to_sym
|
|
337
338
|
|
|
338
339
|
# Fast path: Exact match (e.g., media_pause? for media_pause)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#--
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
|
+
#++
|
|
7
|
+
|
|
8
|
+
module RatatuiRuby
|
|
9
|
+
class Event
|
|
10
|
+
# Synthetic event for synchronizing async operations in tests.
|
|
11
|
+
#
|
|
12
|
+
# Testing async behavior is tricky. You inject an event, but results arrive
|
|
13
|
+
# later. By the time you assert, the async work may not have completed.
|
|
14
|
+
#
|
|
15
|
+
# When a runtime (Tea, Kit) encounters this event, it should wait for all
|
|
16
|
+
# pending async operations to complete before processing the next event.
|
|
17
|
+
# This enables deterministic testing without changing production code paths.
|
|
18
|
+
#
|
|
19
|
+
# Inject this event between user actions and assertions to ensure async
|
|
20
|
+
# results have been processed:
|
|
21
|
+
#
|
|
22
|
+
# === Example
|
|
23
|
+
#
|
|
24
|
+
#--
|
|
25
|
+
# SPDX-SnippetBegin
|
|
26
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
27
|
+
# SPDX-License-Identifier: MIT-0
|
|
28
|
+
#++
|
|
29
|
+
# inject_key("s") # Triggers async command
|
|
30
|
+
# inject_sync # Wait for command to complete
|
|
31
|
+
# inject_key(:q) # Quit after seeing results
|
|
32
|
+
# Tea.run(...)
|
|
33
|
+
# assert_snapshots("after_s_with_results")
|
|
34
|
+
#
|
|
35
|
+
#--
|
|
36
|
+
# SPDX-SnippetEnd
|
|
37
|
+
#++
|
|
38
|
+
# This is not "test mode"—it's a real event that runtimes handle.
|
|
39
|
+
# Production apps could use it too (e.g., "ensure saves complete before quit").
|
|
40
|
+
class Sync < Event
|
|
41
|
+
# Returns true for Sync events.
|
|
42
|
+
def sync?
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Deconstructs the event for pattern matching.
|
|
47
|
+
def deconstruct_keys(keys)
|
|
48
|
+
{ type: :sync }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/ratatui_ruby/event.rb
CHANGED
|
@@ -111,9 +111,14 @@ module RatatuiRuby
|
|
|
111
111
|
false
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
+
# Returns true if this is a Sync event.
|
|
115
|
+
def sync?
|
|
116
|
+
false
|
|
117
|
+
end
|
|
118
|
+
|
|
114
119
|
# Responds to dynamic predicate methods for key checks.
|
|
115
120
|
# All non-Key events return false for any key predicate.
|
|
116
|
-
def method_missing(name, *args, &block)
|
|
121
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
117
122
|
if name.to_s.end_with?("?")
|
|
118
123
|
false
|
|
119
124
|
else
|
|
@@ -155,3 +160,4 @@ require_relative "event/resize"
|
|
|
155
160
|
require_relative "event/paste"
|
|
156
161
|
require_relative "event/focus_gained"
|
|
157
162
|
require_relative "event/focus_lost"
|
|
163
|
+
require_relative "event/sync"
|
|
@@ -148,6 +148,190 @@ module RatatuiRuby
|
|
|
148
148
|
def self.ratio(numerator, denominator)
|
|
149
149
|
new(type: :ratio, value: [Integer(numerator), Integer(denominator)])
|
|
150
150
|
end
|
|
151
|
+
|
|
152
|
+
# Converts an array of lengths into an array of Length constraints.
|
|
153
|
+
#
|
|
154
|
+
# Complex layouts often use multiple fixed-size sections. Manually creating each constraint
|
|
155
|
+
# clutters the code.
|
|
156
|
+
#
|
|
157
|
+
# This method maps over the input, returning a constraint array in one call.
|
|
158
|
+
#
|
|
159
|
+
# === Example
|
|
160
|
+
#
|
|
161
|
+
#--
|
|
162
|
+
# SPDX-SnippetBegin
|
|
163
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
164
|
+
# SPDX-License-Identifier: MIT-0
|
|
165
|
+
#++
|
|
166
|
+
# Constraint.from_lengths([10, 20, 10])
|
|
167
|
+
# # => [Constraint.length(10), Constraint.length(20), Constraint.length(10)]
|
|
168
|
+
#
|
|
169
|
+
#--
|
|
170
|
+
# SPDX-SnippetEnd
|
|
171
|
+
#++
|
|
172
|
+
# [values] Enumerable of Integers.
|
|
173
|
+
def self.from_lengths(values)
|
|
174
|
+
values.map { |v| length(v) }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Converts an array of percentages into an array of Percentage constraints.
|
|
178
|
+
#
|
|
179
|
+
# Percentage-based layouts distribute space proportionally. This method batches the creation.
|
|
180
|
+
#
|
|
181
|
+
# === Example
|
|
182
|
+
#
|
|
183
|
+
#--
|
|
184
|
+
# SPDX-SnippetBegin
|
|
185
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
186
|
+
# SPDX-License-Identifier: MIT-0
|
|
187
|
+
#++
|
|
188
|
+
# Constraint.from_percentages([25, 50, 25])
|
|
189
|
+
# # => [Constraint.percentage(25), Constraint.percentage(50), Constraint.percentage(25)]
|
|
190
|
+
#
|
|
191
|
+
#--
|
|
192
|
+
# SPDX-SnippetEnd
|
|
193
|
+
#++
|
|
194
|
+
# [values] Enumerable of Integers (0-100).
|
|
195
|
+
def self.from_percentages(values)
|
|
196
|
+
values.map { |v| percentage(v) }
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Converts an array of minimums into an array of Min constraints.
|
|
200
|
+
#
|
|
201
|
+
# Minimum constraints ensure sections never shrink below a threshold. Batch them here.
|
|
202
|
+
#
|
|
203
|
+
# === Example
|
|
204
|
+
#
|
|
205
|
+
#--
|
|
206
|
+
# SPDX-SnippetBegin
|
|
207
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
208
|
+
# SPDX-License-Identifier: MIT-0
|
|
209
|
+
#++
|
|
210
|
+
# Constraint.from_mins([5, 10, 5])
|
|
211
|
+
# # => [Constraint.min(5), Constraint.min(10), Constraint.min(5)]
|
|
212
|
+
#
|
|
213
|
+
#--
|
|
214
|
+
# SPDX-SnippetEnd
|
|
215
|
+
#++
|
|
216
|
+
# [values] Enumerable of Integers.
|
|
217
|
+
def self.from_mins(values)
|
|
218
|
+
values.map { |v| min(v) }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Converts an array of maximums into an array of Max constraints.
|
|
222
|
+
#
|
|
223
|
+
# Maximum constraints cap section sizes. Batch them here.
|
|
224
|
+
#
|
|
225
|
+
# === Example
|
|
226
|
+
#
|
|
227
|
+
#--
|
|
228
|
+
# SPDX-SnippetBegin
|
|
229
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
230
|
+
# SPDX-License-Identifier: MIT-0
|
|
231
|
+
#++
|
|
232
|
+
# Constraint.from_maxes([20, 30, 40])
|
|
233
|
+
# # => [Constraint.max(20), Constraint.max(30), Constraint.max(40)]
|
|
234
|
+
#
|
|
235
|
+
#--
|
|
236
|
+
# SPDX-SnippetEnd
|
|
237
|
+
#++
|
|
238
|
+
# [values] Enumerable of Integers.
|
|
239
|
+
def self.from_maxes(values)
|
|
240
|
+
values.map { |v| max(v) }
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Converts an array of weights into an array of Fill constraints.
|
|
244
|
+
#
|
|
245
|
+
# Fill constraints distribute remaining space by weight. Batch them here.
|
|
246
|
+
#
|
|
247
|
+
# === Example
|
|
248
|
+
#
|
|
249
|
+
#--
|
|
250
|
+
# SPDX-SnippetBegin
|
|
251
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
252
|
+
# SPDX-License-Identifier: MIT-0
|
|
253
|
+
#++
|
|
254
|
+
# Constraint.from_fills([1, 2, 1])
|
|
255
|
+
# # => [Constraint.fill(1), Constraint.fill(2), Constraint.fill(1)]
|
|
256
|
+
#
|
|
257
|
+
#--
|
|
258
|
+
# SPDX-SnippetEnd
|
|
259
|
+
#++
|
|
260
|
+
# [values] Enumerable of Integers.
|
|
261
|
+
def self.from_fills(values)
|
|
262
|
+
values.map { |v| fill(v) }
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Converts an array of ratio pairs into an array of Ratio constraints.
|
|
266
|
+
#
|
|
267
|
+
# Ratio constraints define exact fractions of space. Batch them here.
|
|
268
|
+
#
|
|
269
|
+
# === Example
|
|
270
|
+
#
|
|
271
|
+
#--
|
|
272
|
+
# SPDX-SnippetBegin
|
|
273
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
274
|
+
# SPDX-License-Identifier: MIT-0
|
|
275
|
+
#++
|
|
276
|
+
# Constraint.from_ratios([[1, 4], [2, 4], [1, 4]])
|
|
277
|
+
# # => [Constraint.ratio(1, 4), Constraint.ratio(2, 4), Constraint.ratio(1, 4)]
|
|
278
|
+
#
|
|
279
|
+
#--
|
|
280
|
+
# SPDX-SnippetEnd
|
|
281
|
+
#++
|
|
282
|
+
# [pairs] Enumerable of <tt>[numerator, denominator]</tt> arrays.
|
|
283
|
+
def self.from_ratios(pairs)
|
|
284
|
+
pairs.map { |n, d| ratio(n, d) }
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Computes the size this constraint would produce given available space.
|
|
288
|
+
#
|
|
289
|
+
# Layout engines use constraints to compute actual dimensions.
|
|
290
|
+
# Calling apply lets you preview the result without rendering.
|
|
291
|
+
#
|
|
292
|
+
# === Example
|
|
293
|
+
#
|
|
294
|
+
#--
|
|
295
|
+
# SPDX-SnippetBegin
|
|
296
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
297
|
+
# SPDX-License-Identifier: MIT-0
|
|
298
|
+
#++
|
|
299
|
+
# Constraint.percentage(50).apply(100) # => 50
|
|
300
|
+
# Constraint.length(10).apply(100) # => 10
|
|
301
|
+
# Constraint.min(10).apply(5) # => 10
|
|
302
|
+
# Constraint.max(10).apply(15) # => 10
|
|
303
|
+
# Constraint.ratio(1, 4).apply(100) # => 25
|
|
304
|
+
#--
|
|
305
|
+
# SPDX-SnippetEnd
|
|
306
|
+
#++
|
|
307
|
+
#
|
|
308
|
+
# [length] Available space (Integer).
|
|
309
|
+
#
|
|
310
|
+
# Returns the computed size (Integer).
|
|
311
|
+
def apply(length)
|
|
312
|
+
length = Integer(length)
|
|
313
|
+
case type
|
|
314
|
+
when :length
|
|
315
|
+
value
|
|
316
|
+
when :percentage
|
|
317
|
+
(length * value) / 100
|
|
318
|
+
when :min
|
|
319
|
+
[value, length].max
|
|
320
|
+
when :max
|
|
321
|
+
[value, length].min
|
|
322
|
+
when :fill
|
|
323
|
+
length
|
|
324
|
+
when :ratio
|
|
325
|
+
numerator, denominator = value
|
|
326
|
+
denominator.zero? ? 0 : (length * numerator) / denominator
|
|
327
|
+
else
|
|
328
|
+
length
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Ruby-idiomatic alias (TIMTOWTDI)
|
|
333
|
+
# Allows proc-like invocation: constraint.(100)
|
|
334
|
+
alias call apply
|
|
151
335
|
end
|
|
152
336
|
end
|
|
153
337
|
end
|
|
@@ -22,7 +22,7 @@ module RatatuiRuby
|
|
|
22
22
|
# Run the interactive demo from the terminal:
|
|
23
23
|
#
|
|
24
24
|
# ruby examples/widget_layout_split/app.rb
|
|
25
|
-
class Layout < Data.define(:direction, :constraints, :children, :flex)
|
|
25
|
+
class Layout < Data.define(:direction, :constraints, :children, :flex, :margin, :spacing)
|
|
26
26
|
##
|
|
27
27
|
# :attr_reader: direction
|
|
28
28
|
# Direction of the split.
|
|
@@ -81,6 +81,23 @@ module RatatuiRuby
|
|
|
81
81
|
# Flex: space evenly between elements.
|
|
82
82
|
FLEX_SPACE_EVENLY = :space_evenly
|
|
83
83
|
|
|
84
|
+
##
|
|
85
|
+
# :attr_reader: margin
|
|
86
|
+
# Margin around the layout area.
|
|
87
|
+
#
|
|
88
|
+
# Either a single <tt>Integer</tt> for uniform margin on all sides, or a
|
|
89
|
+
# <tt>Hash</tt> with <tt>:horizontal</tt> and <tt>:vertical</tt> keys.
|
|
90
|
+
#
|
|
91
|
+
# layout.margin # => 2
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
# :attr_reader: spacing
|
|
95
|
+
# Gap between segments (in cells).
|
|
96
|
+
#
|
|
97
|
+
# A positive integer that specifies the number of cells between each segment.
|
|
98
|
+
#
|
|
99
|
+
# layout.spacing # => 1
|
|
100
|
+
|
|
84
101
|
# Creates a new Layout.
|
|
85
102
|
#
|
|
86
103
|
# [direction]
|
|
@@ -91,7 +108,11 @@ module RatatuiRuby
|
|
|
91
108
|
# List of widgets to render (optional).
|
|
92
109
|
# [flex]
|
|
93
110
|
# Flex mode for spacing (default: <tt>:legacy</tt>).
|
|
94
|
-
|
|
111
|
+
# [margin]
|
|
112
|
+
# Edge margin in cells (default: <tt>0</tt>).
|
|
113
|
+
# [spacing]
|
|
114
|
+
# Gap between segments in cells (default: <tt>0</tt>).
|
|
115
|
+
def initialize(direction: :vertical, constraints: [], children: [], flex: :legacy, margin: 0, spacing: 0)
|
|
95
116
|
super
|
|
96
117
|
end
|
|
97
118
|
|
|
@@ -134,20 +155,105 @@ module RatatuiRuby
|
|
|
134
155
|
#++
|
|
135
156
|
# Returns an Array of <tt>Rect</tt> objects.
|
|
136
157
|
def self.split(area, direction: :vertical, constraints:, flex: :legacy)
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
# Coerce area to Rect for type safety (supports duck typing via _RectLike interface)
|
|
159
|
+
rect = case area
|
|
160
|
+
when Rect
|
|
161
|
+
area
|
|
162
|
+
when Hash
|
|
163
|
+
Rect.new(
|
|
164
|
+
x: Integer(area.fetch(:x, 0)),
|
|
165
|
+
y: Integer(area.fetch(:y, 0)),
|
|
166
|
+
width: Integer(area.fetch(:width, 0)),
|
|
167
|
+
height: Integer(area.fetch(:height, 0))
|
|
168
|
+
)
|
|
169
|
+
else
|
|
170
|
+
# Duck typing: accept any object responding to x, y, width, height
|
|
171
|
+
if area.respond_to?(:x) && area.respond_to?(:y) && area.respond_to?(:width) && area.respond_to?(:height)
|
|
172
|
+
# @type var rect_like: _RectLike
|
|
173
|
+
rect_like = area
|
|
174
|
+
Rect.new(x: rect_like.x, y: rect_like.y, width: rect_like.width, height: rect_like.height)
|
|
175
|
+
else
|
|
176
|
+
raise ArgumentError, "area must be a Rect, Hash, or respond to x/y/width/height, got #{area.class}"
|
|
177
|
+
end
|
|
147
178
|
end
|
|
148
|
-
raw_rects = _split(
|
|
179
|
+
raw_rects = _split(rect, direction, constraints, flex)
|
|
149
180
|
raw_rects.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
|
|
150
181
|
end
|
|
182
|
+
|
|
183
|
+
# Splits an area into multiple rectangles, returning both segments and spacers.
|
|
184
|
+
#
|
|
185
|
+
# Layout splitting returns only the content areas. But some designs need to
|
|
186
|
+
# render content in the gaps (dividers, separators, decorations).
|
|
187
|
+
#
|
|
188
|
+
# This method returns both the segments (content areas) and the spacers
|
|
189
|
+
# (gaps between segments) as separate arrays. The spacers are the Rects
|
|
190
|
+
# that represent the spacing between each segment.
|
|
191
|
+
#
|
|
192
|
+
# Use it to render custom separators or to calculate layout with spacing.
|
|
193
|
+
#
|
|
194
|
+
# [area]
|
|
195
|
+
# The area to split. Can be a <tt>Rect</tt> or a <tt>Hash</tt> containing <tt>:x</tt>, <tt>:y</tt>, <tt>:width</tt>, and <tt>:height</tt>.
|
|
196
|
+
# [direction]
|
|
197
|
+
# <tt>:vertical</tt> or <tt>:horizontal</tt> (default: <tt>:vertical</tt>).
|
|
198
|
+
# [constraints]
|
|
199
|
+
# Array of <tt>Constraint</tt> objects defining section sizes.
|
|
200
|
+
# [flex]
|
|
201
|
+
#--
|
|
202
|
+
# SPDX-SnippetBegin
|
|
203
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
204
|
+
# SPDX-License-Identifier: MIT-0
|
|
205
|
+
#++
|
|
206
|
+
# Flex mode for spacing (default: <tt>:legacy</tt>).
|
|
207
|
+
#
|
|
208
|
+
#--
|
|
209
|
+
# SPDX-SnippetEnd
|
|
210
|
+
#++
|
|
211
|
+
# Returns an Array of two Arrays: <tt>[segments, spacers]</tt>, each containing <tt>Rect</tt> objects.
|
|
212
|
+
#
|
|
213
|
+
# === Example
|
|
214
|
+
#
|
|
215
|
+
#--
|
|
216
|
+
# SPDX-SnippetBegin
|
|
217
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
218
|
+
# SPDX-License-Identifier: MIT-0
|
|
219
|
+
#++
|
|
220
|
+
# area = Rect.new(x: 0, y: 0, width: 100, height: 10)
|
|
221
|
+
# segments, spacers = Layout.split_with_spacers(
|
|
222
|
+
# area,
|
|
223
|
+
# direction: :horizontal,
|
|
224
|
+
# constraints: [Constraint.length(40), Constraint.length(40)],
|
|
225
|
+
# flex: :space_around
|
|
226
|
+
# )
|
|
227
|
+
# # segments: 2 Rects for content
|
|
228
|
+
# # spacers: Rects for gaps between/around segments
|
|
229
|
+
#--
|
|
230
|
+
# SPDX-SnippetEnd
|
|
231
|
+
#++
|
|
232
|
+
def self.split_with_spacers(area, direction: :vertical, constraints:, flex: :legacy)
|
|
233
|
+
# Coerce area to Rect for type safety
|
|
234
|
+
rect = case area
|
|
235
|
+
when Rect
|
|
236
|
+
area
|
|
237
|
+
when Hash
|
|
238
|
+
Rect.new(
|
|
239
|
+
x: Integer(area.fetch(:x, 0)),
|
|
240
|
+
y: Integer(area.fetch(:y, 0)),
|
|
241
|
+
width: Integer(area.fetch(:width, 0)),
|
|
242
|
+
height: Integer(area.fetch(:height, 0))
|
|
243
|
+
)
|
|
244
|
+
else
|
|
245
|
+
if area.respond_to?(:x) && area.respond_to?(:y) && area.respond_to?(:width) && area.respond_to?(:height)
|
|
246
|
+
rect_like = area
|
|
247
|
+
Rect.new(x: rect_like.x, y: rect_like.y, width: rect_like.width, height: rect_like.height)
|
|
248
|
+
else
|
|
249
|
+
raise ArgumentError, "area must be a Rect, Hash, or respond to x/y/width/height, got #{area.class}"
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
raw_segments, raw_spacers = _split_with_spacers(rect, direction, constraints, flex)
|
|
253
|
+
segments = raw_segments.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
|
|
254
|
+
spacers = raw_spacers.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
|
|
255
|
+
[segments, spacers]
|
|
256
|
+
end
|
|
151
257
|
end
|
|
152
258
|
end
|
|
153
259
|
end
|