ratatui_ruby 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.builds/ruby-3.2.yml +1 -1
- data/.builds/ruby-3.3.yml +1 -1
- data/.builds/ruby-3.4.yml +1 -1
- data/.builds/ruby-4.0.0.yml +1 -1
- data/AGENTS.md +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +26 -1
- data/doc/application_architecture.md +16 -16
- data/doc/application_testing.md +1 -1
- 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/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/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/sourcehut.rake +4 -1
- metadata +62 -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
|
# 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
|
_ => {}
|
|
@@ -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();
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
4
|
use crate::style::{parse_block, parse_style};
|
|
5
|
+
use crate::text::{parse_line, parse_span};
|
|
5
6
|
use crate::widgets::table_state::RubyTableState;
|
|
6
7
|
use bumpalo::Bump;
|
|
7
8
|
use magnus::{prelude::*, Error, Symbol, TryConvert, Value};
|
|
@@ -22,7 +23,7 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
22
23
|
let widths_val: Value = node.funcall("widths", ())?;
|
|
23
24
|
let widths_array = magnus::RArray::from_value(widths_val)
|
|
24
25
|
.ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for widths"))?;
|
|
25
|
-
let
|
|
26
|
+
let row_highlight_style_val: Value = node.funcall("row_highlight_style", ())?;
|
|
26
27
|
let column_highlight_style_val: Value = node.funcall("column_highlight_style", ())?;
|
|
27
28
|
let cell_highlight_style_val: Value = node.funcall("cell_highlight_style", ())?;
|
|
28
29
|
let highlight_symbol_val: Value = node.funcall("highlight_symbol", ())?;
|
|
@@ -73,8 +74,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
73
74
|
table = table.block(parse_block(block_val, &bump)?);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
if !
|
|
77
|
-
table = table.row_highlight_style(parse_style(
|
|
77
|
+
if !row_highlight_style_val.is_nil() {
|
|
78
|
+
table = table.row_highlight_style(parse_style(row_highlight_style_val)?);
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
if !column_highlight_style_val.is_nil() {
|
|
@@ -158,7 +159,7 @@ pub fn render_stateful(
|
|
|
158
159
|
// Build table (ignoring selected_row, selected_column, offset — State is truth)
|
|
159
160
|
let header_val: Value = node.funcall("header", ())?;
|
|
160
161
|
let footer_val: Value = node.funcall("footer", ())?;
|
|
161
|
-
let
|
|
162
|
+
let row_highlight_style_val: Value = node.funcall("row_highlight_style", ())?;
|
|
162
163
|
let column_highlight_style_val: Value = node.funcall("column_highlight_style", ())?;
|
|
163
164
|
let cell_highlight_style_val: Value = node.funcall("cell_highlight_style", ())?;
|
|
164
165
|
let highlight_symbol_val: Value = node.funcall("highlight_symbol", ())?;
|
|
@@ -196,8 +197,8 @@ pub fn render_stateful(
|
|
|
196
197
|
if !block_val.is_nil() {
|
|
197
198
|
table = table.block(parse_block(block_val, &bump)?);
|
|
198
199
|
}
|
|
199
|
-
if !
|
|
200
|
-
table = table.row_highlight_style(parse_style(
|
|
200
|
+
if !row_highlight_style_val.is_nil() {
|
|
201
|
+
table = table.row_highlight_style(parse_style(row_highlight_style_val)?);
|
|
201
202
|
}
|
|
202
203
|
if !column_highlight_style_val.is_nil() {
|
|
203
204
|
table = table.column_highlight_style(parse_style(column_highlight_style_val)?);
|
|
@@ -228,6 +229,53 @@ pub fn render_stateful(
|
|
|
228
229
|
|
|
229
230
|
fn parse_row(row_val: Value) -> Result<Row<'static>, Error> {
|
|
230
231
|
let ruby = magnus::Ruby::get().unwrap();
|
|
232
|
+
|
|
233
|
+
// Check if this is a RatatuiRuby::Row object with cells + style + height + margins
|
|
234
|
+
let class = row_val.class();
|
|
235
|
+
// SAFETY: Immediate conversion to owned string avoids GC-unsafe borrowed reference.
|
|
236
|
+
let class_name = unsafe { class.name() }.into_owned();
|
|
237
|
+
|
|
238
|
+
if class_name == "RatatuiRuby::Widgets::Row" {
|
|
239
|
+
let cells_val: Value = row_val.funcall("cells", ())?;
|
|
240
|
+
let style_val: Value = row_val.funcall("style", ())?;
|
|
241
|
+
let height_val: Value = row_val.funcall("height", ())?;
|
|
242
|
+
let top_margin_val: Value = row_val.funcall("top_margin", ())?;
|
|
243
|
+
let bottom_margin_val: Value = row_val.funcall("bottom_margin", ())?;
|
|
244
|
+
|
|
245
|
+
let cells_array = magnus::RArray::from_value(cells_val).ok_or_else(|| {
|
|
246
|
+
Error::new(ruby.exception_type_error(), "expected array for Row.cells")
|
|
247
|
+
})?;
|
|
248
|
+
|
|
249
|
+
let mut cells = Vec::new();
|
|
250
|
+
for i in 0..cells_array.len() {
|
|
251
|
+
let index = isize::try_from(i)
|
|
252
|
+
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
253
|
+
let entry_val: Value = cells_array.entry(index)?;
|
|
254
|
+
cells.push(parse_cell(entry_val)?);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let mut row = Row::new(cells);
|
|
258
|
+
|
|
259
|
+
if !style_val.is_nil() {
|
|
260
|
+
row = row.style(parse_style(style_val)?);
|
|
261
|
+
}
|
|
262
|
+
if !height_val.is_nil() {
|
|
263
|
+
let h: u16 = height_val.funcall("to_int", ())?;
|
|
264
|
+
row = row.height(h);
|
|
265
|
+
}
|
|
266
|
+
if !top_margin_val.is_nil() {
|
|
267
|
+
let m: u16 = top_margin_val.funcall("to_int", ())?;
|
|
268
|
+
row = row.top_margin(m);
|
|
269
|
+
}
|
|
270
|
+
if !bottom_margin_val.is_nil() {
|
|
271
|
+
let m: u16 = bottom_margin_val.funcall("to_int", ())?;
|
|
272
|
+
row = row.bottom_margin(m);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return Ok(row);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Fallback: plain array of cells
|
|
231
279
|
let row_array = magnus::RArray::from_value(row_val)
|
|
232
280
|
.ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for row"))?;
|
|
233
281
|
|
|
@@ -246,42 +294,39 @@ fn parse_cell(cell_val: Value) -> Result<Cell<'static>, Error> {
|
|
|
246
294
|
// SAFETY: Immediate conversion to owned string avoids GC-unsafe borrowed reference.
|
|
247
295
|
let class_name = unsafe { class.name() }.into_owned();
|
|
248
296
|
|
|
249
|
-
|
|
297
|
+
// Try Text::Line first (contains multiple spans)
|
|
298
|
+
if class_name.contains("Line") {
|
|
299
|
+
if let Ok(line) = parse_line(cell_val) {
|
|
300
|
+
return Ok(Cell::from(line));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Try Text::Span
|
|
305
|
+
if class_name.contains("Span") {
|
|
306
|
+
if let Ok(span) = parse_span(cell_val) {
|
|
307
|
+
return Ok(Cell::from(ratatui::text::Line::from(vec![span])));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if class_name == "RatatuiRuby::Widgets::Paragraph" {
|
|
250
312
|
let text: String = cell_val.funcall("text", ())?;
|
|
251
313
|
let style_val: Value = cell_val.funcall("style", ())?;
|
|
252
314
|
let cell_style = parse_style(style_val)?;
|
|
253
315
|
Ok(Cell::from(text).style(cell_style))
|
|
254
|
-
} else if class_name == "RatatuiRuby::Style" {
|
|
316
|
+
} else if class_name == "RatatuiRuby::Style::Style" {
|
|
255
317
|
Ok(Cell::from("").style(parse_style(cell_val)?))
|
|
256
|
-
} else if class_name == "RatatuiRuby::Cell" {
|
|
257
|
-
|
|
258
|
-
let
|
|
259
|
-
let
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
let mut
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if !bg_val.is_nil() {
|
|
269
|
-
if let Some(color) = crate::style::parse_color_value(bg_val)? {
|
|
270
|
-
style = style.bg(color);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
if let Some(mods_array) = magnus::RArray::from_value(modifiers_val) {
|
|
274
|
-
let ruby = magnus::Ruby::get().unwrap();
|
|
275
|
-
for i in 0..mods_array.len() {
|
|
276
|
-
let index = isize::try_from(i)
|
|
277
|
-
.map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
278
|
-
let mod_str: String = mods_array.entry::<String>(index)?;
|
|
279
|
-
if let Some(modifier) = crate::style::parse_modifier_str(&mod_str) {
|
|
280
|
-
style = style.add_modifier(modifier);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
318
|
+
} else if class_name == "RatatuiRuby::Widgets::Cell" {
|
|
319
|
+
// Widgets::Cell has content (String/Span/Line) and optional style
|
|
320
|
+
let content_val: Value = cell_val.funcall("content", ())?;
|
|
321
|
+
let style_val: Value = cell_val.funcall("style", ())?;
|
|
322
|
+
|
|
323
|
+
// Recursively parse the content (could be String, Span, or Line)
|
|
324
|
+
let mut cell = parse_cell(content_val)?;
|
|
325
|
+
|
|
326
|
+
if !style_val.is_nil() {
|
|
327
|
+
cell = cell.style(parse_style(style_val)?);
|
|
283
328
|
}
|
|
284
|
-
Ok(
|
|
329
|
+
Ok(cell)
|
|
285
330
|
} else {
|
|
286
331
|
let cell_str: String = cell_val.funcall("to_s", ())?;
|
|
287
332
|
Ok(Cell::from(cell_str))
|