ratatui_ruby 0.5.0 → 0.7.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 +10 -4
- data/CHANGELOG.md +79 -7
- data/README.md +37 -5
- data/REUSE.toml +2 -7
- data/doc/application_architecture.md +96 -22
- data/doc/application_testing.md +76 -30
- data/doc/contributors/architectural_overhaul/chat_conversations.md +4952 -0
- data/doc/contributors/architectural_overhaul/implementation_plan.md +60 -0
- data/doc/contributors/architectural_overhaul/task.md +37 -0
- data/doc/contributors/design/ruby_frontend.md +288 -56
- data/doc/contributors/design/rust_backend.md +349 -54
- data/doc/contributors/developing_examples.md +134 -49
- data/doc/contributors/index.md +7 -5
- data/doc/contributors/v1.0.0_blockers.md +1729 -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/index.md +11 -6
- data/doc/interactive_design.md +2 -2
- data/doc/quickstart.md +127 -165
- data/doc/terminal_limitations.md +92 -0
- data/doc/v0.7.0_migration.md +236 -0
- data/doc/why.md +93 -0
- data/examples/app_all_events/README.md +47 -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 +9 -9
- data/examples/app_all_events/view/controls_view.rb +9 -7
- data/examples/app_all_events/view/counts_view.rb +13 -9
- data/examples/app_all_events/view/live_view.rb +9 -8
- data/examples/app_all_events/view/log_view.rb +11 -16
- data/examples/app_color_picker/README.md +84 -42
- 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 +49 -0
- data/examples/app_login_form/app.rb +2 -3
- data/examples/app_stateful_interaction/README.md +33 -0
- data/examples/app_stateful_interaction/app.rb +272 -0
- data/examples/timeout_demo.rb +43 -0
- data/examples/verify_quickstart_dsl/README.md +49 -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 +10 -4
- 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 +50 -0
- data/examples/widget_barchart_demo/app.rb +5 -5
- data/examples/widget_block_demo/README.md +36 -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_calendar_demo/app.rb +5 -1
- 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 +4 -4
- data/examples/widget_overlay_demo/README.md +36 -0
- 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_logo_demo/app.rb +1 -1
- 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_render/app.rb +3 -3
- 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 +65 -12
- 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 +113 -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 +116 -3
- data/ext/ratatui_ruby/src/lib.rs +37 -6
- data/ext/ratatui_ruby/src/rendering.rs +22 -21
- 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 +13 -4
- data/ext/ratatui_ruby/src/widgets/barchart.rs +24 -6
- data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
- 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 +191 -34
- data/ext/ratatui_ruby/src/widgets/table_state.rs +121 -0
- data/lib/ratatui_ruby/buffer/cell.rb +168 -0
- data/lib/ratatui_ruby/buffer.rb +15 -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 +100 -4
- data/lib/ratatui_ruby/layout/constraint.rb +95 -0
- data/lib/ratatui_ruby/layout/layout.rb +106 -0
- data/lib/ratatui_ruby/layout/rect.rb +118 -0
- data/lib/ratatui_ruby/layout.rb +19 -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/layout.rb +1 -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/row.rb +66 -0
- data/lib/ratatui_ruby/schema/style.rb +24 -4
- data/lib/ratatui_ruby/schema/table.rb +29 -11
- data/lib/ratatui_ruby/schema/text.rb +96 -3
- data/lib/ratatui_ruby/scrollbar_state.rb +112 -0
- data/lib/ratatui_ruby/style/style.rb +81 -0
- data/lib/ratatui_ruby/style.rb +15 -0
- 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 +414 -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/tui/buffer_factories.rb +20 -0
- data/lib/ratatui_ruby/tui/canvas_factories.rb +44 -0
- data/lib/ratatui_ruby/tui/core.rb +38 -0
- data/lib/ratatui_ruby/tui/layout_factories.rb +74 -0
- data/lib/ratatui_ruby/tui/state_factories.rb +33 -0
- data/lib/ratatui_ruby/tui/style_factories.rb +20 -0
- data/lib/ratatui_ruby/tui/text_factories.rb +44 -0
- data/lib/ratatui_ruby/tui/widget_factories.rb +195 -0
- data/lib/ratatui_ruby/tui.rb +75 -0
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +47 -0
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +25 -0
- data/lib/ratatui_ruby/widgets/bar_chart.rb +239 -0
- data/lib/ratatui_ruby/widgets/block.rb +192 -0
- data/lib/ratatui_ruby/widgets/calendar.rb +84 -0
- data/lib/ratatui_ruby/widgets/canvas.rb +231 -0
- data/lib/ratatui_ruby/widgets/cell.rb +47 -0
- data/lib/ratatui_ruby/widgets/center.rb +59 -0
- data/lib/ratatui_ruby/widgets/chart.rb +185 -0
- data/lib/ratatui_ruby/widgets/clear.rb +54 -0
- data/lib/ratatui_ruby/widgets/cursor.rb +42 -0
- data/lib/ratatui_ruby/widgets/gauge.rb +72 -0
- data/lib/ratatui_ruby/widgets/line_gauge.rb +80 -0
- data/lib/ratatui_ruby/widgets/list.rb +127 -0
- data/lib/ratatui_ruby/widgets/list_item.rb +43 -0
- data/lib/ratatui_ruby/widgets/overlay.rb +43 -0
- data/lib/ratatui_ruby/widgets/paragraph.rb +99 -0
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +31 -0
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +36 -0
- data/lib/ratatui_ruby/widgets/row.rb +68 -0
- data/lib/ratatui_ruby/widgets/scrollbar.rb +143 -0
- data/lib/ratatui_ruby/widgets/shape/label.rb +68 -0
- data/lib/ratatui_ruby/widgets/sparkline.rb +134 -0
- data/lib/ratatui_ruby/widgets/table.rb +141 -0
- data/lib/ratatui_ruby/widgets/tabs.rb +85 -0
- data/lib/ratatui_ruby/widgets.rb +40 -0
- data/lib/ratatui_ruby.rb +64 -57
- data/sig/examples/app_all_events/view.rbs +1 -1
- data/sig/examples/app_all_events/view_state.rbs +1 -1
- 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/row.rbs +22 -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 +9 -6
- data/sig/ratatui_ruby/scrollbar_state.rbs +18 -0
- data/sig/ratatui_ruby/session.rbs +41 -48
- 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/sig/ratatui_ruby/tui/buffer_factories.rbs +10 -0
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +14 -0
- data/sig/ratatui_ruby/tui/core.rbs +14 -0
- data/sig/ratatui_ruby/tui/layout_factories.rbs +19 -0
- data/sig/ratatui_ruby/tui/state_factories.rbs +12 -0
- data/sig/ratatui_ruby/tui/style_factories.rbs +10 -0
- data/sig/ratatui_ruby/tui/text_factories.rbs +14 -0
- data/sig/ratatui_ruby/tui/widget_factories.rbs +39 -0
- data/sig/ratatui_ruby/tui.rbs +19 -0
- data/tasks/autodoc/examples.rb +79 -0
- data/tasks/autodoc.rake +7 -35
- data/tasks/bump/changelog.rb +3 -3
- data/tasks/bump/links.rb +67 -0
- data/tasks/sourcehut.rake +64 -21
- data/tasks/terminal_preview/app_screenshot.rb +13 -3
- data/tasks/terminal_preview/saved_screenshot.rb +4 -3
- metadata +169 -48
- data/doc/contributors/dwim_dx.md +0 -366
- data/doc/images/app_analytics.png +0 -0
- data/doc/images/app_custom_widget.png +0 -0
- data/doc/images/app_mouse_events.png +0 -0
- 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/doc/images/widget_table_flex.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/lib/ratatui_ruby/session/autodoc.rb +0 -417
- data/lib/ratatui_ruby/session.rb +0 -163
- 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/autodoc/inventory.rb +0 -61
- data/tasks/autodoc/notice.rb +0 -26
- data/tasks/autodoc/rbs.rb +0 -38
- data/tasks/autodoc/rdoc.rb +0 -45
- data/tasks/bump/comparison_links.rb +0 -41
- /data/doc/images/{app_map_demo.png → widget_map_demo.png} +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
module RatatuiRuby
|
|
7
|
+
module Layout
|
|
8
|
+
# Divides an area into smaller chunks.
|
|
9
|
+
#
|
|
10
|
+
# Terminal screens vary in size. Hardcoded positions break when the window resizes. You need a way to organize space dynamically.
|
|
11
|
+
#
|
|
12
|
+
# This class manages geometry. It splits a given area into multiple sections based on a list of constraints.
|
|
13
|
+
#
|
|
14
|
+
# Use layouts to build responsive grids. Stack sections vertically for a sidebar-main structure. Partition them horizontally for headers and footers. Let the layout engine do the math.
|
|
15
|
+
#
|
|
16
|
+
# {rdoc-image:/doc/images/widget_layout_split.png}[link:/examples/widget_layout_split/app_rb.html]
|
|
17
|
+
#
|
|
18
|
+
# === Example
|
|
19
|
+
#
|
|
20
|
+
# Run the interactive demo from the terminal:
|
|
21
|
+
#
|
|
22
|
+
# ruby examples/widget_layout_split/app.rb
|
|
23
|
+
class Layout < Data.define(:direction, :constraints, :children, :flex)
|
|
24
|
+
##
|
|
25
|
+
# :attr_reader: direction
|
|
26
|
+
# Direction of the split.
|
|
27
|
+
#
|
|
28
|
+
# Either <tt>:vertical</tt> (top to bottom) or <tt>:horizontal</tt> (left to right).
|
|
29
|
+
#
|
|
30
|
+
# layout.direction # => :vertical
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# :attr_reader: constraints
|
|
34
|
+
# Array of rules defining section sizes.
|
|
35
|
+
#
|
|
36
|
+
# See RatatuiRuby::Layout::Constraint.
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# :attr_reader: children
|
|
40
|
+
# Widgets to render in each section (optional).
|
|
41
|
+
#
|
|
42
|
+
# If provided, `children[i]` is rendered into the area defined by `constraints[i]`.
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# :attr_reader: flex
|
|
46
|
+
# Strategy for distributing extra space.
|
|
47
|
+
#
|
|
48
|
+
# One of <tt>:legacy</tt>, <tt>:start</tt>, <tt>:center</tt>, <tt>:end</tt>, <tt>:space_between</tt>, <tt>:space_around</tt>.
|
|
49
|
+
|
|
50
|
+
# :nodoc:
|
|
51
|
+
FLEX_MODES = %i[legacy start center end space_between space_around space_evenly].freeze
|
|
52
|
+
|
|
53
|
+
# Creates a new Layout.
|
|
54
|
+
#
|
|
55
|
+
# [direction]
|
|
56
|
+
# <tt>:vertical</tt> or <tt>:horizontal</tt> (default: <tt>:vertical</tt>).
|
|
57
|
+
# [constraints]
|
|
58
|
+
# list of Constraint objects.
|
|
59
|
+
# [children]
|
|
60
|
+
# List of widgets to render (optional).
|
|
61
|
+
# [flex]
|
|
62
|
+
# Flex mode for spacing (default: <tt>:legacy</tt>).
|
|
63
|
+
def initialize(direction: :vertical, constraints: [], children: [], flex: :legacy)
|
|
64
|
+
super
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Splits an area into multiple rectangles.
|
|
68
|
+
#
|
|
69
|
+
# This is a pure calculation helper for hit testing. It computes where
|
|
70
|
+
# widgets *would* be placed without actually rendering them.
|
|
71
|
+
#
|
|
72
|
+
# rects = Layout::Layout.split(
|
|
73
|
+
# area,
|
|
74
|
+
# direction: :horizontal,
|
|
75
|
+
# constraints: [Layout::Constraint.percentage(50), Layout::Constraint.percentage(50)]
|
|
76
|
+
# )
|
|
77
|
+
# left, right = rects
|
|
78
|
+
#
|
|
79
|
+
# [area]
|
|
80
|
+
# 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>.
|
|
81
|
+
# [direction]
|
|
82
|
+
# <tt>:vertical</tt> or <tt>:horizontal</tt> (default: <tt>:vertical</tt>).
|
|
83
|
+
# [constraints]
|
|
84
|
+
# Array of <tt>Constraint</tt> objects defining section sizes.
|
|
85
|
+
# [flex]
|
|
86
|
+
# Flex mode for spacing (default: <tt>:legacy</tt>).
|
|
87
|
+
#
|
|
88
|
+
# Returns an Array of <tt>Rect</tt> objects.
|
|
89
|
+
def self.split(area, direction: :vertical, constraints:, flex: :legacy)
|
|
90
|
+
# Duck-typing: If it lacks geometry methods but can be a Hash, convert it.
|
|
91
|
+
if !area.respond_to?(:x) && area.respond_to?(:to_h)
|
|
92
|
+
# Assume it's a Hash-like object with :x, :y, etc.
|
|
93
|
+
hash = area.to_h
|
|
94
|
+
area = Rect.new(
|
|
95
|
+
x: hash.fetch(:x, 0),
|
|
96
|
+
y: hash.fetch(:y, 0),
|
|
97
|
+
width: hash.fetch(:width, 0),
|
|
98
|
+
height: hash.fetch(:height, 0)
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
raw_rects = _split(area, direction, constraints, flex)
|
|
102
|
+
raw_rects.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
module RatatuiRuby
|
|
7
|
+
module Layout
|
|
8
|
+
# Defines a rectangular area in the terminal grid.
|
|
9
|
+
#
|
|
10
|
+
# Geometry management involves passing groups of four integers (`x, y, width, height`) repeatedly.
|
|
11
|
+
# This is verbose and prone to parameter mismatch errors.
|
|
12
|
+
#
|
|
13
|
+
# This class encapsulates the geometry. It provides a standard primitive for passing area definitions
|
|
14
|
+
# between layout engines and rendering functions.
|
|
15
|
+
#
|
|
16
|
+
# Use it when manual positioning is required or when querying layout results.
|
|
17
|
+
#
|
|
18
|
+
# === Examples
|
|
19
|
+
#
|
|
20
|
+
# area = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
21
|
+
# puts area.width # => 80
|
|
22
|
+
class Rect < Data.define(:x, :y, :width, :height)
|
|
23
|
+
##
|
|
24
|
+
# :attr_reader: x
|
|
25
|
+
# X coordinate (column) of the top-left corner (Integer, coerced via +to_int+ or +to_i+).
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# :attr_reader: y
|
|
29
|
+
# Y coordinate (row) of the top-left corner (Integer, coerced via +to_int+ or +to_i+).
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# :attr_reader: width
|
|
33
|
+
# Width in characters (Integer, coerced via +to_int+ or +to_i+).
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# :attr_reader: height
|
|
37
|
+
# Height in characters (Integer, coerced via +to_int+ or +to_i+).
|
|
38
|
+
|
|
39
|
+
# Creates a new Rect.
|
|
40
|
+
#
|
|
41
|
+
# All parameters accept any object responding to +to_int+ or +to_i+ (duck-typed).
|
|
42
|
+
#
|
|
43
|
+
# [x] Column index (Numeric).
|
|
44
|
+
# [y] Row index (Numeric).
|
|
45
|
+
# [width] Width in columns (Numeric).
|
|
46
|
+
# [height] Height in rows (Numeric).
|
|
47
|
+
def initialize(x: 0, y: 0, width: 0, height: 0)
|
|
48
|
+
super(
|
|
49
|
+
x: Integer(x),
|
|
50
|
+
y: Integer(y),
|
|
51
|
+
width: Integer(width),
|
|
52
|
+
height: Integer(height)
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Tests whether a point is inside this rectangle.
|
|
57
|
+
#
|
|
58
|
+
# Essential for hit testing mouse clicks against layout regions.
|
|
59
|
+
#
|
|
60
|
+
# area = Layout::Rect.new(x: 10, y: 5, width: 20, height: 10)
|
|
61
|
+
# area.contains?(15, 8) # => true
|
|
62
|
+
# area.contains?(5, 8) # => false
|
|
63
|
+
#
|
|
64
|
+
# [px]
|
|
65
|
+
# X coordinate to test (column).
|
|
66
|
+
# [py]
|
|
67
|
+
# Y coordinate to test (row).
|
|
68
|
+
#
|
|
69
|
+
# Returns true if the point (px, py) is within the rectangle bounds.
|
|
70
|
+
def contains?(px, py)
|
|
71
|
+
px >= x && px < x + width && py >= y && py < y + height
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Tests whether this rectangle overlaps with another.
|
|
75
|
+
#
|
|
76
|
+
# Essential for determining if a widget is visible within a viewport or clipping area.
|
|
77
|
+
#
|
|
78
|
+
# viewport = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
79
|
+
# widget = Layout::Rect.new(x: 70, y: 20, width: 20, height: 10)
|
|
80
|
+
# viewport.intersects?(widget) # => true (partial overlap)
|
|
81
|
+
#
|
|
82
|
+
# [other]
|
|
83
|
+
# Another Rect to test against.
|
|
84
|
+
#
|
|
85
|
+
# Returns true if the rectangles overlap.
|
|
86
|
+
def intersects?(other)
|
|
87
|
+
x < other.x + other.width &&
|
|
88
|
+
x + width > other.x &&
|
|
89
|
+
y < other.y + other.height &&
|
|
90
|
+
y + height > other.y
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns the overlapping area between this rectangle and another.
|
|
94
|
+
#
|
|
95
|
+
# Essential for calculating visible portions of widgets inside scroll views.
|
|
96
|
+
#
|
|
97
|
+
# viewport = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
98
|
+
# widget = Layout::Rect.new(x: 70, y: 20, width: 20, height: 10)
|
|
99
|
+
# visible = viewport.intersection(widget)
|
|
100
|
+
# # => Rect(x: 70, y: 20, width: 10, height: 4)
|
|
101
|
+
#
|
|
102
|
+
# [other]
|
|
103
|
+
# Another Rect to intersect with.
|
|
104
|
+
#
|
|
105
|
+
# Returns a new Rect representing the intersection, or +nil+ if no overlap.
|
|
106
|
+
def intersection(other)
|
|
107
|
+
return nil unless intersects?(other)
|
|
108
|
+
|
|
109
|
+
new_x = [x, other.x].max
|
|
110
|
+
new_y = [y, other.y].max
|
|
111
|
+
new_right = [x + width, other.x + other.width].min
|
|
112
|
+
new_bottom = [y + height, other.y + other.height].min
|
|
113
|
+
|
|
114
|
+
Rect.new(x: new_x, y: new_y, width: new_right - new_x, height: new_bottom - new_y)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
module RatatuiRuby
|
|
7
|
+
# Layout primitives for geometry and space distribution.
|
|
8
|
+
#
|
|
9
|
+
# This module mirrors +ratatui::layout+ and contains:
|
|
10
|
+
# - {Rect} — Rectangle geometry
|
|
11
|
+
# - {Constraint} — Sizing rules
|
|
12
|
+
# - {Layout} — Space distribution
|
|
13
|
+
module Layout
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
require_relative "layout/rect"
|
|
18
|
+
require_relative "layout/constraint"
|
|
19
|
+
require_relative "layout/layout"
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
module RatatuiRuby
|
|
7
|
+
# Mutable state object for List widgets.
|
|
8
|
+
#
|
|
9
|
+
# When using {Frame#render_stateful_widget}, the State object is the
|
|
10
|
+
# *single source of truth* for selection and scroll offset. Widget
|
|
11
|
+
# properties (+selected_index+, +offset+) are *ignored* in stateful mode.
|
|
12
|
+
#
|
|
13
|
+
# State objects persist across frames, allowing you to:
|
|
14
|
+
# - Track selection without manual index management
|
|
15
|
+
# - Read back the scroll offset calculated by Ratatui
|
|
16
|
+
# - Implement mouse click-to-row hit testing
|
|
17
|
+
#
|
|
18
|
+
# == Thread/Ractor Safety
|
|
19
|
+
#
|
|
20
|
+
# ListState is *not* Ractor-shareable. It contains mutable internal state.
|
|
21
|
+
# Store it in instance variables, not in immutable Models.
|
|
22
|
+
#
|
|
23
|
+
# == Example
|
|
24
|
+
#
|
|
25
|
+
# @list_state = RatatuiRuby::ListState.new
|
|
26
|
+
# @list_state.select(2) # Select third item
|
|
27
|
+
#
|
|
28
|
+
# RatatuiRuby.draw do |frame|
|
|
29
|
+
# list = RatatuiRuby::Widgets::List.new(items: ["A", "B", "C", "D", "E"])
|
|
30
|
+
# frame.render_stateful_widget(list, frame.area, @list_state)
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# puts @list_state.offset # Scroll position after render
|
|
34
|
+
#
|
|
35
|
+
class ListState
|
|
36
|
+
##
|
|
37
|
+
# :method: new
|
|
38
|
+
# :call-seq: new(selected = nil) -> ListState
|
|
39
|
+
#
|
|
40
|
+
# Creates a new ListState with optional initial selection.
|
|
41
|
+
#
|
|
42
|
+
# (Native method implemented in Rust)
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# :method: select
|
|
46
|
+
# :call-seq: select(index) -> nil
|
|
47
|
+
#
|
|
48
|
+
# Sets the selected index. Pass +nil+ to deselect.
|
|
49
|
+
#
|
|
50
|
+
# (Native method implemented in Rust)
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# :method: selected
|
|
54
|
+
# :call-seq: selected() -> Integer or nil
|
|
55
|
+
#
|
|
56
|
+
# Returns the currently selected index, or +nil+ if nothing is selected.
|
|
57
|
+
#
|
|
58
|
+
# (Native method implemented in Rust)
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# :method: offset
|
|
62
|
+
# :call-seq: offset() -> Integer
|
|
63
|
+
#
|
|
64
|
+
# Returns the current scroll offset.
|
|
65
|
+
#
|
|
66
|
+
# This is the critical read-back method. After +render_stateful_widget+,
|
|
67
|
+
# this returns the scroll position calculated by Ratatui to keep the
|
|
68
|
+
# selection visible.
|
|
69
|
+
#
|
|
70
|
+
# (Native method implemented in Rust)
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# :method: scroll_down_by
|
|
74
|
+
# :call-seq: scroll_down_by(n) -> nil
|
|
75
|
+
#
|
|
76
|
+
# Scrolls down by +n+ items.
|
|
77
|
+
#
|
|
78
|
+
# (Native method implemented in Rust)
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# :method: scroll_up_by
|
|
82
|
+
# :call-seq: scroll_up_by(n) -> nil
|
|
83
|
+
#
|
|
84
|
+
# Scrolls up by +n+ items.
|
|
85
|
+
#
|
|
86
|
+
# (Native method implemented in Rust)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -17,7 +17,7 @@ module RatatuiRuby
|
|
|
17
17
|
|
|
18
18
|
##
|
|
19
19
|
# :attr_reader: label
|
|
20
|
-
# The label of the bar (optional String).
|
|
20
|
+
# The label of the bar (optional String, Text::Span, or Text::Line for rich styling).
|
|
21
21
|
|
|
22
22
|
##
|
|
23
23
|
# :attr_reader: style
|
|
@@ -29,7 +29,7 @@ module RatatuiRuby
|
|
|
29
29
|
|
|
30
30
|
##
|
|
31
31
|
# :attr_reader: text_value
|
|
32
|
-
# The text to display as the value (optional String).
|
|
32
|
+
# The text to display as the value (optional String, Text::Span, or Text::Line for rich styling).
|
|
33
33
|
|
|
34
34
|
def initialize(value:, label: nil, style: nil, value_style: nil, text_value: nil)
|
|
35
35
|
super(
|
|
@@ -15,6 +15,11 @@ module RatatuiRuby
|
|
|
15
15
|
# === Examples
|
|
16
16
|
#
|
|
17
17
|
# Cursor.new(x: 10, y: 5)
|
|
18
|
+
#
|
|
19
|
+
# See also:
|
|
20
|
+
# - {Declarative implementation using Tree API}[link:/examples/app_login_form/app_rb.html]
|
|
21
|
+
# - {Component-based implementation using Frame API}[link:/examples/app_color_picker/app_rb.html]
|
|
22
|
+
# - RatatuiRuby::Frame#set_cursor_position (Frame API alternative)
|
|
18
23
|
class Cursor < Data.define(:x, :y)
|
|
19
24
|
##
|
|
20
25
|
# :attr_reader: x
|
|
@@ -28,6 +28,8 @@ module RatatuiRuby
|
|
|
28
28
|
# :attr_reader: label
|
|
29
29
|
# Text label to display (optional).
|
|
30
30
|
#
|
|
31
|
+
# Accepts String or Text::Span for rich styling.
|
|
32
|
+
#
|
|
31
33
|
# If nil, it often displays the percentage automatically depending on renderer logic,
|
|
32
34
|
# but explicit labels are preferred.
|
|
33
35
|
|
|
@@ -52,7 +54,7 @@ module RatatuiRuby
|
|
|
52
54
|
#
|
|
53
55
|
# [ratio] Float (0.0 - 1.0).
|
|
54
56
|
# [percent] Integer (0 - 100), alternative to ratio.
|
|
55
|
-
# [label] String (optional).
|
|
57
|
+
# [label] String or Text::Span (optional).
|
|
56
58
|
# [style] Style object for the background (optional).
|
|
57
59
|
# [gauge_style] Style object for the filled bar (optional).
|
|
58
60
|
# [block] Block widget (optional).
|
|
@@ -26,7 +26,7 @@ module RatatuiRuby
|
|
|
26
26
|
|
|
27
27
|
##
|
|
28
28
|
# :attr_reader: label
|
|
29
|
-
# Optional label.
|
|
29
|
+
# Optional label (String or Text::Span for rich styling).
|
|
30
30
|
|
|
31
31
|
##
|
|
32
32
|
# :attr_reader: style
|
|
@@ -55,7 +55,7 @@ module RatatuiRuby
|
|
|
55
55
|
# Creates a new LineGauge.
|
|
56
56
|
#
|
|
57
57
|
# [ratio] Float (0.0 - 1.0).
|
|
58
|
-
# [label] String (optional).
|
|
58
|
+
# [label] String or Text::Span (optional).
|
|
59
59
|
# [style] Style (optional, base style for the gauge).
|
|
60
60
|
# [filled_style] Style.
|
|
61
61
|
# [unfilled_style] Style.
|
|
@@ -27,15 +27,34 @@ module RatatuiRuby
|
|
|
27
27
|
# highlight_style: Style.new(bg: :blue),
|
|
28
28
|
# highlight_symbol: ">> "
|
|
29
29
|
# )
|
|
30
|
-
class List < Data.define(:items, :selected_index, :style, :highlight_style, :highlight_symbol, :repeat_highlight_symbol, :highlight_spacing, :direction, :scroll_padding, :block)
|
|
30
|
+
class List < Data.define(:items, :selected_index, :offset, :style, :highlight_style, :highlight_symbol, :repeat_highlight_symbol, :highlight_spacing, :direction, :scroll_padding, :block)
|
|
31
31
|
##
|
|
32
32
|
# :attr_reader: items
|
|
33
|
-
# The items to display
|
|
33
|
+
# The items to display.
|
|
34
|
+
#
|
|
35
|
+
# Accepts Array of Strings, Text::Spans, Text::Lines, or ListItem objects.
|
|
36
|
+
# For styled individual rows, use ListItem with a style.
|
|
34
37
|
|
|
35
38
|
##
|
|
36
39
|
# :attr_reader: selected_index
|
|
37
40
|
# Index of the active selection (Integer or nil).
|
|
38
41
|
|
|
42
|
+
##
|
|
43
|
+
# :attr_reader: offset
|
|
44
|
+
# Scroll offset (Integer or nil).
|
|
45
|
+
#
|
|
46
|
+
# Controls the viewport's starting position in the list.
|
|
47
|
+
#
|
|
48
|
+
# When +nil+ (default), Ratatui auto-scrolls to keep the selection visible ("natural scrolling").
|
|
49
|
+
#
|
|
50
|
+
# When set, forces the viewport to start at this item index. Use this for:
|
|
51
|
+
# - **Passive scrolling**: Scroll through a log viewer without selecting items.
|
|
52
|
+
# - **Click-to-select math**: Calculate which item index corresponds to a click coordinate.
|
|
53
|
+
#
|
|
54
|
+
# *Important*: When both +offset+ and +selected_index+ are set, Ratatui may still adjust
|
|
55
|
+
# the viewport during rendering to ensure the selection stays visible. Set +selected_index+
|
|
56
|
+
# to +nil+ for fully manual scroll control.
|
|
57
|
+
|
|
39
58
|
##
|
|
40
59
|
# :attr_reader: style
|
|
41
60
|
# Base style for unselected items.
|
|
@@ -76,8 +95,9 @@ module RatatuiRuby
|
|
|
76
95
|
#
|
|
77
96
|
# Integer parameters accept any object responding to +to_int+ or +to_i+ (duck-typed).
|
|
78
97
|
#
|
|
79
|
-
# [items] Array of Strings.
|
|
98
|
+
# [items] Array of Strings, Text::Spans, Text::Lines, or ListItem objects.
|
|
80
99
|
# [selected_index] Numeric (nullable, coerced to Integer).
|
|
100
|
+
# [offset] Numeric (nullable, coerced to Integer). Forces scroll position when set.
|
|
81
101
|
# [style] Style object.
|
|
82
102
|
# [highlight_style] Style object.
|
|
83
103
|
# [highlight_symbol] String (default: <tt>"> "</tt>).
|
|
@@ -86,10 +106,11 @@ module RatatuiRuby
|
|
|
86
106
|
# [direction] Symbol (default: <tt>:top_to_bottom</tt>).
|
|
87
107
|
# [scroll_padding] Numeric (nullable, coerced to Integer, default: <tt>nil</tt>).
|
|
88
108
|
# [block] Block (optional).
|
|
89
|
-
def initialize(items: [], selected_index: nil, style: nil, highlight_style: nil, highlight_symbol: "> ", repeat_highlight_symbol: false, highlight_spacing: :when_selected, direction: :top_to_bottom, scroll_padding: nil, block: nil)
|
|
109
|
+
def initialize(items: [], selected_index: nil, offset: nil, style: nil, highlight_style: nil, highlight_symbol: "> ", repeat_highlight_symbol: false, highlight_spacing: :when_selected, direction: :top_to_bottom, scroll_padding: nil, block: nil)
|
|
90
110
|
super(
|
|
91
111
|
items:,
|
|
92
112
|
selected_index: selected_index.nil? ? nil : Integer(selected_index),
|
|
113
|
+
offset: offset.nil? ? nil : Integer(offset),
|
|
93
114
|
style:,
|
|
94
115
|
highlight_style:,
|
|
95
116
|
highlight_symbol:,
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
module RatatuiRuby
|
|
7
|
+
# A styled list item combining content with optional style.
|
|
8
|
+
#
|
|
9
|
+
# By default, List items are strings. For more control over styling individual rows,
|
|
10
|
+
# wrap the content in a ListItem to apply a style specific to that item.
|
|
11
|
+
#
|
|
12
|
+
# The content can be a String, Text::Span, or Text::Line. The style applies to the
|
|
13
|
+
# entire row background.
|
|
14
|
+
#
|
|
15
|
+
# === Examples
|
|
16
|
+
#
|
|
17
|
+
# # Item with red background
|
|
18
|
+
# ListItem.new(content: "Error", style: Style.new(bg: :red))
|
|
19
|
+
#
|
|
20
|
+
# # Item with styled content
|
|
21
|
+
# ListItem.new(
|
|
22
|
+
# content: Text::Span.new(content: "Status: OK", style: Style.new(fg: :green, modifiers: [:bold]))
|
|
23
|
+
# )
|
|
24
|
+
class ListItem < Data.define(:content, :style)
|
|
25
|
+
##
|
|
26
|
+
# :attr_reader: content
|
|
27
|
+
# The content to display (String, Text::Span, or Text::Line).
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# :attr_reader: style
|
|
31
|
+
# The style to apply to the item (optional Style).
|
|
32
|
+
|
|
33
|
+
# Creates a new ListItem.
|
|
34
|
+
#
|
|
35
|
+
# [content] String, Text::Span, or Text::Line.
|
|
36
|
+
# [style] Style object (optional).
|
|
37
|
+
def initialize(content:, style: nil)
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -69,5 +69,48 @@ module RatatuiRuby
|
|
|
69
69
|
def contains?(px, py)
|
|
70
70
|
px >= x && px < x + width && py >= y && py < y + height
|
|
71
71
|
end
|
|
72
|
+
|
|
73
|
+
# Tests whether this rectangle overlaps with another.
|
|
74
|
+
#
|
|
75
|
+
# Essential for determining if a widget is visible within a viewport or clipping area.
|
|
76
|
+
#
|
|
77
|
+
# viewport = Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
78
|
+
# widget = Rect.new(x: 70, y: 20, width: 20, height: 10)
|
|
79
|
+
# viewport.intersects?(widget) # => true (partial overlap)
|
|
80
|
+
#
|
|
81
|
+
# [other]
|
|
82
|
+
# Another Rect to test against.
|
|
83
|
+
#
|
|
84
|
+
# Returns true if the rectangles overlap.
|
|
85
|
+
def intersects?(other)
|
|
86
|
+
x < other.x + other.width &&
|
|
87
|
+
x + width > other.x &&
|
|
88
|
+
y < other.y + other.height &&
|
|
89
|
+
y + height > other.y
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns the overlapping area between this rectangle and another.
|
|
93
|
+
#
|
|
94
|
+
# Essential for calculating visible portions of widgets inside scroll views.
|
|
95
|
+
#
|
|
96
|
+
# viewport = Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
97
|
+
# widget = Rect.new(x: 70, y: 20, width: 20, height: 10)
|
|
98
|
+
# visible = viewport.intersection(widget)
|
|
99
|
+
# # => Rect(x: 70, y: 20, width: 10, height: 4)
|
|
100
|
+
#
|
|
101
|
+
# [other]
|
|
102
|
+
# Another Rect to intersect with.
|
|
103
|
+
#
|
|
104
|
+
# Returns a new Rect representing the intersection, or +nil+ if no overlap.
|
|
105
|
+
def intersection(other)
|
|
106
|
+
return nil unless intersects?(other)
|
|
107
|
+
|
|
108
|
+
new_x = [x, other.x].max
|
|
109
|
+
new_y = [y, other.y].max
|
|
110
|
+
new_right = [x + width, other.x + other.width].min
|
|
111
|
+
new_bottom = [y + height, other.y + other.height].min
|
|
112
|
+
|
|
113
|
+
Rect.new(x: new_x, y: new_y, width: new_right - new_x, height: new_bottom - new_y)
|
|
114
|
+
end
|
|
72
115
|
end
|
|
73
116
|
end
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
module RatatuiRuby
|
|
7
|
+
# A styled table row combining cells with optional row-level styling.
|
|
8
|
+
#
|
|
9
|
+
# By default, Table rows are arrays of cell content. For more control over styling
|
|
10
|
+
# individual rows, wrap the cells in a Row object to apply row-level style.
|
|
11
|
+
#
|
|
12
|
+
# The cells can be Strings, Text::Spans, Text::Lines, Paragraphs, or Cells.
|
|
13
|
+
# The style applies to the entire row background.
|
|
14
|
+
#
|
|
15
|
+
# === Examples
|
|
16
|
+
#
|
|
17
|
+
# # Row with red background
|
|
18
|
+
# Row.new(cells: ["Error", "Something went wrong"], style: Style.new(bg: :red))
|
|
19
|
+
#
|
|
20
|
+
# # Row with styled cells and custom height
|
|
21
|
+
# Row.new(
|
|
22
|
+
# cells: [
|
|
23
|
+
# Text::Span.new(content: "Status", style: Style.new(modifiers: [:bold])),
|
|
24
|
+
# Text::Span.new(content: "OK", style: Style.new(fg: :green))
|
|
25
|
+
# ],
|
|
26
|
+
# height: 2
|
|
27
|
+
# )
|
|
28
|
+
class Row < Data.define(:cells, :style, :height, :top_margin, :bottom_margin)
|
|
29
|
+
##
|
|
30
|
+
# :attr_reader: cells
|
|
31
|
+
# The cells to display (Array of Strings, Text::Spans, Text::Lines, Paragraphs, or Cells).
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# :attr_reader: style
|
|
35
|
+
# The style to apply to the row (optional Style).
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# :attr_reader: height
|
|
39
|
+
# Fixed row height in lines (optional Integer).
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# :attr_reader: top_margin
|
|
43
|
+
# Margin above the row in lines (optional Integer).
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# :attr_reader: bottom_margin
|
|
47
|
+
# Margin below the row in lines (optional Integer).
|
|
48
|
+
|
|
49
|
+
# Creates a new Row.
|
|
50
|
+
#
|
|
51
|
+
# [cells] Array of Strings, Text::Spans, Text::Lines, Paragraphs, or Cells.
|
|
52
|
+
# [style] Style object (optional).
|
|
53
|
+
# [height] Integer for fixed height (optional).
|
|
54
|
+
# [top_margin] Integer for top margin (optional).
|
|
55
|
+
# [bottom_margin] Integer for bottom margin (optional).
|
|
56
|
+
def initialize(cells:, style: nil, height: nil, top_margin: nil, bottom_margin: nil)
|
|
57
|
+
super(
|
|
58
|
+
cells:,
|
|
59
|
+
style:,
|
|
60
|
+
height: height.nil? ? nil : Integer(height),
|
|
61
|
+
top_margin: top_margin.nil? ? nil : Integer(top_margin),
|
|
62
|
+
bottom_margin: bottom_margin.nil? ? nil : Integer(bottom_margin)
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -20,18 +20,38 @@ module RatatuiRuby
|
|
|
20
20
|
#
|
|
21
21
|
# # Hex colors
|
|
22
22
|
# Style.new(fg: "#ff00ff")
|
|
23
|
+
#
|
|
24
|
+
# === Supported Colors
|
|
25
|
+
#
|
|
26
|
+
# ==== Integer
|
|
27
|
+
# Represents an indexed color from the Xterm 256-color palette (0-255).
|
|
28
|
+
# * <tt>0</tt>–<tt>15</tt>: Standard and bright ANSI colors.
|
|
29
|
+
# * <tt>16</tt>–<tt>231</tt>: {6x6x6 Color Cube}[https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit].
|
|
30
|
+
# * <tt>232</tt>–<tt>255</tt>: Grayscale ramp.
|
|
31
|
+
#
|
|
32
|
+
# ==== Symbol
|
|
33
|
+
# Represents a named color from the standard ANSI palette. Supported values:
|
|
34
|
+
# * <tt>:black</tt>, <tt>:red</tt>, <tt>:green</tt>, <tt>:yellow</tt>,
|
|
35
|
+
# <tt>:blue</tt>, <tt>:magenta</tt>, <tt>:cyan</tt>, <tt>:gray</tt>
|
|
36
|
+
# * <tt>:dark_gray</tt>, <tt>:light_red</tt>, <tt>:light_green</tt>,
|
|
37
|
+
# <tt>:light_yellow</tt>, <tt>:light_blue</tt>, <tt>:light_magenta</tt>,
|
|
38
|
+
# <tt>:light_cyan</tt>, <tt>:white</tt>
|
|
39
|
+
#
|
|
40
|
+
# ==== String
|
|
41
|
+
# Represents a specific RGB color using a Hex code (<tt>"#RRGGBB"</tt>).
|
|
42
|
+
# Requires a terminal emulator with "True Color" (24-bit color) support.
|
|
23
43
|
class Style < Data.define(:fg, :bg, :modifiers)
|
|
24
44
|
##
|
|
25
45
|
# :attr_reader: fg
|
|
26
46
|
# Foreground color.
|
|
27
47
|
#
|
|
28
|
-
# Symbol (<tt>:red</tt>)
|
|
48
|
+
# Symbol (<tt>:red</tt>), Hex String (<tt>"#ffffff"</tt>), or Integer (0-255).
|
|
29
49
|
|
|
30
50
|
##
|
|
31
51
|
# :attr_reader: bg
|
|
32
52
|
# Background color.
|
|
33
53
|
#
|
|
34
|
-
# Symbol (<tt>:black</tt>)
|
|
54
|
+
# Symbol (<tt>:black</tt>), Hex String (<tt>"#000000"</tt>), or Integer (0-255).
|
|
35
55
|
|
|
36
56
|
##
|
|
37
57
|
# :attr_reader: modifiers
|
|
@@ -42,8 +62,8 @@ module RatatuiRuby
|
|
|
42
62
|
|
|
43
63
|
# Creates a new Style.
|
|
44
64
|
#
|
|
45
|
-
# [fg] Color (Symbol/String).
|
|
46
|
-
# [bg] Color (Symbol/String).
|
|
65
|
+
# [fg] Color (Symbol/String/Integer).
|
|
66
|
+
# [bg] Color (Symbol/String/Integer).
|
|
47
67
|
# [modifiers] Array of Symbols.
|
|
48
68
|
def initialize(fg: nil, bg: nil, modifiers: [])
|
|
49
69
|
super
|