ratatui_ruby 0.8.0 → 0.9.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 +2 -2
- data/.builds/ruby-3.3.yml +2 -2
- data/.builds/ruby-3.4.yml +2 -2
- data/.builds/ruby-4.0.0.yml +2 -2
- data/.pre-commit-config.yaml +1 -1
- data/AGENTS.md +3 -3
- data/CHANGELOG.md +53 -1
- data/LICENSES/LGPL-3.0-or-later.txt +304 -0
- data/LICENSES/MIT-0.txt +16 -0
- data/README.md +33 -5
- data/Rakefile +1 -1
- data/doc/concepts/application_architecture.md +44 -3
- data/doc/concepts/application_testing.md +43 -1
- data/doc/concepts/async.md +32 -2
- data/doc/concepts/custom_widgets.md +247 -0
- data/doc/concepts/event_handling.md +32 -3
- data/doc/concepts/interactive_design.md +32 -2
- data/doc/contributors/auditing/parity.md +7 -1
- data/doc/contributors/design/ruby_frontend.md +85 -1
- data/doc/contributors/design/rust_backend.md +67 -1
- data/doc/contributors/developing_examples.md +56 -2
- data/doc/contributors/documentation_style.md +20 -3
- data/doc/contributors/future_work.md +169 -0
- data/doc/contributors/index.md +1 -1
- data/doc/contributors/v1.0.0_blockers.md +15 -175
- data/doc/getting_started/quickstart.md +22 -4
- data/doc/getting_started/why.md +1 -1
- data/doc/index.md +2 -1
- data/doc/troubleshooting/debugging.md +32 -2
- data/doc/troubleshooting/terminal_limitations.md +8 -2
- data/doc/troubleshooting/tui_output.md +42 -0
- data/examples/app_all_events/README.md +14 -2
- data/examples/app_all_events/app.rb +1 -1
- data/examples/app_all_events/model/app_model.rb +1 -1
- data/examples/app_all_events/model/event_color_cycle.rb +1 -1
- data/examples/app_all_events/model/event_entry.rb +1 -1
- data/examples/app_all_events/model/msg.rb +1 -1
- data/examples/app_all_events/model/timestamp.rb +1 -1
- data/examples/app_all_events/update.rb +1 -1
- data/examples/app_all_events/view/app_view.rb +1 -1
- data/examples/app_all_events/view/controls_view.rb +1 -1
- data/examples/app_all_events/view/counts_view.rb +1 -1
- data/examples/app_all_events/view/live_view.rb +1 -1
- data/examples/app_all_events/view/log_view.rb +1 -1
- data/examples/app_all_events/view.rb +1 -1
- data/examples/app_color_picker/README.md +20 -2
- data/examples/app_color_picker/app.rb +1 -1
- data/examples/app_color_picker/clipboard.rb +1 -1
- data/examples/app_color_picker/color.rb +1 -1
- data/examples/app_color_picker/controls.rb +1 -1
- data/examples/app_color_picker/copy_dialog.rb +1 -1
- data/examples/app_color_picker/export_pane.rb +1 -1
- data/examples/app_color_picker/harmony.rb +1 -1
- data/examples/app_color_picker/input.rb +1 -1
- data/examples/app_color_picker/main_container.rb +1 -1
- data/examples/app_color_picker/palette.rb +1 -1
- data/examples/app_login_form/README.md +8 -2
- data/examples/app_login_form/app.rb +1 -1
- data/examples/app_stateful_interaction/README.md +2 -2
- data/examples/app_stateful_interaction/app.rb +71 -17
- data/examples/timeout_demo.rb +1 -1
- data/examples/verify_quickstart_dsl/README.md +6 -0
- data/examples/verify_quickstart_dsl/app.rb +3 -3
- data/examples/verify_quickstart_layout/README.md +6 -0
- data/examples/verify_quickstart_layout/app.rb +3 -3
- data/examples/verify_quickstart_lifecycle/README.md +6 -0
- data/examples/verify_quickstart_lifecycle/app.rb +3 -3
- data/examples/verify_readme_usage/README.md +6 -0
- data/examples/verify_readme_usage/app.rb +3 -3
- data/examples/widget_barchart/README.md +6 -0
- data/examples/widget_barchart/app.rb +2 -2
- data/examples/widget_block/README.md +7 -1
- data/examples/widget_block/app.rb +2 -2
- data/examples/widget_box/README.md +6 -0
- data/examples/widget_box/app.rb +9 -6
- data/examples/widget_calendar/README.md +6 -0
- data/examples/widget_calendar/app.rb +2 -2
- data/examples/widget_canvas/README.md +4 -0
- data/examples/widget_canvas/app.rb +2 -2
- data/examples/widget_cell/README.md +6 -0
- data/examples/widget_cell/app.rb +2 -3
- data/examples/widget_center/README.md +4 -0
- data/examples/widget_center/app.rb +2 -2
- data/examples/widget_chart/README.md +6 -0
- data/examples/widget_chart/app.rb +2 -2
- data/examples/widget_gauge/README.md +6 -0
- data/examples/widget_gauge/app.rb +2 -2
- data/examples/widget_layout_split/README.md +6 -0
- data/examples/widget_layout_split/app.rb +3 -3
- data/examples/widget_line_gauge/README.md +6 -0
- data/examples/widget_line_gauge/app.rb +2 -2
- data/examples/widget_list/README.md +6 -0
- data/examples/widget_list/app.rb +2 -2
- data/examples/widget_map/README.md +8 -2
- data/examples/widget_map/app.rb +2 -2
- data/examples/widget_overlay/README.md +7 -1
- data/examples/widget_overlay/app.rb +2 -2
- data/examples/widget_popup/README.md +6 -0
- data/examples/widget_popup/app.rb +2 -2
- data/examples/widget_ratatui_logo/README.md +6 -0
- data/examples/widget_ratatui_logo/app.rb +2 -3
- data/examples/widget_ratatui_mascot/README.md +6 -0
- data/examples/widget_ratatui_mascot/app.rb +2 -2
- data/examples/widget_rect/README.md +12 -0
- data/examples/widget_rect/app.rb +40 -26
- data/examples/widget_render/README.md +6 -0
- data/examples/widget_render/app.rb +2 -2
- data/examples/widget_render/app.rbs +41 -0
- data/examples/widget_rich_text/README.md +6 -0
- data/examples/widget_rich_text/app.rb +2 -2
- data/examples/widget_scroll_text/README.md +6 -0
- data/examples/widget_scroll_text/app.rb +2 -2
- data/examples/widget_scrollbar/README.md +6 -0
- data/examples/widget_scrollbar/app.rb +2 -2
- data/examples/widget_sparkline/README.md +6 -0
- data/examples/widget_sparkline/app.rb +2 -2
- data/examples/widget_style_colors/README.md +6 -0
- data/examples/widget_style_colors/app.rb +2 -2
- data/examples/widget_table/README.md +8 -2
- data/examples/widget_table/app.rb +2 -2
- data/examples/widget_tabs/README.md +6 -0
- data/examples/widget_tabs/app.rb +2 -2
- data/examples/widget_text_width/README.md +6 -0
- data/examples/widget_text_width/app.rb +4 -4
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/ext/ratatui_ruby/extconf.rb +2 -2
- data/ext/ratatui_ruby/src/rendering.rs +1 -1
- data/ext/ratatui_ruby/src/style.rs +0 -8
- data/ext/ratatui_ruby/src/widgets/chart.rs +0 -118
- data/ext/ratatui_ruby/src/widgets/list_state.rs +36 -0
- data/lib/ratatui_ruby/buffer/cell.rb +34 -2
- data/lib/ratatui_ruby/buffer.rb +2 -2
- data/lib/ratatui_ruby/cell.rb +34 -2
- data/lib/ratatui_ruby/event/focus_gained.rb +26 -2
- data/lib/ratatui_ruby/event/focus_lost.rb +26 -2
- data/lib/ratatui_ruby/event/key/character.rb +18 -2
- data/lib/ratatui_ruby/event/key/media.rb +2 -2
- data/lib/ratatui_ruby/event/key/modifier.rb +10 -2
- data/lib/ratatui_ruby/event/key/navigation.rb +2 -2
- data/lib/ratatui_ruby/event/key/system.rb +2 -2
- data/lib/ratatui_ruby/event/key.rb +114 -2
- data/lib/ratatui_ruby/event/mouse.rb +42 -2
- data/lib/ratatui_ruby/event/none.rb +10 -2
- data/lib/ratatui_ruby/event/paste.rb +34 -2
- data/lib/ratatui_ruby/event/resize.rb +34 -2
- data/lib/ratatui_ruby/event.rb +26 -2
- data/lib/ratatui_ruby/frame.rb +74 -2
- data/lib/ratatui_ruby/layout/constraint.rb +58 -2
- data/lib/ratatui_ruby/layout/layout.rb +47 -2
- data/lib/ratatui_ruby/layout/rect.rb +403 -2
- data/lib/ratatui_ruby/layout.rb +2 -2
- data/lib/ratatui_ruby/list_state.rb +113 -2
- data/lib/ratatui_ruby/output_guard.rb +26 -3
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +2 -2
- data/lib/ratatui_ruby/schema/bar_chart.rb +50 -2
- data/lib/ratatui_ruby/schema/block.rb +21 -15
- data/lib/ratatui_ruby/schema/calendar.rb +2 -2
- data/lib/ratatui_ruby/schema/canvas.rb +10 -2
- data/lib/ratatui_ruby/schema/center.rb +10 -2
- data/lib/ratatui_ruby/schema/chart.rb +2 -28
- data/lib/ratatui_ruby/schema/clear.rb +10 -2
- data/lib/ratatui_ruby/schema/constraint.rb +58 -2
- data/lib/ratatui_ruby/schema/cursor.rb +10 -2
- data/lib/ratatui_ruby/schema/draw.rb +10 -2
- data/lib/ratatui_ruby/schema/gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/layout.rb +18 -2
- data/lib/ratatui_ruby/schema/line_gauge.rb +2 -2
- data/lib/ratatui_ruby/schema/list.rb +10 -2
- data/lib/ratatui_ruby/schema/list_item.rb +10 -2
- data/lib/ratatui_ruby/schema/overlay.rb +10 -2
- data/lib/ratatui_ruby/schema/paragraph.rb +10 -2
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +2 -2
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +2 -2
- data/lib/ratatui_ruby/schema/rect.rb +58 -2
- data/lib/ratatui_ruby/schema/row.rb +10 -2
- data/lib/ratatui_ruby/schema/scrollbar.rb +2 -2
- data/lib/ratatui_ruby/schema/shape/label.rb +10 -2
- data/lib/ratatui_ruby/schema/sparkline.rb +10 -2
- data/lib/ratatui_ruby/schema/style.rb +18 -2
- data/lib/ratatui_ruby/schema/table.rb +2 -2
- data/lib/ratatui_ruby/schema/tabs.rb +2 -2
- data/lib/ratatui_ruby/schema/text.rb +34 -2
- data/lib/ratatui_ruby/scrollbar_state.rb +10 -2
- data/lib/ratatui_ruby/style/style.rb +18 -2
- data/lib/ratatui_ruby/style.rb +2 -2
- data/lib/ratatui_ruby/table_state.rb +10 -2
- data/lib/ratatui_ruby/terminal_lifecycle.rb +18 -3
- data/lib/ratatui_ruby/test_helper/event_injection.rb +34 -2
- data/lib/ratatui_ruby/test_helper/snapshot.rb +74 -9
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +98 -2
- data/lib/ratatui_ruby/test_helper/terminal.rb +50 -2
- data/lib/ratatui_ruby/test_helper/test_doubles.rb +18 -2
- data/lib/ratatui_ruby/test_helper.rb +10 -2
- data/lib/ratatui_ruby/tui/buffer_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/canvas_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/core.rb +2 -2
- data/lib/ratatui_ruby/tui/layout_factories.rb +32 -2
- data/lib/ratatui_ruby/tui/state_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/style_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/text_factories.rb +2 -2
- data/lib/ratatui_ruby/tui/widget_factories.rb +2 -2
- data/lib/ratatui_ruby/tui.rb +11 -3
- data/lib/ratatui_ruby/version.rb +3 -3
- data/lib/ratatui_ruby/widgets/bar_chart/bar.rb +2 -2
- data/lib/ratatui_ruby/widgets/bar_chart/bar_group.rb +2 -2
- data/lib/ratatui_ruby/widgets/bar_chart.rb +58 -2
- data/lib/ratatui_ruby/widgets/block.rb +37 -15
- data/lib/ratatui_ruby/widgets/calendar.rb +2 -2
- data/lib/ratatui_ruby/widgets/canvas.rb +10 -2
- data/lib/ratatui_ruby/widgets/cell.rb +10 -2
- data/lib/ratatui_ruby/widgets/center.rb +10 -2
- data/lib/ratatui_ruby/widgets/chart.rb +2 -28
- data/lib/ratatui_ruby/widgets/clear.rb +10 -2
- data/lib/ratatui_ruby/widgets/cursor.rb +10 -2
- data/lib/ratatui_ruby/widgets/gauge.rb +16 -2
- data/lib/ratatui_ruby/widgets/line_gauge.rb +16 -2
- data/lib/ratatui_ruby/widgets/list.rb +41 -2
- data/lib/ratatui_ruby/widgets/list_item.rb +10 -2
- data/lib/ratatui_ruby/widgets/overlay.rb +10 -2
- data/lib/ratatui_ruby/widgets/paragraph.rb +10 -2
- data/lib/ratatui_ruby/widgets/ratatui_logo.rb +2 -2
- data/lib/ratatui_ruby/widgets/ratatui_mascot.rb +2 -2
- data/lib/ratatui_ruby/widgets/row.rb +10 -2
- data/lib/ratatui_ruby/widgets/scrollbar.rb +2 -2
- data/lib/ratatui_ruby/widgets/shape/label.rb +10 -2
- data/lib/ratatui_ruby/widgets/sparkline.rb +10 -2
- data/lib/ratatui_ruby/widgets/table.rb +62 -2
- data/lib/ratatui_ruby/widgets/tabs.rb +2 -2
- data/lib/ratatui_ruby/widgets.rb +2 -2
- data/lib/ratatui_ruby.rb +90 -2
- data/sig/examples/app_all_events/view.rbs +7 -1
- data/sig/examples/app_all_events/view_state.rbs +7 -1
- data/sig/examples/app_color_picker/app.rbs +5 -0
- data/sig/examples/app_stateful_interaction/app.rbs +7 -1
- data/sig/examples/verify_quickstart_dsl/app.rbs +7 -1
- data/sig/examples/verify_quickstart_lifecycle/app.rbs +7 -1
- data/sig/examples/verify_readme_usage/app.rbs +7 -1
- data/sig/examples/widget_block_demo/app.rbs +6 -0
- data/sig/examples/widget_box_demo/app.rbs +7 -1
- data/sig/examples/widget_calendar_demo/app.rbs +7 -1
- data/sig/examples/widget_cell_demo/app.rbs +7 -1
- data/sig/examples/widget_chart_demo/app.rbs +7 -1
- data/sig/examples/widget_gauge_demo/app.rbs +7 -1
- data/sig/examples/widget_layout_split/app.rbs +7 -1
- data/sig/examples/widget_line_gauge_demo/app.rbs +7 -1
- data/sig/examples/widget_list_demo/app.rbs +5 -0
- data/sig/examples/widget_map_demo/app.rbs +7 -1
- data/sig/examples/widget_popup_demo/app.rbs +7 -1
- data/sig/examples/widget_ratatui_logo_demo/app.rbs +7 -1
- data/sig/examples/widget_ratatui_mascot_demo/app.rbs +7 -1
- data/sig/examples/widget_rect/app.rbs +7 -1
- data/sig/examples/widget_render/app.rbs +7 -1
- data/sig/examples/widget_rich_text/app.rbs +7 -1
- data/sig/examples/widget_scroll_text/app.rbs +7 -1
- data/sig/examples/widget_scrollbar_demo/app.rbs +7 -1
- data/sig/examples/widget_sparkline_demo/app.rbs +7 -1
- data/sig/examples/widget_style_colors/app.rbs +7 -1
- data/sig/examples/widget_table_demo/app.rbs +7 -1
- data/sig/examples/widget_text_width/app.rbs +7 -1
- data/sig/ratatui_ruby/event.rbs +7 -1
- data/sig/ratatui_ruby/frame.rbs +15 -3
- data/sig/ratatui_ruby/list_state.rbs +11 -1
- data/sig/ratatui_ruby/ratatui_ruby.rbs +8 -2
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +7 -1
- data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +6 -0
- data/sig/ratatui_ruby/schema/bar_chart.rbs +6 -0
- data/sig/ratatui_ruby/schema/block.rbs +7 -1
- data/sig/ratatui_ruby/schema/calendar.rbs +6 -0
- data/sig/ratatui_ruby/schema/canvas.rbs +6 -0
- data/sig/ratatui_ruby/schema/center.rbs +6 -0
- data/sig/ratatui_ruby/schema/chart.rbs +6 -9
- data/sig/ratatui_ruby/schema/constraint.rbs +6 -0
- data/sig/ratatui_ruby/schema/cursor.rbs +6 -0
- data/sig/ratatui_ruby/schema/draw.rbs +6 -0
- data/sig/ratatui_ruby/schema/gauge.rbs +9 -1
- data/sig/ratatui_ruby/schema/layout.rbs +6 -0
- data/sig/ratatui_ruby/schema/line_gauge.rbs +9 -1
- data/sig/ratatui_ruby/schema/list.rbs +9 -1
- data/sig/ratatui_ruby/schema/list_item.rbs +7 -1
- data/sig/ratatui_ruby/schema/overlay.rbs +6 -0
- data/sig/ratatui_ruby/schema/paragraph.rbs +6 -0
- data/sig/ratatui_ruby/schema/ratatui_logo.rbs +6 -0
- data/sig/ratatui_ruby/schema/ratatui_mascot.rbs +5 -0
- data/sig/ratatui_ruby/schema/rect.rbs +30 -0
- data/sig/ratatui_ruby/schema/row.rbs +7 -1
- data/sig/ratatui_ruby/schema/scrollbar.rbs +6 -0
- data/sig/ratatui_ruby/schema/sparkline.rbs +6 -0
- data/sig/ratatui_ruby/schema/style.rbs +7 -1
- data/sig/ratatui_ruby/schema/table.rbs +11 -1
- data/sig/ratatui_ruby/schema/tabs.rbs +6 -0
- data/sig/ratatui_ruby/schema/text.rbs +7 -1
- data/sig/ratatui_ruby/scrollbar_state.rbs +7 -1
- data/sig/ratatui_ruby/session.rbs +7 -1
- data/sig/ratatui_ruby/table_state.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/event_injection.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/snapshot.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/style_assertions.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/terminal.rbs +7 -1
- data/sig/ratatui_ruby/test_helper/test_doubles.rbs +7 -1
- data/sig/ratatui_ruby/test_helper.rbs +7 -1
- data/sig/ratatui_ruby/tui/buffer_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/canvas_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/core.rbs +7 -1
- data/sig/ratatui_ruby/tui/layout_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/state_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/style_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/text_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui/widget_factories.rbs +7 -1
- data/sig/ratatui_ruby/tui.rbs +7 -1
- data/sig/ratatui_ruby/version.rbs +6 -0
- data/tasks/autodoc/examples.rb +1 -1
- data/tasks/autodoc/member.rb +1 -1
- data/tasks/autodoc/name.rb +1 -1
- data/tasks/bump/cargo_lockfile.rb +1 -1
- data/tasks/bump/changelog.rb +1 -1
- data/tasks/bump/header.rb +1 -1
- data/tasks/bump/history.rb +1 -1
- data/tasks/bump/links.rb +1 -1
- data/tasks/bump/manifest.rb +1 -1
- data/tasks/bump/ruby_gem.rb +1 -1
- data/tasks/bump/sem_ver.rb +1 -1
- data/tasks/bump/unreleased_section.rb +1 -1
- data/tasks/license/headers_md.rb +223 -0
- data/tasks/license/headers_rb.rb +210 -0
- data/tasks/license/license_utils.rb +130 -0
- data/tasks/license/snippets_md.rb +315 -0
- data/tasks/license/snippets_rdoc.rb +150 -0
- data/tasks/license.rake +91 -0
- data/tasks/rdoc_config.rb +1 -1
- data/tasks/resources/build.yml.erb +13 -7
- data/tasks/sourcehut.rake +3 -1
- data/tasks/terminal_preview/app_screenshot.rb +1 -1
- data/tasks/terminal_preview/crash_report.rb +1 -1
- data/tasks/terminal_preview/example_app.rb +1 -1
- data/tasks/terminal_preview/launcher_script.rb +1 -1
- data/tasks/terminal_preview/preview_collection.rb +1 -1
- data/tasks/terminal_preview/preview_timing.rb +1 -1
- data/tasks/terminal_preview/safety_confirmation.rb +1 -1
- data/tasks/terminal_preview/saved_screenshot.rb +1 -1
- data/tasks/terminal_preview/system_appearance.rb +1 -1
- data/tasks/terminal_preview/terminal_window.rb +1 -1
- data/tasks/terminal_preview/window_id.rb +1 -1
- data/tasks/website/index_page.rb +1 -1
- data/tasks/website/version.rb +1 -1
- data/tasks/website/version_menu.rb +1 -1
- data/tasks/website/versioned_documentation.rb +1 -1
- data/tasks/website/website.rb +1 -1
- metadata +13 -3
- data/doc/migration/v0_7_0.md +0 -236
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
#--
|
|
4
|
-
# SPDX-FileCopyrightText:
|
|
5
|
-
# SPDX-License-Identifier:
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
8
|
module RatatuiRuby
|
|
@@ -19,8 +19,16 @@ module RatatuiRuby
|
|
|
19
19
|
#
|
|
20
20
|
# === Examples
|
|
21
21
|
#
|
|
22
|
+
#--
|
|
23
|
+
# SPDX-SnippetBegin
|
|
24
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
25
|
+
# SPDX-License-Identifier: MIT-0
|
|
26
|
+
#++
|
|
22
27
|
# area = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
23
28
|
# puts area.width # => 80
|
|
29
|
+
#--
|
|
30
|
+
# SPDX-SnippetEnd
|
|
31
|
+
#++
|
|
24
32
|
class Rect < Data.define(:x, :y, :width, :height)
|
|
25
33
|
##
|
|
26
34
|
# :attr_reader: x
|
|
@@ -59,15 +67,31 @@ module RatatuiRuby
|
|
|
59
67
|
#
|
|
60
68
|
# Essential for hit testing mouse clicks against layout regions.
|
|
61
69
|
#
|
|
70
|
+
#--
|
|
71
|
+
# SPDX-SnippetBegin
|
|
72
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
73
|
+
# SPDX-License-Identifier: MIT-0
|
|
74
|
+
#++
|
|
62
75
|
# area = Layout::Rect.new(x: 10, y: 5, width: 20, height: 10)
|
|
63
76
|
# area.contains?(15, 8) # => true
|
|
64
77
|
# area.contains?(5, 8) # => false
|
|
65
78
|
#
|
|
79
|
+
#--
|
|
80
|
+
# SPDX-SnippetEnd
|
|
81
|
+
#++
|
|
66
82
|
# [px]
|
|
67
83
|
# X coordinate to test (column).
|
|
68
84
|
# [py]
|
|
85
|
+
#--
|
|
86
|
+
# SPDX-SnippetBegin
|
|
87
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
88
|
+
# SPDX-License-Identifier: MIT-0
|
|
89
|
+
#++
|
|
69
90
|
# Y coordinate to test (row).
|
|
70
91
|
#
|
|
92
|
+
#--
|
|
93
|
+
# SPDX-SnippetEnd
|
|
94
|
+
#++
|
|
71
95
|
# Returns true if the point (px, py) is within the rectangle bounds.
|
|
72
96
|
def contains?(px, py)
|
|
73
97
|
px >= x && px < x + width && py >= y && py < y + height
|
|
@@ -77,13 +101,29 @@ module RatatuiRuby
|
|
|
77
101
|
#
|
|
78
102
|
# Essential for determining if a widget is visible within a viewport or clipping area.
|
|
79
103
|
#
|
|
104
|
+
#--
|
|
105
|
+
# SPDX-SnippetBegin
|
|
106
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
107
|
+
# SPDX-License-Identifier: MIT-0
|
|
108
|
+
#++
|
|
80
109
|
# viewport = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
81
110
|
# widget = Layout::Rect.new(x: 70, y: 20, width: 20, height: 10)
|
|
82
111
|
# viewport.intersects?(widget) # => true (partial overlap)
|
|
83
112
|
#
|
|
113
|
+
#--
|
|
114
|
+
# SPDX-SnippetEnd
|
|
115
|
+
#++
|
|
84
116
|
# [other]
|
|
117
|
+
#--
|
|
118
|
+
# SPDX-SnippetBegin
|
|
119
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
120
|
+
# SPDX-License-Identifier: MIT-0
|
|
121
|
+
#++
|
|
85
122
|
# Another Rect to test against.
|
|
86
123
|
#
|
|
124
|
+
#--
|
|
125
|
+
# SPDX-SnippetEnd
|
|
126
|
+
#++
|
|
87
127
|
# Returns true if the rectangles overlap.
|
|
88
128
|
def intersects?(other)
|
|
89
129
|
x < other.x + other.width &&
|
|
@@ -96,14 +136,30 @@ module RatatuiRuby
|
|
|
96
136
|
#
|
|
97
137
|
# Essential for calculating visible portions of widgets inside scroll views.
|
|
98
138
|
#
|
|
139
|
+
#--
|
|
140
|
+
# SPDX-SnippetBegin
|
|
141
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
142
|
+
# SPDX-License-Identifier: MIT-0
|
|
143
|
+
#++
|
|
99
144
|
# viewport = Layout::Rect.new(x: 0, y: 0, width: 80, height: 24)
|
|
100
145
|
# widget = Layout::Rect.new(x: 70, y: 20, width: 20, height: 10)
|
|
101
146
|
# visible = viewport.intersection(widget)
|
|
102
147
|
# # => Rect(x: 70, y: 20, width: 10, height: 4)
|
|
103
148
|
#
|
|
149
|
+
#--
|
|
150
|
+
# SPDX-SnippetEnd
|
|
151
|
+
#++
|
|
104
152
|
# [other]
|
|
153
|
+
#--
|
|
154
|
+
# SPDX-SnippetBegin
|
|
155
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
156
|
+
# SPDX-License-Identifier: MIT-0
|
|
157
|
+
#++
|
|
105
158
|
# Another Rect to intersect with.
|
|
106
159
|
#
|
|
160
|
+
#--
|
|
161
|
+
# SPDX-SnippetEnd
|
|
162
|
+
#++
|
|
107
163
|
# Returns a new Rect representing the intersection, or +nil+ if no overlap.
|
|
108
164
|
def intersection(other)
|
|
109
165
|
return nil unless intersects?(other)
|
|
@@ -115,6 +171,351 @@ module RatatuiRuby
|
|
|
115
171
|
|
|
116
172
|
Rect.new(x: new_x, y: new_y, width: new_right - new_x, height: new_bottom - new_y)
|
|
117
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
|
+
# Moves the rect without changing size.
|
|
383
|
+
#
|
|
384
|
+
# Animations and drag-and-drop shift widgets.
|
|
385
|
+
# Adding offsets to x and y inline clutters the code.
|
|
386
|
+
#
|
|
387
|
+
# This method returns a translated copy.
|
|
388
|
+
#
|
|
389
|
+
# [dx] Horizontal shift (positive moves right).
|
|
390
|
+
# [dy] Vertical shift (positive moves down).
|
|
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: 5, width: 20, height: 10)
|
|
400
|
+
# rect.offset(5, 3) # => Rect(x: 15, y: 8, width: 20, height: 10)
|
|
401
|
+
#--
|
|
402
|
+
# SPDX-SnippetEnd
|
|
403
|
+
#++
|
|
404
|
+
def offset(dx, dy)
|
|
405
|
+
Rect.new(x: x + dx, y: y + dy, width:, height:)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Constrains the rect to fit inside bounds.
|
|
409
|
+
#
|
|
410
|
+
# Popups and tooltips may extend beyond screen edges.
|
|
411
|
+
# Manually clamping x, y, width, and height is verbose and error-prone.
|
|
412
|
+
#
|
|
413
|
+
# This method repositions and shrinks the rect to stay within bounds.
|
|
414
|
+
#
|
|
415
|
+
# [other] Bounding Rect.
|
|
416
|
+
#
|
|
417
|
+
# === Example
|
|
418
|
+
#
|
|
419
|
+
#--
|
|
420
|
+
# SPDX-SnippetBegin
|
|
421
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
422
|
+
# SPDX-License-Identifier: MIT-0
|
|
423
|
+
#++
|
|
424
|
+
# screen = Layout::Rect.new(x: 0, y: 0, width: 100, height: 100)
|
|
425
|
+
# popup = Layout::Rect.new(x: 80, y: 80, width: 30, height: 30)
|
|
426
|
+
# popup.clamp(screen) # => Rect(x: 70, y: 70, width: 30, height: 30)
|
|
427
|
+
#--
|
|
428
|
+
# SPDX-SnippetEnd
|
|
429
|
+
#++
|
|
430
|
+
def clamp(other)
|
|
431
|
+
clamped_width = [width, other.width].min
|
|
432
|
+
clamped_height = [height, other.height].min
|
|
433
|
+
clamped_x = x.clamp(other.left, other.right - clamped_width)
|
|
434
|
+
clamped_y = y.clamp(other.top, other.bottom - clamped_height)
|
|
435
|
+
|
|
436
|
+
Rect.new(x: clamped_x, y: clamped_y, width: clamped_width, height: clamped_height)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Iterates over horizontal slices.
|
|
440
|
+
#
|
|
441
|
+
# Lists render line by line. Looping <tt>height.times</tt> and constructing rects inline is noisy.
|
|
442
|
+
#
|
|
443
|
+
# This method yields each row as a Rect with height 1. Returns an Enumerator if no block given.
|
|
444
|
+
#
|
|
445
|
+
# === Example
|
|
446
|
+
#
|
|
447
|
+
#--
|
|
448
|
+
# SPDX-SnippetBegin
|
|
449
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
450
|
+
# SPDX-License-Identifier: MIT-0
|
|
451
|
+
#++
|
|
452
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 5, height: 3)
|
|
453
|
+
# rect.rows.map { |r| r.y } # => [0, 1, 2]
|
|
454
|
+
#--
|
|
455
|
+
# SPDX-SnippetEnd
|
|
456
|
+
#++
|
|
457
|
+
def rows
|
|
458
|
+
return to_enum(:rows) unless block_given?
|
|
459
|
+
|
|
460
|
+
height.times do |i|
|
|
461
|
+
yield Rect.new(x:, y: y + i, width:, height: 1)
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# Iterates over vertical slices.
|
|
466
|
+
#
|
|
467
|
+
# Grids render column by column. Looping <tt>width.times</tt> and constructing rects inline is noisy.
|
|
468
|
+
#
|
|
469
|
+
# This method yields each column as a Rect with width 1. Returns an Enumerator if no block given.
|
|
470
|
+
#
|
|
471
|
+
# === Example
|
|
472
|
+
#
|
|
473
|
+
#--
|
|
474
|
+
# SPDX-SnippetBegin
|
|
475
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
476
|
+
# SPDX-License-Identifier: MIT-0
|
|
477
|
+
#++
|
|
478
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 5, height: 3)
|
|
479
|
+
# rect.columns.map { |c| c.x } # => [0, 1, 2, 3, 4]
|
|
480
|
+
#--
|
|
481
|
+
# SPDX-SnippetEnd
|
|
482
|
+
#++
|
|
483
|
+
def columns
|
|
484
|
+
return to_enum(:columns) unless block_given?
|
|
485
|
+
|
|
486
|
+
width.times do |i|
|
|
487
|
+
yield Rect.new(x: x + i, y:, width: 1, height:)
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# Iterates over every cell in row-major order.
|
|
492
|
+
#
|
|
493
|
+
# Hit testing and pixel rendering touch every position.
|
|
494
|
+
# Nested loops with manual coordinate math are verbose.
|
|
495
|
+
#
|
|
496
|
+
# This method yields <tt>[x, y]</tt> pairs. Returns an Enumerator if no block given.
|
|
497
|
+
#
|
|
498
|
+
# === Example
|
|
499
|
+
#
|
|
500
|
+
#--
|
|
501
|
+
# SPDX-SnippetBegin
|
|
502
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
503
|
+
# SPDX-License-Identifier: MIT-0
|
|
504
|
+
#++
|
|
505
|
+
# rect = Layout::Rect.new(x: 0, y: 0, width: 2, height: 2)
|
|
506
|
+
# rect.positions.to_a # => [[0, 0], [1, 0], [0, 1], [1, 1]]
|
|
507
|
+
#--
|
|
508
|
+
# SPDX-SnippetEnd
|
|
509
|
+
#++
|
|
510
|
+
def positions
|
|
511
|
+
return to_enum(:positions) unless block_given?
|
|
512
|
+
|
|
513
|
+
height.times do |row|
|
|
514
|
+
width.times do |col|
|
|
515
|
+
yield [x + col, y + row]
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
end
|
|
118
519
|
end
|
|
119
520
|
end
|
|
120
521
|
end
|
data/lib/ratatui_ruby/layout.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
#--
|
|
4
|
-
# SPDX-FileCopyrightText:
|
|
5
|
-
# SPDX-License-Identifier:
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
8
|
module RatatuiRuby
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
#--
|
|
4
|
-
# SPDX-FileCopyrightText:
|
|
5
|
-
# SPDX-License-Identifier:
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
8
|
module RatatuiRuby
|
|
@@ -24,6 +24,11 @@ module RatatuiRuby
|
|
|
24
24
|
#
|
|
25
25
|
# == Example
|
|
26
26
|
#
|
|
27
|
+
#--
|
|
28
|
+
# SPDX-SnippetBegin
|
|
29
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
30
|
+
# SPDX-License-Identifier: MIT-0
|
|
31
|
+
#++
|
|
27
32
|
# @list_state = RatatuiRuby::ListState.new
|
|
28
33
|
# @list_state.select(2) # Select third item
|
|
29
34
|
#
|
|
@@ -34,6 +39,9 @@ module RatatuiRuby
|
|
|
34
39
|
#
|
|
35
40
|
# puts @list_state.offset # Scroll position after render
|
|
36
41
|
#
|
|
42
|
+
#--
|
|
43
|
+
# SPDX-SnippetEnd
|
|
44
|
+
#++
|
|
37
45
|
class ListState
|
|
38
46
|
##
|
|
39
47
|
# :method: new
|
|
@@ -86,5 +94,108 @@ module RatatuiRuby
|
|
|
86
94
|
# Scrolls up by +n+ items.
|
|
87
95
|
#
|
|
88
96
|
# (Native method implemented in Rust)
|
|
97
|
+
|
|
98
|
+
##
|
|
99
|
+
# :method: select_next
|
|
100
|
+
# :call-seq: select_next() -> nil
|
|
101
|
+
#
|
|
102
|
+
# Moves selection to the next item. Selects first item if nothing selected.
|
|
103
|
+
#
|
|
104
|
+
# === Optimistic Indexing
|
|
105
|
+
#
|
|
106
|
+
# Increments the index immediately, even past list bounds. The renderer
|
|
107
|
+
# clamps to valid range on draw. Reading <tt>selected</tt> between this
|
|
108
|
+
# call and render may return an out-of-bounds value.
|
|
109
|
+
#
|
|
110
|
+
# Matches upstream Ratatui behavior. See
|
|
111
|
+
# {ListState#select_next}[https://docs.rs/ratatui/0.30/ratatui/widgets/struct.ListState.html#method.select_next].
|
|
112
|
+
#
|
|
113
|
+
# To detect actual selection changes, check bounds first:
|
|
114
|
+
#
|
|
115
|
+
#--
|
|
116
|
+
# SPDX-SnippetBegin
|
|
117
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
118
|
+
# SPDX-License-Identifier: MIT-0
|
|
119
|
+
#++
|
|
120
|
+
# max_index = items.size - 1
|
|
121
|
+
# return if (state.selected || 0) >= max_index
|
|
122
|
+
# state.select_next
|
|
123
|
+
#
|
|
124
|
+
#--
|
|
125
|
+
# SPDX-SnippetEnd
|
|
126
|
+
#++
|
|
127
|
+
# (Native method implemented in Rust)
|
|
128
|
+
|
|
129
|
+
##
|
|
130
|
+
# :method: select_previous
|
|
131
|
+
# :call-seq: select_previous() -> nil
|
|
132
|
+
#
|
|
133
|
+
# Moves selection to the previous item. Selects last item if nothing selected.
|
|
134
|
+
#
|
|
135
|
+
# === Optimistic Indexing
|
|
136
|
+
#
|
|
137
|
+
# At index 0, does nothing. With no selection, sets index to maximum value;
|
|
138
|
+
# the renderer clamps to actual last item on draw.
|
|
139
|
+
#
|
|
140
|
+
# To detect actual selection changes, check bounds first:
|
|
141
|
+
#
|
|
142
|
+
#--
|
|
143
|
+
# SPDX-SnippetBegin
|
|
144
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
145
|
+
# SPDX-License-Identifier: MIT-0
|
|
146
|
+
#++
|
|
147
|
+
# return if (state.selected || 0) <= 0
|
|
148
|
+
# state.select_previous
|
|
149
|
+
#
|
|
150
|
+
#--
|
|
151
|
+
# SPDX-SnippetEnd
|
|
152
|
+
#++
|
|
153
|
+
# (Native method implemented in Rust)
|
|
154
|
+
|
|
155
|
+
##
|
|
156
|
+
# :method: select_first
|
|
157
|
+
# :call-seq: select_first() -> nil
|
|
158
|
+
#
|
|
159
|
+
# Jumps selection to the first item (index 0).
|
|
160
|
+
#
|
|
161
|
+
# To detect actual selection changes:
|
|
162
|
+
#
|
|
163
|
+
#--
|
|
164
|
+
# SPDX-SnippetBegin
|
|
165
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
166
|
+
# SPDX-License-Identifier: MIT-0
|
|
167
|
+
#++
|
|
168
|
+
# return if (state.selected || 0) == 0
|
|
169
|
+
# state.select_first
|
|
170
|
+
#
|
|
171
|
+
#--
|
|
172
|
+
# SPDX-SnippetEnd
|
|
173
|
+
#++
|
|
174
|
+
# (Native method implemented in Rust)
|
|
175
|
+
|
|
176
|
+
##
|
|
177
|
+
# :method: select_last
|
|
178
|
+
# :call-seq: select_last() -> nil
|
|
179
|
+
#
|
|
180
|
+
# Jumps selection to the last item.
|
|
181
|
+
#
|
|
182
|
+
# === Optimistic Indexing
|
|
183
|
+
#
|
|
184
|
+
# Sets index to maximum possible value. The renderer clamps to actual last
|
|
185
|
+
# item on draw. To get or check the real last index, track item count:
|
|
186
|
+
#
|
|
187
|
+
#--
|
|
188
|
+
# SPDX-SnippetBegin
|
|
189
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
190
|
+
# SPDX-License-Identifier: MIT-0
|
|
191
|
+
#++
|
|
192
|
+
# max_index = items.size - 1
|
|
193
|
+
# return if (state.selected || 0) == max_index
|
|
194
|
+
# state.select(max_index)
|
|
195
|
+
#
|
|
196
|
+
#--
|
|
197
|
+
# SPDX-SnippetEnd
|
|
198
|
+
#++
|
|
199
|
+
# (Native method implemented in Rust)
|
|
89
200
|
end
|
|
90
201
|
end
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
#--
|
|
4
|
-
# SPDX-FileCopyrightText:
|
|
5
|
-
#
|
|
6
|
-
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
7
6
|
#++
|
|
8
7
|
|
|
9
8
|
module RatatuiRuby
|
|
@@ -70,13 +69,26 @@ module RatatuiRuby
|
|
|
70
69
|
# (like lazygit does when editing a commit message), use
|
|
71
70
|
# {restore_terminal} and {init_terminal} instead:
|
|
72
71
|
#
|
|
72
|
+
#--
|
|
73
|
+
# SPDX-SnippetBegin
|
|
74
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
75
|
+
# SPDX-License-Identifier: MIT-0
|
|
76
|
+
#++
|
|
73
77
|
# RatatuiRuby.restore_terminal
|
|
74
78
|
# puts "Press enter to continue..."
|
|
75
79
|
# gets
|
|
76
80
|
# RatatuiRuby.init_terminal
|
|
77
81
|
#
|
|
82
|
+
#--
|
|
83
|
+
# SPDX-SnippetEnd
|
|
84
|
+
#++
|
|
78
85
|
# === Example
|
|
79
86
|
#
|
|
87
|
+
#--
|
|
88
|
+
# SPDX-SnippetBegin
|
|
89
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
90
|
+
# SPDX-License-Identifier: MIT-0
|
|
91
|
+
#++
|
|
80
92
|
# if ARGV.include?("--no-tui")
|
|
81
93
|
# RatatuiRuby.headless!
|
|
82
94
|
# process_batch_work # guard_io calls are silent no-ops
|
|
@@ -86,6 +98,9 @@ module RatatuiRuby
|
|
|
86
98
|
# end
|
|
87
99
|
# end
|
|
88
100
|
#
|
|
101
|
+
#--
|
|
102
|
+
# SPDX-SnippetEnd
|
|
103
|
+
#++
|
|
89
104
|
# Note: Calling {run} or {init_terminal} after {headless!} raises
|
|
90
105
|
# {Error::Invariant}. The block is never executed.
|
|
91
106
|
#
|
|
@@ -117,12 +132,20 @@ module RatatuiRuby
|
|
|
117
132
|
#
|
|
118
133
|
# === Example
|
|
119
134
|
#
|
|
135
|
+
#--
|
|
136
|
+
# SPDX-SnippetBegin
|
|
137
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
138
|
+
# SPDX-License-Identifier: MIT-0
|
|
139
|
+
#++
|
|
120
140
|
# RatatuiRuby.run do |tui|
|
|
121
141
|
# RatatuiRuby.guard_io do
|
|
122
142
|
# SomeChattyGem.do_something # Any puts/warn calls are swallowed
|
|
123
143
|
# end
|
|
124
144
|
# end
|
|
125
145
|
#
|
|
146
|
+
#--
|
|
147
|
+
# SPDX-SnippetEnd
|
|
148
|
+
#++
|
|
126
149
|
# @see headless!
|
|
127
150
|
def guard_io
|
|
128
151
|
# TUI active: guard the output
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
#--
|
|
4
|
-
# SPDX-FileCopyrightText:
|
|
5
|
-
# SPDX-License-Identifier:
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
8
|
module RatatuiRuby
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
#--
|
|
4
|
-
# SPDX-FileCopyrightText:
|
|
5
|
-
# SPDX-License-Identifier:
|
|
4
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: LGPL-3.0-or-later
|
|
6
6
|
#++
|
|
7
7
|
|
|
8
8
|
module RatatuiRuby
|