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,95 @@
|
|
|
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
|
+
class Event
|
|
8
|
+
class Key < Event
|
|
9
|
+
# Methods and logic for modifier keys.
|
|
10
|
+
module Modifier
|
|
11
|
+
# Returns true if CTRL is held OR if this is a left_control/right_control key event.
|
|
12
|
+
def ctrl?
|
|
13
|
+
@modifiers.include?("ctrl") || @code == "left_control" || @code == "right_control"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Alias for {#ctrl?}.
|
|
17
|
+
alias control? ctrl?
|
|
18
|
+
|
|
19
|
+
# Returns true if ALT is held OR if this is a left_alt/right_alt key event.
|
|
20
|
+
def alt?
|
|
21
|
+
@modifiers.include?("alt") || @code == "left_alt" || @code == "right_alt"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Alias for {#alt?}.
|
|
25
|
+
alias option? alt?
|
|
26
|
+
|
|
27
|
+
# Returns true if SHIFT is held OR if this is a left_shift/right_shift key event.
|
|
28
|
+
def shift?
|
|
29
|
+
@modifiers.include?("shift") || @code == "left_shift" || @code == "right_shift"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns true if SUPER is held OR if this is a left_super/right_super key event.
|
|
33
|
+
# Also responds to platform aliases: win?, command?, cmd?, tux?
|
|
34
|
+
def super?
|
|
35
|
+
@modifiers.include?("super") || @code == "left_super" || @code == "right_super"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Alias for {#super?}.
|
|
39
|
+
alias win? super?
|
|
40
|
+
# Alias for {#super?}.
|
|
41
|
+
alias command? super?
|
|
42
|
+
# Alias for {#super?}.
|
|
43
|
+
alias cmd? super?
|
|
44
|
+
# Alias for {#super?}.
|
|
45
|
+
alias tux? super?
|
|
46
|
+
|
|
47
|
+
# Returns true if HYPER is held OR if this is a left_hyper/right_hyper key event.
|
|
48
|
+
def hyper?
|
|
49
|
+
@modifiers.include?("hyper") || @code == "left_hyper" || @code == "right_hyper"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns true if META is held OR if this is a left_meta/right_meta key event.
|
|
53
|
+
def meta?
|
|
54
|
+
@modifiers.include?("meta") || @code == "left_meta" || @code == "right_meta"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns true if this is a modifier key event.
|
|
58
|
+
#
|
|
59
|
+
# Some applications need to know if an event represents a generic key
|
|
60
|
+
# press or a specific modifier key (like CTRL or ALT) being pressed on
|
|
61
|
+
# its own.
|
|
62
|
+
#
|
|
63
|
+
# This method identifies if the key event itself is a modifier key.
|
|
64
|
+
#
|
|
65
|
+
# === Example
|
|
66
|
+
#
|
|
67
|
+
# if event.modifier?
|
|
68
|
+
# # Handle solo modifier key press
|
|
69
|
+
# end
|
|
70
|
+
def modifier?
|
|
71
|
+
@kind == :modifier
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Handles modifier-specific DWIM logic for method_missing.
|
|
75
|
+
private def match_modifier_dwim?(key_name, key_sym)
|
|
76
|
+
# Platform modifier aliases
|
|
77
|
+
modifier_aliases = {
|
|
78
|
+
win: "super",
|
|
79
|
+
command: "super",
|
|
80
|
+
cmd: "super",
|
|
81
|
+
tux: "super",
|
|
82
|
+
}.freeze
|
|
83
|
+
|
|
84
|
+
target_modifier = modifier_aliases[key_sym]
|
|
85
|
+
if target_modifier
|
|
86
|
+
return true if @modifiers.include?(target_modifier)
|
|
87
|
+
return true if @code == "left_#{target_modifier}" || @code == "right_#{target_modifier}"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
false
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
class Event
|
|
8
|
+
class Key < Event
|
|
9
|
+
# Methods and logic for navigation keys.
|
|
10
|
+
module Navigation
|
|
11
|
+
# Returns true if this is a standard key.
|
|
12
|
+
#
|
|
13
|
+
# Standard keys include: characters, Enter, Tab, arrow keys, navigation keys.
|
|
14
|
+
#
|
|
15
|
+
# event.standard? # => true for "a", "enter", "up", etc.
|
|
16
|
+
def standard?
|
|
17
|
+
@kind == :standard
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Alias for {#standard?}.
|
|
21
|
+
#
|
|
22
|
+
# Provided for semantic clarity when checking if a key has no special category.
|
|
23
|
+
#
|
|
24
|
+
# event.unmodified? # => true for standard keys like "a", "enter", "up"
|
|
25
|
+
alias unmodified? standard?
|
|
26
|
+
|
|
27
|
+
# Handles navigation-specific DWIM logic for method_missing.
|
|
28
|
+
private def match_navigation_dwim?(key_name, key_sym)
|
|
29
|
+
# DWIM: reverse_tab? matches both BackTab key and Shift+Tab combo
|
|
30
|
+
if key_name == "reverse_tab"
|
|
31
|
+
return true if @code == "back_tab"
|
|
32
|
+
return true if @code == "tab" && @modifiers.include?("shift")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# DWIM: Check explicit aliases
|
|
36
|
+
navigation_aliases = {
|
|
37
|
+
return: "enter",
|
|
38
|
+
back: "backspace",
|
|
39
|
+
del: "delete",
|
|
40
|
+
ins: "insert",
|
|
41
|
+
pgup: "page_up",
|
|
42
|
+
pageup: "page_up",
|
|
43
|
+
pgdn: "page_down",
|
|
44
|
+
pagedown: "page_down",
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
target_code = navigation_aliases[key_sym]
|
|
48
|
+
return true if target_code && @code == target_code && @modifiers.empty?
|
|
49
|
+
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
class Event
|
|
8
|
+
class Key < Event
|
|
9
|
+
# Methods and logic for system and function keys.
|
|
10
|
+
module System
|
|
11
|
+
# Returns true if this is a system key.
|
|
12
|
+
#
|
|
13
|
+
# System keys include: Esc, CapsLock, ScrollLock, NumLock, PrintScreen, Pause, Menu, KeypadBegin.
|
|
14
|
+
#
|
|
15
|
+
# event.system? # => true for pause, esc, caps_lock, etc.
|
|
16
|
+
def system?
|
|
17
|
+
@kind == :system
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns true if this is a function key (F1-F24).
|
|
21
|
+
#
|
|
22
|
+
# event.function? # => true for f1, f2, ..., f24
|
|
23
|
+
def function?
|
|
24
|
+
@kind == :function
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Handles system-specific DWIM logic for method_missing.
|
|
28
|
+
private def match_system_dwim?(key_name, key_sym)
|
|
29
|
+
system_aliases = {
|
|
30
|
+
scrlk: "scroll_lock",
|
|
31
|
+
scroll: "scroll_lock",
|
|
32
|
+
prtsc: "print_screen",
|
|
33
|
+
print: "print_screen",
|
|
34
|
+
escape: "esc",
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
target_code = system_aliases[key_sym]
|
|
38
|
+
return true if target_code && @code == target_code && @modifiers.empty?
|
|
39
|
+
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
4
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
|
|
6
|
+
require_relative "key/character"
|
|
7
|
+
require_relative "key/media"
|
|
8
|
+
require_relative "key/modifier"
|
|
9
|
+
require_relative "key/navigation"
|
|
10
|
+
require_relative "key/system"
|
|
11
|
+
|
|
6
12
|
module RatatuiRuby
|
|
7
13
|
class Event
|
|
8
14
|
# Captures a keyboard interaction.
|
|
@@ -33,7 +39,34 @@ module RatatuiRuby
|
|
|
33
39
|
# in type: :key, code: "c", modifiers: ["ctrl"]
|
|
34
40
|
# exit
|
|
35
41
|
# end
|
|
42
|
+
#
|
|
43
|
+
# === Terminal Compatibility
|
|
44
|
+
#
|
|
45
|
+
# Some key combinations never reach your application. Terminal emulators intercept them for
|
|
46
|
+
# built-in features like tab switching. Common culprits:
|
|
47
|
+
#
|
|
48
|
+
# * Ctrl+PageUp/PageDown (tab switching in Terminal.app, iTerm2)
|
|
49
|
+
# * Ctrl+Tab (tab switching)
|
|
50
|
+
# * Cmd+key combinations (macOS system shortcuts)
|
|
51
|
+
#
|
|
52
|
+
# If modifiers appear missing, test with a different terminal. Kitty, WezTerm, and Alacritty
|
|
53
|
+
# pass more keys through. See <tt>doc/terminal_limitations.md</tt> for details.
|
|
54
|
+
#
|
|
55
|
+
# === Enhanced Keys (Kitty Protocol)
|
|
56
|
+
#
|
|
57
|
+
# Terminals supporting the Kitty keyboard protocol report additional keys:
|
|
58
|
+
#
|
|
59
|
+
# * Media keys: <tt>:play</tt>, <tt>:play_pause</tt>, <tt>:track_next</tt>, <tt>:mute_volume</tt>
|
|
60
|
+
# * Individual modifiers: <tt>:left_shift</tt>, <tt>:right_control</tt>, <tt>:left_super</tt>
|
|
61
|
+
#
|
|
62
|
+
# These keys will not work in Terminal.app, iTerm2, or GNOME Terminal.
|
|
36
63
|
class Key < Event
|
|
64
|
+
include Character
|
|
65
|
+
include Media
|
|
66
|
+
include Modifier
|
|
67
|
+
include Navigation
|
|
68
|
+
include System
|
|
69
|
+
|
|
37
70
|
# The key code (e.g., <tt>"a"</tt>, <tt>"enter"</tt>, <tt>"up"</tt>).
|
|
38
71
|
#
|
|
39
72
|
# puts event.code # => "enter"
|
|
@@ -44,6 +77,15 @@ module RatatuiRuby
|
|
|
44
77
|
# puts event.modifiers # => ["ctrl", "shift"]
|
|
45
78
|
attr_reader :modifiers
|
|
46
79
|
|
|
80
|
+
# The category of the key.
|
|
81
|
+
#
|
|
82
|
+
# One of: <tt>:standard</tt>, <tt>:function</tt>, <tt>:media</tt>, <tt>:modifier</tt>, <tt>:system</tt>.
|
|
83
|
+
#
|
|
84
|
+
# This allows grouping keys by their logical type without parsing the code string.
|
|
85
|
+
#
|
|
86
|
+
# event.kind # => :media
|
|
87
|
+
attr_reader :kind
|
|
88
|
+
|
|
47
89
|
# Returns true for Key events.
|
|
48
90
|
#
|
|
49
91
|
# event.key? # => true
|
|
@@ -59,17 +101,21 @@ module RatatuiRuby
|
|
|
59
101
|
# The key code (String).
|
|
60
102
|
# [modifiers]
|
|
61
103
|
# List of modifiers (Array<String>).
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
104
|
+
# [kind]
|
|
105
|
+
# The key category (Symbol). One of: <tt>:standard</tt>, <tt>:function</tt>,
|
|
106
|
+
# <tt>:media</tt>, <tt>:modifier</tt>, <tt>:system</tt>. Defaults to <tt>:standard</tt>.
|
|
107
|
+
def initialize(code:, modifiers: [], kind: :standard)
|
|
108
|
+
@code = code.freeze
|
|
109
|
+
@modifiers = modifiers.map(&:freeze).sort.freeze
|
|
110
|
+
@kind = kind
|
|
65
111
|
end
|
|
66
112
|
|
|
67
113
|
# Compares the event with another object.
|
|
68
114
|
#
|
|
69
115
|
# - If +other+ is a +Symbol+, compares against #to_sym.
|
|
70
116
|
# - If +other+ is a +String+, compares against #to_s.
|
|
71
|
-
# -
|
|
72
|
-
# - Otherwise, compares
|
|
117
|
+
# - If +other+ is a +Key+, compares as a value object.
|
|
118
|
+
# - Otherwise, compares using standard equality.
|
|
73
119
|
def ==(other)
|
|
74
120
|
case other
|
|
75
121
|
when Symbol
|
|
@@ -91,9 +137,25 @@ module RatatuiRuby
|
|
|
91
137
|
# === Supported Keys
|
|
92
138
|
#
|
|
93
139
|
# [Standard]
|
|
94
|
-
# <tt>:enter</tt>, <tt>:backspace</tt>, <tt>:tab</tt>, <tt>:
|
|
140
|
+
# <tt>:enter</tt>, <tt>:backspace</tt>, <tt>:tab</tt>, <tt>:back_tab</tt>, <tt>:esc</tt>, <tt>:null</tt>
|
|
95
141
|
# [Navigation]
|
|
96
|
-
# <tt>:up</tt>, <tt>:down</tt>, <tt>:left</tt>, <tt>:right</tt
|
|
142
|
+
# <tt>:up</tt>, <tt>:down</tt>, <tt>:left</tt>, <tt>:right</tt>, <tt>:home</tt>, <tt>:end</tt>,
|
|
143
|
+
# <tt>:page_up</tt>, <tt>:page_down</tt>, <tt>:insert</tt>, <tt>:delete</tt>
|
|
144
|
+
# [Function Keys]
|
|
145
|
+
# <tt>:f1</tt> through <tt>:f12</tt> (and beyond, e.g. <tt>:f24</tt>)
|
|
146
|
+
# [Lock Keys]
|
|
147
|
+
# <tt>:caps_lock</tt>, <tt>:scroll_lock</tt>, <tt>:num_lock</tt>
|
|
148
|
+
# [System Keys]
|
|
149
|
+
# <tt>:print_screen</tt>, <tt>:pause</tt>, <tt>:menu</tt>, <tt>:keypad_begin</tt>
|
|
150
|
+
# [Media Keys]
|
|
151
|
+
# <tt>:play</tt>, <tt>:media_pause</tt>, <tt>:play_pause</tt>, <tt>:reverse</tt>, <tt>:stop</tt>,
|
|
152
|
+
# <tt>:fast_forward</tt>, <tt>:rewind</tt>, <tt>:track_next</tt>, <tt>:track_previous</tt>,
|
|
153
|
+
# <tt>:record</tt>, <tt>:lower_volume</tt>, <tt>:raise_volume</tt>, <tt>:mute_volume</tt>
|
|
154
|
+
# [Modifier Keys]
|
|
155
|
+
# <tt>:left_shift</tt>, <tt>:left_control</tt>, <tt>:left_alt</tt>, <tt>:left_super</tt>,
|
|
156
|
+
# <tt>:left_hyper</tt>, <tt>:left_meta</tt>, <tt>:right_shift</tt>, <tt>:right_control</tt>,
|
|
157
|
+
# <tt>:right_alt</tt>, <tt>:right_super</tt>, <tt>:right_hyper</tt>, <tt>:right_meta</tt>,
|
|
158
|
+
# <tt>:iso_level3_shift</tt>, <tt>:iso_level5_shift</tt>
|
|
97
159
|
# [Characters]
|
|
98
160
|
# <tt>:a</tt>, <tt>:b</tt>, <tt>:1</tt>, <tt>:space</tt>, etc.
|
|
99
161
|
#
|
|
@@ -131,46 +193,7 @@ module RatatuiRuby
|
|
|
131
193
|
|
|
132
194
|
# Returns inspection string.
|
|
133
195
|
def inspect
|
|
134
|
-
"#<#{self.class} code=#{@code.inspect} modifiers=#{@modifiers.inspect}>"
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# Returns true if CTRL is held.
|
|
138
|
-
def ctrl?
|
|
139
|
-
@modifiers.include?("ctrl")
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# Returns true if ALT is held.
|
|
143
|
-
def alt?
|
|
144
|
-
@modifiers.include?("alt")
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# Returns true if SHIFT is held.
|
|
148
|
-
def shift?
|
|
149
|
-
@modifiers.include?("shift")
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Returns true if the key represents a single printable character.
|
|
153
|
-
#
|
|
154
|
-
# RatatuiRuby::Event::Key.new(code: "a").text? # => true
|
|
155
|
-
# RatatuiRuby::Event::Key.new(code: "enter").text? # => false
|
|
156
|
-
# RatatuiRuby::Event::Key.new(code: "space").text? # => false ("space" is not 1 char, " " is)
|
|
157
|
-
def text?
|
|
158
|
-
@code.length == 1
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
# Returns the key as a printable character (if applicable).
|
|
162
|
-
#
|
|
163
|
-
# [Printable Characters]
|
|
164
|
-
# Returns the character itself (e.g., <tt>"a"</tt>, <tt>"1"</tt>, <tt>" "</tt>).
|
|
165
|
-
# [Special Keys]
|
|
166
|
-
# Returns an empty string (e.g., <tt>"enter"</tt>, <tt>"up"</tt>, <tt>"f1"</tt>).
|
|
167
|
-
#
|
|
168
|
-
# This is equivalent to +to_s+.
|
|
169
|
-
#
|
|
170
|
-
# RatatuiRuby::Event::Key.new(code: "a").char # => "a"
|
|
171
|
-
# RatatuiRuby::Event::Key.new(code: "enter").char # => ""
|
|
172
|
-
def char
|
|
173
|
-
to_s
|
|
196
|
+
"#<#{self.class} code=#{@code.inspect} modifiers=#{@modifiers.inspect} kind=#{@kind.inspect}>"
|
|
174
197
|
end
|
|
175
198
|
|
|
176
199
|
# Supports dynamic key predicate methods via method_missing.
|
|
@@ -184,12 +207,47 @@ module RatatuiRuby
|
|
|
184
207
|
#
|
|
185
208
|
# The method name is converted to a symbol and compared against the event.
|
|
186
209
|
# This works for any key code or modifier+key combination.
|
|
210
|
+
#
|
|
211
|
+
# === Smart Predicates (DWIM)
|
|
212
|
+
#
|
|
213
|
+
# For convenience, generic predicates match both system and media variants:
|
|
214
|
+
#
|
|
215
|
+
# event.pause? # => true for BOTH system "pause" AND "media_pause"
|
|
216
|
+
# event.play? # => true for "media_play"
|
|
217
|
+
# event.stop? # => true for "media_stop"
|
|
218
|
+
#
|
|
219
|
+
# This "Do What I Mean" behavior reduces boilerplate when you just want to
|
|
220
|
+
# respond to a conceptual action (e.g., "pause the playback") regardless of
|
|
221
|
+
# whether the user pressed a keyboard key or a media button.
|
|
222
|
+
#
|
|
223
|
+
# For strict matching, use the full predicate or compare the code directly:
|
|
224
|
+
#
|
|
225
|
+
# event.media_pause? # => true ONLY for media pause
|
|
226
|
+
# event.code == "pause" # => true ONLY for system pause
|
|
187
227
|
def method_missing(name, *args, &block)
|
|
188
228
|
if name.to_s.end_with?("?")
|
|
189
|
-
|
|
190
|
-
|
|
229
|
+
key_name = name.to_s[0...-1]
|
|
230
|
+
key_sym = key_name.to_sym
|
|
231
|
+
|
|
232
|
+
# Fast path: Exact match (e.g., media_pause? for media_pause)
|
|
233
|
+
return true if self == key_sym
|
|
234
|
+
|
|
235
|
+
# Delegate category-specific DWIM logic to mixins
|
|
236
|
+
return true if match_media_dwim?(key_name)
|
|
237
|
+
return true if match_modifier_dwim?(key_name, key_sym)
|
|
238
|
+
return true if match_navigation_dwim?(key_name, key_sym)
|
|
239
|
+
return true if match_system_dwim?(key_name, key_sym)
|
|
240
|
+
|
|
241
|
+
# DWIM: Universal underscore-insensitivity
|
|
242
|
+
# Normalize both predicate and code by stripping underscores
|
|
243
|
+
normalized_predicate = key_name.delete("_")
|
|
244
|
+
normalized_code = @code.delete("_")
|
|
245
|
+
return true if normalized_predicate == normalized_code && @modifiers.empty?
|
|
246
|
+
|
|
247
|
+
false
|
|
248
|
+
else
|
|
249
|
+
super
|
|
191
250
|
end
|
|
192
|
-
super
|
|
193
251
|
end
|
|
194
252
|
|
|
195
253
|
# Declares that this class responds to dynamic predicate methods.
|
|
@@ -202,9 +260,11 @@ module RatatuiRuby
|
|
|
202
260
|
# case event
|
|
203
261
|
# in type: :key, code: "c", modifiers: ["ctrl"]
|
|
204
262
|
# puts "Ctrl+C pressed"
|
|
263
|
+
# in type: :key, kind: :media
|
|
264
|
+
# puts "Media key pressed"
|
|
205
265
|
# end
|
|
206
266
|
def deconstruct_keys(keys)
|
|
207
|
-
{ type: :key, code: @code, modifiers: @modifiers }
|
|
267
|
+
{ type: :key, code: @code, modifiers: @modifiers, kind: @kind }
|
|
208
268
|
end
|
|
209
269
|
end
|
|
210
270
|
end
|
|
@@ -67,11 +67,11 @@ module RatatuiRuby
|
|
|
67
67
|
# [modifiers]
|
|
68
68
|
# List of modifiers (Array<String>).
|
|
69
69
|
def initialize(kind:, x:, y:, button:, modifiers: [])
|
|
70
|
-
@kind = kind
|
|
70
|
+
@kind = kind.freeze
|
|
71
71
|
@x = x
|
|
72
72
|
@y = y
|
|
73
|
-
@button = button || "none"
|
|
74
|
-
@modifiers = modifiers.sort
|
|
73
|
+
@button = (button || "none").freeze
|
|
74
|
+
@modifiers = modifiers.map(&:freeze).sort.freeze
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
# Returns true if mouse button was pressed down.
|
data/lib/ratatui_ruby/frame.rb
CHANGED
|
@@ -16,12 +16,20 @@ module RatatuiRuby
|
|
|
16
16
|
# Use it inside a <tt>RatatuiRuby.draw</tt> block to render widgets with full
|
|
17
17
|
# control over placement.
|
|
18
18
|
#
|
|
19
|
+
# == Thread/Ractor Safety
|
|
20
|
+
#
|
|
21
|
+
# Frame is an *I/O handle*, not a data object. It has side effects
|
|
22
|
+
# (render_widget, set_cursor_position) and is intentionally *not*
|
|
23
|
+
# Ractor-shareable. Passing it to helper methods during the draw block is
|
|
24
|
+
# fine. However, do not include it in immutable Models/Messages or pass
|
|
25
|
+
# it to other Ractors. Frame is only valid during the draw block's execution.
|
|
26
|
+
#
|
|
19
27
|
# === Examples
|
|
20
28
|
#
|
|
21
29
|
# Basic usage with a single widget:
|
|
22
30
|
#
|
|
23
31
|
# RatatuiRuby.draw do |frame|
|
|
24
|
-
# paragraph = RatatuiRuby::Paragraph.new(text: "Hello, world!")
|
|
32
|
+
# paragraph = RatatuiRuby::Widgets::Paragraph.new(text: "Hello, world!")
|
|
25
33
|
# frame.render_widget(paragraph, frame.area)
|
|
26
34
|
# end
|
|
27
35
|
#
|
|
@@ -32,8 +40,8 @@ module RatatuiRuby
|
|
|
32
40
|
# frame.area,
|
|
33
41
|
# direction: :horizontal,
|
|
34
42
|
# constraints: [
|
|
35
|
-
# RatatuiRuby::Constraint.length(20),
|
|
36
|
-
# RatatuiRuby::Constraint.fill(1)
|
|
43
|
+
# RatatuiRuby::Layout::Constraint.length(20),
|
|
44
|
+
# RatatuiRuby::Layout::Constraint.fill(1)
|
|
37
45
|
# ]
|
|
38
46
|
# )
|
|
39
47
|
#
|
|
@@ -78,10 +86,98 @@ module RatatuiRuby
|
|
|
78
86
|
# === Example
|
|
79
87
|
#
|
|
80
88
|
# RatatuiRuby.draw do |frame|
|
|
81
|
-
# para = RatatuiRuby::Paragraph.new(text: "Content")
|
|
89
|
+
# para = RatatuiRuby::Widgets::Paragraph.new(text: "Content")
|
|
82
90
|
# frame.render_widget(para, frame.area)
|
|
83
91
|
# end
|
|
84
92
|
#
|
|
85
93
|
# (Native method implemented in Rust)
|
|
94
|
+
|
|
95
|
+
##
|
|
96
|
+
# :method: render_stateful_widget
|
|
97
|
+
# :call-seq: render_stateful_widget(widget, area, state) -> nil
|
|
98
|
+
#
|
|
99
|
+
# Renders a widget with persistent state.
|
|
100
|
+
#
|
|
101
|
+
# Some UI components (like List or Table) have **runtime status** (Status) that
|
|
102
|
+
# changes during rendering, such as the current scroll offset.
|
|
103
|
+
#
|
|
104
|
+
# Since Widget definitions (Configuration Definition) are immutable inputs,
|
|
105
|
+
# you must pass a separate mutable State object (Output Status) to capture
|
|
106
|
+
# these changes.
|
|
107
|
+
#
|
|
108
|
+
# Note: The Widget configuration is *always* required. The State object is
|
|
109
|
+
# only used for specific widgets that need to persist runtime status.
|
|
110
|
+
#
|
|
111
|
+
#
|
|
112
|
+
# [widget]
|
|
113
|
+
# The immutable widget configuration (Input) (e.g., RatatuiRuby::List).
|
|
114
|
+
# [area]
|
|
115
|
+
# The Rect area to render into.
|
|
116
|
+
# [state]
|
|
117
|
+
# The mutable state object (Output) (e.g., RatatuiRuby::ListState).
|
|
118
|
+
#
|
|
119
|
+
# === Example
|
|
120
|
+
#
|
|
121
|
+
# # Initialize state once (outside the loop)
|
|
122
|
+
# @list_state = RatatuiRuby::ListState.new
|
|
123
|
+
#
|
|
124
|
+
# RatatuiRuby.draw do |frame|
|
|
125
|
+
# list = RatatuiRuby::Widgets::List.new(items: ["A", "B"])
|
|
126
|
+
# frame.render_stateful_widget(list, frame.area, @list_state)
|
|
127
|
+
# end
|
|
128
|
+
#
|
|
129
|
+
# # Read back the offset calculated by Ratatui
|
|
130
|
+
# puts @list_state.offset
|
|
131
|
+
#
|
|
132
|
+
# (Native method implemented in Rust)
|
|
133
|
+
|
|
134
|
+
##
|
|
135
|
+
# :method: set_cursor_position
|
|
136
|
+
# :call-seq: set_cursor_position(x, y) -> nil
|
|
137
|
+
#
|
|
138
|
+
# Positions the blinking cursor at the given coordinates.
|
|
139
|
+
#
|
|
140
|
+
# Text input fields show users where typed characters will appear. Without
|
|
141
|
+
# a visible cursor, users cannot tell if the input is focused or where text
|
|
142
|
+
# will insert.
|
|
143
|
+
#
|
|
144
|
+
# This method moves the terminal cursor to a specific cell. Coordinates are
|
|
145
|
+
# 0-indexed from the terminal's top-left corner.
|
|
146
|
+
#
|
|
147
|
+
# Use it when building login forms, search bars, or command palettes.
|
|
148
|
+
#
|
|
149
|
+
# [x]
|
|
150
|
+
# Column position (<tt>0</tt> = leftmost column).
|
|
151
|
+
# [y]
|
|
152
|
+
# Row position (<tt>0</tt> = topmost row).
|
|
153
|
+
#
|
|
154
|
+
# === Example
|
|
155
|
+
#
|
|
156
|
+
# Position the cursor at the end of typed text in a login form:
|
|
157
|
+
#
|
|
158
|
+
# PREFIX = "Username: [ "
|
|
159
|
+
# username = "alice"
|
|
160
|
+
#
|
|
161
|
+
# RatatuiRuby.draw do |frame|
|
|
162
|
+
# # Render the input field
|
|
163
|
+
# prompt = RatatuiRuby::Widgets::Paragraph.new(
|
|
164
|
+
# text: "#{PREFIX}#{username} ]",
|
|
165
|
+
# block: RatatuiRuby::Widgets::Block.new(borders: :all)
|
|
166
|
+
# )
|
|
167
|
+
# frame.render_widget(prompt, frame.area)
|
|
168
|
+
#
|
|
169
|
+
# # Position cursor after the typed text
|
|
170
|
+
# # Account for border (1) + prefix length + username length
|
|
171
|
+
# cursor_x = 1 + PREFIX.length + username.length
|
|
172
|
+
# cursor_y = 1 # First line inside border
|
|
173
|
+
# frame.set_cursor_position(cursor_x, cursor_y)
|
|
174
|
+
# end
|
|
175
|
+
#
|
|
176
|
+
# See also:
|
|
177
|
+
# - {Component-based implementation using Frame API}[link:/examples/app_color_picker/app_rb.html]
|
|
178
|
+
# - {Declarative implementation using Tree API}[link:/examples/app_login_form/app_rb.html]
|
|
179
|
+
# - RatatuiRuby::Cursor (Tree API alternative)
|
|
180
|
+
#
|
|
181
|
+
# (Native method implemented in Rust)
|
|
86
182
|
end
|
|
87
183
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
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 the sizing rule for a layout section.
|
|
9
|
+
#
|
|
10
|
+
# Flexible layouts need rules. You can't just place widgets at absolute coordinates; they must adapt to changing terminal sizes.
|
|
11
|
+
#
|
|
12
|
+
# This class defines the rules of engagement. It tells the layout engine exactly how much space a section requires relative to others.
|
|
13
|
+
#
|
|
14
|
+
# Mix and match fixed lengths, percentages, ratios, and minimums. Build layouts that breathe.
|
|
15
|
+
#
|
|
16
|
+
# === Examples
|
|
17
|
+
#
|
|
18
|
+
# Layout::Constraint.length(5) # Exactly 5 cells
|
|
19
|
+
# Layout::Constraint.percentage(50) # Half the available space
|
|
20
|
+
# Layout::Constraint.min(10) # At least 10 cells, maybe more
|
|
21
|
+
# Layout::Constraint.fill(1) # Fill remaining space (weight 1)
|
|
22
|
+
class Constraint < Data.define(:type, :value)
|
|
23
|
+
##
|
|
24
|
+
# :attr_reader: type
|
|
25
|
+
# The type of constraint.
|
|
26
|
+
#
|
|
27
|
+
# <tt>:length</tt>, <tt>:percentage</tt>, <tt>:min</tt>, <tt>:max</tt>, <tt>:fill</tt>, or <tt>:ratio</tt>.
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# :attr_reader: value
|
|
31
|
+
# The numeric value (or array for ratio) associated with the rule.
|
|
32
|
+
|
|
33
|
+
# Requests a fixed size.
|
|
34
|
+
#
|
|
35
|
+
# Layout::Constraint.length(10) # 10 characters wide/high
|
|
36
|
+
#
|
|
37
|
+
# [v] Number of cells (Integer).
|
|
38
|
+
def self.length(v)
|
|
39
|
+
new(type: :length, value: Integer(v))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Requests a percentage of available space.
|
|
43
|
+
#
|
|
44
|
+
# Layout::Constraint.percentage(25) # 25% of the area
|
|
45
|
+
#
|
|
46
|
+
# [v] Percentage 0-100 (Integer).
|
|
47
|
+
def self.percentage(v)
|
|
48
|
+
new(type: :percentage, value: Integer(v))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Enforces a minimum size.
|
|
52
|
+
#
|
|
53
|
+
# Layout::Constraint.min(5) # At least 5 cells
|
|
54
|
+
#
|
|
55
|
+
# This section will grow if space permits, but never shrink below +v+.
|
|
56
|
+
#
|
|
57
|
+
# [v] Minimum cells (Integer).
|
|
58
|
+
def self.min(v)
|
|
59
|
+
new(type: :min, value: Integer(v))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Enforces a maximum size.
|
|
63
|
+
#
|
|
64
|
+
# Layout::Constraint.max(10) # At most 10 cells
|
|
65
|
+
#
|
|
66
|
+
# [v] Maximum cells (Integer).
|
|
67
|
+
def self.max(v)
|
|
68
|
+
new(type: :max, value: Integer(v))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Fills remaining space proportionally.
|
|
72
|
+
#
|
|
73
|
+
# Layout::Constraint.fill(1) # Equal share
|
|
74
|
+
# Layout::Constraint.fill(2) # Double share
|
|
75
|
+
#
|
|
76
|
+
# Fill constraints distribute any space left after satisfying strict rules.
|
|
77
|
+
# They behave like flex-grow. A fill(2) takes twice as much space as a fill(1).
|
|
78
|
+
#
|
|
79
|
+
# [v] Proportional weight (Integer, default: 1).
|
|
80
|
+
def self.fill(v = 1)
|
|
81
|
+
new(type: :fill, value: Integer(v))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Requests a specific ratio of the total space.
|
|
85
|
+
#
|
|
86
|
+
# Layout::Constraint.ratio(1, 3) # 1/3rd of the area
|
|
87
|
+
#
|
|
88
|
+
# [numerator] Top part of fraction (Integer).
|
|
89
|
+
# [denominator] Bottom part of fraction (Integer).
|
|
90
|
+
def self.ratio(numerator, denominator)
|
|
91
|
+
new(type: :ratio, value: [Integer(numerator), Integer(denominator)])
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|