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,77 @@
|
|
|
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 Widgets
|
|
10
|
+
# Mixin that provides DWIM hash coercion for widget classes.
|
|
11
|
+
#
|
|
12
|
+
# When users call `tui.table(hash)` instead of `tui.table(**hash)`,
|
|
13
|
+
# Ruby's `...` forwarding passes the Hash as a positional argument,
|
|
14
|
+
# causing cryptic TypeErrors at the Rust FFI boundary.
|
|
15
|
+
#
|
|
16
|
+
# This mixin provides a `coerce_args` class method that detects
|
|
17
|
+
# this pattern and automatically splats the hash into keyword arguments.
|
|
18
|
+
#
|
|
19
|
+
# === Behavior
|
|
20
|
+
#
|
|
21
|
+
# - **Production mode**: Unknown keys are silently ignored
|
|
22
|
+
# - **Debug mode (RR_DEBUG=1)**: Raises ArgumentError to catch typos early
|
|
23
|
+
#
|
|
24
|
+
# === Usage
|
|
25
|
+
#
|
|
26
|
+
#--
|
|
27
|
+
# SPDX-SnippetBegin
|
|
28
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
29
|
+
# SPDX-License-Identifier: MIT-0
|
|
30
|
+
#++
|
|
31
|
+
# class Table < Data.define(:rows, :widths, ...)
|
|
32
|
+
# include CoerceableWidget
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# # In WidgetFactories:
|
|
36
|
+
# def table(first = nil, **kwargs)
|
|
37
|
+
# Widgets::Table.coerce_args(first, kwargs)
|
|
38
|
+
# end
|
|
39
|
+
#--
|
|
40
|
+
# SPDX-SnippetEnd
|
|
41
|
+
#++
|
|
42
|
+
module CoerceableWidget
|
|
43
|
+
##
|
|
44
|
+
# Hook called when this module is included in a widget class.
|
|
45
|
+
#
|
|
46
|
+
# Extends the class with ClassMethods and defines KNOWN_KEYS constant
|
|
47
|
+
# from the Data.define members for validation.
|
|
48
|
+
#
|
|
49
|
+
# [base] The class including this module.
|
|
50
|
+
def self.included(base)
|
|
51
|
+
base.extend(ClassMethods)
|
|
52
|
+
base.const_set(:KNOWN_KEYS, base.members.freeze) unless base.const_defined?(:KNOWN_KEYS)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Class methods extended onto widget classes.
|
|
56
|
+
module ClassMethods
|
|
57
|
+
# Coerces a bare Hash argument into keyword arguments.
|
|
58
|
+
#
|
|
59
|
+
# @param first [Hash, nil] First positional argument (bare hash case)
|
|
60
|
+
# @param kwargs [Hash] Keyword arguments (normal splatted case)
|
|
61
|
+
# @return [Object] New instance of the widget class
|
|
62
|
+
# @raise [ArgumentError] In debug mode, if unknown keys are present
|
|
63
|
+
def coerce_args(first, kwargs)
|
|
64
|
+
if first.is_a?(Hash) && kwargs.empty?
|
|
65
|
+
unknown = first.keys - self::KNOWN_KEYS
|
|
66
|
+
if unknown.any? && RatatuiRuby::Debug.enabled?
|
|
67
|
+
raise ArgumentError, "#{name}: unknown keys #{unknown.inspect}"
|
|
68
|
+
end
|
|
69
|
+
new(**first.slice(*self::KNOWN_KEYS))
|
|
70
|
+
else
|
|
71
|
+
new(**kwargs)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
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 Widgets
|
|
10
|
+
# Controls the terminal cursor position.
|
|
11
|
+
#
|
|
12
|
+
# Interfaces are not just output; they require input. Users need a visual cue—a blinking block or line—to know where their keystrokes will appear.
|
|
13
|
+
#
|
|
14
|
+
# This widget renders a ghost. It does not draw a character but instructs the terminal to place the hardware cursor at specific coordinates.
|
|
15
|
+
#
|
|
16
|
+
# Use it for text editors, input fields, or command prompts.
|
|
17
|
+
#
|
|
18
|
+
# === Examples
|
|
19
|
+
#
|
|
20
|
+
#--
|
|
21
|
+
# SPDX-SnippetBegin
|
|
22
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
23
|
+
# SPDX-License-Identifier: MIT-0
|
|
24
|
+
#++
|
|
25
|
+
# Cursor.new(x: 10, y: 5)
|
|
26
|
+
#
|
|
27
|
+
#--
|
|
28
|
+
# SPDX-SnippetEnd
|
|
29
|
+
#++
|
|
30
|
+
# See also:
|
|
31
|
+
# - {Declarative implementation using Tree API}[link:/examples/app_login_form/app_rb.html]
|
|
32
|
+
# - {Component-based implementation using Frame API}[link:/examples/app_color_picker/app_rb.html]
|
|
33
|
+
# - RatatuiRuby::Frame#set_cursor_position (Frame API alternative)
|
|
34
|
+
class Cursor < Data.define(:x, :y)
|
|
35
|
+
include CoerceableWidget
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# :attr_reader: x
|
|
39
|
+
# X coordinate (column).
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# :attr_reader: y
|
|
43
|
+
# Y coordinate (row).
|
|
44
|
+
|
|
45
|
+
# Creates a new Cursor.
|
|
46
|
+
#
|
|
47
|
+
# [x] Integer.
|
|
48
|
+
# [y] Integer.
|
|
49
|
+
def initialize(x:, y:)
|
|
50
|
+
super(x: Integer(x), y: Integer(y))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
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 Widgets
|
|
10
|
+
# Displays a standard progress bar.
|
|
11
|
+
#
|
|
12
|
+
# Long-running tasks create anxiety. Users need to know that the system is working and how much is left to do.
|
|
13
|
+
#
|
|
14
|
+
# This widget visualizes completion. It fills a bar based on a percentage.
|
|
15
|
+
#
|
|
16
|
+
# Use it for downloads, installations, or processing jobs.
|
|
17
|
+
#
|
|
18
|
+
# {rdoc-image:/doc/images/widget_gauge.png}[link:/examples/widget_gauge/app_rb.html]
|
|
19
|
+
#
|
|
20
|
+
# === Example
|
|
21
|
+
#
|
|
22
|
+
# Run the interactive demo from the terminal:
|
|
23
|
+
#
|
|
24
|
+
# ruby examples/widget_gauge/app.rb
|
|
25
|
+
class Gauge < Data.define(:ratio, :label, :style, :gauge_style, :block, :use_unicode)
|
|
26
|
+
include CoerceableWidget
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# :attr_reader: ratio
|
|
30
|
+
# Progress ratio from 0.0 to 1.0.
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# :attr_reader: label
|
|
34
|
+
# Text label to display (optional).
|
|
35
|
+
#
|
|
36
|
+
# Accepts String or Text::Span for rich styling.
|
|
37
|
+
#
|
|
38
|
+
# If nil, it often displays the percentage automatically depending on renderer logic,
|
|
39
|
+
# but explicit labels are preferred.
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# :attr_reader: style
|
|
43
|
+
# Base style applied to the entire gauge background (optional).
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# :attr_reader: gauge_style
|
|
47
|
+
# Style applied specifically to the filled bar portion (optional).
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# :attr_reader: block
|
|
51
|
+
# Optional wrapping block.
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# :attr_reader: use_unicode
|
|
55
|
+
# Whether to use Unicode block characters (true) or ASCII characters (false).
|
|
56
|
+
# Default is false (ASCII) to be conservative, though Ratatui defaults to true.
|
|
57
|
+
|
|
58
|
+
# Creates a new Gauge.
|
|
59
|
+
#
|
|
60
|
+
# [ratio] Float (0.0 - 1.0).
|
|
61
|
+
# [percent] Integer (0 - 100), alternative to ratio.
|
|
62
|
+
# [label] String or Text::Span (optional).
|
|
63
|
+
# [style] Style object for the background (optional).
|
|
64
|
+
# [gauge_style] Style object for the filled bar (optional).
|
|
65
|
+
# [block] Block widget (optional).
|
|
66
|
+
# [use_unicode] Boolean (default: true).
|
|
67
|
+
#
|
|
68
|
+
# Raises ArgumentError if percent is not 0..100.
|
|
69
|
+
def initialize(ratio: nil, percent: nil, label: nil, style: nil, gauge_style: nil, block: nil, use_unicode: true)
|
|
70
|
+
if percent
|
|
71
|
+
float_percent = Float(percent)
|
|
72
|
+
unless float_percent.between?(0, 100)
|
|
73
|
+
raise ArgumentError, "percent must be between 0 and 100 (got #{percent.inspect})"
|
|
74
|
+
end
|
|
75
|
+
# Float(Numeric) incorrectly returns Float? -- https://github.com/ruby/rbs/issues/2793
|
|
76
|
+
ratio = float_percent / 100.0 #: Float
|
|
77
|
+
end
|
|
78
|
+
ratio = Float(ratio || 0.0)
|
|
79
|
+
super(ratio:, label:, style:, gauge_style:, block:, use_unicode:)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns true if the gauge has any fill (ratio > 0).
|
|
83
|
+
#
|
|
84
|
+
# === Example
|
|
85
|
+
#
|
|
86
|
+
#--
|
|
87
|
+
# SPDX-SnippetBegin
|
|
88
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
89
|
+
# SPDX-License-Identifier: MIT-0
|
|
90
|
+
#++
|
|
91
|
+
# Widgets::Gauge.new(ratio: 0.0).filled? # => false
|
|
92
|
+
# Widgets::Gauge.new(ratio: 0.5).filled? # => true
|
|
93
|
+
#--
|
|
94
|
+
# SPDX-SnippetEnd
|
|
95
|
+
#++
|
|
96
|
+
def filled?
|
|
97
|
+
ratio > 0
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns true if the gauge is at 100% or more (ratio >= 1.0).
|
|
101
|
+
#
|
|
102
|
+
# === Example
|
|
103
|
+
#
|
|
104
|
+
#--
|
|
105
|
+
# SPDX-SnippetBegin
|
|
106
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
107
|
+
# SPDX-License-Identifier: MIT-0
|
|
108
|
+
#++
|
|
109
|
+
# Widgets::Gauge.new(ratio: 0.99).complete? # => false
|
|
110
|
+
# Widgets::Gauge.new(ratio: 1.0).complete? # => true
|
|
111
|
+
#--
|
|
112
|
+
# SPDX-SnippetEnd
|
|
113
|
+
#++
|
|
114
|
+
def complete?
|
|
115
|
+
ratio >= 1.0
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Returns the progress as an integer percentage (0-100).
|
|
119
|
+
#
|
|
120
|
+
# Gauge stores progress as a ratio (0.0 to 1.0). User-facing code often
|
|
121
|
+
# displays percentages. Converting manually is tedious.
|
|
122
|
+
#
|
|
123
|
+
# This is the inverse of passing <tt>percent:</tt> to the constructor.
|
|
124
|
+
# Rounds down to the nearest integer.
|
|
125
|
+
#
|
|
126
|
+
# === Example
|
|
127
|
+
#
|
|
128
|
+
#--
|
|
129
|
+
# SPDX-SnippetBegin
|
|
130
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
131
|
+
# SPDX-License-Identifier: MIT-0
|
|
132
|
+
#++
|
|
133
|
+
# gauge = Widgets::Gauge.new(percent: 75)
|
|
134
|
+
# gauge.percent # => 75
|
|
135
|
+
#
|
|
136
|
+
# gauge = Widgets::Gauge.new(ratio: 0.456)
|
|
137
|
+
# gauge.percent # => 45
|
|
138
|
+
#--
|
|
139
|
+
# SPDX-SnippetEnd
|
|
140
|
+
#++
|
|
141
|
+
def percent
|
|
142
|
+
(ratio * 100).to_i
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
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 Widgets
|
|
10
|
+
# Displays a compact, single-line progress bar.
|
|
11
|
+
#
|
|
12
|
+
# Screen space is precious. Standard block gauges are bulky and consume multiple rows.
|
|
13
|
+
#
|
|
14
|
+
# This widget compresses the feedback. It draws a progress bar using line characters, fitting perfectly into tight layouts or lists.
|
|
15
|
+
#
|
|
16
|
+
# Use it when you need to show status without stealing focus or space.
|
|
17
|
+
#
|
|
18
|
+
# {rdoc-image:/doc/images/widget_line_gauge.png}[link:/examples/widget_line_gauge/app_rb.html]
|
|
19
|
+
#
|
|
20
|
+
# === Example
|
|
21
|
+
#
|
|
22
|
+
# Run the interactive demo from the terminal:
|
|
23
|
+
#
|
|
24
|
+
# ruby examples/widget_line_gauge/app.rb
|
|
25
|
+
class LineGauge < Data.define(:ratio, :label, :style, :filled_style, :unfilled_style, :block, :filled_symbol, :unfilled_symbol)
|
|
26
|
+
include CoerceableWidget
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# :attr_reader: ratio
|
|
30
|
+
# Progress ratio from 0.0 to 1.0.
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# :attr_reader: label
|
|
34
|
+
# Optional label (String or Text::Span for rich styling).
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# :attr_reader: style
|
|
38
|
+
# Base style applied to the entire gauge.
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# :attr_reader: filled_style
|
|
42
|
+
# Style for the completed portion.
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# :attr_reader: unfilled_style
|
|
46
|
+
# Style for the remainder.
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# :attr_reader: block
|
|
50
|
+
# Optional wrapping block.
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# :attr_reader: filled_symbol
|
|
54
|
+
# Character for filled segments.
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# :attr_reader: unfilled_symbol
|
|
58
|
+
# Character for empty segments.
|
|
59
|
+
|
|
60
|
+
# Creates a new LineGauge.
|
|
61
|
+
#
|
|
62
|
+
# [ratio] Float (0.0 - 1.0).
|
|
63
|
+
# [percent] Integer (0 - 100), alternative to ratio.
|
|
64
|
+
# [label] String or Text::Span (optional).
|
|
65
|
+
# [style] Style (optional, base style for the gauge).
|
|
66
|
+
# [filled_style] Style.
|
|
67
|
+
# [unfilled_style] Style.
|
|
68
|
+
# [block] Block.
|
|
69
|
+
# [filled_symbol] String (default: <tt>"█"</tt>).
|
|
70
|
+
# [unfilled_symbol] String (default: <tt>"░"</tt>).
|
|
71
|
+
#
|
|
72
|
+
# Raises ArgumentError if percent is not 0..100.
|
|
73
|
+
def initialize(ratio: nil, percent: nil, label: nil, style: nil, filled_style: nil, unfilled_style: nil, block: nil, filled_symbol: "█", unfilled_symbol: "░")
|
|
74
|
+
if percent
|
|
75
|
+
float_percent = Float(percent)
|
|
76
|
+
unless float_percent.between?(0, 100)
|
|
77
|
+
raise ArgumentError, "percent must be between 0 and 100 (got #{percent.inspect})"
|
|
78
|
+
end
|
|
79
|
+
ratio = float_percent / 100.0
|
|
80
|
+
end
|
|
81
|
+
ratio = Float(ratio || 0.0)
|
|
82
|
+
super(
|
|
83
|
+
ratio:,
|
|
84
|
+
label:,
|
|
85
|
+
style:,
|
|
86
|
+
filled_style:,
|
|
87
|
+
unfilled_style:,
|
|
88
|
+
block:,
|
|
89
|
+
filled_symbol:,
|
|
90
|
+
unfilled_symbol:
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns true if the gauge has any fill (ratio > 0).
|
|
95
|
+
#
|
|
96
|
+
# === Example
|
|
97
|
+
#
|
|
98
|
+
#--
|
|
99
|
+
# SPDX-SnippetBegin
|
|
100
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
101
|
+
# SPDX-License-Identifier: MIT-0
|
|
102
|
+
#++
|
|
103
|
+
# Widgets::LineGauge.new(ratio: 0.0).filled? # => false
|
|
104
|
+
# Widgets::LineGauge.new(ratio: 0.5).filled? # => true
|
|
105
|
+
#--
|
|
106
|
+
# SPDX-SnippetEnd
|
|
107
|
+
#++
|
|
108
|
+
def filled?
|
|
109
|
+
ratio > 0
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns true if the gauge is at 100% or more (ratio >= 1.0).
|
|
113
|
+
#
|
|
114
|
+
# === Example
|
|
115
|
+
#
|
|
116
|
+
#--
|
|
117
|
+
# SPDX-SnippetBegin
|
|
118
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
119
|
+
# SPDX-License-Identifier: MIT-0
|
|
120
|
+
#++
|
|
121
|
+
# Widgets::LineGauge.new(ratio: 0.99).complete? # => false
|
|
122
|
+
# Widgets::LineGauge.new(ratio: 1.0).complete? # => true
|
|
123
|
+
#--
|
|
124
|
+
# SPDX-SnippetEnd
|
|
125
|
+
#++
|
|
126
|
+
def complete?
|
|
127
|
+
ratio >= 1.0
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Returns the progress as an integer percentage (0-100).
|
|
131
|
+
#
|
|
132
|
+
# LineGauge stores progress as a ratio (0.0 to 1.0). User-facing code often
|
|
133
|
+
# displays percentages. Converting manually is tedious.
|
|
134
|
+
#
|
|
135
|
+
# This is the inverse of passing <tt>percent:</tt> to the constructor.
|
|
136
|
+
# Rounds down to the nearest integer.
|
|
137
|
+
#
|
|
138
|
+
# === Example
|
|
139
|
+
#
|
|
140
|
+
#--
|
|
141
|
+
# SPDX-SnippetBegin
|
|
142
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
143
|
+
# SPDX-License-Identifier: MIT-0
|
|
144
|
+
#++
|
|
145
|
+
# lg = Widgets::LineGauge.new(percent: 75)
|
|
146
|
+
# lg.percent # => 75
|
|
147
|
+
#
|
|
148
|
+
# lg = Widgets::LineGauge.new(ratio: 0.456)
|
|
149
|
+
# lg.percent # => 45
|
|
150
|
+
#--
|
|
151
|
+
# SPDX-SnippetEnd
|
|
152
|
+
#++
|
|
153
|
+
def percent
|
|
154
|
+
(ratio * 100).to_i
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,252 @@
|
|
|
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 Widgets
|
|
10
|
+
# Displays a selectable list of items.
|
|
11
|
+
#
|
|
12
|
+
# Users need to choose from options. Menus, file explorers, and selectors are everywhere.
|
|
13
|
+
# Implementing navigation, highlighting, and scrolling state from scratch is tedious.
|
|
14
|
+
#
|
|
15
|
+
# This widget manages the list. It renders the items. It highlights the selection. It handles the scrolling window.
|
|
16
|
+
#
|
|
17
|
+
# Use it to build main menus, navigation sidebars, or logs.
|
|
18
|
+
#
|
|
19
|
+
# {rdoc-image:/doc/images/widget_list.png}[link:/examples/widget_list/app_rb.html]
|
|
20
|
+
#
|
|
21
|
+
# === Examples
|
|
22
|
+
#
|
|
23
|
+
#--
|
|
24
|
+
# SPDX-SnippetBegin
|
|
25
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
26
|
+
# SPDX-License-Identifier: MIT-0
|
|
27
|
+
#++
|
|
28
|
+
# # Basic List
|
|
29
|
+
# List.new(items: ["Item 1", "Item 2"])
|
|
30
|
+
#
|
|
31
|
+
# # Navigation Menu
|
|
32
|
+
# List.new(
|
|
33
|
+
# items: ["New Game", "Load Game", "Options", "Quit"],
|
|
34
|
+
# selected_index: 0,
|
|
35
|
+
# highlight_style: Style.new(bg: :blue),
|
|
36
|
+
# highlight_symbol: ">> "
|
|
37
|
+
# )
|
|
38
|
+
#--
|
|
39
|
+
# SPDX-SnippetEnd
|
|
40
|
+
#++
|
|
41
|
+
class List < Data.define(:items, :selected_index, :offset, :style, :highlight_style, :highlight_symbol, :repeat_highlight_symbol, :highlight_spacing, :direction, :scroll_padding, :block)
|
|
42
|
+
include CoerceableWidget
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Highlight spacing: always show the spacing column.
|
|
46
|
+
HIGHLIGHT_ALWAYS = :always
|
|
47
|
+
##
|
|
48
|
+
# Highlight spacing: show spacing only when an item is selected (default).
|
|
49
|
+
HIGHLIGHT_WHEN_SELECTED = :when_selected
|
|
50
|
+
##
|
|
51
|
+
# Highlight spacing: never show the spacing column.
|
|
52
|
+
HIGHLIGHT_NEVER = :never
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# Direction: render items from top to bottom (default).
|
|
56
|
+
DIRECTION_TOP_TO_BOTTOM = :top_to_bottom
|
|
57
|
+
##
|
|
58
|
+
# Direction: render items from bottom to top.
|
|
59
|
+
DIRECTION_BOTTOM_TO_TOP = :bottom_to_top
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# :attr_reader: items
|
|
63
|
+
# The items to display.
|
|
64
|
+
#
|
|
65
|
+
# Accepts Array of Strings, Text::Spans, Text::Lines, or ListItem objects.
|
|
66
|
+
# For styled individual rows, use ListItem with a style.
|
|
67
|
+
|
|
68
|
+
##
|
|
69
|
+
# :attr_reader: selected_index
|
|
70
|
+
# Index of the active selection (Integer or nil).
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# :attr_reader: offset
|
|
74
|
+
# Scroll offset (Integer or nil).
|
|
75
|
+
#
|
|
76
|
+
# Controls the viewport's starting position in the list.
|
|
77
|
+
#
|
|
78
|
+
# When +nil+ (default), Ratatui auto-scrolls to keep the selection visible ("natural scrolling").
|
|
79
|
+
#
|
|
80
|
+
# When set, forces the viewport to start at this item index. Use this for:
|
|
81
|
+
# - **Passive scrolling**: Scroll through a log viewer without selecting items.
|
|
82
|
+
# - **Click-to-select math**: Calculate which item index corresponds to a click coordinate.
|
|
83
|
+
#
|
|
84
|
+
# *Important*: When both +offset+ and +selected_index+ are set, Ratatui may still adjust
|
|
85
|
+
# the viewport during rendering to ensure the selection stays visible. Set +selected_index+
|
|
86
|
+
# to +nil+ for fully manual scroll control.
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
# :attr_reader: style
|
|
90
|
+
# Base style for unselected items.
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# :attr_reader: highlight_style
|
|
94
|
+
# Style for the selected item.
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
# :attr_reader: highlight_symbol
|
|
98
|
+
# Symbol drawn before the selected item.
|
|
99
|
+
|
|
100
|
+
##
|
|
101
|
+
# :attr_reader: repeat_highlight_symbol
|
|
102
|
+
# Whether to repeat the highlight symbol for each line of the selected item.
|
|
103
|
+
|
|
104
|
+
##
|
|
105
|
+
# :attr_reader: highlight_spacing
|
|
106
|
+
# When to show the highlight symbol column.
|
|
107
|
+
#
|
|
108
|
+
# <tt>:always</tt>, <tt>:when_selected</tt>, or <tt>:never</tt>.
|
|
109
|
+
|
|
110
|
+
##
|
|
111
|
+
# :attr_reader: direction
|
|
112
|
+
# Render direction.
|
|
113
|
+
#
|
|
114
|
+
# <tt>:top_to_bottom</tt> or <tt>:bottom_to_top</tt>.
|
|
115
|
+
|
|
116
|
+
##
|
|
117
|
+
# :attr_reader: scroll_padding
|
|
118
|
+
# Number of items to keep visible above/below the selected item when scrolling (Integer or nil).
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
# :attr_reader: block
|
|
122
|
+
# Optional wrapping block.
|
|
123
|
+
|
|
124
|
+
# Creates a new List.
|
|
125
|
+
#
|
|
126
|
+
# Integer parameters accept any object responding to +to_int+ or +to_i+ (duck-typed).
|
|
127
|
+
#
|
|
128
|
+
# [items] Array of Strings, Text::Spans, Text::Lines, or ListItem objects.
|
|
129
|
+
# [selected_index] Numeric (nullable, coerced to Integer).
|
|
130
|
+
# [offset] Numeric (nullable, coerced to Integer). Forces scroll position when set.
|
|
131
|
+
# [style] Style object.
|
|
132
|
+
# [highlight_style] Style object.
|
|
133
|
+
# [highlight_symbol] String (default: <tt>"> "</tt>).
|
|
134
|
+
# [repeat_highlight_symbol] Boolean (default: <tt>false</tt>).
|
|
135
|
+
# [highlight_spacing] Symbol (default: <tt>:when_selected</tt>).
|
|
136
|
+
# [direction] Symbol (default: <tt>:top_to_bottom</tt>).
|
|
137
|
+
# [scroll_padding] Numeric (nullable, coerced to Integer, default: <tt>nil</tt>).
|
|
138
|
+
# [block] Block (optional).
|
|
139
|
+
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)
|
|
140
|
+
super(
|
|
141
|
+
items:,
|
|
142
|
+
selected_index: selected_index.nil? ? nil : Integer(selected_index),
|
|
143
|
+
offset: offset.nil? ? nil : Integer(offset),
|
|
144
|
+
style:,
|
|
145
|
+
highlight_style:,
|
|
146
|
+
highlight_symbol:,
|
|
147
|
+
repeat_highlight_symbol:,
|
|
148
|
+
highlight_spacing:,
|
|
149
|
+
direction:,
|
|
150
|
+
scroll_padding: scroll_padding.nil? ? nil : Integer(scroll_padding),
|
|
151
|
+
block:
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Returns true if an item is selected.
|
|
156
|
+
#
|
|
157
|
+
# === Example
|
|
158
|
+
#
|
|
159
|
+
#--
|
|
160
|
+
# SPDX-SnippetBegin
|
|
161
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
162
|
+
# SPDX-License-Identifier: MIT-0
|
|
163
|
+
#++
|
|
164
|
+
# list = Widgets::List.new(items: %w[a b c])
|
|
165
|
+
# list.selected? # => false
|
|
166
|
+
#
|
|
167
|
+
# list = Widgets::List.new(items: %w[a b c], selected_index: 1)
|
|
168
|
+
# list.selected? # => true
|
|
169
|
+
#
|
|
170
|
+
#--
|
|
171
|
+
# SPDX-SnippetEnd
|
|
172
|
+
#++
|
|
173
|
+
# Returns: Boolean.
|
|
174
|
+
def selected?
|
|
175
|
+
!selected_index.nil?
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Returns true if the list contains no items.
|
|
179
|
+
#
|
|
180
|
+
# === Example
|
|
181
|
+
#
|
|
182
|
+
#--
|
|
183
|
+
# SPDX-SnippetBegin
|
|
184
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
185
|
+
# SPDX-License-Identifier: MIT-0
|
|
186
|
+
#++
|
|
187
|
+
# list = Widgets::List.new(items: [])
|
|
188
|
+
# list.empty? # => true
|
|
189
|
+
#
|
|
190
|
+
#--
|
|
191
|
+
# SPDX-SnippetEnd
|
|
192
|
+
#++
|
|
193
|
+
# Returns: Boolean.
|
|
194
|
+
def empty?
|
|
195
|
+
items.empty?
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Returns the number of items in the list.
|
|
199
|
+
#
|
|
200
|
+
# === Example
|
|
201
|
+
#
|
|
202
|
+
#--
|
|
203
|
+
# SPDX-SnippetBegin
|
|
204
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
205
|
+
# SPDX-License-Identifier: MIT-0
|
|
206
|
+
#++
|
|
207
|
+
# list = Widgets::List.new(items: %w[alpha beta gamma])
|
|
208
|
+
# list.len # => 3
|
|
209
|
+
#
|
|
210
|
+
#--
|
|
211
|
+
# SPDX-SnippetEnd
|
|
212
|
+
#++
|
|
213
|
+
# Returns: Integer.
|
|
214
|
+
def len
|
|
215
|
+
items.length
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
alias length len
|
|
219
|
+
alias size len
|
|
220
|
+
|
|
221
|
+
# NOTE: No 'selection' alias - it's ambiguous whether it returns an item or index.
|
|
222
|
+
# Use selected_index for the index, selected_item for the item.
|
|
223
|
+
|
|
224
|
+
# Returns the currently selected item, or nil if nothing is selected.
|
|
225
|
+
#
|
|
226
|
+
# Accessing the selected item directly requires looking up +items[selected_index]+
|
|
227
|
+
# after checking that +selected_index+ is not nil. This is verbose.
|
|
228
|
+
#
|
|
229
|
+
# This method encapsulates that pattern.
|
|
230
|
+
#
|
|
231
|
+
# === Example
|
|
232
|
+
#
|
|
233
|
+
#--
|
|
234
|
+
# SPDX-SnippetBegin
|
|
235
|
+
# SPDX-FileCopyrightText: 2026 Kerrick Long
|
|
236
|
+
# SPDX-License-Identifier: MIT-0
|
|
237
|
+
#++
|
|
238
|
+
# list = Widgets::List.new(items: %w[alpha beta gamma], selected_index: 1)
|
|
239
|
+
# list.selected_item # => "beta"
|
|
240
|
+
#
|
|
241
|
+
#--
|
|
242
|
+
# SPDX-SnippetEnd
|
|
243
|
+
#++
|
|
244
|
+
# Returns: The item at the selected index, or nil if no selection.
|
|
245
|
+
def selected_item
|
|
246
|
+
return nil if selected_index.nil?
|
|
247
|
+
|
|
248
|
+
items[selected_index]
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|