ratatui_ruby 0.6.0 → 0.7.1
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 +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +26 -1
- data/doc/application_architecture.md +16 -16
- data/doc/application_testing.md +1 -1
- data/doc/async.md +160 -0
- 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 +277 -81
- data/doc/contributors/design/rust_backend.md +349 -55
- data/doc/contributors/developing_examples.md +5 -5
- data/doc/contributors/index.md +7 -5
- data/doc/contributors/v1.0.0_blockers.md +1729 -0
- data/doc/debugging.md +71 -0
- data/doc/index.md +11 -6
- data/doc/interactive_design.md +2 -2
- data/doc/quickstart.md +66 -97
- data/doc/v0.7.0_migration.md +236 -0
- data/doc/why.md +93 -0
- data/examples/app_all_events/README.md +6 -4
- data/examples/app_all_events/app.rb +1 -1
- data/examples/app_all_events/model/app_model.rb +1 -1
- data/examples/app_all_events/model/msg.rb +1 -1
- data/examples/app_all_events/update.rb +1 -1
- data/examples/app_all_events/view/app_view.rb +1 -1
- data/examples/app_all_events/view/controls_view.rb +1 -1
- data/examples/app_all_events/view/counts_view.rb +1 -1
- data/examples/app_all_events/view/live_view.rb +1 -1
- data/examples/app_all_events/view/log_view.rb +1 -1
- data/examples/app_color_picker/README.md +7 -5
- data/examples/app_color_picker/app.rb +1 -1
- data/examples/app_login_form/README.md +2 -0
- data/examples/app_stateful_interaction/README.md +2 -0
- data/examples/app_stateful_interaction/app.rb +1 -1
- data/examples/verify_quickstart_dsl/README.md +4 -3
- data/examples/verify_quickstart_dsl/app.rb +1 -1
- data/examples/verify_quickstart_layout/README.md +1 -1
- data/examples/verify_quickstart_lifecycle/README.md +3 -3
- data/examples/verify_quickstart_lifecycle/app.rb +2 -2
- data/examples/verify_readme_usage/README.md +1 -1
- data/examples/widget_barchart_demo/README.md +2 -1
- data/examples/widget_block_demo/README.md +2 -0
- data/examples/widget_box_demo/README.md +3 -3
- data/examples/widget_calendar_demo/README.md +3 -3
- data/examples/widget_calendar_demo/app.rb +5 -1
- data/examples/widget_canvas_demo/README.md +3 -3
- data/examples/widget_cell_demo/README.md +3 -3
- data/examples/widget_center_demo/README.md +3 -3
- data/examples/widget_chart_demo/README.md +3 -3
- data/examples/widget_gauge_demo/README.md +3 -3
- data/examples/widget_layout_split/README.md +3 -3
- data/examples/widget_line_gauge_demo/README.md +3 -3
- data/examples/widget_list_demo/README.md +3 -3
- data/examples/widget_map_demo/README.md +3 -3
- data/examples/widget_map_demo/app.rb +2 -2
- data/examples/widget_overlay_demo/README.md +36 -0
- data/examples/widget_popup_demo/README.md +3 -3
- data/examples/widget_ratatui_logo_demo/README.md +3 -3
- data/examples/widget_ratatui_logo_demo/app.rb +1 -1
- data/examples/widget_ratatui_mascot_demo/README.md +3 -3
- data/examples/widget_rect/README.md +3 -3
- data/examples/widget_render/README.md +3 -3
- data/examples/widget_render/app.rb +3 -3
- data/examples/widget_rich_text/README.md +3 -3
- data/examples/widget_scroll_text/README.md +3 -3
- data/examples/widget_scrollbar_demo/README.md +3 -3
- data/examples/widget_sparkline_demo/README.md +3 -3
- data/examples/widget_style_colors/README.md +3 -3
- data/examples/widget_table_demo/README.md +3 -3
- data/examples/widget_table_demo/app.rb +19 -4
- data/examples/widget_tabs_demo/README.md +3 -3
- data/examples/widget_text_width/README.md +3 -3
- data/examples/widget_text_width/app.rb +8 -1
- data/ext/ratatui_ruby/Cargo.lock +1 -1
- data/ext/ratatui_ruby/Cargo.toml +1 -1
- data/ext/ratatui_ruby/src/frame.rs +6 -5
- data/ext/ratatui_ruby/src/lib.rs +3 -2
- data/ext/ratatui_ruby/src/rendering.rs +22 -21
- data/ext/ratatui_ruby/src/style.rs +25 -9
- data/ext/ratatui_ruby/src/text.rs +12 -3
- data/ext/ratatui_ruby/src/widgets/canvas.rs +5 -5
- data/ext/ratatui_ruby/src/widgets/table.rs +81 -36
- data/lib/ratatui_ruby/buffer/cell.rb +168 -0
- data/lib/ratatui_ruby/buffer.rb +15 -0
- data/lib/ratatui_ruby/frame.rb +8 -8
- 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 +2 -2
- data/lib/ratatui_ruby/schema/layout.rb +1 -1
- data/lib/ratatui_ruby/schema/row.rb +66 -0
- data/lib/ratatui_ruby/schema/table.rb +10 -10
- data/lib/ratatui_ruby/schema/text.rb +27 -2
- data/lib/ratatui_ruby/style/style.rb +81 -0
- data/lib/ratatui_ruby/style.rb +15 -0
- data/lib/ratatui_ruby/table_state.rb +1 -1
- data/lib/ratatui_ruby/test_helper/snapshot.rb +24 -0
- data/lib/ratatui_ruby/test_helper/style_assertions.rb +1 -1
- 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 +23 -39
- data/sig/examples/app_all_events/view.rbs +1 -1
- data/sig/examples/app_all_events/view_state.rbs +1 -1
- data/sig/ratatui_ruby/schema/row.rbs +22 -0
- data/sig/ratatui_ruby/schema/table.rbs +1 -1
- data/sig/ratatui_ruby/schema/text.rbs +1 -0
- data/sig/ratatui_ruby/session.rbs +29 -49
- 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.rake +1 -35
- data/tasks/bump/changelog.rb +8 -0
- data/tasks/bump/ruby_gem.rb +12 -0
- data/tasks/bump/unreleased_section.rb +16 -0
- data/tasks/sourcehut.rake +4 -1
- metadata +64 -15
- data/doc/contributors/dwim_dx.md +0 -366
- data/doc/contributors/examples_audit/p1_high.md +0 -21
- data/doc/contributors/examples_audit/p2_moderate.md +0 -81
- data/doc/contributors/examples_audit.md +0 -41
- 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/widget_table_flex.png +0 -0
- data/lib/ratatui_ruby/session/autodoc.rb +0 -482
- data/lib/ratatui_ruby/session.rb +0 -178
- data/tasks/autodoc/inventory.rb +0 -63
- data/tasks/autodoc/notice.rb +0 -26
- data/tasks/autodoc/rbs.rb +0 -38
- data/tasks/autodoc/rdoc.rb +0 -45
|
@@ -5,6 +5,8 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# Ratatui Mascot Example
|
|
7
7
|
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
8
10
|
Demonstrates the project mascot widget for adding personality.
|
|
9
11
|
|
|
10
12
|
Interfaces can feel clinical. A friendly mascot adds charm and brand identity to your terminal application.
|
|
@@ -29,6 +31,4 @@ ruby examples/widget_ratatui_mascot_demo/app.rb
|
|
|
29
31
|
|
|
30
32
|
Use this example if you need to...
|
|
31
33
|
- Add visual flair to your UI.
|
|
32
|
-
- Create a friendly empty state or success screen.
|
|
33
|
-
|
|
34
|
-

|
|
34
|
+
- Create a friendly empty state or success screen.
|
|
@@ -5,6 +5,8 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# Rect (Geometry) Widget Example
|
|
7
7
|
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
8
10
|
Demonstrates the Rect geometry primitive and hit-testing patterns.
|
|
9
11
|
|
|
10
12
|
TUI layouts are composed of rectangles. Understanding how to manipulate `Rect` objects, reuse them from the layout phase, and use them for mouse interaction is critical for building interactive apps.
|
|
@@ -33,6 +35,4 @@ ruby examples/widget_rect/app.rb
|
|
|
33
35
|
Use this example if you need to...
|
|
34
36
|
- Handle mouse clicks on specific buttons or areas.
|
|
35
37
|
- Create resizable panes (like a split pane in an IDE).
|
|
36
|
-
- Debug layout issues by inspecting Rect coordinates.
|
|
37
|
-
|
|
38
|
-

|
|
38
|
+
- Debug layout issues by inspecting Rect coordinates.
|
|
@@ -5,6 +5,8 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# Render (Custom Widget) Example
|
|
7
7
|
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
8
10
|
Demonstrates how to build Custom Widgets using absolute coordinates.
|
|
9
11
|
|
|
10
12
|
Sometimes standard widgets aren't enough. You need to draw custom shapes, games, or graphs. This example shows how to implement the `render(area)` contract to draw anything you want while respecting layout boundaries.
|
|
@@ -32,6 +34,4 @@ ruby examples/widget_render/app.rb
|
|
|
32
34
|
Use this example if you need to...
|
|
33
35
|
- Build a game (Snake, Tetris) inside the terminal.
|
|
34
36
|
- Create a specialized visualization (Network topology graph).
|
|
35
|
-
- Draw custom UI elements not provided by the library.
|
|
36
|
-
|
|
37
|
-

|
|
37
|
+
- Draw custom UI elements not provided by the library.
|
|
@@ -21,7 +21,7 @@ class DiagonalWidget
|
|
|
21
21
|
area.x + i,
|
|
22
22
|
area.y + i,
|
|
23
23
|
"\\",
|
|
24
|
-
RatatuiRuby::Style.new(fg: :red, modifiers: [:bold])
|
|
24
|
+
RatatuiRuby::Style::Style.new(fg: :red, modifiers: [:bold])
|
|
25
25
|
)
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -47,7 +47,7 @@ class CheckerboardWidget
|
|
|
47
47
|
area.x + col,
|
|
48
48
|
area.y + row,
|
|
49
49
|
@char,
|
|
50
|
-
RatatuiRuby::Style.new(fg: :cyan)
|
|
50
|
+
RatatuiRuby::Style::Style.new(fg: :cyan)
|
|
51
51
|
)
|
|
52
52
|
end
|
|
53
53
|
end
|
|
@@ -63,7 +63,7 @@ end
|
|
|
63
63
|
class BorderWidget
|
|
64
64
|
def render(area)
|
|
65
65
|
result = []
|
|
66
|
-
style = RatatuiRuby::Style.new(fg: :green)
|
|
66
|
+
style = RatatuiRuby::Style::Style.new(fg: :green)
|
|
67
67
|
|
|
68
68
|
# Top and bottom
|
|
69
69
|
(0...area.width).each do |x| # rubocop:disable Lint/AmbiguousRange
|
|
@@ -5,6 +5,8 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# Rich Text Example
|
|
7
7
|
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
8
10
|
Demonstrates styling individual words and characters.
|
|
9
11
|
|
|
10
12
|
Standard strings are monochromatic. "Rich Text" is composed of `Lines` containing multiple `Spans`, where each Span has its own style. This allows for multi-colored, multi-styled text blocks.
|
|
@@ -30,6 +32,4 @@ ruby examples/widget_rich_text/app.rb
|
|
|
30
32
|
Use this example if you need to...
|
|
31
33
|
- Highlight keywords in code (Syntax highlighting).
|
|
32
34
|
- Create status lines with icons (e.g., "✔ Success" where the checkmark is green).
|
|
33
|
-
- Emphasize specific data points in a paragraph.
|
|
34
|
-
|
|
35
|
-

|
|
35
|
+
- Emphasize specific data points in a paragraph.
|
|
@@ -5,6 +5,8 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# Scroll Text Example
|
|
7
7
|
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
8
10
|
Demonstrates scrolling long text content within a fixed viewport.
|
|
9
11
|
|
|
10
12
|
Sometimes text exceeds the available space. The `Paragraph` widget supports a `scroll` parameter to simulate a viewport, allowing users to pan vertically and horizontally.
|
|
@@ -32,6 +34,4 @@ ruby examples/widget_scroll_text/app.rb
|
|
|
32
34
|
Use this example if you need to...
|
|
33
35
|
- Build a log viewer.
|
|
34
36
|
- Create a "terms and conditions" scrollbox.
|
|
35
|
-
- Display code snippets that might be wider than the terminal.
|
|
36
|
-
|
|
37
|
-

|
|
37
|
+
- Display code snippets that might be wider than the terminal.
|
|
@@ -5,6 +5,8 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# Scrollbar Widget Example
|
|
7
7
|
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
8
10
|
Demonstrates explicit scrollbars for navigation feedback.
|
|
9
11
|
|
|
10
12
|
Content overflows. Users get lost in long lists. Scrollbars provide essential spatial awareness ("How far down am I?") and navigation controls.
|
|
@@ -32,6 +34,4 @@ ruby examples/widget_scrollbar_demo/app.rb
|
|
|
32
34
|
|
|
33
35
|
Use this example if you need to...
|
|
34
36
|
- Add visual scroll indicators to Lists or Tables.
|
|
35
|
-
- Implement specialized inputs like sliders or volume controls.
|
|
36
|
-
|
|
37
|
-

|
|
37
|
+
- Implement specialized inputs like sliders or volume controls.
|
|
@@ -5,6 +5,8 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# Sparkline Widget Example
|
|
7
7
|
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
8
10
|
Demonstrates high-density data visualization in a condensed footprint.
|
|
9
11
|
|
|
10
12
|
Users need context. A single number ("90% CPU") tells you status, but not the trend. Full charts take up too much space. Sparklines condense history into a single line, perfect for headers and dashboards.
|
|
@@ -37,6 +39,4 @@ ruby examples/widget_sparkline_demo/app.rb
|
|
|
37
39
|
Use this example if you need to...
|
|
38
40
|
- Add a "CPU Load" graph to your header.
|
|
39
41
|
- Visualize stock price trends in a list row.
|
|
40
|
-
- Monitor memory usage over the last 60 seconds.
|
|
41
|
-
|
|
42
|
-

|
|
42
|
+
- Monitor memory usage over the last 60 seconds.
|
|
@@ -5,6 +5,8 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# Style Colors Example
|
|
7
7
|
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
8
10
|
Demonstrates high-fidelity color support.
|
|
9
11
|
|
|
10
12
|
Terminals support millions of colors. This example generates a mathematically precise HSL gradient to prove the rendering engine's color fidelity.
|
|
@@ -29,6 +31,4 @@ ruby examples/widget_style_colors/app.rb
|
|
|
29
31
|
Use this example if you need to...
|
|
30
32
|
- Create meaningful heatmaps.
|
|
31
33
|
- Generate color palettes dynamically.
|
|
32
|
-
- Test your terminal's color support capabilities.
|
|
33
|
-
|
|
34
|
-

|
|
34
|
+
- Test your terminal's color support capabilities.
|
|
@@ -5,6 +5,8 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# Table Widget Example
|
|
7
7
|
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
8
10
|
Demonstrates advanced options for the `Table` widget, including selection, row-level highlighting, and column-level highlighting.
|
|
9
11
|
|
|
10
12
|
Data grids are complex. Users expect to navigate them with keys, select rows, and clearly see which cell is active. The `Table` widget provides these features out of the box efficiently.
|
|
@@ -43,6 +45,4 @@ ruby examples/widget_table_demo/app.rb
|
|
|
43
45
|
Use this example if you need to...
|
|
44
46
|
- Build a file explorer or process list.
|
|
45
47
|
- Create a data-heavy dashboard.
|
|
46
|
-
- Handle conflicting style requirements (e.g., "Highlight this row, but make this error cell red").
|
|
47
|
-
|
|
48
|
-

|
|
48
|
+
- Handle conflicting style requirements (e.g., "Highlight this row, but make this error cell red").
|
|
@@ -82,8 +82,23 @@ class WidgetTableDemo
|
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
private def render(frame)
|
|
85
|
-
# Create table rows
|
|
86
|
-
rows = PROCESSES.map
|
|
85
|
+
# v0.7.0: Create table rows using table_row and table_cell for per-cell styling
|
|
86
|
+
rows = PROCESSES.map do |p|
|
|
87
|
+
cpu_style = case p[:cpu]
|
|
88
|
+
when 0...10 then @tui.style(fg: :green)
|
|
89
|
+
when 10...30 then @tui.style(fg: :yellow)
|
|
90
|
+
else @tui.style(fg: :red, modifiers: [:bold])
|
|
91
|
+
end
|
|
92
|
+
@tui.table_row(
|
|
93
|
+
cells: [
|
|
94
|
+
p[:pid].to_s,
|
|
95
|
+
p[:name],
|
|
96
|
+
@tui.table_cell(content: "#{p[:cpu]}%", style: cpu_style),
|
|
97
|
+
],
|
|
98
|
+
# Apply alternating row backgrounds for readability
|
|
99
|
+
style: p[:pid].even? ? @tui.style(bg: :dark_gray) : nil
|
|
100
|
+
)
|
|
101
|
+
end
|
|
87
102
|
|
|
88
103
|
# Define column widths
|
|
89
104
|
widths = [
|
|
@@ -93,7 +108,7 @@ class WidgetTableDemo
|
|
|
93
108
|
]
|
|
94
109
|
|
|
95
110
|
# Create highlight style (yellow text)
|
|
96
|
-
|
|
111
|
+
row_highlight_style = @tui.style(fg: :yellow)
|
|
97
112
|
|
|
98
113
|
current_style_entry = @styles[@current_style_index]
|
|
99
114
|
current_spacing_entry = HIGHLIGHT_SPACINGS[@highlight_spacing_index]
|
|
@@ -114,7 +129,7 @@ class WidgetTableDemo
|
|
|
114
129
|
selected_row: effective_selection,
|
|
115
130
|
selected_column: @selected_col,
|
|
116
131
|
offset: effective_offset,
|
|
117
|
-
|
|
132
|
+
row_highlight_style:,
|
|
118
133
|
highlight_symbol: "> ",
|
|
119
134
|
highlight_spacing: current_spacing_entry[:spacing],
|
|
120
135
|
column_highlight_style: @show_column_highlight ? @column_highlight_style : nil,
|
|
@@ -5,6 +5,8 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# Tabs Widget Example
|
|
7
7
|
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
8
10
|
Demonstrates view segregation with interactive navigation.
|
|
9
11
|
|
|
10
12
|
Screen real estate is limited. You cannot show everything at once. Tabs segregate content into specialized views (modes), allowing users to switch contexts easily.
|
|
@@ -36,6 +38,4 @@ ruby examples/widget_tabs_demo/app.rb
|
|
|
36
38
|
Use this example if you need to...
|
|
37
39
|
- Build a multi-pane dashboard.
|
|
38
40
|
- Create a "Settings" screen with different categories.
|
|
39
|
-
- Implement a "wizard" interface with steps.
|
|
40
|
-
|
|
41
|
-

|
|
41
|
+
- Implement a "wizard" interface with steps.
|
|
@@ -5,6 +5,8 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|
|
5
5
|
|
|
6
6
|
# Text Width Calculator
|
|
7
7
|
|
|
8
|
+
[](app.rb)
|
|
9
|
+
|
|
8
10
|
Demonstrates string width calculation in a terminal environment.
|
|
9
11
|
|
|
10
12
|
Not all characters are created equal. In a TUI, "Width" means cell count, not string length. Emoji (`👍`) take 2 cells. Chinese characters (`你`) take 2 cells. The `tui.text_width` helper tells you the visual width of a string.
|
|
@@ -30,6 +32,4 @@ ruby examples/widget_text_width/app.rb
|
|
|
30
32
|
Use this example if you need to...
|
|
31
33
|
- Align text correctly in columns.
|
|
32
34
|
- Truncate strings that are too long for a widget.
|
|
33
|
-
- Build your own custom layout engine.
|
|
34
|
-
|
|
35
|
-

|
|
35
|
+
- Build your own custom layout engine.
|
|
@@ -49,11 +49,18 @@ class WidgetTextWidth
|
|
|
49
49
|
sample = @text_samples[@selected_index]
|
|
50
50
|
measured_width = @tui.text_width(sample[:text])
|
|
51
51
|
|
|
52
|
+
# v0.7.0: Text::Line#width instance method for rich text measurement
|
|
53
|
+
styled_line = @tui.text_line(spans: [
|
|
54
|
+
@tui.text_span(content: sample[:text], style: @tui.style(fg: :cyan)),
|
|
55
|
+
])
|
|
56
|
+
line_width = styled_line.width
|
|
57
|
+
|
|
52
58
|
# Build content text with newlines
|
|
53
59
|
content = []
|
|
54
60
|
content << "Sample: #{sample[:text]}"
|
|
55
61
|
content << ""
|
|
56
|
-
content << "Display Width: #{measured_width} cells"
|
|
62
|
+
content << "Display Width (text_width): #{measured_width} cells"
|
|
63
|
+
content << "Display Width (line.width): #{line_width} cells"
|
|
57
64
|
content << "Character Count: #{sample[:text].length}"
|
|
58
65
|
content << ""
|
|
59
66
|
content << sample[:desc]
|
data/ext/ratatui_ruby/Cargo.lock
CHANGED
data/ext/ratatui_ruby/Cargo.toml
CHANGED
|
@@ -118,9 +118,10 @@ impl RubyFrame {
|
|
|
118
118
|
// The ensure_active() check above guarantees we're still in the callback.
|
|
119
119
|
let area = unsafe { (*self.inner.get()).as_ref().area() };
|
|
120
120
|
|
|
121
|
-
// Create a Ruby Rect object
|
|
121
|
+
// Create a Ruby Layout::Rect object
|
|
122
122
|
let module = ruby.define_module("RatatuiRuby")?;
|
|
123
|
-
let
|
|
123
|
+
let layout_mod = module.const_get::<_, magnus::RModule>("Layout")?;
|
|
124
|
+
let class = layout_mod.const_get::<_, magnus::RClass>("Rect")?;
|
|
124
125
|
class.funcall("new", (area.x, area.y, area.width, area.height))
|
|
125
126
|
}
|
|
126
127
|
|
|
@@ -190,13 +191,13 @@ impl RubyFrame {
|
|
|
190
191
|
let state_class = unsafe { state.class().name() }.into_owned();
|
|
191
192
|
|
|
192
193
|
match (widget_class.as_str(), state_class.as_str()) {
|
|
193
|
-
("RatatuiRuby::List", "RatatuiRuby::ListState") => {
|
|
194
|
+
("RatatuiRuby::Widgets::List", "RatatuiRuby::ListState") => {
|
|
194
195
|
widgets::list::render_stateful(frame, rect, widget, state)
|
|
195
196
|
}
|
|
196
|
-
("RatatuiRuby::Table", "RatatuiRuby::TableState") => {
|
|
197
|
+
("RatatuiRuby::Widgets::Table", "RatatuiRuby::TableState") => {
|
|
197
198
|
widgets::table::render_stateful(frame, rect, widget, state)
|
|
198
199
|
}
|
|
199
|
-
("RatatuiRuby::Scrollbar", "RatatuiRuby::ScrollbarState") => {
|
|
200
|
+
("RatatuiRuby::Widgets::Scrollbar", "RatatuiRuby::ScrollbarState") => {
|
|
200
201
|
widgets::scrollbar::render_stateful(frame, rect, widget, state)
|
|
201
202
|
}
|
|
202
203
|
_ => Err(Error::new(
|
data/ext/ratatui_ruby/src/lib.rs
CHANGED
|
@@ -161,8 +161,9 @@ fn init() -> Result<(), Error> {
|
|
|
161
161
|
m.define_module_function("_get_cell_at", function!(terminal::get_cell_at, 2))?;
|
|
162
162
|
m.define_module_function("resize_terminal", function!(terminal::resize_terminal, 2))?;
|
|
163
163
|
|
|
164
|
-
// Register Layout.split on the Layout class
|
|
165
|
-
let
|
|
164
|
+
// Register Layout.split on the Layout::Layout class (inside the Layout module)
|
|
165
|
+
let layout_mod = m.const_get::<_, magnus::RModule>("Layout")?;
|
|
166
|
+
let layout_class = layout_mod.const_get::<_, magnus::RClass>("Layout")?;
|
|
166
167
|
layout_class.define_singleton_method("_split", function!(widgets::layout::split_layout, 4))?;
|
|
167
168
|
|
|
168
169
|
// Paragraph metrics
|
|
@@ -11,7 +11,8 @@ pub fn render_node(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Err
|
|
|
11
11
|
let ruby = magnus::Ruby::get().unwrap();
|
|
12
12
|
let ruby_area = {
|
|
13
13
|
let module = ruby.define_module("RatatuiRuby")?;
|
|
14
|
-
let
|
|
14
|
+
let layout_mod = module.const_get::<_, magnus::RModule>("Layout")?;
|
|
15
|
+
let class = layout_mod.const_get::<_, magnus::RClass>("Rect")?;
|
|
15
16
|
class.funcall::<_, _, Value>("new", (area.x, area.y, area.width, area.height))?
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -35,28 +36,28 @@ pub fn render_node(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Err
|
|
|
35
36
|
let class_name = unsafe { node.class().name() }.into_owned();
|
|
36
37
|
|
|
37
38
|
match class_name.as_str() {
|
|
38
|
-
"RatatuiRuby::Paragraph" => widgets::paragraph::render(frame, area, node)?,
|
|
39
|
-
"RatatuiRuby::Clear" => widgets::clear::render(frame, area, node)?,
|
|
40
|
-
"RatatuiRuby::Cursor" => widgets::cursor::render(frame, area, node)?,
|
|
41
|
-
"RatatuiRuby::Overlay" => widgets::overlay::render(frame, area, node)?,
|
|
42
|
-
"RatatuiRuby::Center" => widgets::center::render(frame, area, node)?,
|
|
43
|
-
"RatatuiRuby::Layout" => widgets::layout::render(frame, area, node)?,
|
|
44
|
-
"RatatuiRuby::List" => widgets::list::render(frame, area, node)?,
|
|
45
|
-
"RatatuiRuby::Gauge" => widgets::gauge::render(frame, area, node)?,
|
|
46
|
-
"RatatuiRuby::LineGauge" => widgets::line_gauge::render(frame, area, node)?,
|
|
47
|
-
"RatatuiRuby::Table" => widgets::table::render(frame, area, node)?,
|
|
48
|
-
"RatatuiRuby::Block" => widgets::block::render(frame, area, node)?,
|
|
49
|
-
"RatatuiRuby::Tabs" => widgets::tabs::render(frame, area, node)?,
|
|
50
|
-
"RatatuiRuby::Scrollbar" => widgets::scrollbar::render(frame, area, node)?,
|
|
51
|
-
"RatatuiRuby::BarChart" => widgets::barchart::render(frame, area, node)?,
|
|
52
|
-
"RatatuiRuby::Canvas" => widgets::canvas::render(frame, area, node)?,
|
|
53
|
-
"RatatuiRuby::Calendar" => widgets::calendar::render(frame, area, node)?,
|
|
54
|
-
"RatatuiRuby::Sparkline" => widgets::sparkline::render(frame, area, node)?,
|
|
55
|
-
"RatatuiRuby::Chart" | "RatatuiRuby::LineChart" => {
|
|
39
|
+
"RatatuiRuby::Widgets::Paragraph" => widgets::paragraph::render(frame, area, node)?,
|
|
40
|
+
"RatatuiRuby::Widgets::Clear" => widgets::clear::render(frame, area, node)?,
|
|
41
|
+
"RatatuiRuby::Widgets::Cursor" => widgets::cursor::render(frame, area, node)?,
|
|
42
|
+
"RatatuiRuby::Widgets::Overlay" => widgets::overlay::render(frame, area, node)?,
|
|
43
|
+
"RatatuiRuby::Widgets::Center" => widgets::center::render(frame, area, node)?,
|
|
44
|
+
"RatatuiRuby::Layout::Layout" => widgets::layout::render(frame, area, node)?,
|
|
45
|
+
"RatatuiRuby::Widgets::List" => widgets::list::render(frame, area, node)?,
|
|
46
|
+
"RatatuiRuby::Widgets::Gauge" => widgets::gauge::render(frame, area, node)?,
|
|
47
|
+
"RatatuiRuby::Widgets::LineGauge" => widgets::line_gauge::render(frame, area, node)?,
|
|
48
|
+
"RatatuiRuby::Widgets::Table" => widgets::table::render(frame, area, node)?,
|
|
49
|
+
"RatatuiRuby::Widgets::Block" => widgets::block::render(frame, area, node)?,
|
|
50
|
+
"RatatuiRuby::Widgets::Tabs" => widgets::tabs::render(frame, area, node)?,
|
|
51
|
+
"RatatuiRuby::Widgets::Scrollbar" => widgets::scrollbar::render(frame, area, node)?,
|
|
52
|
+
"RatatuiRuby::Widgets::BarChart" => widgets::barchart::render(frame, area, node)?,
|
|
53
|
+
"RatatuiRuby::Widgets::Canvas" => widgets::canvas::render(frame, area, node)?,
|
|
54
|
+
"RatatuiRuby::Widgets::Calendar" => widgets::calendar::render(frame, area, node)?,
|
|
55
|
+
"RatatuiRuby::Widgets::Sparkline" => widgets::sparkline::render(frame, area, node)?,
|
|
56
|
+
"RatatuiRuby::Widgets::Chart" | "RatatuiRuby::LineChart" => {
|
|
56
57
|
widgets::chart::render(frame, area, node)?;
|
|
57
58
|
}
|
|
58
|
-
"RatatuiRuby::RatatuiLogo" => widgets::ratatui_logo::render(frame, area, node),
|
|
59
|
-
"RatatuiRuby::RatatuiMascot" => {
|
|
59
|
+
"RatatuiRuby::Widgets::RatatuiLogo" => widgets::ratatui_logo::render(frame, area, node),
|
|
60
|
+
"RatatuiRuby::Widgets::RatatuiMascot" => {
|
|
60
61
|
widgets::ratatui_mascot::render_ratatui_mascot(frame, area, node)?;
|
|
61
62
|
}
|
|
62
63
|
_ => {}
|
|
@@ -12,6 +12,8 @@ use ratatui::{
|
|
|
12
12
|
widgets::{Block, BorderType, Borders, Padding},
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
use crate::text::parse_line;
|
|
16
|
+
|
|
15
17
|
pub fn parse_color(color_str: &str) -> Option<Color> {
|
|
16
18
|
// Try standard ratatui parsing first (named colors, indexed, etc.)
|
|
17
19
|
if let Ok(color) = color_str.parse::<Color>() {
|
|
@@ -274,15 +276,22 @@ fn parse_titles(block_val: Value, mut block: Block<'_>) -> Result<Block<'_>, Err
|
|
|
274
276
|
let index = isize::try_from(i)
|
|
275
277
|
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
276
278
|
let title_item: Value = titles_array.entry(index)?;
|
|
277
|
-
let mut content = String::new();
|
|
278
279
|
let mut alignment = Alignment::Left;
|
|
279
280
|
let mut is_bottom = false;
|
|
280
281
|
let mut style = Style::default();
|
|
282
|
+
let mut line: Option<Line<'static>> = None;
|
|
281
283
|
|
|
282
284
|
if let Some(hash) = magnus::RHash::from_value(title_item) {
|
|
283
285
|
if let Ok(v) = hash.lookup::<_, Value>(ruby.to_symbol("content")) {
|
|
284
286
|
if !v.is_nil() {
|
|
285
|
-
|
|
287
|
+
// First, try to parse as a Line object (preserves styling)
|
|
288
|
+
if let Ok(parsed_line) = parse_line(v) {
|
|
289
|
+
line = Some(parsed_line);
|
|
290
|
+
} else {
|
|
291
|
+
// Fallback to string
|
|
292
|
+
let content: String = v.funcall("to_s", ())?;
|
|
293
|
+
line = Some(Line::from(content));
|
|
294
|
+
}
|
|
286
295
|
}
|
|
287
296
|
}
|
|
288
297
|
if let Ok(v) = hash.lookup::<_, Value>(ruby.to_symbol("alignment")) {
|
|
@@ -307,15 +316,22 @@ fn parse_titles(block_val: Value, mut block: Block<'_>) -> Result<Block<'_>, Err
|
|
|
307
316
|
}
|
|
308
317
|
}
|
|
309
318
|
} else {
|
|
310
|
-
content = title_item.funcall("to_s", ())?;
|
|
319
|
+
let content: String = title_item.funcall("to_s", ())?;
|
|
320
|
+
line = Some(Line::from(content));
|
|
311
321
|
}
|
|
312
322
|
|
|
313
|
-
let
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
323
|
+
if let Some(mut l) = line {
|
|
324
|
+
l = l.alignment(alignment);
|
|
325
|
+
// Only apply style if the line doesn't already have styled spans
|
|
326
|
+
if style != Style::default() {
|
|
327
|
+
l = l.style(style);
|
|
328
|
+
}
|
|
329
|
+
block = if is_bottom {
|
|
330
|
+
block.title_bottom(l)
|
|
331
|
+
} else {
|
|
332
|
+
block.title_top(l)
|
|
333
|
+
};
|
|
334
|
+
}
|
|
319
335
|
}
|
|
320
336
|
}
|
|
321
337
|
}
|
|
@@ -131,6 +131,8 @@ pub fn parse_line(value: Value) -> Result<Line<'static>, Error> {
|
|
|
131
131
|
|
|
132
132
|
// Extract spans from the Ruby Line
|
|
133
133
|
let spans_val: Value = value.funcall("spans", ())?;
|
|
134
|
+
// v0.7.0: Extract style from the Ruby Line
|
|
135
|
+
let style_val: Value = value.funcall("style", ())?;
|
|
134
136
|
|
|
135
137
|
if spans_val.is_nil() {
|
|
136
138
|
return Ok(Line::from(""));
|
|
@@ -164,11 +166,18 @@ pub fn parse_line(value: Value) -> Result<Line<'static>, Error> {
|
|
|
164
166
|
}
|
|
165
167
|
}
|
|
166
168
|
|
|
167
|
-
if spans.is_empty() {
|
|
168
|
-
|
|
169
|
+
let mut line = if spans.is_empty() {
|
|
170
|
+
Line::from("")
|
|
169
171
|
} else {
|
|
170
|
-
|
|
172
|
+
Line::from(spans)
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// v0.7.0: Apply line-level style if present
|
|
176
|
+
if !style_val.is_nil() {
|
|
177
|
+
line = line.style(parse_style(style_val)?);
|
|
171
178
|
}
|
|
179
|
+
|
|
180
|
+
Ok(line)
|
|
172
181
|
}
|
|
173
182
|
|
|
174
183
|
#[cfg(test)]
|
|
@@ -57,7 +57,7 @@ pub fn render(frame: &mut Frame, area: ratatui::layout::Rect, node: Value) -> Re
|
|
|
57
57
|
let class_name = unsafe { class.name() }.into_owned();
|
|
58
58
|
|
|
59
59
|
match class_name.as_str() {
|
|
60
|
-
"RatatuiRuby::Shape::Line" => {
|
|
60
|
+
"RatatuiRuby::Widgets::Shape::Line" => {
|
|
61
61
|
let x1: f64 = shape_val.funcall("x1", ()).unwrap_or(0.0);
|
|
62
62
|
let y1: f64 = shape_val.funcall("y1", ()).unwrap_or(0.0);
|
|
63
63
|
let x2: f64 = shape_val.funcall("x2", ()).unwrap_or(0.0);
|
|
@@ -73,7 +73,7 @@ pub fn render(frame: &mut Frame, area: ratatui::layout::Rect, node: Value) -> Re
|
|
|
73
73
|
color,
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
|
-
"RatatuiRuby::Shape::Rectangle" => {
|
|
76
|
+
"RatatuiRuby::Widgets::Shape::Rectangle" => {
|
|
77
77
|
let x: f64 = shape_val.funcall("x", ()).unwrap_or(0.0);
|
|
78
78
|
let y: f64 = shape_val.funcall("y", ()).unwrap_or(0.0);
|
|
79
79
|
let width: f64 = shape_val.funcall("width", ()).unwrap_or(0.0);
|
|
@@ -89,7 +89,7 @@ pub fn render(frame: &mut Frame, area: ratatui::layout::Rect, node: Value) -> Re
|
|
|
89
89
|
color,
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
-
"RatatuiRuby::Shape::Circle" => {
|
|
92
|
+
"RatatuiRuby::Widgets::Shape::Circle" => {
|
|
93
93
|
let x: f64 = shape_val.funcall("x", ()).unwrap_or(0.0);
|
|
94
94
|
let y: f64 = shape_val.funcall("y", ()).unwrap_or(0.0);
|
|
95
95
|
let radius: f64 = shape_val.funcall("radius", ()).unwrap_or(0.0);
|
|
@@ -103,7 +103,7 @@ pub fn render(frame: &mut Frame, area: ratatui::layout::Rect, node: Value) -> Re
|
|
|
103
103
|
color,
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
|
-
"RatatuiRuby::Shape::Map" => {
|
|
106
|
+
"RatatuiRuby::Widgets::Shape::Map" => {
|
|
107
107
|
let color_val: Value = shape_val.funcall("color", ()).unwrap();
|
|
108
108
|
let color =
|
|
109
109
|
parse_color(&color_val.to_string()).unwrap_or(ratatui::style::Color::Reset);
|
|
@@ -114,7 +114,7 @@ pub fn render(frame: &mut Frame, area: ratatui::layout::Rect, node: Value) -> Re
|
|
|
114
114
|
};
|
|
115
115
|
ctx.draw(&Map { color, resolution });
|
|
116
116
|
}
|
|
117
|
-
"RatatuiRuby::Shape::Label" => {
|
|
117
|
+
"RatatuiRuby::Widgets::Shape::Label" => {
|
|
118
118
|
let x: f64 = shape_val.funcall("x", ()).unwrap_or(0.0);
|
|
119
119
|
let y: f64 = shape_val.funcall("y", ()).unwrap_or(0.0);
|
|
120
120
|
let text_val: Value = shape_val.funcall("text", ()).unwrap();
|