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,166 @@
|
|
|
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
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
7
|
+
require "ratatui_ruby"
|
|
8
|
+
require "test_helper"
|
|
9
|
+
require_relative "app"
|
|
10
|
+
|
|
11
|
+
class TestRichTextApp < Minitest::Test
|
|
12
|
+
include RatatuiRuby::TestHelper
|
|
13
|
+
|
|
14
|
+
include RatatuiRuby::TestHelper
|
|
15
|
+
|
|
16
|
+
def test_simple_span
|
|
17
|
+
span = RatatuiRuby::Text::Span.new(content: "hello", style: nil)
|
|
18
|
+
assert_equal "hello", span.content
|
|
19
|
+
assert_nil span.style
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_styled_span
|
|
23
|
+
style = RatatuiRuby::Style.new(fg: :red, modifiers: [:bold])
|
|
24
|
+
span = RatatuiRuby::Text::Span.new(content: "error", style:)
|
|
25
|
+
assert_equal "error", span.content
|
|
26
|
+
assert_equal style, span.style
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_span_styled_helper
|
|
30
|
+
style = RatatuiRuby::Style.new(modifiers: [:italic])
|
|
31
|
+
span = RatatuiRuby::Text::Span.styled("italic text", style)
|
|
32
|
+
assert_equal "italic text", span.content
|
|
33
|
+
assert_equal style, span.style
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_span_styled_helper_without_style
|
|
37
|
+
span = RatatuiRuby::Text::Span.styled("plain")
|
|
38
|
+
assert_equal "plain", span.content
|
|
39
|
+
assert_nil span.style
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_simple_line
|
|
43
|
+
spans = [
|
|
44
|
+
RatatuiRuby::Text::Span.new(content: "hello", style: nil),
|
|
45
|
+
RatatuiRuby::Text::Span.new(content: " world", style: nil)
|
|
46
|
+
]
|
|
47
|
+
line = RatatuiRuby::Text::Line.new(spans:)
|
|
48
|
+
assert_equal spans, line.spans
|
|
49
|
+
assert_nil line.alignment
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_line_with_alignment
|
|
53
|
+
spans = [RatatuiRuby::Text::Span.new(content: "centered", style: nil)]
|
|
54
|
+
line = RatatuiRuby::Text::Line.new(spans:, alignment: :center)
|
|
55
|
+
assert_equal :center, line.alignment
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_line_from_string
|
|
59
|
+
line = RatatuiRuby::Text::Line.from_string("test content")
|
|
60
|
+
assert_equal 1, line.spans.length
|
|
61
|
+
assert_equal "test content", line.spans[0].content
|
|
62
|
+
assert_nil line.spans[0].style
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_line_from_string_with_alignment
|
|
66
|
+
line = RatatuiRuby::Text::Line.from_string("right aligned", alignment: :right)
|
|
67
|
+
assert_equal :right, line.alignment
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_paragraph_with_string
|
|
71
|
+
para = RatatuiRuby::Paragraph.new(text: "simple text")
|
|
72
|
+
assert_equal "simple text", para.text
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_paragraph_with_span
|
|
76
|
+
span = RatatuiRuby::Text::Span.new(content: "bold", style: RatatuiRuby::Style.new(modifiers: [:bold]))
|
|
77
|
+
para = RatatuiRuby::Paragraph.new(text: span)
|
|
78
|
+
assert_equal span, para.text
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_paragraph_with_line
|
|
82
|
+
line = RatatuiRuby::Text::Line.new(
|
|
83
|
+
spans: [
|
|
84
|
+
RatatuiRuby::Text::Span.new(content: "hello ", style: nil),
|
|
85
|
+
RatatuiRuby::Text::Span.new(content: "world", style: RatatuiRuby::Style.new(fg: :green))
|
|
86
|
+
]
|
|
87
|
+
)
|
|
88
|
+
para = RatatuiRuby::Paragraph.new(text: line)
|
|
89
|
+
assert_equal line, para.text
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_paragraph_with_multiple_lines
|
|
93
|
+
lines = [
|
|
94
|
+
RatatuiRuby::Text::Line.new(
|
|
95
|
+
spans: [RatatuiRuby::Text::Span.new(content: "line 1", style: nil)]
|
|
96
|
+
),
|
|
97
|
+
RatatuiRuby::Text::Line.new(
|
|
98
|
+
spans: [RatatuiRuby::Text::Span.new(content: "line 2", style: RatatuiRuby::Style.new(fg: :red))]
|
|
99
|
+
)
|
|
100
|
+
]
|
|
101
|
+
para = RatatuiRuby::Paragraph.new(text: lines)
|
|
102
|
+
assert_equal lines, para.text
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_paragraph_renders_with_rich_text
|
|
106
|
+
with_test_terminal do
|
|
107
|
+
# Test that a paragraph with rich text can be rendered without error
|
|
108
|
+
line = RatatuiRuby::Text::Line.new(
|
|
109
|
+
spans: [
|
|
110
|
+
RatatuiRuby::Text::Span.new(content: "normal ", style: nil),
|
|
111
|
+
RatatuiRuby::Text::Span.new(content: "bold", style: RatatuiRuby::Style.new(modifiers: [:bold])),
|
|
112
|
+
RatatuiRuby::Text::Span.new(content: " text", style: nil)
|
|
113
|
+
]
|
|
114
|
+
)
|
|
115
|
+
para = RatatuiRuby::Paragraph.new(
|
|
116
|
+
text: line,
|
|
117
|
+
block: RatatuiRuby::Block.new(title: "Test", borders: [:all])
|
|
118
|
+
)
|
|
119
|
+
# Should not raise an error when rendering
|
|
120
|
+
RatatuiRuby.draw(para)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_paragraph_renders_multiple_rich_lines
|
|
125
|
+
with_test_terminal do
|
|
126
|
+
lines = [
|
|
127
|
+
RatatuiRuby::Text::Line.new(
|
|
128
|
+
spans: [
|
|
129
|
+
RatatuiRuby::Text::Span.new(content: "✓ ", style: RatatuiRuby::Style.new(fg: :green, modifiers: [:bold])),
|
|
130
|
+
RatatuiRuby::Text::Span.new(content: "Complete", style: nil)
|
|
131
|
+
]
|
|
132
|
+
),
|
|
133
|
+
RatatuiRuby::Text::Line.new(
|
|
134
|
+
spans: [
|
|
135
|
+
RatatuiRuby::Text::Span.new(content: "✗ ", style: RatatuiRuby::Style.new(fg: :red, modifiers: [:bold])),
|
|
136
|
+
RatatuiRuby::Text::Span.new(content: "Failed", style: nil)
|
|
137
|
+
]
|
|
138
|
+
)
|
|
139
|
+
]
|
|
140
|
+
para = RatatuiRuby::Paragraph.new(text: lines)
|
|
141
|
+
# Should not raise an error when rendering
|
|
142
|
+
RatatuiRuby.draw(para)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
require_relative "app"
|
|
148
|
+
|
|
149
|
+
class TestRichTextApp < Minitest::Test
|
|
150
|
+
include RatatuiRuby::TestHelper
|
|
151
|
+
|
|
152
|
+
include RatatuiRuby::TestHelper
|
|
153
|
+
|
|
154
|
+
def test_app_runs
|
|
155
|
+
with_test_terminal do
|
|
156
|
+
inject_key(:q)
|
|
157
|
+
RichTextApp.new.run
|
|
158
|
+
|
|
159
|
+
assert buffer_content.any? { |line| line.include?("Simple Rich Text") }
|
|
160
|
+
assert buffer_content.any? { |line| line.include?("Status Report") }
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
+
|
|
7
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
8
|
+
require "ratatui_ruby"
|
|
9
|
+
|
|
10
|
+
# Demo: Scrollable Paragraph
|
|
11
|
+
# Shows how to scroll through long text content using arrow keys
|
|
12
|
+
class ScrollTextApp
|
|
13
|
+
def initialize
|
|
14
|
+
@scroll_x = 0
|
|
15
|
+
@scroll_y = 0
|
|
16
|
+
|
|
17
|
+
@lines = (1..100).map { |i| "Line #{i}: This is a long line of text that can be scrolled horizontally" }
|
|
18
|
+
@hotkey_style = RatatuiRuby::Style.new(modifiers: [:bold, :underlined])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run
|
|
22
|
+
RatatuiRuby.run do
|
|
23
|
+
loop do
|
|
24
|
+
draw
|
|
25
|
+
break if handle_input == :quit
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def render
|
|
31
|
+
draw
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def handle_input
|
|
35
|
+
case RatatuiRuby.poll_event
|
|
36
|
+
in {type: :key, code: "q"} | {type: :key, code: "c", modifiers: ["ctrl"]}
|
|
37
|
+
:quit
|
|
38
|
+
in type: :key, code: "up"
|
|
39
|
+
@scroll_y = [@scroll_y - 1, 0].max
|
|
40
|
+
in type: :key, code: "down"
|
|
41
|
+
@scroll_y = [@scroll_y + 1, @lines.length].min
|
|
42
|
+
in type: :key, code: "left"
|
|
43
|
+
@scroll_x = [@scroll_x - 1, 0].max
|
|
44
|
+
in type: :key, code: "right"
|
|
45
|
+
@scroll_x = [@scroll_x + 1, 100].min
|
|
46
|
+
else
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def draw
|
|
54
|
+
text = @lines.join("\n")
|
|
55
|
+
|
|
56
|
+
# Main content
|
|
57
|
+
main_paragraph = RatatuiRuby::Paragraph.new(
|
|
58
|
+
text: text,
|
|
59
|
+
scroll: [@scroll_y, @scroll_x],
|
|
60
|
+
block: RatatuiRuby::Block.new(
|
|
61
|
+
title: "Scrollable Text (#{text.lines.count} lines)",
|
|
62
|
+
borders: [:all]
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Bottom control panel
|
|
67
|
+
control_panel = RatatuiRuby::Block.new(
|
|
68
|
+
title: "Controls",
|
|
69
|
+
borders: [:all],
|
|
70
|
+
children: [
|
|
71
|
+
RatatuiRuby::Paragraph.new(
|
|
72
|
+
text: [
|
|
73
|
+
RatatuiRuby::Text::Line.new(spans: [
|
|
74
|
+
RatatuiRuby::Text::Span.new(content: "NAVIGATION (Size: #{main_paragraph.line_count(65535)}x#{main_paragraph.line_width})", style: RatatuiRuby::Style.new(modifiers: [:bold]))
|
|
75
|
+
]),
|
|
76
|
+
RatatuiRuby::Text::Line.new(spans: [
|
|
77
|
+
RatatuiRuby::Text::Span.new(content: "↑/↓", style: @hotkey_style),
|
|
78
|
+
RatatuiRuby::Text::Span.new(content: ": Vert Scroll (#{@scroll_y}/#{main_paragraph.line_count(65535)}) "),
|
|
79
|
+
RatatuiRuby::Text::Span.new(content: "←/→", style: @hotkey_style),
|
|
80
|
+
RatatuiRuby::Text::Span.new(content: ": Horz Scroll (#{@scroll_x}/#{main_paragraph.line_width}) "),
|
|
81
|
+
RatatuiRuby::Text::Span.new(content: "q", style: @hotkey_style),
|
|
82
|
+
RatatuiRuby::Text::Span.new(content: ": Quit")
|
|
83
|
+
])
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
]
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Vertical Layout
|
|
90
|
+
layout = RatatuiRuby::Layout.new(
|
|
91
|
+
direction: :vertical,
|
|
92
|
+
constraints: [
|
|
93
|
+
RatatuiRuby::Constraint.fill(1),
|
|
94
|
+
RatatuiRuby::Constraint.length(5),
|
|
95
|
+
],
|
|
96
|
+
children: [main_paragraph, control_panel]
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
RatatuiRuby.draw(layout)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
ScrollTextApp.new.run if __FILE__ == $PROGRAM_NAME
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
5
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
6
|
+
|
|
7
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
8
|
+
require "minitest/autorun"
|
|
9
|
+
require "ratatui_ruby/test_helper"
|
|
10
|
+
|
|
11
|
+
require_relative "app"
|
|
12
|
+
|
|
13
|
+
class TestScrollTextApp < Minitest::Test
|
|
14
|
+
include RatatuiRuby::TestHelper
|
|
15
|
+
|
|
16
|
+
def setup
|
|
17
|
+
@app = ScrollTextApp.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_demo_initialization
|
|
21
|
+
assert_instance_of ScrollTextApp, @app
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_initial_rendering
|
|
25
|
+
with_test_terminal do
|
|
26
|
+
# Queue quit
|
|
27
|
+
inject_key(:q)
|
|
28
|
+
|
|
29
|
+
@app.run
|
|
30
|
+
|
|
31
|
+
content = buffer_content
|
|
32
|
+
|
|
33
|
+
# Should show Line 1 somewhere in the output
|
|
34
|
+
assert content.any? { |line| line.include?("Line 1") }
|
|
35
|
+
# Should show controls
|
|
36
|
+
assert content.any? { |line| line.include?("Controls") }
|
|
37
|
+
assert content.any? { |line| line.include?("Vert Scroll (0/102)") }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_scroll_down
|
|
42
|
+
with_test_terminal do
|
|
43
|
+
# Scroll down then quit
|
|
44
|
+
inject_keys(:down, :q)
|
|
45
|
+
|
|
46
|
+
@app.run
|
|
47
|
+
|
|
48
|
+
content = buffer_content
|
|
49
|
+
|
|
50
|
+
# Should show controls with updated Y position
|
|
51
|
+
assert content.any? { |line| line.include?("Vert Scroll (1/102)") }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_scroll_right
|
|
56
|
+
with_test_terminal do
|
|
57
|
+
# Scroll right then quit
|
|
58
|
+
inject_keys(:right, :q)
|
|
59
|
+
|
|
60
|
+
@app.run
|
|
61
|
+
|
|
62
|
+
content = buffer_content
|
|
63
|
+
|
|
64
|
+
# Should render without error
|
|
65
|
+
assert content.any? { |line| line.include?("Controls") }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_scroll_left_at_edge
|
|
70
|
+
with_test_terminal do
|
|
71
|
+
# Scroll left then quit (boundary test)
|
|
72
|
+
inject_keys(:left, :q)
|
|
73
|
+
|
|
74
|
+
@app.run
|
|
75
|
+
|
|
76
|
+
content = buffer_content
|
|
77
|
+
|
|
78
|
+
# Should still show the controls
|
|
79
|
+
assert content.any? { |line| line.include?("Vert Scroll") }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def test_scroll_up_at_top
|
|
84
|
+
with_test_terminal do
|
|
85
|
+
# Scroll up then quit (boundary test)
|
|
86
|
+
inject_keys(:up, :q)
|
|
87
|
+
|
|
88
|
+
@app.run
|
|
89
|
+
|
|
90
|
+
content = buffer_content
|
|
91
|
+
|
|
92
|
+
# Should still show the controls
|
|
93
|
+
assert content.any? { |line| line.include?("Controls") }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_multiple_scrolls
|
|
98
|
+
with_test_terminal do
|
|
99
|
+
# Scroll down and right multiple times
|
|
100
|
+
inject_keys(:down, :down, :right, :right, :right, :q)
|
|
101
|
+
|
|
102
|
+
@app.run
|
|
103
|
+
|
|
104
|
+
content = buffer_content
|
|
105
|
+
|
|
106
|
+
# Should render without error
|
|
107
|
+
assert content.any? { |line| line.include?("Scrollable Text") }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
|
|
7
|
+
require "ratatui_ruby"
|
|
8
|
+
|
|
9
|
+
# Simple Scrollbar Demo
|
|
10
|
+
class ScrollbarDemoApp
|
|
11
|
+
def initialize
|
|
12
|
+
@scroll_position = 0
|
|
13
|
+
@content_length = 50
|
|
14
|
+
@lines = (1..@content_length).map { |i| "Line #{i}" }
|
|
15
|
+
@orientation_index = 0
|
|
16
|
+
@orientations = [
|
|
17
|
+
:vertical,
|
|
18
|
+
:vertical_right,
|
|
19
|
+
:vertical_left,
|
|
20
|
+
:horizontal,
|
|
21
|
+
:horizontal_bottom,
|
|
22
|
+
:horizontal_top
|
|
23
|
+
]
|
|
24
|
+
@theme_index = 0
|
|
25
|
+
@themes = [
|
|
26
|
+
{
|
|
27
|
+
name: "Standard",
|
|
28
|
+
track_symbol: nil,
|
|
29
|
+
thumb_symbol: "█",
|
|
30
|
+
track_style: nil,
|
|
31
|
+
thumb_style: nil,
|
|
32
|
+
begin_symbol: nil,
|
|
33
|
+
end_symbol: nil
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "Rounded",
|
|
37
|
+
track_symbol: "│",
|
|
38
|
+
thumb_symbol: "┃",
|
|
39
|
+
track_style: { fg: "dark_gray" },
|
|
40
|
+
thumb_style: { fg: "cyan" },
|
|
41
|
+
begin_symbol: "▲",
|
|
42
|
+
end_symbol: "▼"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "ASCII",
|
|
46
|
+
track_symbol: "|",
|
|
47
|
+
thumb_symbol: "#",
|
|
48
|
+
track_style: { fg: "white" },
|
|
49
|
+
thumb_style: { fg: "red" },
|
|
50
|
+
begin_symbol: "^",
|
|
51
|
+
end_symbol: "v"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "Minimal",
|
|
55
|
+
track_symbol: " ",
|
|
56
|
+
thumb_symbol: "▐",
|
|
57
|
+
track_style: nil,
|
|
58
|
+
thumb_style: { fg: "yellow" },
|
|
59
|
+
begin_symbol: nil,
|
|
60
|
+
end_symbol: nil
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def run
|
|
66
|
+
RatatuiRuby.run do
|
|
67
|
+
loop do
|
|
68
|
+
draw
|
|
69
|
+
event = RatatuiRuby.poll_event
|
|
70
|
+
break if event == "q" || event == :ctrl_c
|
|
71
|
+
|
|
72
|
+
handle_event(event)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private def handle_event(event)
|
|
78
|
+
return unless event
|
|
79
|
+
|
|
80
|
+
if event.mouse?
|
|
81
|
+
case event.kind
|
|
82
|
+
when "scroll_up"
|
|
83
|
+
@scroll_position = [@scroll_position - 1, 0].max
|
|
84
|
+
when "scroll_down"
|
|
85
|
+
@scroll_position = [@scroll_position + 1, @content_length].min
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if event.key? && event.to_s == "s"
|
|
90
|
+
@theme_index = (@theme_index + 1) % @themes.length
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if event.key? && event.to_s == "o"
|
|
94
|
+
@orientation_index = (@orientation_index + 1) % @orientations.length
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private def draw
|
|
99
|
+
# Calculate visible lines based on scroll position
|
|
100
|
+
# In a real app, you'd want to know the height of the available area.
|
|
101
|
+
# For this demo, we'll just show all lines but offset the text.
|
|
102
|
+
visible_lines = @lines[@scroll_position..-1] || []
|
|
103
|
+
|
|
104
|
+
# Paragraph with content
|
|
105
|
+
theme = @themes[@theme_index]
|
|
106
|
+
orientation = @orientations[@orientation_index]
|
|
107
|
+
|
|
108
|
+
p = RatatuiRuby::Paragraph.new(
|
|
109
|
+
text: visible_lines.join("\n"),
|
|
110
|
+
block: RatatuiRuby::Block.new(
|
|
111
|
+
titles: [
|
|
112
|
+
{ content: "Scroll with Mouse Wheel | Theme: #{theme[:name]} | Orientation: #{orientation}" },
|
|
113
|
+
{ content: "Press 's' to cycle theme, 'o' to cycle orientation", position: :bottom, alignment: :center }
|
|
114
|
+
],
|
|
115
|
+
borders: [:all]
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Scrollbar
|
|
120
|
+
s = RatatuiRuby::Scrollbar.new(
|
|
121
|
+
content_length: @content_length,
|
|
122
|
+
position: @scroll_position,
|
|
123
|
+
orientation: orientation,
|
|
124
|
+
track_symbol: theme[:track_symbol],
|
|
125
|
+
thumb_symbol: theme[:thumb_symbol],
|
|
126
|
+
track_style: theme[:track_style],
|
|
127
|
+
thumb_style: theme[:thumb_style],
|
|
128
|
+
begin_symbol: theme[:begin_symbol],
|
|
129
|
+
end_symbol: theme[:end_symbol]
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Use Overlay to stack Scrollbar on top of Paragraph.
|
|
133
|
+
# The Scrollbar will position itself on the correct edge (top/bottom/left/right)
|
|
134
|
+
# based on its orientation, demonstrating that the property works independently of layout.
|
|
135
|
+
overlay = RatatuiRuby::Overlay.new(
|
|
136
|
+
layers: [p, s]
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
RatatuiRuby.draw(overlay)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
ScrollbarDemoApp.new.run if __FILE__ == $PROGRAM_NAME
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
require "test_helper"
|
|
7
|
+
require_relative "app"
|
|
8
|
+
|
|
9
|
+
class TestScrollbarDemoApp < Minitest::Test
|
|
10
|
+
include RatatuiRuby::TestHelper
|
|
11
|
+
|
|
12
|
+
def setup
|
|
13
|
+
@app = ScrollbarDemoApp.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_initial_render
|
|
17
|
+
with_test_terminal do
|
|
18
|
+
# Queue quit event to exit loop immediately
|
|
19
|
+
inject_key(:q)
|
|
20
|
+
|
|
21
|
+
# Stub init_terminal/restore_terminal to use test terminal
|
|
22
|
+
@app.run
|
|
23
|
+
|
|
24
|
+
content = buffer_content
|
|
25
|
+
|
|
26
|
+
# Top border with title (truncated due to small width)
|
|
27
|
+
assert_includes content[0], "Scroll"
|
|
28
|
+
# First line of content
|
|
29
|
+
assert_includes content[1], "Line 1"
|
|
30
|
+
# Scrollbar area (far right)
|
|
31
|
+
assert_equal "█", content[1][-1]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_scroll_down
|
|
36
|
+
with_test_terminal do
|
|
37
|
+
# Queue scroll down + quit
|
|
38
|
+
inject_event(RatatuiRuby::Event::Mouse.new(kind: "scroll_down", x: 0, y: 0, button: "none"))
|
|
39
|
+
inject_key(:q)
|
|
40
|
+
|
|
41
|
+
@app.run
|
|
42
|
+
|
|
43
|
+
content = buffer_content
|
|
44
|
+
|
|
45
|
+
# Now it should start from Line 2
|
|
46
|
+
assert_includes content[1], "Line 2"
|
|
47
|
+
refute_includes content[1], "Line 1"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_scroll_up
|
|
52
|
+
with_test_terminal do
|
|
53
|
+
# Queue scroll down + scroll up + quit
|
|
54
|
+
inject_event(RatatuiRuby::Event::Mouse.new(kind: "scroll_down", x: 0, y: 0, button: "none"))
|
|
55
|
+
inject_event(RatatuiRuby::Event::Mouse.new(kind: "scroll_up", x: 0, y: 0, button: "none"))
|
|
56
|
+
inject_key(:q)
|
|
57
|
+
|
|
58
|
+
@app.run
|
|
59
|
+
|
|
60
|
+
content = buffer_content
|
|
61
|
+
|
|
62
|
+
assert_includes content[1], "Line 1"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_theme_cycling
|
|
67
|
+
with_test_terminal do
|
|
68
|
+
# Queue 's' + quit
|
|
69
|
+
inject_keys(:s, :q)
|
|
70
|
+
|
|
71
|
+
@app.run
|
|
72
|
+
|
|
73
|
+
content = buffer_content
|
|
74
|
+
assert_match(/Theme: Rounded/, content[0])
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|