ratatui_ruby 0.3.1 → 0.4.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 +14 -12
- data/.builds/ruby-3.3.yml +14 -12
- data/.builds/ruby-3.4.yml +14 -12
- data/.builds/ruby-4.0.0.yml +14 -12
- data/AGENTS.md +54 -13
- data/CHANGELOG.md +186 -1
- data/README.md +17 -15
- data/doc/application_architecture.md +116 -0
- data/doc/application_testing.md +12 -7
- data/doc/contributors/better_dx.md +543 -0
- data/doc/contributors/design/ruby_frontend.md +1 -1
- data/doc/contributors/developing_examples.md +203 -0
- data/doc/contributors/documentation_style.md +97 -0
- data/doc/contributors/dwim_dx.md +366 -0
- data/doc/contributors/example_analysis.md +82 -0
- data/doc/custom.css +14 -0
- data/doc/event_handling.md +119 -0
- data/doc/images/all_events.png +0 -0
- data/doc/images/analytics.png +0 -0
- data/doc/images/block_padding.png +0 -0
- data/doc/images/block_titles.png +0 -0
- data/doc/images/box_demo.png +0 -0
- data/doc/images/calendar_demo.png +0 -0
- data/doc/images/cell_demo.png +0 -0
- data/doc/images/chart_demo.png +0 -0
- data/doc/images/custom_widget.png +0 -0
- data/doc/images/flex_layout.png +0 -0
- data/doc/images/gauge_demo.png +0 -0
- data/doc/images/hit_test.png +0 -0
- data/doc/images/line_gauge_demo.png +0 -0
- data/doc/images/list_demo.png +0 -0
- data/doc/images/list_styles.png +0 -0
- data/doc/images/login_form.png +0 -0
- data/doc/images/map_demo.png +0 -0
- data/doc/images/mouse_events.png +0 -0
- data/doc/images/popup_demo.png +0 -0
- data/doc/images/quickstart_dsl.png +0 -0
- data/doc/images/quickstart_lifecycle.png +0 -0
- data/doc/images/ratatui_logo_demo.png +0 -0
- data/doc/images/readme_usage.png +0 -0
- data/doc/images/rich_text.png +0 -0
- data/doc/images/scroll_text.png +0 -0
- data/doc/images/scrollbar_demo.png +0 -0
- data/doc/images/sparkline_demo.png +0 -0
- data/doc/images/table_flex.png +0 -0
- data/doc/images/table_select.png +0 -0
- data/doc/images/widget_style_colors.png +0 -0
- data/doc/index.md +1 -0
- data/doc/interactive_design.md +121 -0
- data/doc/quickstart.md +147 -72
- data/examples/all_events/app.rb +169 -0
- data/examples/all_events/app.rbs +7 -0
- data/examples/all_events/test_app.rb +139 -0
- data/examples/analytics/app.rb +258 -0
- data/examples/analytics/app.rbs +7 -0
- data/examples/analytics/test_app.rb +132 -0
- data/examples/block_padding/app.rb +63 -0
- data/examples/block_padding/app.rbs +7 -0
- data/examples/block_padding/test_app.rb +31 -0
- data/examples/block_titles/app.rb +61 -0
- data/examples/block_titles/app.rbs +7 -0
- data/examples/block_titles/test_app.rb +34 -0
- data/examples/box_demo/app.rb +216 -0
- data/examples/box_demo/app.rbs +7 -0
- data/examples/box_demo/test_app.rb +88 -0
- data/examples/calendar_demo/app.rb +101 -0
- data/examples/calendar_demo/app.rbs +7 -0
- data/examples/calendar_demo/test_app.rb +108 -0
- data/examples/cell_demo/app.rb +108 -0
- data/examples/cell_demo/app.rbs +7 -0
- data/examples/cell_demo/test_app.rb +36 -0
- data/examples/chart_demo/app.rb +203 -0
- data/examples/chart_demo/app.rbs +7 -0
- data/examples/chart_demo/test_app.rb +102 -0
- data/examples/custom_widget/app.rb +51 -0
- data/examples/custom_widget/app.rbs +7 -0
- data/examples/custom_widget/test_app.rb +30 -0
- data/examples/flex_layout/app.rb +156 -0
- data/examples/flex_layout/app.rbs +7 -0
- data/examples/flex_layout/test_app.rb +65 -0
- data/examples/gauge_demo/app.rb +182 -0
- data/examples/gauge_demo/app.rbs +7 -0
- data/examples/gauge_demo/test_app.rb +120 -0
- data/examples/hit_test/app.rb +175 -0
- data/examples/hit_test/app.rbs +7 -0
- data/examples/hit_test/test_app.rb +102 -0
- data/examples/line_gauge_demo/app.rb +190 -0
- data/examples/line_gauge_demo/app.rbs +7 -0
- data/examples/line_gauge_demo/test_app.rb +129 -0
- data/examples/list_demo/app.rb +253 -0
- data/examples/list_demo/app.rbs +12 -0
- data/examples/list_demo/test_app.rb +237 -0
- data/examples/list_styles/app.rb +140 -0
- data/examples/list_styles/app.rbs +7 -0
- data/examples/list_styles/test_app.rb +157 -0
- data/examples/{login_form.rb → login_form/app.rb} +12 -16
- data/examples/login_form/app.rbs +7 -0
- data/examples/login_form/test_app.rb +51 -0
- data/examples/map_demo/app.rb +90 -0
- data/examples/map_demo/app.rbs +7 -0
- data/examples/map_demo/test_app.rb +149 -0
- data/examples/{mouse_events.rb → mouse_events/app.rb} +29 -27
- data/examples/mouse_events/app.rbs +7 -0
- data/examples/mouse_events/test_app.rb +53 -0
- data/examples/{popup_demo.rb → popup_demo/app.rb} +15 -17
- data/examples/popup_demo/app.rbs +7 -0
- data/examples/{test_popup_demo.rb → popup_demo/test_app.rb} +18 -26
- data/examples/quickstart_dsl/app.rb +36 -0
- data/examples/quickstart_dsl/app.rbs +7 -0
- data/examples/quickstart_dsl/test_app.rb +29 -0
- data/examples/quickstart_lifecycle/app.rb +39 -0
- data/examples/quickstart_lifecycle/app.rbs +7 -0
- data/examples/quickstart_lifecycle/test_app.rb +29 -0
- data/examples/ratatui_logo_demo/app.rb +79 -0
- data/examples/ratatui_logo_demo/app.rbs +7 -0
- data/examples/ratatui_logo_demo/test_app.rb +51 -0
- data/examples/ratatui_mascot_demo/app.rb +84 -0
- data/examples/ratatui_mascot_demo/app.rbs +7 -0
- data/examples/ratatui_mascot_demo/test_app.rb +47 -0
- data/examples/readme_usage/app.rb +29 -0
- data/examples/readme_usage/app.rbs +7 -0
- data/examples/readme_usage/test_app.rb +29 -0
- data/examples/rich_text/app.rb +141 -0
- data/examples/rich_text/app.rbs +7 -0
- data/examples/rich_text/test_app.rb +166 -0
- data/examples/scroll_text/app.rb +103 -0
- data/examples/scroll_text/app.rbs +7 -0
- data/examples/scroll_text/test_app.rb +110 -0
- data/examples/scrollbar_demo/app.rb +143 -0
- data/examples/scrollbar_demo/app.rbs +7 -0
- data/examples/scrollbar_demo/test_app.rb +77 -0
- data/examples/sparkline_demo/app.rb +240 -0
- data/examples/sparkline_demo/app.rbs +10 -0
- data/examples/sparkline_demo/test_app.rb +107 -0
- data/examples/table_flex/app.rb +65 -0
- data/examples/table_flex/app.rbs +7 -0
- data/examples/table_flex/test_app.rb +36 -0
- data/examples/table_select/app.rb +198 -0
- data/examples/table_select/app.rbs +7 -0
- data/examples/table_select/test_app.rb +180 -0
- data/examples/widget_style_colors/app.rb +104 -0
- data/examples/widget_style_colors/app.rbs +14 -0
- data/examples/widget_style_colors/test_app.rb +48 -0
- data/ext/ratatui_ruby/Cargo.lock +889 -115
- data/ext/ratatui_ruby/Cargo.toml +4 -3
- data/ext/ratatui_ruby/clippy.toml +7 -0
- data/ext/ratatui_ruby/extconf.rb +7 -0
- data/ext/ratatui_ruby/src/events.rs +218 -229
- data/ext/ratatui_ruby/src/lib.rs +38 -10
- data/ext/ratatui_ruby/src/rendering.rs +90 -10
- data/ext/ratatui_ruby/src/style.rs +281 -98
- data/ext/ratatui_ruby/src/terminal.rs +119 -25
- data/ext/ratatui_ruby/src/text.rs +171 -0
- data/ext/ratatui_ruby/src/widgets/barchart.rs +97 -24
- data/ext/ratatui_ruby/src/widgets/block.rs +31 -3
- data/ext/ratatui_ruby/src/widgets/calendar.rs +45 -44
- data/ext/ratatui_ruby/src/widgets/canvas.rs +46 -29
- data/ext/ratatui_ruby/src/widgets/chart.rs +69 -27
- data/ext/ratatui_ruby/src/widgets/clear.rs +3 -1
- data/ext/ratatui_ruby/src/widgets/gauge.rs +11 -4
- data/ext/ratatui_ruby/src/widgets/layout.rs +218 -15
- data/ext/ratatui_ruby/src/widgets/line_gauge.rs +92 -0
- data/ext/ratatui_ruby/src/widgets/list.rs +91 -11
- data/ext/ratatui_ruby/src/widgets/mod.rs +3 -0
- data/ext/ratatui_ruby/src/widgets/overlay.rs +3 -2
- data/ext/ratatui_ruby/src/widgets/paragraph.rs +35 -13
- data/ext/ratatui_ruby/src/widgets/ratatui_logo.rs +29 -0
- data/ext/ratatui_ruby/src/widgets/ratatui_mascot.rs +44 -0
- data/ext/ratatui_ruby/src/widgets/scrollbar.rs +59 -7
- data/ext/ratatui_ruby/src/widgets/sparkline.rs +70 -6
- data/ext/ratatui_ruby/src/widgets/table.rs +173 -64
- data/ext/ratatui_ruby/src/widgets/tabs.rs +105 -5
- data/lib/ratatui_ruby/cell.rb +166 -0
- data/lib/ratatui_ruby/event/focus_gained.rb +49 -0
- data/lib/ratatui_ruby/event/focus_lost.rb +50 -0
- data/lib/ratatui_ruby/event/key.rb +211 -0
- data/lib/ratatui_ruby/event/mouse.rb +124 -0
- data/lib/ratatui_ruby/event/paste.rb +71 -0
- data/lib/ratatui_ruby/event/resize.rb +80 -0
- data/lib/ratatui_ruby/event.rb +79 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar.rb +45 -0
- data/lib/ratatui_ruby/schema/bar_chart/bar_group.rb +27 -0
- data/lib/ratatui_ruby/schema/bar_chart.rb +228 -19
- data/lib/ratatui_ruby/schema/block.rb +186 -14
- data/lib/ratatui_ruby/schema/calendar.rb +74 -17
- data/lib/ratatui_ruby/schema/canvas.rb +215 -48
- data/lib/ratatui_ruby/schema/center.rb +49 -11
- data/lib/ratatui_ruby/schema/chart.rb +151 -41
- data/lib/ratatui_ruby/schema/clear.rb +41 -72
- data/lib/ratatui_ruby/schema/constraint.rb +82 -22
- data/lib/ratatui_ruby/schema/cursor.rb +27 -9
- data/lib/ratatui_ruby/schema/draw.rb +53 -0
- data/lib/ratatui_ruby/schema/gauge.rb +59 -15
- data/lib/ratatui_ruby/schema/layout.rb +95 -13
- data/lib/ratatui_ruby/schema/line_gauge.rb +78 -0
- data/lib/ratatui_ruby/schema/list.rb +93 -19
- data/lib/ratatui_ruby/schema/overlay.rb +34 -8
- data/lib/ratatui_ruby/schema/paragraph.rb +87 -30
- data/lib/ratatui_ruby/schema/ratatui_logo.rb +25 -0
- data/lib/ratatui_ruby/schema/ratatui_mascot.rb +29 -0
- data/lib/ratatui_ruby/schema/rect.rb +64 -15
- data/lib/ratatui_ruby/schema/scrollbar.rb +132 -24
- data/lib/ratatui_ruby/schema/shape/label.rb +66 -0
- data/lib/ratatui_ruby/schema/sparkline.rb +122 -15
- data/lib/ratatui_ruby/schema/style.rb +49 -21
- data/lib/ratatui_ruby/schema/table.rb +119 -21
- data/lib/ratatui_ruby/schema/tabs.rb +75 -13
- data/lib/ratatui_ruby/schema/text.rb +90 -0
- data/lib/ratatui_ruby/session.rb +146 -0
- data/lib/ratatui_ruby/test_helper.rb +156 -13
- data/lib/ratatui_ruby/version.rb +1 -1
- data/lib/ratatui_ruby.rb +143 -23
- data/sig/ratatui_ruby/event.rbs +69 -0
- data/sig/ratatui_ruby/ratatui_ruby.rbs +2 -1
- data/sig/ratatui_ruby/schema/bar_chart/bar.rbs +16 -0
- data/sig/ratatui_ruby/schema/bar_chart/bar_group.rbs +13 -0
- data/sig/ratatui_ruby/schema/bar_chart.rbs +20 -2
- data/sig/ratatui_ruby/schema/block.rbs +5 -4
- data/sig/ratatui_ruby/schema/calendar.rbs +6 -2
- data/sig/ratatui_ruby/schema/canvas.rbs +52 -39
- data/sig/ratatui_ruby/schema/center.rbs +3 -3
- data/sig/ratatui_ruby/schema/chart.rbs +8 -5
- data/sig/ratatui_ruby/schema/constraint.rbs +8 -5
- data/sig/ratatui_ruby/schema/cursor.rbs +1 -1
- data/sig/ratatui_ruby/schema/draw.rbs +23 -0
- data/sig/ratatui_ruby/schema/gauge.rbs +4 -2
- data/sig/ratatui_ruby/schema/layout.rbs +11 -1
- data/sig/ratatui_ruby/schema/line_gauge.rbs +16 -0
- data/sig/ratatui_ruby/schema/list.rbs +5 -1
- data/sig/ratatui_ruby/schema/paragraph.rbs +4 -1
- data/{lib/ratatui_ruby/output.rb → sig/ratatui_ruby/schema/ratatui_logo.rbs} +3 -2
- data/sig/ratatui_ruby/{buffer.rbs → schema/ratatui_mascot.rbs} +4 -3
- data/sig/ratatui_ruby/schema/rect.rbs +2 -1
- data/sig/ratatui_ruby/schema/scrollbar.rbs +18 -2
- data/sig/ratatui_ruby/schema/sparkline.rbs +6 -2
- data/sig/ratatui_ruby/schema/table.rbs +8 -1
- data/sig/ratatui_ruby/schema/tabs.rbs +5 -1
- data/sig/ratatui_ruby/schema/text.rbs +22 -0
- data/tasks/resources/build.yml.erb +13 -11
- data/tasks/terminal_preview/app_screenshot.rb +35 -0
- data/tasks/terminal_preview/crash_report.rb +54 -0
- data/tasks/terminal_preview/example_app.rb +25 -0
- data/tasks/terminal_preview/launcher_script.rb +48 -0
- data/tasks/terminal_preview/preview_collection.rb +60 -0
- data/tasks/terminal_preview/preview_timing.rb +22 -0
- data/tasks/terminal_preview/safety_confirmation.rb +58 -0
- data/tasks/terminal_preview/saved_screenshot.rb +55 -0
- data/tasks/terminal_preview/system_appearance.rb +11 -0
- data/tasks/terminal_preview/terminal_window.rb +138 -0
- data/tasks/terminal_preview/window_id.rb +14 -0
- data/tasks/terminal_preview.rake +28 -0
- data/tasks/test.rake +1 -1
- metadata +174 -53
- data/doc/images/examples-analytics.rb.png +0 -0
- data/doc/images/examples-box_demo.rb.png +0 -0
- data/doc/images/examples-calendar_demo.rb.png +0 -0
- data/doc/images/examples-chart_demo.rb.png +0 -0
- data/doc/images/examples-custom_widget.rb.png +0 -0
- data/doc/images/examples-dashboard.rb.png +0 -0
- data/doc/images/examples-list_styles.rb.png +0 -0
- data/doc/images/examples-login_form.rb.png +0 -0
- data/doc/images/examples-map_demo.rb.png +0 -0
- data/doc/images/examples-mouse_events.rb.png +0 -0
- data/doc/images/examples-popup_demo.rb.gif +0 -0
- data/doc/images/examples-quickstart_lifecycle.rb.png +0 -0
- data/doc/images/examples-scroll_text.rb.png +0 -0
- data/doc/images/examples-scrollbar_demo.rb.png +0 -0
- data/doc/images/examples-stock_ticker.rb.png +0 -0
- data/doc/images/examples-system_monitor.rb.png +0 -0
- data/doc/images/examples-table_select.rb.png +0 -0
- data/examples/analytics.rb +0 -88
- data/examples/box_demo.rb +0 -71
- data/examples/calendar_demo.rb +0 -55
- data/examples/chart_demo.rb +0 -84
- data/examples/custom_widget.rb +0 -43
- data/examples/dashboard.rb +0 -72
- data/examples/list_styles.rb +0 -66
- data/examples/map_demo.rb +0 -58
- data/examples/quickstart_dsl.rb +0 -30
- data/examples/quickstart_lifecycle.rb +0 -40
- data/examples/readme_usage.rb +0 -21
- data/examples/scroll_text.rb +0 -74
- data/examples/scrollbar_demo.rb +0 -75
- data/examples/stock_ticker.rb +0 -93
- data/examples/system_monitor.rb +0 -94
- data/examples/table_select.rb +0 -70
- data/examples/test_analytics.rb +0 -65
- data/examples/test_box_demo.rb +0 -38
- data/examples/test_calendar_demo.rb +0 -66
- data/examples/test_dashboard.rb +0 -38
- data/examples/test_list_styles.rb +0 -61
- data/examples/test_login_form.rb +0 -63
- data/examples/test_map_demo.rb +0 -100
- data/examples/test_scroll_text.rb +0 -130
- data/examples/test_stock_ticker.rb +0 -39
- data/examples/test_system_monitor.rb +0 -40
- data/examples/test_table_select.rb +0 -37
- data/ext/ratatui_ruby/src/buffer.rs +0 -54
- data/lib/ratatui_ruby/dsl.rb +0 -64
|
@@ -0,0 +1,90 @@
|
|
|
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
|
+
# Namespace for rich text components (Span and Line).
|
|
8
|
+
# Distinct from canvas shapes and other Line usages.
|
|
9
|
+
module Text
|
|
10
|
+
# A styled string fragment.
|
|
11
|
+
#
|
|
12
|
+
# Text is rarely uniform. You need to bold a keyword, colorize an error, or dim a timestamp.
|
|
13
|
+
#
|
|
14
|
+
# This class attaches style to content. It pairs a string with visual attributes.
|
|
15
|
+
#
|
|
16
|
+
# combine spans into a {Line} to create rich text.
|
|
17
|
+
#
|
|
18
|
+
# === Examples
|
|
19
|
+
#
|
|
20
|
+
# Text::Span.new(content: "Error", style: Style.new(fg: :red, modifiers: [:bold]))
|
|
21
|
+
class Span < Data.define(:content, :style)
|
|
22
|
+
##
|
|
23
|
+
# :attr_reader: content
|
|
24
|
+
# The text content.
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# :attr_reader: style
|
|
28
|
+
# The style to apply.
|
|
29
|
+
|
|
30
|
+
# Creates a new Span.
|
|
31
|
+
#
|
|
32
|
+
# [content] String.
|
|
33
|
+
# [style] Style object (optional).
|
|
34
|
+
def initialize(content:, style: nil)
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Concise helper for styling.
|
|
39
|
+
#
|
|
40
|
+
# Text::Span.styled("Bold", Style.new(modifiers: [:bold]))
|
|
41
|
+
def self.styled(content, style = nil)
|
|
42
|
+
new(content:, style:)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# A sequence of styled spans.
|
|
47
|
+
#
|
|
48
|
+
# Words form sentences. Spans form lines.
|
|
49
|
+
#
|
|
50
|
+
# This class composes multiple {Span} objects into a single horizontal row of text.
|
|
51
|
+
# It handles the layout of rich text fragments within the flow of a paragraph.
|
|
52
|
+
#
|
|
53
|
+
# Use it to build multi-colored headers, status messages, or log entries.
|
|
54
|
+
#
|
|
55
|
+
# === Examples
|
|
56
|
+
#
|
|
57
|
+
# Text::Line.new(
|
|
58
|
+
# spans: [
|
|
59
|
+
# Text::Span.styled("User: ", Style.new(modifiers: [:bold])),
|
|
60
|
+
# Text::Span.styled("kerrick", Style.new(fg: :blue))
|
|
61
|
+
# ]
|
|
62
|
+
# )
|
|
63
|
+
class Line < Data.define(:spans, :alignment)
|
|
64
|
+
##
|
|
65
|
+
# :attr_reader: spans
|
|
66
|
+
# Array of Span objects.
|
|
67
|
+
|
|
68
|
+
##
|
|
69
|
+
# :attr_reader: alignment
|
|
70
|
+
# Alignment within the container.
|
|
71
|
+
#
|
|
72
|
+
# <tt>:left</tt>, <tt>:center</tt>, or <tt>:right</tt>.
|
|
73
|
+
|
|
74
|
+
# Creates a new Line.
|
|
75
|
+
#
|
|
76
|
+
# [spans] Array of Span objects (or Strings).
|
|
77
|
+
# [alignment] Symbol (optional).
|
|
78
|
+
def initialize(spans: [], alignment: nil)
|
|
79
|
+
super
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Creates a simple line from a string.
|
|
83
|
+
#
|
|
84
|
+
# Text::Line.from_string("Hello")
|
|
85
|
+
def self.from_string(content, alignment: nil)
|
|
86
|
+
new(spans: [Span.new(content:, style: nil)], alignment:)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
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
|
+
# Manages the terminal lifecycle and provides a concise API for the render loop.
|
|
8
|
+
#
|
|
9
|
+
# Writing a TUI loop involves repetitive boilerplate. You constantly instantiate widgets (<tt>RatatuiRuby::Paragraph.new</tt>) and call global methods (<tt>RatatuiRuby.draw</tt>). This is verbose and hard to read.
|
|
10
|
+
#
|
|
11
|
+
# The Session object simplifies this. It acts as a factory and a facade. It provides short helper methods for every widget and delegates core commands to the main module.
|
|
12
|
+
#
|
|
13
|
+
# Use it within <tt>RatatuiRuby.run</tt> to build your interface cleanly.
|
|
14
|
+
#
|
|
15
|
+
# == Available Methods
|
|
16
|
+
#
|
|
17
|
+
# The session dynamically defines factory methods for all RatatuiRuby constants.
|
|
18
|
+
#
|
|
19
|
+
# * <tt>draw(node)</tt> -> Delegates to <tt>RatatuiRuby.draw</tt>
|
|
20
|
+
# * <tt>poll_event</tt> -> Delegates to <tt>RatatuiRuby.poll_event</tt>
|
|
21
|
+
#
|
|
22
|
+
# === Widget Factories
|
|
23
|
+
#
|
|
24
|
+
# The session acts as a dynamic factory. It creates a helper method for **every** class defined in the `RatatuiRuby` module.
|
|
25
|
+
#
|
|
26
|
+
# **The Rule:**
|
|
27
|
+
# To instantiate a class like `RatatuiRuby::SomeWidget`, call `tui.some_widget(...)`.
|
|
28
|
+
#
|
|
29
|
+
# **Common Examples:**
|
|
30
|
+
# * <tt>paragraph(...)</tt> -> <tt>RatatuiRuby::Paragraph.new(...)</tt>
|
|
31
|
+
# * <tt>block(...)</tt> -> <tt>RatatuiRuby::Block.new(...)</tt>
|
|
32
|
+
# * <tt>layout(...)</tt> -> <tt>RatatuiRuby::Layout.new(...)</tt>
|
|
33
|
+
# * <tt>list(...)</tt> -> <tt>RatatuiRuby::List.new(...)</tt>
|
|
34
|
+
# * <tt>table(...)</tt> -> <tt>RatatuiRuby::Table.new(...)</tt>
|
|
35
|
+
# * <tt>style(...)</tt> -> <tt>RatatuiRuby::Style.new(...)</tt>
|
|
36
|
+
#
|
|
37
|
+
# If a new class is added to the library, it is automatically available here.
|
|
38
|
+
#
|
|
39
|
+
# === Nested Helpers
|
|
40
|
+
#
|
|
41
|
+
# * <tt>text_span(...)</tt> -> <tt>RatatuiRuby::Text::Span.new(...)</tt>
|
|
42
|
+
# * <tt>text_line(...)</tt> -> <tt>RatatuiRuby::Text::Line.new(...)</tt>
|
|
43
|
+
#
|
|
44
|
+
# === Examples
|
|
45
|
+
#
|
|
46
|
+
# ==== Basic Usage (Recommended)
|
|
47
|
+
#
|
|
48
|
+
# RatatuiRuby.run do |tui|
|
|
49
|
+
# loop do
|
|
50
|
+
# tui.draw \
|
|
51
|
+
# tui.paragraph \
|
|
52
|
+
# text: "Hello, Ratatui! Press 'q' to quit.",
|
|
53
|
+
# alignment: :center,
|
|
54
|
+
# block: tui.block(
|
|
55
|
+
# title: "My Ruby TUI App",
|
|
56
|
+
# borders: [:all],
|
|
57
|
+
# border_color: "cyan"
|
|
58
|
+
# )
|
|
59
|
+
# event = tui.poll_event
|
|
60
|
+
# break if event == "q" || event == :ctrl_c
|
|
61
|
+
# end
|
|
62
|
+
# end
|
|
63
|
+
#
|
|
64
|
+
# ==== Raw API (Verbose)
|
|
65
|
+
#
|
|
66
|
+
# RatatuiRuby.run do
|
|
67
|
+
# loop do
|
|
68
|
+
# RatatuiRuby.draw \
|
|
69
|
+
# RatatuiRuby::Paragraph.new(
|
|
70
|
+
# text: "Hello, Ratatui! Press 'q' to quit.",
|
|
71
|
+
# alignment: :center,
|
|
72
|
+
# block: RatatuiRuby::Block.new(
|
|
73
|
+
# title: "My Ruby TUI App",
|
|
74
|
+
# borders: [:all],
|
|
75
|
+
# border_color: "cyan"
|
|
76
|
+
# )
|
|
77
|
+
# )
|
|
78
|
+
# event = RatatuiRuby.poll_event
|
|
79
|
+
# break if event == "q" || event == :ctrl_c
|
|
80
|
+
# end
|
|
81
|
+
# end
|
|
82
|
+
#
|
|
83
|
+
# ==== Mixed Usage (Flexible)
|
|
84
|
+
#
|
|
85
|
+
# RatatuiRuby.run do |tui|
|
|
86
|
+
# loop do
|
|
87
|
+
# RatatuiRuby.draw \
|
|
88
|
+
# tui.paragraph \
|
|
89
|
+
# text: "Hello, Ratatui! Press 'q' to quit.",
|
|
90
|
+
# alignment: :center,
|
|
91
|
+
# block: tui.block(
|
|
92
|
+
# title: "My Ruby TUI App",
|
|
93
|
+
# borders: [:all],
|
|
94
|
+
# border_color: "cyan"
|
|
95
|
+
# )
|
|
96
|
+
# event = RatatuiRuby.poll_event
|
|
97
|
+
# break if event == "q" || event == :ctrl_c
|
|
98
|
+
# end
|
|
99
|
+
# end
|
|
100
|
+
class Session
|
|
101
|
+
# Wrap methods directly
|
|
102
|
+
RatatuiRuby.singleton_methods(false).each do |method_name|
|
|
103
|
+
define_method(method_name) do |*args, **kwargs, &block|
|
|
104
|
+
RatatuiRuby.public_send(method_name, *args, **kwargs, &block)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Wrap classes as snake_case factories
|
|
109
|
+
RatatuiRuby.constants.each do |const_name|
|
|
110
|
+
next if const_name == :Buffer
|
|
111
|
+
|
|
112
|
+
klass = RatatuiRuby.const_get(const_name)
|
|
113
|
+
next unless klass.is_a?(Class)
|
|
114
|
+
|
|
115
|
+
method_name = const_name.to_s
|
|
116
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
117
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
118
|
+
.downcase
|
|
119
|
+
|
|
120
|
+
define_method(method_name) do |*args, **kwargs, &block|
|
|
121
|
+
klass.new(*args, **kwargs, &block)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Wrap nested module classes with prefixed names (e.g., shape_line, text_span)
|
|
126
|
+
{ Shape: :shape, Text: :text }.each do |mod_name, prefix|
|
|
127
|
+
next unless RatatuiRuby.const_defined?(mod_name)
|
|
128
|
+
|
|
129
|
+
mod = RatatuiRuby.const_get(mod_name)
|
|
130
|
+
mod.constants.each do |const_name|
|
|
131
|
+
klass = mod.const_get(const_name)
|
|
132
|
+
next unless klass.is_a?(Class)
|
|
133
|
+
|
|
134
|
+
class_snake = const_name.to_s
|
|
135
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
136
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
137
|
+
.downcase
|
|
138
|
+
method_name = "#{prefix}_#{class_snake}"
|
|
139
|
+
|
|
140
|
+
define_method(method_name) do |*args, **kwargs, &block|
|
|
141
|
+
klass.new(*args, **kwargs, &block)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require "timeout"
|
|
3
|
+
require "minitest/mock"
|
|
4
|
+
|
|
5
|
+
|
|
2
6
|
|
|
3
7
|
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
4
8
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
@@ -8,7 +12,7 @@ module RatatuiRuby
|
|
|
8
12
|
# Helpers for testing RatatuiRuby applications.
|
|
9
13
|
#
|
|
10
14
|
# This module provides methods to set up a test terminal, capture buffer content,
|
|
11
|
-
# and
|
|
15
|
+
# and inject events, making it easier to write unit tests for your TUI apps.
|
|
12
16
|
#
|
|
13
17
|
# == Usage
|
|
14
18
|
#
|
|
@@ -18,24 +22,50 @@ module RatatuiRuby
|
|
|
18
22
|
# include RatatuiRuby::TestHelper
|
|
19
23
|
#
|
|
20
24
|
# def test_rendering
|
|
21
|
-
# with_test_terminal(
|
|
25
|
+
# with_test_terminal(80, 24) do
|
|
22
26
|
# # ... render your app ...
|
|
23
27
|
# assert_includes buffer_content, "Hello World"
|
|
24
28
|
# end
|
|
25
29
|
# end
|
|
30
|
+
#
|
|
31
|
+
# def test_key_handling
|
|
32
|
+
# inject_event(RatatuiRuby::Event::Key.new(code: "q"))
|
|
33
|
+
# result = @app.handle_input
|
|
34
|
+
# assert_equal :quit, result
|
|
35
|
+
# end
|
|
26
36
|
# end
|
|
27
37
|
module TestHelper
|
|
28
38
|
##
|
|
29
39
|
# Initializes a test terminal context with specified dimensions.
|
|
30
40
|
# Restores the original terminal state after the block executes.
|
|
31
41
|
#
|
|
32
|
-
# +width+:: width of the test terminal (default:
|
|
33
|
-
# +height+:: height of the test terminal (default:
|
|
42
|
+
# +width+:: width of the test terminal (default: 80)
|
|
43
|
+
# +height+:: height of the test terminal (default: 24)
|
|
44
|
+
#
|
|
45
|
+
# +timeout+:: maximum execution time in seconds (default: 2). Pass nil to disable.
|
|
34
46
|
#
|
|
35
47
|
# If a block is given, it is executed within the test terminal context.
|
|
36
|
-
def with_test_terminal(width =
|
|
48
|
+
def with_test_terminal(width = 80, height = 24, timeout: 2)
|
|
37
49
|
RatatuiRuby.init_test_terminal(width, height)
|
|
38
|
-
|
|
50
|
+
# Flush any lingering events from previous tests
|
|
51
|
+
while RatatuiRuby.poll_event; end
|
|
52
|
+
|
|
53
|
+
RatatuiRuby.stub :init_terminal, nil do
|
|
54
|
+
RatatuiRuby.stub :restore_terminal, nil do
|
|
55
|
+
begin
|
|
56
|
+
@_ratatui_test_terminal_active = true
|
|
57
|
+
if timeout
|
|
58
|
+
Timeout.timeout(timeout) do
|
|
59
|
+
yield
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
yield
|
|
63
|
+
end
|
|
64
|
+
ensure
|
|
65
|
+
@_ratatui_test_terminal_active = false
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
39
69
|
ensure
|
|
40
70
|
RatatuiRuby.restore_terminal
|
|
41
71
|
end
|
|
@@ -61,15 +91,128 @@ module RatatuiRuby
|
|
|
61
91
|
end
|
|
62
92
|
|
|
63
93
|
##
|
|
64
|
-
# Injects
|
|
94
|
+
# Injects an event into the event queue for testing.
|
|
95
|
+
#
|
|
96
|
+
# Pass any RatatuiRuby::Event object. The event will be returned by
|
|
97
|
+
# the next call to RatatuiRuby.poll_event.
|
|
98
|
+
#
|
|
99
|
+
# Raises a +RuntimeError+ if called outside of a +with_test_terminal+ block.
|
|
65
100
|
#
|
|
66
|
-
#
|
|
67
|
-
# +data+:: a Hash containing event data
|
|
101
|
+
# == Examples
|
|
68
102
|
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
# with_test_terminal do
|
|
104
|
+
# # Key events
|
|
105
|
+
# inject_event(RatatuiRuby::Event::Key.new(code: "q"))
|
|
106
|
+
# inject_event(RatatuiRuby::Event::Key.new(code: "s", modifiers: ["ctrl"]))
|
|
107
|
+
#
|
|
108
|
+
# # Mouse events
|
|
109
|
+
# inject_event(RatatuiRuby::Event::Mouse.new(kind: "down", button: "left", x: 10, y: 5))
|
|
110
|
+
#
|
|
111
|
+
# # Resize events
|
|
112
|
+
# inject_event(RatatuiRuby::Event::Resize.new(width: 120, height: 40))
|
|
113
|
+
#
|
|
114
|
+
# # Paste events
|
|
115
|
+
# inject_event(RatatuiRuby::Event::Paste.new(content: "Hello"))
|
|
116
|
+
#
|
|
117
|
+
# # Focus events
|
|
118
|
+
# inject_event(RatatuiRuby::Event::FocusGained.new)
|
|
119
|
+
# inject_event(RatatuiRuby::Event::FocusLost.new)
|
|
120
|
+
# end
|
|
121
|
+
def inject_event(event)
|
|
122
|
+
unless @_ratatui_test_terminal_active
|
|
123
|
+
raise "Events must be injected inside a `with_test_terminal` block. " \
|
|
124
|
+
"Calling this method outside the block causes a race condition where the event " \
|
|
125
|
+
"is flushed before the application starts."
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
case event
|
|
129
|
+
when RatatuiRuby::Event::Key
|
|
130
|
+
RatatuiRuby.inject_test_event("key", { code: event.code, modifiers: event.modifiers })
|
|
131
|
+
when RatatuiRuby::Event::Mouse
|
|
132
|
+
RatatuiRuby.inject_test_event("mouse", {
|
|
133
|
+
kind: event.kind,
|
|
134
|
+
button: event.button,
|
|
135
|
+
x: event.x,
|
|
136
|
+
y: event.y,
|
|
137
|
+
modifiers: event.modifiers
|
|
138
|
+
})
|
|
139
|
+
when RatatuiRuby::Event::Resize
|
|
140
|
+
RatatuiRuby.inject_test_event("resize", { width: event.width, height: event.height })
|
|
141
|
+
when RatatuiRuby::Event::Paste
|
|
142
|
+
RatatuiRuby.inject_test_event("paste", { content: event.content })
|
|
143
|
+
when RatatuiRuby::Event::FocusGained
|
|
144
|
+
RatatuiRuby.inject_test_event("focus_gained", {})
|
|
145
|
+
when RatatuiRuby::Event::FocusLost
|
|
146
|
+
RatatuiRuby.inject_test_event("focus_lost", {})
|
|
147
|
+
else
|
|
148
|
+
raise ArgumentError, "Unknown event type: #{event.class}"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
##
|
|
153
|
+
# Injects multiple Key events into the queue.
|
|
154
|
+
#
|
|
155
|
+
# Supports multiple formats for convenience:
|
|
156
|
+
#
|
|
157
|
+
# * String: Converted to a Key event with that code.
|
|
158
|
+
# * Symbol: Parsed as modifier_code (e.g., <tt>:ctrl_c</tt>, <tt>:enter</tt>).
|
|
159
|
+
# * Hash: Passed to Key.new constructor.
|
|
160
|
+
# * Key: Passed directly.
|
|
161
|
+
#
|
|
162
|
+
# == Examples
|
|
163
|
+
#
|
|
164
|
+
# with_test_terminal do
|
|
165
|
+
# inject_keys("a", "b", "c")
|
|
166
|
+
# inject_keys(:enter, :esc)
|
|
167
|
+
# inject_keys(:ctrl_c, :alt_shift_left)
|
|
168
|
+
# inject_keys("j", { code: "k", modifiers: ["ctrl"] })
|
|
169
|
+
# end
|
|
170
|
+
def inject_keys(*args)
|
|
171
|
+
args.each do |arg|
|
|
172
|
+
event = case arg
|
|
173
|
+
when String
|
|
174
|
+
RatatuiRuby::Event::Key.new(code: arg)
|
|
175
|
+
when Symbol
|
|
176
|
+
parts = arg.to_s.split("_")
|
|
177
|
+
code = parts.pop
|
|
178
|
+
modifiers = parts
|
|
179
|
+
RatatuiRuby::Event::Key.new(code: code, modifiers: modifiers)
|
|
180
|
+
when Hash
|
|
181
|
+
RatatuiRuby::Event::Key.new(**arg)
|
|
182
|
+
when RatatuiRuby::Event::Key
|
|
183
|
+
arg
|
|
184
|
+
else
|
|
185
|
+
raise ArgumentError, "Invalid key argument: #{arg.inspect}. Expected String, Symbol, Hash, or Key event."
|
|
186
|
+
end
|
|
187
|
+
inject_event(event)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
alias inject_key inject_keys
|
|
191
|
+
|
|
192
|
+
##
|
|
193
|
+
# Returns the cell attributes at the given coordinates.
|
|
194
|
+
#
|
|
195
|
+
# get_cell(0, 0)
|
|
196
|
+
# # => { "symbol" => "H", "fg" => :red, "bg" => nil }
|
|
197
|
+
def get_cell(x, y)
|
|
198
|
+
RatatuiRuby.get_cell_at(x, y)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
##
|
|
202
|
+
# Asserts that the cell at the given coordinates has the expected attributes.
|
|
203
|
+
#
|
|
204
|
+
# assert_cell_style(0, 0, char: "H", fg: :red)
|
|
205
|
+
def assert_cell_style(x, y, **expected_attributes)
|
|
206
|
+
cell = get_cell(x, y)
|
|
207
|
+
expected_attributes.each do |key, value|
|
|
208
|
+
actual_value = cell.public_send(key)
|
|
209
|
+
if value.nil?
|
|
210
|
+
assert_nil actual_value, "Expected cell at (#{x}, #{y}) to have #{key}=nil, but got #{actual_value.inspect}"
|
|
211
|
+
else
|
|
212
|
+
assert_equal value, actual_value, "Expected cell at (#{x}, #{y}) to have #{key}=#{value.inspect}, but got #{actual_value.inspect}"
|
|
213
|
+
end
|
|
214
|
+
end
|
|
73
215
|
end
|
|
74
216
|
end
|
|
75
217
|
end
|
|
218
|
+
|
data/lib/ratatui_ruby/version.rb
CHANGED
data/lib/ratatui_ruby.rb
CHANGED
|
@@ -12,9 +12,12 @@ require_relative "ratatui_ruby/schema/constraint"
|
|
|
12
12
|
require_relative "ratatui_ruby/schema/list"
|
|
13
13
|
require_relative "ratatui_ruby/schema/style"
|
|
14
14
|
require_relative "ratatui_ruby/schema/gauge"
|
|
15
|
+
require_relative "ratatui_ruby/schema/line_gauge"
|
|
15
16
|
require_relative "ratatui_ruby/schema/table"
|
|
16
17
|
require_relative "ratatui_ruby/schema/tabs"
|
|
17
18
|
require_relative "ratatui_ruby/schema/bar_chart"
|
|
19
|
+
require_relative "ratatui_ruby/schema/bar_chart/bar"
|
|
20
|
+
require_relative "ratatui_ruby/schema/bar_chart/bar_group"
|
|
18
21
|
require_relative "ratatui_ruby/schema/sparkline"
|
|
19
22
|
require_relative "ratatui_ruby/schema/chart"
|
|
20
23
|
require_relative "ratatui_ruby/schema/clear"
|
|
@@ -23,7 +26,14 @@ require_relative "ratatui_ruby/schema/overlay"
|
|
|
23
26
|
require_relative "ratatui_ruby/schema/center"
|
|
24
27
|
require_relative "ratatui_ruby/schema/scrollbar"
|
|
25
28
|
require_relative "ratatui_ruby/schema/canvas"
|
|
29
|
+
require_relative "ratatui_ruby/schema/shape/label"
|
|
26
30
|
require_relative "ratatui_ruby/schema/calendar"
|
|
31
|
+
require_relative "ratatui_ruby/schema/ratatui_logo"
|
|
32
|
+
require_relative "ratatui_ruby/schema/ratatui_mascot"
|
|
33
|
+
require_relative "ratatui_ruby/schema/text"
|
|
34
|
+
require_relative "ratatui_ruby/schema/draw"
|
|
35
|
+
require_relative "ratatui_ruby/event"
|
|
36
|
+
require_relative "ratatui_ruby/cell"
|
|
27
37
|
|
|
28
38
|
begin
|
|
29
39
|
require "ratatui_ruby/ratatui_ruby"
|
|
@@ -32,20 +42,53 @@ rescue LoadError
|
|
|
32
42
|
require_relative "ratatui_ruby/ratatui_ruby"
|
|
33
43
|
end
|
|
34
44
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
45
|
+
# Main entry point for the library.
|
|
46
|
+
#
|
|
47
|
+
# Terminal UIs require low-level control using C/Rust and high-level abstraction in Ruby.
|
|
48
|
+
#
|
|
49
|
+
# This module bridges the gap. It provides the native methods to initialize the terminal, handle raw mode, and render the widget tree.
|
|
50
|
+
#
|
|
51
|
+
# Use `RatatuiRuby.run` to start your application.
|
|
37
52
|
module RatatuiRuby
|
|
38
53
|
# Generic error class for RatatuiRuby.
|
|
39
54
|
class Error < StandardError; end
|
|
40
55
|
|
|
41
56
|
##
|
|
42
|
-
# :method: init_terminal
|
|
43
|
-
# :call-seq: init_terminal() -> nil
|
|
44
|
-
#
|
|
45
57
|
# Initializes the terminal for TUI mode.
|
|
46
58
|
# Enters alternate screen and enables raw mode.
|
|
47
59
|
#
|
|
48
|
-
#
|
|
60
|
+
# [focus_events] whether to enable focus gain/loss events (default: true).
|
|
61
|
+
# [bracketed_paste] whether to enable bracketed paste mode (default: true).
|
|
62
|
+
def self.init_terminal(focus_events: true, bracketed_paste: true)
|
|
63
|
+
_init_terminal(focus_events, bracketed_paste)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@experimental_warnings = true
|
|
67
|
+
class << self
|
|
68
|
+
##
|
|
69
|
+
# :attr_accessor: experimental_warnings
|
|
70
|
+
# Whether to show warnings when using experimental features (default: true).
|
|
71
|
+
attr_accessor :experimental_warnings
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Warns about usage of an experimental feature unless warnings are suppressed.
|
|
76
|
+
#
|
|
77
|
+
# [feature_name] String name of the feature (e.g., "Paragraph#line_count")
|
|
78
|
+
#
|
|
79
|
+
# This warns only once per feature name per session.
|
|
80
|
+
def self.warn_experimental_feature(feature_name)
|
|
81
|
+
return unless experimental_warnings
|
|
82
|
+
|
|
83
|
+
@warned_features ||= {}
|
|
84
|
+
return if @warned_features[feature_name]
|
|
85
|
+
|
|
86
|
+
warn "WARNING: #{feature_name} is an experimental feature and may change in future versions. Disable this warning with RatatuiRuby.experimental_warnings = false."
|
|
87
|
+
@warned_features[feature_name] = true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# (Native method _init_terminal implemented in Rust)
|
|
91
|
+
private_class_method :_init_terminal
|
|
49
92
|
|
|
50
93
|
##
|
|
51
94
|
# :method: restore_terminal
|
|
@@ -67,14 +110,49 @@ module RatatuiRuby
|
|
|
67
110
|
|
|
68
111
|
##
|
|
69
112
|
# :method: poll_event
|
|
70
|
-
# :call-seq: poll_event() ->
|
|
113
|
+
# :call-seq: poll_event() -> Event, nil
|
|
71
114
|
#
|
|
72
|
-
#
|
|
115
|
+
# Checks for user input.
|
|
73
116
|
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
117
|
+
# Returns a discrete event (Key, Mouse, Resize) if one is available in the queue.
|
|
118
|
+
# Returns nil immediately if the queue is empty (non-blocking).
|
|
76
119
|
#
|
|
77
|
-
#
|
|
120
|
+
# === Example
|
|
121
|
+
#
|
|
122
|
+
# event = RatatuiRuby.poll_event
|
|
123
|
+
# puts "Key pressed" if event.is_a?(RatatuiRuby::Event::Key)
|
|
124
|
+
#
|
|
125
|
+
def self.poll_event
|
|
126
|
+
raw = _poll_event
|
|
127
|
+
return nil if raw.nil?
|
|
128
|
+
|
|
129
|
+
case raw[:type]
|
|
130
|
+
when :key
|
|
131
|
+
Event::Key.new(code: raw[:code], modifiers: raw[:modifiers] || [])
|
|
132
|
+
when :mouse
|
|
133
|
+
Event::Mouse.new(
|
|
134
|
+
kind: raw[:kind].to_s,
|
|
135
|
+
x: raw[:x],
|
|
136
|
+
y: raw[:y],
|
|
137
|
+
button: raw[:button].to_s,
|
|
138
|
+
modifiers: raw[:modifiers] || []
|
|
139
|
+
)
|
|
140
|
+
when :resize
|
|
141
|
+
Event::Resize.new(width: raw[:width], height: raw[:height])
|
|
142
|
+
when :paste
|
|
143
|
+
Event::Paste.new(content: raw[:content])
|
|
144
|
+
when :focus_gained
|
|
145
|
+
Event::FocusGained.new
|
|
146
|
+
when :focus_lost
|
|
147
|
+
Event::FocusLost.new
|
|
148
|
+
else
|
|
149
|
+
# Fallback for unknown events, though ideally we cover them all
|
|
150
|
+
nil
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# (Native method _poll_event implemented in Rust)
|
|
155
|
+
private_class_method :_poll_event
|
|
78
156
|
|
|
79
157
|
##
|
|
80
158
|
# :method: inject_test_event
|
|
@@ -89,21 +167,63 @@ module RatatuiRuby
|
|
|
89
167
|
# (Native method implemented in Rust)
|
|
90
168
|
|
|
91
169
|
##
|
|
92
|
-
#
|
|
93
|
-
#
|
|
170
|
+
# :method: run
|
|
171
|
+
# :call-seq: run { |session| ... } -> Object
|
|
172
|
+
#
|
|
173
|
+
# Starts the TUI application lifecycle.
|
|
174
|
+
#
|
|
175
|
+
# Managing generic setup/teardown (raw mode, alternate screen) manualy is error-prone. If your app crashes, the terminal might be left in a broken state.
|
|
176
|
+
#
|
|
177
|
+
# This method handles the safety net. It initializes the terminal, yields a {Session}, and ensures the terminal state is restored even if exceptions occur.
|
|
178
|
+
#
|
|
179
|
+
# === Example
|
|
94
180
|
#
|
|
95
|
-
# RatatuiRuby.
|
|
96
|
-
# draw
|
|
97
|
-
#
|
|
98
|
-
# break if event && event[:type] == :key && event[:code] == "q"
|
|
181
|
+
# RatatuiRuby.run(focus_events: false) do |tui|
|
|
182
|
+
# tui.draw(tui.paragraph(text: "Hi"))
|
|
183
|
+
# sleep 1
|
|
99
184
|
# end
|
|
100
|
-
def self.
|
|
101
|
-
require_relative "ratatui_ruby/
|
|
102
|
-
init_terminal
|
|
103
|
-
|
|
104
|
-
yield DSL.new
|
|
105
|
-
end
|
|
185
|
+
def self.run(focus_events: true, bracketed_paste: true)
|
|
186
|
+
require_relative "ratatui_ruby/session"
|
|
187
|
+
init_terminal(focus_events: focus_events, bracketed_paste: bracketed_paste)
|
|
188
|
+
yield Session.new
|
|
106
189
|
ensure
|
|
107
190
|
restore_terminal
|
|
108
191
|
end
|
|
192
|
+
|
|
193
|
+
##
|
|
194
|
+
# :method: get_cell_at
|
|
195
|
+
# :call-seq: get_cell_at(x, y) -> Cell
|
|
196
|
+
#
|
|
197
|
+
# Inspects the terminal buffer at specific coordinates.
|
|
198
|
+
#
|
|
199
|
+
# When writing tests, you need to verify that your widget drew the correct characters and styles.
|
|
200
|
+
# This method provides deep inspection of the cell's state (symbol, colors, modifiers).
|
|
201
|
+
#
|
|
202
|
+
# Returns a {Cell} object.
|
|
203
|
+
#
|
|
204
|
+
# Values depend on what the backend has rendered. If nothing has been rendered to a cell, it may contain defaults (empty symbol, nil colors).
|
|
205
|
+
#
|
|
206
|
+
# === Example
|
|
207
|
+
#
|
|
208
|
+
# cell = RatatuiRuby.get_cell_at(10, 5)
|
|
209
|
+
# expect(cell.symbol).to eq("X")
|
|
210
|
+
# expect(cell.fg).to eq(:red)
|
|
211
|
+
# expect(cell).to be_bold
|
|
212
|
+
#
|
|
213
|
+
def self.get_cell_at(x, y)
|
|
214
|
+
raw = _get_cell_at(x, y)
|
|
215
|
+
Cell.new(
|
|
216
|
+
char: raw["char"],
|
|
217
|
+
fg: raw["fg"],
|
|
218
|
+
bg: raw["bg"],
|
|
219
|
+
modifiers: raw["modifiers"] || []
|
|
220
|
+
)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# (Native method _get_cell_at implemented in Rust)
|
|
224
|
+
private_class_method :_get_cell_at
|
|
225
|
+
|
|
226
|
+
# Hide native Layout._split helper
|
|
227
|
+
Layout.singleton_class.send(:private, :_split)
|
|
228
|
+
|
|
109
229
|
end
|