ratatui_ruby 1.4.0-x86_64-linux
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 +7 -0
- data/LICENSE +15 -0
- data/LICENSES/AGPL-3.0-or-later.txt +661 -0
- data/LICENSES/CC-BY-SA-4.0.txt +427 -0
- data/LICENSES/CC0-1.0.txt +121 -0
- data/LICENSES/LGPL-3.0-or-later.txt +304 -0
- data/LICENSES/MIT-0.txt +16 -0
- data/LICENSES/MIT.txt +21 -0
- data/REUSE.toml +42 -0
- data/exe/.gitkeep +0 -0
- data/ext/ratatui_ruby/.cargo/config.toml +13 -0
- data/ext/ratatui_ruby/.gitignore +4 -0
- data/ext/ratatui_ruby/Cargo.lock +1737 -0
- data/ext/ratatui_ruby/Cargo.toml +24 -0
- data/ext/ratatui_ruby/clippy.toml +7 -0
- data/ext/ratatui_ruby/extconf.rb +21 -0
- 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 +700 -0
- data/ext/ratatui_ruby/src/frame.rs +241 -0
- data/ext/ratatui_ruby/src/lib.rs +343 -0
- data/ext/ratatui_ruby/src/lib_header.rs +11 -0
- data/ext/ratatui_ruby/src/rendering.rs +158 -0
- data/ext/ratatui_ruby/src/string_width.rs +101 -0
- data/ext/ratatui_ruby/src/style.rs +469 -0
- data/ext/ratatui_ruby/src/terminal/capabilities.rs +46 -0
- data/ext/ratatui_ruby/src/terminal/init.rs +233 -0
- data/ext/ratatui_ruby/src/terminal/mod.rs +42 -0
- data/ext/ratatui_ruby/src/terminal/mutations.rs +158 -0
- data/ext/ratatui_ruby/src/terminal/queries.rs +231 -0
- data/ext/ratatui_ruby/src/terminal/query.rs +400 -0
- data/ext/ratatui_ruby/src/terminal/storage.rs +109 -0
- data/ext/ratatui_ruby/src/terminal/wrapper.rs +16 -0
- data/ext/ratatui_ruby/src/text.rs +225 -0
- data/ext/ratatui_ruby/src/widgets/barchart.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/block.rs +41 -0
- data/ext/ratatui_ruby/src/widgets/calendar.rs +84 -0
- data/ext/ratatui_ruby/src/widgets/canvas.rs +183 -0
- data/ext/ratatui_ruby/src/widgets/center.rs +79 -0
- data/ext/ratatui_ruby/src/widgets/chart.rs +222 -0
- data/ext/ratatui_ruby/src/widgets/clear.rs +39 -0
- data/ext/ratatui_ruby/src/widgets/cursor.rs +32 -0
- data/ext/ratatui_ruby/src/widgets/gauge.rs +65 -0
- data/ext/ratatui_ruby/src/widgets/layout.rs +379 -0
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +100 -0
- data/ext/ratatui_ruby/src/widgets/list.rs +378 -0
- data/ext/ratatui_ruby/src/widgets/list_state.rs +173 -0
- data/ext/ratatui_ruby/src/widgets/mod.rs +26 -0
- data/ext/ratatui_ruby/src/widgets/overlay.rs +24 -0
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +87 -0
- data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +40 -0
- data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +55 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +214 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar_state.rs +169 -0
- data/ext/ratatui_ruby/src/widgets/sparkline.rs +127 -0
- data/ext/ratatui_ruby/src/widgets/table.rs +415 -0
- data/ext/ratatui_ruby/src/widgets/table_state.rs +203 -0
- data/ext/ratatui_ruby/src/widgets/tabs.rs +194 -0
- data/lib/ratatui_ruby/backend/window_size.rb +50 -0
- data/lib/ratatui_ruby/backend.rb +59 -0
- data/lib/ratatui_ruby/buffer/cell.rb +212 -0
- data/lib/ratatui_ruby/buffer.rb +149 -0
- data/lib/ratatui_ruby/cell.rb +208 -0
- data/lib/ratatui_ruby/debug.rb +215 -0
- data/lib/ratatui_ruby/draw.rb +63 -0
- data/lib/ratatui_ruby/event/focus_gained.rb +125 -0
- data/lib/ratatui_ruby/event/focus_lost.rb +127 -0
- data/lib/ratatui_ruby/event/key/character.rb +53 -0
- data/lib/ratatui_ruby/event/key/dwim.rb +301 -0
- data/lib/ratatui_ruby/event/key/media.rb +46 -0
- data/lib/ratatui_ruby/event/key/modifier.rb +107 -0
- data/lib/ratatui_ruby/event/key/navigation.rb +72 -0
- data/lib/ratatui_ruby/event/key/system.rb +47 -0
- data/lib/ratatui_ruby/event/key.rb +479 -0
- data/lib/ratatui_ruby/event/mouse.rb +291 -0
- data/lib/ratatui_ruby/event/none.rb +53 -0
- data/lib/ratatui_ruby/event/paste.rb +130 -0
- data/lib/ratatui_ruby/event/resize.rb +221 -0
- data/lib/ratatui_ruby/event/sync.rb +52 -0
- data/lib/ratatui_ruby/event.rb +163 -0
- data/lib/ratatui_ruby/frame.rb +257 -0
- data/lib/ratatui_ruby/labs/a11y.rb +182 -0
- data/lib/ratatui_ruby/labs/frame_a11y_capture.rb +50 -0
- data/lib/ratatui_ruby/labs.rb +47 -0
- data/lib/ratatui_ruby/layout/alignment.rb +91 -0
- data/lib/ratatui_ruby/layout/constraint.rb +337 -0
- data/lib/ratatui_ruby/layout/layout.rb +258 -0
- data/lib/ratatui_ruby/layout/position.rb +81 -0
- data/lib/ratatui_ruby/layout/rect.rb +733 -0
- data/lib/ratatui_ruby/layout/size.rb +62 -0
- data/lib/ratatui_ruby/layout.rb +29 -0
- data/lib/ratatui_ruby/list_state.rb +201 -0
- data/lib/ratatui_ruby/output_guard.rb +171 -0
- data/lib/ratatui_ruby/ratatui_ruby.so +0 -0
- data/lib/ratatui_ruby/scrollbar_state.rb +122 -0
- data/lib/ratatui_ruby/style/color.rb +149 -0
- data/lib/ratatui_ruby/style/style.rb +147 -0
- data/lib/ratatui_ruby/style.rb +19 -0
- data/lib/ratatui_ruby/symbols.rb +435 -0
- data/lib/ratatui_ruby/synthetic_events.rb +106 -0
- data/lib/ratatui_ruby/table_state.rb +251 -0
- data/lib/ratatui_ruby/terminal/capabilities.rb +316 -0
- data/lib/ratatui_ruby/terminal/viewport.rb +80 -0
- data/lib/ratatui_ruby/terminal.rb +66 -0
- data/lib/ratatui_ruby/terminal_lifecycle.rb +303 -0
- data/lib/ratatui_ruby/terminal_lifecycle.rb.bak +197 -0
- data/lib/ratatui_ruby/test_helper/event_injection.rb +241 -0
- data/lib/ratatui_ruby/test_helper/global_state.rb +111 -0
- data/lib/ratatui_ruby/test_helper/snapshot.rb +568 -0
- data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.ansi +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/axis_labels_alignment.txt +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.ansi +5 -0
- data/lib/ratatui_ruby/test_helper/snapshots/barchart_styled_label.txt +5 -0
- data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.ansi +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/chart_rendering.txt +24 -0
- data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/half_block_marker.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_bottom.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_left.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_right.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.ansi +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/legend_position_top.txt +12 -0
- data/lib/ratatui_ruby/test_helper/snapshots/my_snapshot.txt +1 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.ansi +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_axis_title.txt +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.ansi +10 -0
- data/lib/ratatui_ruby/test_helper/snapshots/styled_dataset_name.txt +10 -0
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +449 -0
- data/lib/ratatui_ruby/test_helper/subprocess_timeout.rb +35 -0
- data/lib/ratatui_ruby/test_helper/terminal.rb +187 -0
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +86 -0
- data/lib/ratatui_ruby/test_helper.rb +115 -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/buffer_factories.rb +22 -0
- data/lib/ratatui_ruby/tui/canvas_factories.rb +149 -0
- data/lib/ratatui_ruby/tui/core.rb +67 -0
- data/lib/ratatui_ruby/tui/layout_factories.rb +153 -0
- data/lib/ratatui_ruby/tui/state_factories.rb +77 -0
- data/lib/ratatui_ruby/tui/style_factories.rb +22 -0
- data/lib/ratatui_ruby/tui/text_factories.rb +86 -0
- data/lib/ratatui_ruby/tui/widget_factories.rb +272 -0
- data/lib/ratatui_ruby/tui.rb +106 -0
- data/lib/ratatui_ruby/version.rb +12 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +51 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +29 -0
- data/lib/ratatui_ruby/widgets/bar_chart.rb +308 -0
- data/lib/ratatui_ruby/widgets/block.rb +266 -0
- data/lib/ratatui_ruby/widgets/calendar.rb +88 -0
- data/lib/ratatui_ruby/widgets/canvas.rb +297 -0
- data/lib/ratatui_ruby/widgets/cell.rb +59 -0
- data/lib/ratatui_ruby/widgets/center.rb +71 -0
- data/lib/ratatui_ruby/widgets/chart.rb +172 -0
- data/lib/ratatui_ruby/widgets/clear.rb +66 -0
- data/lib/ratatui_ruby/widgets/coerceable_widget.rb +77 -0
- data/lib/ratatui_ruby/widgets/cursor.rb +54 -0
- data/lib/ratatui_ruby/widgets/gauge.rb +146 -0
- data/lib/ratatui_ruby/widgets/line_gauge.rb +158 -0
- data/lib/ratatui_ruby/widgets/list.rb +252 -0
- data/lib/ratatui_ruby/widgets/list_item.rb +55 -0
- data/lib/ratatui_ruby/widgets/overlay.rb +55 -0
- data/lib/ratatui_ruby/widgets/paragraph.rb +113 -0
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +35 -0
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +40 -0
- data/lib/ratatui_ruby/widgets/row.rb +123 -0
- data/lib/ratatui_ruby/widgets/scrollbar.rb +147 -0
- data/lib/ratatui_ruby/widgets/shape/label.rb +80 -0
- data/lib/ratatui_ruby/widgets/sparkline.rb +153 -0
- data/lib/ratatui_ruby/widgets/table.rb +213 -0
- data/lib/ratatui_ruby/widgets/tabs.rb +91 -0
- data/lib/ratatui_ruby/widgets.rb +43 -0
- data/lib/ratatui_ruby.rb +555 -0
- data/sig/examples/app_all_events/app.rbs +11 -0
- data/sig/examples/app_all_events/model/app_model.rbs +23 -0
- data/sig/examples/app_all_events/model/event_entry.rbs +23 -0
- data/sig/examples/app_all_events/model/timestamp.rbs +11 -0
- data/sig/examples/app_all_events/view/app_view.rbs +8 -0
- data/sig/examples/app_all_events/view/controls_view.rbs +6 -0
- data/sig/examples/app_all_events/view/counts_view.rbs +6 -0
- data/sig/examples/app_all_events/view/live_view.rbs +6 -0
- data/sig/examples/app_all_events/view/log_view.rbs +6 -0
- data/sig/examples/app_all_events/view.rbs +14 -0
- data/sig/examples/app_cli_rich_moments/app.rbs +12 -0
- data/sig/examples/app_color_picker/app.rbs +17 -0
- data/sig/examples/app_external_editor/app.rbs +12 -0
- data/sig/examples/app_login_form/app.rbs +11 -0
- data/sig/examples/app_stateful_interaction/app.rbs +39 -0
- data/sig/examples/verify_quickstart_dsl/app.rbs +17 -0
- data/sig/examples/verify_quickstart_lifecycle/app.rbs +17 -0
- data/sig/examples/verify_readme_usage/app.rbs +17 -0
- data/sig/examples/widget_block_demo/app.rbs +38 -0
- data/sig/examples/widget_box_demo/app.rbs +17 -0
- data/sig/examples/widget_calendar_demo/app.rbs +17 -0
- data/sig/examples/widget_cell_demo/app.rbs +17 -0
- data/sig/examples/widget_chart_demo/app.rbs +17 -0
- data/sig/examples/widget_gauge_demo/app.rbs +17 -0
- data/sig/examples/widget_layout_split/app.rbs +16 -0
- data/sig/examples/widget_line_gauge_demo/app.rbs +17 -0
- data/sig/examples/widget_list_demo/app.rbs +17 -0
- data/sig/examples/widget_map_demo/app.rbs +17 -0
- data/sig/examples/widget_popup_demo/app.rbs +17 -0
- data/sig/examples/widget_ratatui_logo_demo/app.rbs +17 -0
- data/sig/examples/widget_ratatui_mascot_demo/app.rbs +17 -0
- data/sig/examples/widget_rect/app.rbs +18 -0
- data/sig/examples/widget_render/app.rbs +16 -0
- data/sig/examples/widget_rich_text/app.rbs +17 -0
- data/sig/examples/widget_scroll_text/app.rbs +17 -0
- data/sig/examples/widget_scrollbar_demo/app.rbs +17 -0
- data/sig/examples/widget_sparkline_demo/app.rbs +16 -0
- data/sig/examples/widget_style_colors/app.rbs +20 -0
- data/sig/examples/widget_table_demo/app.rbs +17 -0
- data/sig/examples/widget_text_width/app.rbs +16 -0
- data/sig/generated/event_key_predicates.rbs +1348 -0
- data/sig/manifest.yaml +5 -0
- data/sig/patches/data.rbs +26 -0
- data/sig/patches/debugger__.rbs +8 -0
- data/sig/ratatui_ruby/backend/window_size.rbs +17 -0
- data/sig/ratatui_ruby/backend.rbs +12 -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 +249 -0
- data/sig/ratatui_ruby/frame.rbs +23 -0
- data/sig/ratatui_ruby/interfaces.rbs +25 -0
- data/sig/ratatui_ruby/labs.rbs +90 -0
- data/sig/ratatui_ruby/layout/alignment.rbs +26 -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/list_state.rbs +23 -0
- data/sig/ratatui_ruby/output_guard.rbs +23 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +113 -0
- data/sig/ratatui_ruby/rect.rbs +17 -0
- data/sig/ratatui_ruby/scrollbar_state.rbs +24 -0
- data/sig/ratatui_ruby/session.rbs +93 -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 +24 -0
- data/sig/ratatui_ruby/table_state.rbs +27 -0
- data/sig/ratatui_ruby/terminal/capabilities.rbs +38 -0
- data/sig/ratatui_ruby/terminal/viewport.rbs +33 -0
- data/sig/ratatui_ruby/terminal_lifecycle.rbs +39 -0
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +22 -0
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +37 -0
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +77 -0
- data/sig/ratatui_ruby/test_helper/terminal.rbs +20 -0
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +32 -0
- data/sig/ratatui_ruby/test_helper.rbs +18 -0
- 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 +16 -0
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +38 -0
- data/sig/ratatui_ruby/tui/core.rbs +23 -0
- data/sig/ratatui_ruby/tui/layout_factories.rbs +39 -0
- data/sig/ratatui_ruby/tui/state_factories.rbs +23 -0
- data/sig/ratatui_ruby/tui/style_factories.rbs +18 -0
- data/sig/ratatui_ruby/tui/text_factories.rbs +23 -0
- data/sig/ratatui_ruby/tui/widget_factories.rbs +138 -0
- data/sig/ratatui_ruby/tui.rbs +25 -0
- data/sig/ratatui_ruby/version.rbs +12 -0
- 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/widgets.rbs +16 -0
- data/vendor/goodcop/base.yml +1047 -0
- metadata +729 -0
|
@@ -0,0 +1,733 @@
|
|
|
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
|
+
module Layout
|
|
10
|
+
# Defines a rectangular area in the terminal grid.
|
|
11
|
+
#
|
|
12
|
+
# Geometry management involves passing groups of four integers (`x, y, width, height`) repeatedly.
|
|
13
|
+
# This is verbose and prone to parameter mismatch errors.
|
|
14
|
+
#
|
|
15
|
+
# This class encapsulates the geometry. It provides a standard primitive for passing area definitions
|
|
16
|
+
# between layout engines and rendering functions.
|
|
17
|
+
#
|
|
18
|
+
# Use it when manual positioning is required or when querying layout results.
|
|
19
|
+
#
|
|
20
|
+
# === Examples
|
|
21
|
+
#
|
|
22
|
+
#--
|
|
23
|
+
# SPDX-SnippetBegin
|
|
24
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
25
|
+
# SPDX-License-Identifier: MIT-0
|
|
26
|
+
#++
|
|
27
|
+
# area = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
28
|
+
# puts area.width # => 80
|
|
29
|
+
#--
|
|
30
|
+
# SPDX-SnippetEnd
|
|
31
|
+
#++
|
|
32
|
+
class Rect < Data.define(:x, :y, :width, :height)
|
|
33
|
+
##
|
|
34
|
+
# :attr_reader: x
|
|
35
|
+
# X coordinate (column) of the top-left corner (Integer, coerced via +to_int+ or +to_i+).
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# :attr_reader: y
|
|
39
|
+
# Y coordinate (row) of the top-left corner (Integer, coerced via +to_int+ or +to_i+).
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# :attr_reader: width
|
|
43
|
+
# Width in characters (Integer, coerced via +to_int+ or +to_i+).
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# :attr_reader: height
|
|
47
|
+
# Height in characters (Integer, coerced via +to_int+ or +to_i+).
|
|
48
|
+
|
|
49
|
+
# Creates a new Rect.
|
|
50
|
+
#
|
|
51
|
+
# All parameters accept any object responding to +to_int+ or +to_i+ (duck-typed).
|
|
52
|
+
#
|
|
53
|
+
# [x] Column index (Numeric).
|
|
54
|
+
# [y] Row index (Numeric).
|
|
55
|
+
# [width] Width in columns (Numeric).
|
|
56
|
+
# [height] Height in rows (Numeric).
|
|
57
|
+
def initialize(x: 0, y: 0, width: 0, height: 0)
|
|
58
|
+
super(
|
|
59
|
+
x: Integer(x),
|
|
60
|
+
y: Integer(y),
|
|
61
|
+
width: Integer(width),
|
|
62
|
+
height: Integer(height)
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Tests whether a point is inside this rectangle.
|
|
67
|
+
#
|
|
68
|
+
# Essential for hit testing mouse clicks against layout regions.
|
|
69
|
+
#
|
|
70
|
+
#--
|
|
71
|
+
# SPDX-SnippetBegin
|
|
72
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
73
|
+
# SPDX-License-Identifier: MIT-0
|
|
74
|
+
#++
|
|
75
|
+
# area = Layout::Rect.new(x: 10, y: 5, width: 20, height: 10)
|
|
76
|
+
# area.contains?(15, 8) # => true
|
|
77
|
+
# area.contains?(5, 8) # => false
|
|
78
|
+
#
|
|
79
|
+
#--
|
|
80
|
+
# SPDX-SnippetEnd
|
|
81
|
+
#++
|
|
82
|
+
# [px]
|
|
83
|
+
# X coordinate to test (column).
|
|
84
|
+
# [py]
|
|
85
|
+
#--
|
|
86
|
+
# SPDX-SnippetBegin
|
|
87
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
88
|
+
# SPDX-License-Identifier: MIT-0
|
|
89
|
+
#++
|
|
90
|
+
# Y coordinate to test (row).
|
|
91
|
+
#
|
|
92
|
+
#--
|
|
93
|
+
# SPDX-SnippetEnd
|
|
94
|
+
#++
|
|
95
|
+
# Returns true if the point (px, py) is within the rectangle bounds.
|
|
96
|
+
def contains?(px, py)
|
|
97
|
+
px >= x && px < x + width && py >= y && py < y + height
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Tests whether this rectangle overlaps with another.
|
|
101
|
+
#
|
|
102
|
+
# Essential for determining if a widget is visible within a viewport or clipping area.
|
|
103
|
+
#
|
|
104
|
+
#--
|
|
105
|
+
# SPDX-SnippetBegin
|
|
106
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
107
|
+
# SPDX-License-Identifier: MIT-0
|
|
108
|
+
#++
|
|
109
|
+
# viewport = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
110
|
+
# widget = Layout::Rect.new(x: 70, y: 20, width: 20, height: 10)
|
|
111
|
+
# viewport.intersects?(widget) # => true (partial overlap)
|
|
112
|
+
#
|
|
113
|
+
#--
|
|
114
|
+
# SPDX-SnippetEnd
|
|
115
|
+
#++
|
|
116
|
+
# [other]
|
|
117
|
+
#--
|
|
118
|
+
# SPDX-SnippetBegin
|
|
119
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
120
|
+
# SPDX-License-Identifier: MIT-0
|
|
121
|
+
#++
|
|
122
|
+
# Another Rect to test against.
|
|
123
|
+
#
|
|
124
|
+
#--
|
|
125
|
+
# SPDX-SnippetEnd
|
|
126
|
+
#++
|
|
127
|
+
# Returns true if the rectangles overlap.
|
|
128
|
+
def intersects?(other)
|
|
129
|
+
x < other.x + other.width &&
|
|
130
|
+
x + width > other.x &&
|
|
131
|
+
y < other.y + other.height &&
|
|
132
|
+
y + height > other.y
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Returns the overlapping area between this rectangle and another.
|
|
136
|
+
#
|
|
137
|
+
# Essential for calculating visible portions of widgets inside scroll views.
|
|
138
|
+
#
|
|
139
|
+
#--
|
|
140
|
+
# SPDX-SnippetBegin
|
|
141
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
142
|
+
# SPDX-License-Identifier: MIT-0
|
|
143
|
+
#++
|
|
144
|
+
# viewport = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
145
|
+
# widget = Layout::Rect.new(x: 70, y: 20, width: 20, height: 10)
|
|
146
|
+
# visible = viewport.intersection(widget)
|
|
147
|
+
# # => Rect(x: 70, y: 20, width: 10, height: 4)
|
|
148
|
+
#
|
|
149
|
+
#--
|
|
150
|
+
# SPDX-SnippetEnd
|
|
151
|
+
#++
|
|
152
|
+
# [other]
|
|
153
|
+
#--
|
|
154
|
+
# SPDX-SnippetBegin
|
|
155
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
156
|
+
# SPDX-License-Identifier: MIT-0
|
|
157
|
+
#++
|
|
158
|
+
# Another Rect to intersect with.
|
|
159
|
+
#
|
|
160
|
+
#--
|
|
161
|
+
# SPDX-SnippetEnd
|
|
162
|
+
#++
|
|
163
|
+
# Returns a new Rect representing the intersection, or +nil+ if no overlap.
|
|
164
|
+
def intersection(other)
|
|
165
|
+
return nil unless intersects?(other)
|
|
166
|
+
|
|
167
|
+
new_x = [x, other.x].max
|
|
168
|
+
new_y = [y, other.y].max
|
|
169
|
+
new_right = [x + width, other.x + other.width].min
|
|
170
|
+
new_bottom = [y + height, other.y + other.height].min
|
|
171
|
+
|
|
172
|
+
Rect.new(x: new_x, y: new_y, width: new_right - new_x, height: new_bottom - new_y)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Left edge coordinate.
|
|
176
|
+
#
|
|
177
|
+
# Layout algorithms compute bounding boxes and check overlaps.
|
|
178
|
+
# Reading <tt>rect.x</tt> forces you to remember that x means "left."
|
|
179
|
+
#
|
|
180
|
+
# Call <tt>left</tt> instead. Your code reads like prose.
|
|
181
|
+
#
|
|
182
|
+
# === Example
|
|
183
|
+
#
|
|
184
|
+
#--
|
|
185
|
+
# SPDX-SnippetBegin
|
|
186
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
187
|
+
# SPDX-License-Identifier: MIT-0
|
|
188
|
+
#++
|
|
189
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 80, height: 24)
|
|
190
|
+
# rect.left # => 10
|
|
191
|
+
#--
|
|
192
|
+
# SPDX-SnippetEnd
|
|
193
|
+
#++
|
|
194
|
+
def left
|
|
195
|
+
x
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Right edge coordinate.
|
|
199
|
+
#
|
|
200
|
+
# Bounds checks compare edges. Writing <tt>x + width</tt> inline clutters conditions.
|
|
201
|
+
# Errors creep in when you forget the addition.
|
|
202
|
+
#
|
|
203
|
+
# This method computes and names the boundary. Returns the first column outside the rect.
|
|
204
|
+
#
|
|
205
|
+
# === Example
|
|
206
|
+
#
|
|
207
|
+
#--
|
|
208
|
+
# SPDX-SnippetBegin
|
|
209
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
210
|
+
# SPDX-License-Identifier: MIT-0
|
|
211
|
+
#++
|
|
212
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 80, height: 24)
|
|
213
|
+
# rect.right # => 90
|
|
214
|
+
#--
|
|
215
|
+
# SPDX-SnippetEnd
|
|
216
|
+
#++
|
|
217
|
+
def right
|
|
218
|
+
x + width
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Top edge coordinate.
|
|
222
|
+
#
|
|
223
|
+
# Layout algorithms compute bounding boxes and check overlaps.
|
|
224
|
+
# Reading <tt>rect.y</tt> forces you to remember that y means "top."
|
|
225
|
+
#
|
|
226
|
+
# Call <tt>top</tt> instead. Your code reads like prose.
|
|
227
|
+
#
|
|
228
|
+
# === Example
|
|
229
|
+
#
|
|
230
|
+
#--
|
|
231
|
+
# SPDX-SnippetBegin
|
|
232
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
233
|
+
# SPDX-License-Identifier: MIT-0
|
|
234
|
+
#++
|
|
235
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 80, height: 24)
|
|
236
|
+
# rect.top # => 5
|
|
237
|
+
#--
|
|
238
|
+
# SPDX-SnippetEnd
|
|
239
|
+
#++
|
|
240
|
+
def top
|
|
241
|
+
y
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Bottom edge coordinate.
|
|
245
|
+
#
|
|
246
|
+
# Bounds checks compare edges. Writing <tt>y + height</tt> inline clutters conditions.
|
|
247
|
+
# Errors creep in when you forget the addition.
|
|
248
|
+
#
|
|
249
|
+
# This method computes and names the boundary. Returns the first row outside the rect.
|
|
250
|
+
#
|
|
251
|
+
# === Example
|
|
252
|
+
#
|
|
253
|
+
#--
|
|
254
|
+
# SPDX-SnippetBegin
|
|
255
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
256
|
+
# SPDX-License-Identifier: MIT-0
|
|
257
|
+
#++
|
|
258
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 80, height: 24)
|
|
259
|
+
# rect.bottom # => 29
|
|
260
|
+
#--
|
|
261
|
+
# SPDX-SnippetEnd
|
|
262
|
+
#++
|
|
263
|
+
def bottom
|
|
264
|
+
y + height
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Total area in cells.
|
|
268
|
+
#
|
|
269
|
+
# Size comparisons and allocation calculations need area.
|
|
270
|
+
# Computing <tt>width * height</tt> inline is noisy and error-prone.
|
|
271
|
+
#
|
|
272
|
+
# This method does the multiplication once.
|
|
273
|
+
#
|
|
274
|
+
# === Example
|
|
275
|
+
#
|
|
276
|
+
#--
|
|
277
|
+
# SPDX-SnippetBegin
|
|
278
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
279
|
+
# SPDX-License-Identifier: MIT-0
|
|
280
|
+
#++
|
|
281
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 10, height: 5)
|
|
282
|
+
# rect.area # => 50
|
|
283
|
+
#--
|
|
284
|
+
# SPDX-SnippetEnd
|
|
285
|
+
#++
|
|
286
|
+
def area
|
|
287
|
+
width * height
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# True when the rect has zero area.
|
|
291
|
+
#
|
|
292
|
+
# Zero-width or zero-height rects break layout math.
|
|
293
|
+
# Checking <tt>width == 0 || height == 0</tt> inline is tedious and easy to forget.
|
|
294
|
+
#
|
|
295
|
+
# Guard clauses call <tt>empty?</tt> to skip degenerate rects.
|
|
296
|
+
#
|
|
297
|
+
# === Example
|
|
298
|
+
#
|
|
299
|
+
#--
|
|
300
|
+
# SPDX-SnippetBegin
|
|
301
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
302
|
+
# SPDX-License-Identifier: MIT-0
|
|
303
|
+
#++
|
|
304
|
+
# Layout::Rect.new(width: 0, height: 10).empty? # => true
|
|
305
|
+
# Layout::Rect.new(width: 10, height: 5).empty? # => false
|
|
306
|
+
#--
|
|
307
|
+
# SPDX-SnippetEnd
|
|
308
|
+
#++
|
|
309
|
+
def empty?
|
|
310
|
+
width.zero? || height.zero?
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Bounding box containing both rectangles.
|
|
314
|
+
#
|
|
315
|
+
# Damage tracking and hit testing combine rects.
|
|
316
|
+
# Computing min/max of all four edges inline is tedious and error-prone.
|
|
317
|
+
#
|
|
318
|
+
# This method returns the smallest rect that encloses both.
|
|
319
|
+
#
|
|
320
|
+
# [other] Rect to merge.
|
|
321
|
+
#
|
|
322
|
+
# === Example
|
|
323
|
+
#
|
|
324
|
+
#--
|
|
325
|
+
# SPDX-SnippetBegin
|
|
326
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
327
|
+
# SPDX-License-Identifier: MIT-0
|
|
328
|
+
#++
|
|
329
|
+
# r1 = Layout::Rect.new(x: 0, y: 0, width: 10, height: 10)
|
|
330
|
+
# r2 = Layout::Rect.new(x: 5, y: 5, width: 10, height: 10)
|
|
331
|
+
# r1.union(r2) # => Rect(x: 0, y: 0, width: 15, height: 15)
|
|
332
|
+
#--
|
|
333
|
+
# SPDX-SnippetEnd
|
|
334
|
+
#++
|
|
335
|
+
def union(other)
|
|
336
|
+
new_x = [left, other.left].min
|
|
337
|
+
new_y = [top, other.top].min
|
|
338
|
+
new_right = [right, other.right].max
|
|
339
|
+
new_bottom = [bottom, other.bottom].max
|
|
340
|
+
|
|
341
|
+
Rect.new(
|
|
342
|
+
x: new_x,
|
|
343
|
+
y: new_y,
|
|
344
|
+
width: new_right - new_x,
|
|
345
|
+
height: new_bottom - new_y
|
|
346
|
+
)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Shrinks the rect by a uniform margin on all sides.
|
|
350
|
+
#
|
|
351
|
+
# Widgets render text inside borders. Subtracting margin from all four edges inline is verbose.
|
|
352
|
+
# Off-by-one errors happen when you forget to double the margin.
|
|
353
|
+
#
|
|
354
|
+
# This method computes the content area. Returns a zero-area rect if margin exceeds dimensions.
|
|
355
|
+
#
|
|
356
|
+
# [margin] Integer padding on all sides.
|
|
357
|
+
#
|
|
358
|
+
# === Example
|
|
359
|
+
#
|
|
360
|
+
#--
|
|
361
|
+
# SPDX-SnippetBegin
|
|
362
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
363
|
+
# SPDX-License-Identifier: MIT-0
|
|
364
|
+
#++
|
|
365
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 20, height: 10)
|
|
366
|
+
# rect.inner(2) # => Rect(x: 2, y: 2, width: 16, height: 6)
|
|
367
|
+
#--
|
|
368
|
+
# SPDX-SnippetEnd
|
|
369
|
+
#++
|
|
370
|
+
def inner(margin)
|
|
371
|
+
doubled = margin * 2
|
|
372
|
+
return Rect.new(x: 0, y: 0, width: 0, height: 0) if width < doubled || height < doubled
|
|
373
|
+
|
|
374
|
+
Rect.new(
|
|
375
|
+
x: x + margin,
|
|
376
|
+
y: y + margin,
|
|
377
|
+
width: width - doubled,
|
|
378
|
+
height: height - doubled
|
|
379
|
+
)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# Expands the rect by a uniform margin on all sides.
|
|
383
|
+
#
|
|
384
|
+
# Containers wrap content with decorations. Adding margin to all four edges inline is verbose.
|
|
385
|
+
# Off-by-one errors happen when you forget to double the margin.
|
|
386
|
+
#
|
|
387
|
+
# This method computes the outer area. Saturates x/y at 0 when margin exceeds position.
|
|
388
|
+
# Use Rect#clamp to constrain the result if it may exceed screen bounds.
|
|
389
|
+
#
|
|
390
|
+
# [margin] Integer expansion on all sides.
|
|
391
|
+
#
|
|
392
|
+
# === Example
|
|
393
|
+
#
|
|
394
|
+
#--
|
|
395
|
+
# SPDX-SnippetBegin
|
|
396
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
397
|
+
# SPDX-License-Identifier: MIT-0
|
|
398
|
+
#++
|
|
399
|
+
# rect = Layout::Rect.new(x: 10, y: 10, width: 20, height: 10)
|
|
400
|
+
# rect.outer(5) # => Rect(x: 5, y: 5, width: 30, height: 20)
|
|
401
|
+
#--
|
|
402
|
+
# SPDX-SnippetEnd
|
|
403
|
+
#++
|
|
404
|
+
def outer(margin)
|
|
405
|
+
new_x = [x - margin, 0].max
|
|
406
|
+
new_y = [y - margin, 0].max
|
|
407
|
+
new_width = right + margin - new_x
|
|
408
|
+
new_height = bottom + margin - new_y
|
|
409
|
+
|
|
410
|
+
Rect.new(x: new_x, y: new_y, width: new_width, height: new_height)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Moves the rect without changing size.
|
|
414
|
+
#
|
|
415
|
+
# Animations and drag-and-drop shift widgets.
|
|
416
|
+
# Adding offsets to x and y inline clutters the code.
|
|
417
|
+
#
|
|
418
|
+
# This method returns a translated copy.
|
|
419
|
+
#
|
|
420
|
+
# [dx] Horizontal shift (positive moves right).
|
|
421
|
+
# [dy] Vertical shift (positive moves down).
|
|
422
|
+
#
|
|
423
|
+
# === Example
|
|
424
|
+
#
|
|
425
|
+
#--
|
|
426
|
+
# SPDX-SnippetBegin
|
|
427
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
428
|
+
# SPDX-License-Identifier: MIT-0
|
|
429
|
+
#++
|
|
430
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 20, height: 10)
|
|
431
|
+
# rect.offset(5, 3) # => Rect(x: 15, y: 8, width: 20, height: 10)
|
|
432
|
+
#--
|
|
433
|
+
# SPDX-SnippetEnd
|
|
434
|
+
#++
|
|
435
|
+
def offset(dx, dy)
|
|
436
|
+
Rect.new(x: x + dx, y: y + dy, width:, height:)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Changes dimensions while preserving position.
|
|
440
|
+
#
|
|
441
|
+
# Window resizing and responsive layouts adjust size mid-session.
|
|
442
|
+
# Creating a new rect with the same position but different size is common.
|
|
443
|
+
#
|
|
444
|
+
# This method returns a resized copy. Position unchanged.
|
|
445
|
+
#
|
|
446
|
+
# [new_size] Size object with new dimensions.
|
|
447
|
+
#
|
|
448
|
+
# === Example
|
|
449
|
+
#
|
|
450
|
+
#--
|
|
451
|
+
# SPDX-SnippetBegin
|
|
452
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
453
|
+
# SPDX-License-Identifier: MIT-0
|
|
454
|
+
#++
|
|
455
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 20, height: 10)
|
|
456
|
+
# rect.resize(Size.new(width: 40, height: 20))
|
|
457
|
+
# # => Rect(x: 10, y: 5, width: 40, height: 20)
|
|
458
|
+
#--
|
|
459
|
+
# SPDX-SnippetEnd
|
|
460
|
+
#++
|
|
461
|
+
def resize(new_size)
|
|
462
|
+
Rect.new(x:, y:, width: new_size.width, height: new_size.height)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# Constrains the rect to fit inside bounds.
|
|
466
|
+
#
|
|
467
|
+
# Popups and tooltips may extend beyond screen edges.
|
|
468
|
+
# Manually clamping x, y, width, and height is verbose and error-prone.
|
|
469
|
+
#
|
|
470
|
+
# This method repositions and shrinks the rect to stay within bounds.
|
|
471
|
+
#
|
|
472
|
+
# [other] Bounding Rect.
|
|
473
|
+
#
|
|
474
|
+
# === Example
|
|
475
|
+
#
|
|
476
|
+
#--
|
|
477
|
+
# SPDX-SnippetBegin
|
|
478
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
479
|
+
# SPDX-License-Identifier: MIT-0
|
|
480
|
+
#++
|
|
481
|
+
# screen = Layout::Rect.new(x: 0, y: 0, width: 100, height: 100)
|
|
482
|
+
# popup = Layout::Rect.new(x: 80, y: 80, width: 30, height: 30)
|
|
483
|
+
# popup.clamp(screen) # => Rect(x: 70, y: 70, width: 30, height: 30)
|
|
484
|
+
#--
|
|
485
|
+
# SPDX-SnippetEnd
|
|
486
|
+
#++
|
|
487
|
+
def clamp(other)
|
|
488
|
+
clamped_width = [width, other.width].min
|
|
489
|
+
clamped_height = [height, other.height].min
|
|
490
|
+
clamped_x = x.clamp(other.left, other.right - clamped_width)
|
|
491
|
+
clamped_y = y.clamp(other.top, other.bottom - clamped_height)
|
|
492
|
+
|
|
493
|
+
Rect.new(x: clamped_x, y: clamped_y, width: clamped_width, height: clamped_height)
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Iterates over horizontal slices.
|
|
497
|
+
#
|
|
498
|
+
# Lists render line by line. Looping <tt>height.times</tt> and constructing rects inline is noisy.
|
|
499
|
+
#
|
|
500
|
+
# This method yields each row as a Rect with height 1. Returns an Enumerator if no block given.
|
|
501
|
+
#
|
|
502
|
+
# === Example
|
|
503
|
+
#
|
|
504
|
+
#--
|
|
505
|
+
# SPDX-SnippetBegin
|
|
506
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
507
|
+
# SPDX-License-Identifier: MIT-0
|
|
508
|
+
#++
|
|
509
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 5, height: 3)
|
|
510
|
+
# rect.rows.map { |r| r.y } # => [0, 1, 2]
|
|
511
|
+
#--
|
|
512
|
+
# SPDX-SnippetEnd
|
|
513
|
+
#++
|
|
514
|
+
def rows
|
|
515
|
+
return to_enum(:rows) unless block_given?
|
|
516
|
+
|
|
517
|
+
height.times do |i|
|
|
518
|
+
yield Rect.new(x:, y: y + i, width:, height: 1)
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# Iterates over vertical slices.
|
|
523
|
+
#
|
|
524
|
+
# Grids render column by column. Looping <tt>width.times</tt> and constructing rects inline is noisy.
|
|
525
|
+
#
|
|
526
|
+
# This method yields each column as a Rect with width 1. Returns an Enumerator if no block given.
|
|
527
|
+
#
|
|
528
|
+
# === Example
|
|
529
|
+
#
|
|
530
|
+
#--
|
|
531
|
+
# SPDX-SnippetBegin
|
|
532
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
533
|
+
# SPDX-License-Identifier: MIT-0
|
|
534
|
+
#++
|
|
535
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 5, height: 3)
|
|
536
|
+
# rect.columns.map { |c| c.x } # => [0, 1, 2, 3, 4]
|
|
537
|
+
#--
|
|
538
|
+
# SPDX-SnippetEnd
|
|
539
|
+
#++
|
|
540
|
+
def columns
|
|
541
|
+
return to_enum(:columns) unless block_given?
|
|
542
|
+
|
|
543
|
+
width.times do |i|
|
|
544
|
+
yield Rect.new(x: x + i, y:, width: 1, height:)
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
# Iterates over every cell in row-major order.
|
|
549
|
+
#
|
|
550
|
+
# Hit testing and pixel rendering touch every position.
|
|
551
|
+
# Nested loops with manual coordinate math are verbose.
|
|
552
|
+
#
|
|
553
|
+
# This method yields <tt>[x, y]</tt> pairs. Returns an Enumerator if no block given.
|
|
554
|
+
#
|
|
555
|
+
# === Example
|
|
556
|
+
#
|
|
557
|
+
#--
|
|
558
|
+
# SPDX-SnippetBegin
|
|
559
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
560
|
+
# SPDX-License-Identifier: MIT-0
|
|
561
|
+
#++
|
|
562
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 2, height: 2)
|
|
563
|
+
# rect.positions.to_a # => [[0, 0], [1, 0], [0, 1], [1, 1]]
|
|
564
|
+
#--
|
|
565
|
+
# SPDX-SnippetEnd
|
|
566
|
+
#++
|
|
567
|
+
def positions
|
|
568
|
+
return to_enum(:positions) unless block_given?
|
|
569
|
+
|
|
570
|
+
height.times do |row|
|
|
571
|
+
width.times do |col|
|
|
572
|
+
yield [x + col, y + row]
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# Extracts the position (x, y) from this rect.
|
|
578
|
+
#
|
|
579
|
+
# Layout code sometimes separates position from size.
|
|
580
|
+
# Extracting x and y into multiple variables is verbose.
|
|
581
|
+
#
|
|
582
|
+
# This method returns a Position object containing just the coordinates.
|
|
583
|
+
#
|
|
584
|
+
# === Example
|
|
585
|
+
#
|
|
586
|
+
#--
|
|
587
|
+
# SPDX-SnippetBegin
|
|
588
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
589
|
+
# SPDX-License-Identifier: MIT-0
|
|
590
|
+
#++
|
|
591
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 80, height: 24)
|
|
592
|
+
# rect.as_position # => Position(x: 10, y: 5)
|
|
593
|
+
#--
|
|
594
|
+
# SPDX-SnippetEnd
|
|
595
|
+
#++
|
|
596
|
+
def as_position
|
|
597
|
+
Position.new(x:, y:)
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
# Extracts the size (width, height) from this rect.
|
|
601
|
+
#
|
|
602
|
+
# Layout code sometimes separates size from position.
|
|
603
|
+
# Extracting width and height into multiple variables is verbose.
|
|
604
|
+
#
|
|
605
|
+
# This method returns a Size object containing just the dimensions.
|
|
606
|
+
#
|
|
607
|
+
# === Example
|
|
608
|
+
#
|
|
609
|
+
#--
|
|
610
|
+
# SPDX-SnippetBegin
|
|
611
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
612
|
+
# SPDX-License-Identifier: MIT-0
|
|
613
|
+
#++
|
|
614
|
+
# rect = Layout::Rect.new(x: 10, y: 5, width: 80, height: 24)
|
|
615
|
+
# rect.as_size # => Size(width: 80, height: 24)
|
|
616
|
+
#--
|
|
617
|
+
# SPDX-SnippetEnd
|
|
618
|
+
#++
|
|
619
|
+
def as_size
|
|
620
|
+
Size.new(width:, height:)
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
# Returns a new Rect, centered horizontally within this rect based on the constraint.
|
|
624
|
+
#
|
|
625
|
+
# Modal dialogs and centered content need horizontal centering.
|
|
626
|
+
# Computing the left offset manually is error-prone.
|
|
627
|
+
#
|
|
628
|
+
# This method uses Layout to compute the centered position.
|
|
629
|
+
#
|
|
630
|
+
# [constraint] Constraint defining the width of the centered area.
|
|
631
|
+
#
|
|
632
|
+
# === Example
|
|
633
|
+
#
|
|
634
|
+
#--
|
|
635
|
+
# SPDX-SnippetBegin
|
|
636
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
637
|
+
# SPDX-License-Identifier: MIT-0
|
|
638
|
+
#++
|
|
639
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 100, height: 24)
|
|
640
|
+
# rect.centered_horizontally(Constraint.length(40))
|
|
641
|
+
# # => Rect(x: 30, y: 0, width: 40, height: 24)
|
|
642
|
+
#--
|
|
643
|
+
# SPDX-SnippetEnd
|
|
644
|
+
#++
|
|
645
|
+
def centered_horizontally(constraint)
|
|
646
|
+
areas = Layout.split(self, direction: :horizontal, constraints: [constraint], flex: :center)
|
|
647
|
+
areas.first
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# Returns a new Rect, centered vertically within this rect based on the constraint.
|
|
651
|
+
#
|
|
652
|
+
# Modal dialogs and centered content need vertical centering.
|
|
653
|
+
# Computing the top offset manually is error-prone.
|
|
654
|
+
#
|
|
655
|
+
# This method uses Layout to compute the centered position.
|
|
656
|
+
#
|
|
657
|
+
# [constraint] Constraint defining the height of the centered area.
|
|
658
|
+
#
|
|
659
|
+
# === Example
|
|
660
|
+
#
|
|
661
|
+
#--
|
|
662
|
+
# SPDX-SnippetBegin
|
|
663
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
664
|
+
# SPDX-License-Identifier: MIT-0
|
|
665
|
+
#++
|
|
666
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 80, height: 100)
|
|
667
|
+
# rect.centered_vertically(Constraint.length(20))
|
|
668
|
+
# # => Rect(x: 0, y: 40, width: 80, height: 20)
|
|
669
|
+
#--
|
|
670
|
+
# SPDX-SnippetEnd
|
|
671
|
+
#++
|
|
672
|
+
def centered_vertically(constraint)
|
|
673
|
+
areas = Layout.split(self, direction: :vertical, constraints: [constraint], flex: :center)
|
|
674
|
+
areas.first
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# Returns a new Rect, centered both horizontally and vertically within this rect.
|
|
678
|
+
#
|
|
679
|
+
# Modal dialogs often need exact centering on both axes.
|
|
680
|
+
# Computing both offsets manually is tedious.
|
|
681
|
+
#
|
|
682
|
+
# This method chains centered_horizontally and centered_vertically.
|
|
683
|
+
#
|
|
684
|
+
# [horizontal_constraint] Constraint defining the width of the centered area.
|
|
685
|
+
# [vertical_constraint] Constraint defining the height of the centered area.
|
|
686
|
+
#
|
|
687
|
+
# === Example
|
|
688
|
+
#
|
|
689
|
+
#--
|
|
690
|
+
# SPDX-SnippetBegin
|
|
691
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
692
|
+
# SPDX-License-Identifier: MIT-0
|
|
693
|
+
#++
|
|
694
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 100, height: 100)
|
|
695
|
+
# rect.centered(Constraint.length(40), Constraint.length(20))
|
|
696
|
+
# # => Rect(x: 30, y: 40, width: 40, height: 20)
|
|
697
|
+
#--
|
|
698
|
+
# SPDX-SnippetEnd
|
|
699
|
+
#++
|
|
700
|
+
def centered(horizontal_constraint, vertical_constraint)
|
|
701
|
+
centered_horizontally(horizontal_constraint).centered_vertically(vertical_constraint)
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
# Enables array destructuring of the rectangle.
|
|
705
|
+
#
|
|
706
|
+
# Inline viewports and layout code often need position and size together.
|
|
707
|
+
# Accessing x, y, width, height individually is verbose.
|
|
708
|
+
#
|
|
709
|
+
# This method allows convenient array destructuring.
|
|
710
|
+
#
|
|
711
|
+
# === Example
|
|
712
|
+
#
|
|
713
|
+
#--
|
|
714
|
+
# SPDX-SnippetBegin
|
|
715
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
716
|
+
# SPDX-License-Identifier: MIT-0
|
|
717
|
+
#++
|
|
718
|
+
# area = tui.viewport_area
|
|
719
|
+
# x, y, width, height = area
|
|
720
|
+
# # Now you can use x, y, width, height directly
|
|
721
|
+
#--
|
|
722
|
+
# SPDX-SnippetEnd
|
|
723
|
+
#++
|
|
724
|
+
def to_ary
|
|
725
|
+
[x, y, width, height]
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
# Ruby-idiomatic aliases (TIMTOWTDI)
|
|
729
|
+
alias position as_position
|
|
730
|
+
alias size as_size
|
|
731
|
+
end
|
|
732
|
+
end
|
|
733
|
+
end
|