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
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
|
-
use crate::style::{parse_block, parse_color};
|
|
4
|
+
use crate::style::{parse_block, parse_color, parse_style};
|
|
5
|
+
use crate::text::parse_text;
|
|
6
|
+
use bumpalo::Bump;
|
|
5
7
|
use magnus::{prelude::*, Error, RArray, Symbol, Value};
|
|
6
8
|
use ratatui::{
|
|
7
9
|
symbols::Marker,
|
|
@@ -10,6 +12,7 @@ use ratatui::{
|
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
pub fn render(frame: &mut Frame, area: ratatui::layout::Rect, node: Value) -> Result<(), Error> {
|
|
15
|
+
let bump = Bump::new();
|
|
13
16
|
let shapes_val: RArray = node.funcall("shapes", ())?;
|
|
14
17
|
let x_bounds_val: RArray = node.funcall("x_bounds", ())?;
|
|
15
18
|
let y_bounds_val: RArray = node.funcall("y_bounds", ())?;
|
|
@@ -23,6 +26,10 @@ pub fn render(frame: &mut Frame, area: ratatui::layout::Rect, node: Value) -> Re
|
|
|
23
26
|
"dot" => Marker::Dot,
|
|
24
27
|
"block" => Marker::Block,
|
|
25
28
|
"bar" => Marker::Bar,
|
|
29
|
+
"half_block" => Marker::HalfBlock,
|
|
30
|
+
"quadrant" => Marker::Quadrant,
|
|
31
|
+
"sextant" => Marker::Sextant,
|
|
32
|
+
"octant" => Marker::Octant,
|
|
26
33
|
_ => Marker::Braille,
|
|
27
34
|
};
|
|
28
35
|
|
|
@@ -32,16 +39,24 @@ pub fn render(frame: &mut Frame, area: ratatui::layout::Rect, node: Value) -> Re
|
|
|
32
39
|
.marker(marker);
|
|
33
40
|
|
|
34
41
|
if !block_val.is_nil() {
|
|
35
|
-
canvas = canvas.block(parse_block(block_val)?);
|
|
42
|
+
canvas = canvas.block(parse_block(block_val, &bump)?);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let background_color_val: Value = node.funcall("background_color", ())?;
|
|
46
|
+
if !background_color_val.is_nil() {
|
|
47
|
+
let background_color =
|
|
48
|
+
parse_color(&background_color_val.to_string()).unwrap_or(ratatui::style::Color::Reset);
|
|
49
|
+
canvas = canvas.background_color(background_color);
|
|
36
50
|
}
|
|
37
51
|
|
|
38
52
|
let canvas = canvas.paint(|ctx| {
|
|
39
53
|
for shape_val in shapes_val {
|
|
40
54
|
let class = shape_val.class();
|
|
41
|
-
|
|
55
|
+
// SAFETY: Immediate conversion to owned string avoids GC-unsafe borrowed reference.
|
|
56
|
+
let class_name = unsafe { class.name() }.into_owned();
|
|
42
57
|
|
|
43
|
-
match class_name.
|
|
44
|
-
"RatatuiRuby::Line" => {
|
|
58
|
+
match class_name.as_str() {
|
|
59
|
+
"RatatuiRuby::Shape::Line" => {
|
|
45
60
|
let x1: f64 = shape_val.funcall("x1", ()).unwrap_or(0.0);
|
|
46
61
|
let y1: f64 = shape_val.funcall("y1", ()).unwrap_or(0.0);
|
|
47
62
|
let x2: f64 = shape_val.funcall("x2", ()).unwrap_or(0.0);
|
|
@@ -49,15 +64,9 @@ pub fn render(frame: &mut Frame, area: ratatui::layout::Rect, node: Value) -> Re
|
|
|
49
64
|
let color_val: Value = shape_val.funcall("color", ()).unwrap();
|
|
50
65
|
let color =
|
|
51
66
|
parse_color(&color_val.to_string()).unwrap_or(ratatui::style::Color::Reset);
|
|
52
|
-
ctx.draw(&Line {
|
|
53
|
-
x1,
|
|
54
|
-
y1,
|
|
55
|
-
x2,
|
|
56
|
-
y2,
|
|
57
|
-
color,
|
|
58
|
-
});
|
|
67
|
+
ctx.draw(&Line { x1, y1, x2, y2, color });
|
|
59
68
|
}
|
|
60
|
-
"RatatuiRuby::Rectangle" => {
|
|
69
|
+
"RatatuiRuby::Shape::Rectangle" => {
|
|
61
70
|
let x: f64 = shape_val.funcall("x", ()).unwrap_or(0.0);
|
|
62
71
|
let y: f64 = shape_val.funcall("y", ()).unwrap_or(0.0);
|
|
63
72
|
let width: f64 = shape_val.funcall("width", ()).unwrap_or(0.0);
|
|
@@ -65,29 +74,18 @@ pub fn render(frame: &mut Frame, area: ratatui::layout::Rect, node: Value) -> Re
|
|
|
65
74
|
let color_val: Value = shape_val.funcall("color", ()).unwrap();
|
|
66
75
|
let color =
|
|
67
76
|
parse_color(&color_val.to_string()).unwrap_or(ratatui::style::Color::Reset);
|
|
68
|
-
ctx.draw(&Rectangle {
|
|
69
|
-
x,
|
|
70
|
-
y,
|
|
71
|
-
width,
|
|
72
|
-
height,
|
|
73
|
-
color,
|
|
74
|
-
});
|
|
77
|
+
ctx.draw(&Rectangle { x, y, width, height, color });
|
|
75
78
|
}
|
|
76
|
-
"RatatuiRuby::Circle" => {
|
|
79
|
+
"RatatuiRuby::Shape::Circle" => {
|
|
77
80
|
let x: f64 = shape_val.funcall("x", ()).unwrap_or(0.0);
|
|
78
81
|
let y: f64 = shape_val.funcall("y", ()).unwrap_or(0.0);
|
|
79
82
|
let radius: f64 = shape_val.funcall("radius", ()).unwrap_or(0.0);
|
|
80
83
|
let color_val: Value = shape_val.funcall("color", ()).unwrap();
|
|
81
84
|
let color =
|
|
82
85
|
parse_color(&color_val.to_string()).unwrap_or(ratatui::style::Color::Reset);
|
|
83
|
-
ctx.draw(&Circle {
|
|
84
|
-
x,
|
|
85
|
-
y,
|
|
86
|
-
radius,
|
|
87
|
-
color,
|
|
88
|
-
});
|
|
86
|
+
ctx.draw(&Circle { x, y, radius, color });
|
|
89
87
|
}
|
|
90
|
-
"RatatuiRuby::Map" => {
|
|
88
|
+
"RatatuiRuby::Shape::Map" => {
|
|
91
89
|
let color_val: Value = shape_val.funcall("color", ()).unwrap();
|
|
92
90
|
let color =
|
|
93
91
|
parse_color(&color_val.to_string()).unwrap_or(ratatui::style::Color::Reset);
|
|
@@ -98,6 +96,26 @@ pub fn render(frame: &mut Frame, area: ratatui::layout::Rect, node: Value) -> Re
|
|
|
98
96
|
};
|
|
99
97
|
ctx.draw(&Map { color, resolution });
|
|
100
98
|
}
|
|
99
|
+
"RatatuiRuby::Shape::Label" => {
|
|
100
|
+
let x: f64 = shape_val.funcall("x", ()).unwrap_or(0.0);
|
|
101
|
+
let y: f64 = shape_val.funcall("y", ()).unwrap_or(0.0);
|
|
102
|
+
let text_val: Value = shape_val.funcall("text", ()).unwrap();
|
|
103
|
+
let style_val: Value = shape_val.funcall("style", ()).unwrap();
|
|
104
|
+
|
|
105
|
+
// Parse text - take first line if multiple
|
|
106
|
+
if let Ok(lines) = parse_text(text_val) {
|
|
107
|
+
let mut line = lines.into_iter().next().unwrap_or_default();
|
|
108
|
+
|
|
109
|
+
// Apply style if provided
|
|
110
|
+
if !style_val.is_nil() {
|
|
111
|
+
if let Ok(style) = parse_style(style_val) {
|
|
112
|
+
line = line.style(style);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
ctx.print(x, y, line);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
101
119
|
_ => {}
|
|
102
120
|
}
|
|
103
121
|
}
|
|
@@ -132,7 +150,6 @@ mod tests {
|
|
|
132
150
|
let mut buf = Buffer::empty(Rect::new(0, 0, 5, 5));
|
|
133
151
|
canvas.render(Rect::new(0, 0, 5, 5), &mut buf);
|
|
134
152
|
|
|
135
|
-
// Verify that some Braille characters are rendered
|
|
136
153
|
let mut found_braille = false;
|
|
137
154
|
for cell in buf.content() {
|
|
138
155
|
if !cell.symbol().trim().is_empty() {
|
|
@@ -2,19 +2,21 @@
|
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
4
|
use crate::style::{parse_block, parse_style};
|
|
5
|
+
use bumpalo::Bump;
|
|
5
6
|
use magnus::{prelude::*, Error, Symbol, Value};
|
|
6
7
|
use ratatui::{
|
|
7
8
|
layout::Rect,
|
|
8
9
|
symbols,
|
|
9
10
|
text::Span,
|
|
10
|
-
widgets::{Axis, Chart, Dataset, GraphType},
|
|
11
|
+
widgets::{Axis, Chart, Dataset, GraphType, LegendPosition},
|
|
11
12
|
Frame,
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
16
|
+
let bump = Bump::new();
|
|
15
17
|
let ruby = magnus::Ruby::get().unwrap();
|
|
16
|
-
|
|
17
|
-
let class_name = unsafe { class.name() };
|
|
18
|
+
// SAFETY: Immediate conversion to owned string avoids GC-unsafe borrowed reference.
|
|
19
|
+
let class_name = unsafe { node.class().name() }.into_owned();
|
|
18
20
|
|
|
19
21
|
if class_name == "RatatuiRuby::LineChart" {
|
|
20
22
|
return render_line_chart(frame, area, node);
|
|
@@ -25,18 +27,22 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
25
27
|
let y_axis_val: Value = node.funcall("y_axis", ())?;
|
|
26
28
|
let block_val: Value = node.funcall("block", ())?;
|
|
27
29
|
let style_val: Value = node.funcall("style", ())?;
|
|
30
|
+
let legend_position_val: Value = node.funcall("legend_position", ())?;
|
|
31
|
+
let hidden_legend_constraints_val: Value = node.funcall("hidden_legend_constraints", ())?;
|
|
28
32
|
|
|
29
33
|
let mut datasets = Vec::new();
|
|
30
34
|
// We need to keep the data alive until the chart is rendered
|
|
31
35
|
let mut data_storage: Vec<Vec<(f64, f64)>> = Vec::new();
|
|
32
36
|
|
|
33
37
|
for i in 0..datasets_val.len() {
|
|
34
|
-
let
|
|
38
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
39
|
+
let ds_val: Value = datasets_val.entry(index)?;
|
|
35
40
|
let data_array: magnus::RArray = ds_val.funcall("data", ())?;
|
|
36
41
|
|
|
37
42
|
let mut points = Vec::new();
|
|
38
43
|
for j in 0..data_array.len() {
|
|
39
|
-
let
|
|
44
|
+
let index = isize::try_from(j).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
45
|
+
let point_array_val: Value = data_array.entry(index)?;
|
|
40
46
|
let point_array = magnus::RArray::from_value(point_array_val).ok_or_else(|| {
|
|
41
47
|
Error::new(ruby.exception_type_error(), "expected array for point")
|
|
42
48
|
})?;
|
|
@@ -48,13 +54,13 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
for (i, points) in data_storage.iter().enumerate() {
|
|
51
|
-
let
|
|
57
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
58
|
+
let ds_val: Value = datasets_val.entry(index)?;
|
|
52
59
|
let name: String = ds_val.funcall("name", ())?;
|
|
53
60
|
let marker_sym: Symbol = ds_val.funcall("marker", ())?;
|
|
54
61
|
let graph_type_sym: Symbol = ds_val.funcall("graph_type", ())?;
|
|
55
62
|
|
|
56
63
|
let marker = match marker_sym.to_string().as_str() {
|
|
57
|
-
"dot" => symbols::Marker::Dot,
|
|
58
64
|
"braille" => symbols::Marker::Braille,
|
|
59
65
|
"block" => symbols::Marker::Block,
|
|
60
66
|
"bar" => symbols::Marker::Bar,
|
|
@@ -62,18 +68,14 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
62
68
|
};
|
|
63
69
|
|
|
64
70
|
let graph_type = match graph_type_sym.to_string().as_str() {
|
|
65
|
-
"line" => GraphType::Line,
|
|
66
71
|
"scatter" => GraphType::Scatter,
|
|
67
72
|
_ => GraphType::Line,
|
|
68
73
|
};
|
|
69
74
|
|
|
70
75
|
let mut ds_style = ratatui::style::Style::default();
|
|
71
|
-
let
|
|
72
|
-
if !
|
|
73
|
-
|
|
74
|
-
if let Some(color) = crate::style::parse_color(&color_str) {
|
|
75
|
-
ds_style = ds_style.fg(color);
|
|
76
|
-
}
|
|
76
|
+
let style_val: Value = ds_val.funcall("style", ())?;
|
|
77
|
+
if !style_val.is_nil() {
|
|
78
|
+
ds_style = parse_style(style_val)?;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
let ds = Dataset::default()
|
|
@@ -91,28 +93,53 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
91
93
|
let mut chart = Chart::new(datasets).x_axis(x_axis).y_axis(y_axis);
|
|
92
94
|
|
|
93
95
|
if !block_val.is_nil() {
|
|
94
|
-
chart = chart.block(parse_block(block_val)?);
|
|
96
|
+
chart = chart.block(parse_block(block_val, &bump)?);
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
if !style_val.is_nil() {
|
|
98
100
|
chart = chart.style(parse_style(style_val)?);
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
if !legend_position_val.is_nil() {
|
|
104
|
+
let pos_sym: Symbol = legend_position_val.funcall("to_sym", ())?;
|
|
105
|
+
let pos = match pos_sym.to_string().as_str() {
|
|
106
|
+
"top_left" => LegendPosition::TopLeft,
|
|
107
|
+
"bottom_left" => LegendPosition::BottomLeft,
|
|
108
|
+
"bottom_right" => LegendPosition::BottomRight,
|
|
109
|
+
_ => LegendPosition::TopRight,
|
|
110
|
+
};
|
|
111
|
+
chart = chart.legend_position(Some(pos));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if !hidden_legend_constraints_val.is_nil() {
|
|
115
|
+
let constraints_array: magnus::RArray = hidden_legend_constraints_val.funcall("to_a", ())?;
|
|
116
|
+
if constraints_array.len() == 2 {
|
|
117
|
+
let width_val: Value = constraints_array.entry(0)?;
|
|
118
|
+
let height_val: Value = constraints_array.entry(1)?;
|
|
119
|
+
let width_constraint = super::layout::parse_constraint(width_val)?;
|
|
120
|
+
let height_constraint = super::layout::parse_constraint(height_val)?;
|
|
121
|
+
chart = chart.hidden_legend_constraints((width_constraint, height_constraint));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
101
125
|
frame.render_widget(chart, area);
|
|
102
126
|
Ok(())
|
|
103
127
|
}
|
|
104
128
|
|
|
105
129
|
fn parse_axis(axis_val: Value) -> Result<Axis<'static>, Error> {
|
|
130
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
106
131
|
let title: String = axis_val.funcall("title", ())?;
|
|
107
132
|
let bounds_val: magnus::RArray = axis_val.funcall("bounds", ())?;
|
|
108
133
|
let labels_val: magnus::RArray = axis_val.funcall("labels", ())?;
|
|
109
134
|
let style_val: Value = axis_val.funcall("style", ())?;
|
|
135
|
+
let labels_alignment_val: Value = axis_val.funcall("labels_alignment", ())?;
|
|
110
136
|
|
|
111
137
|
let bounds: [f64; 2] = [bounds_val.entry(0)?, bounds_val.entry(1)?];
|
|
112
138
|
|
|
113
139
|
let mut labels = Vec::new();
|
|
114
140
|
for i in 0..labels_val.len() {
|
|
115
|
-
let
|
|
141
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
142
|
+
let label: String = labels_val.entry(index)?;
|
|
116
143
|
labels.push(Span::from(label));
|
|
117
144
|
}
|
|
118
145
|
|
|
@@ -122,10 +149,21 @@ fn parse_axis(axis_val: Value) -> Result<Axis<'static>, Error> {
|
|
|
122
149
|
axis = axis.style(parse_style(style_val)?);
|
|
123
150
|
}
|
|
124
151
|
|
|
152
|
+
if !labels_alignment_val.is_nil() {
|
|
153
|
+
let alignment_sym: Symbol = labels_alignment_val.funcall("to_sym", ())?;
|
|
154
|
+
let alignment = match alignment_sym.to_string().as_str() {
|
|
155
|
+
"left" => ratatui::layout::HorizontalAlignment::Left,
|
|
156
|
+
"right" => ratatui::layout::HorizontalAlignment::Right,
|
|
157
|
+
_ => ratatui::layout::HorizontalAlignment::Center,
|
|
158
|
+
};
|
|
159
|
+
axis = axis.labels_alignment(alignment);
|
|
160
|
+
}
|
|
161
|
+
|
|
125
162
|
Ok(axis)
|
|
126
163
|
}
|
|
127
164
|
|
|
128
165
|
fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
166
|
+
let bump = Bump::new();
|
|
129
167
|
let ruby = magnus::Ruby::get().unwrap();
|
|
130
168
|
let datasets_val: magnus::RArray = node.funcall("datasets", ())?;
|
|
131
169
|
let x_labels_val: magnus::RArray = node.funcall("x_labels", ())?;
|
|
@@ -137,12 +175,14 @@ fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
137
175
|
let mut data_storage: Vec<Vec<(f64, f64)>> = Vec::new();
|
|
138
176
|
|
|
139
177
|
for i in 0..datasets_val.len() {
|
|
140
|
-
let
|
|
178
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
179
|
+
let ds_val: Value = datasets_val.entry(index)?;
|
|
141
180
|
let data_array: magnus::RArray = ds_val.funcall("data", ())?;
|
|
142
181
|
|
|
143
182
|
let mut points = Vec::new();
|
|
144
183
|
for j in 0..data_array.len() {
|
|
145
|
-
let
|
|
184
|
+
let index = isize::try_from(j).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
185
|
+
let point_array_val: Value = data_array.entry(index)?;
|
|
146
186
|
let point_array = magnus::RArray::from_value(point_array_val).ok_or_else(|| {
|
|
147
187
|
Error::new(ruby.exception_type_error(), "expected array for point")
|
|
148
188
|
})?;
|
|
@@ -154,14 +194,14 @@ fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
154
194
|
}
|
|
155
195
|
|
|
156
196
|
for (i, points) in data_storage.iter().enumerate() {
|
|
157
|
-
let
|
|
197
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
198
|
+
let ds_val: Value = datasets_val.entry(index)?;
|
|
158
199
|
let name: String = ds_val.funcall("name", ())?;
|
|
159
200
|
|
|
160
201
|
let mut ds_style = ratatui::style::Style::default();
|
|
161
|
-
let
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
ds_style = ds_style.fg(color);
|
|
202
|
+
let style_val: Value = ds_val.funcall("style", ())?;
|
|
203
|
+
if !style_val.is_nil() {
|
|
204
|
+
ds_style = parse_style(style_val)?;
|
|
165
205
|
}
|
|
166
206
|
|
|
167
207
|
let ds = Dataset::default()
|
|
@@ -174,13 +214,15 @@ fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
174
214
|
|
|
175
215
|
let mut x_labels = Vec::new();
|
|
176
216
|
for i in 0..x_labels_val.len() {
|
|
177
|
-
let
|
|
217
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
218
|
+
let label: String = x_labels_val.entry(index)?;
|
|
178
219
|
x_labels.push(Span::from(label));
|
|
179
220
|
}
|
|
180
221
|
|
|
181
222
|
let mut y_labels = Vec::new();
|
|
182
223
|
for i in 0..y_labels_val.len() {
|
|
183
|
-
let
|
|
224
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
225
|
+
let label: String = y_labels_val.entry(index)?;
|
|
184
226
|
y_labels.push(Span::from(label));
|
|
185
227
|
}
|
|
186
228
|
// Ratatui 0.29+ requires labels to be present for the axis line to render
|
|
@@ -210,7 +252,7 @@ fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
210
252
|
}
|
|
211
253
|
}
|
|
212
254
|
}
|
|
213
|
-
if min_x
|
|
255
|
+
if (min_x - max_x).abs() < f64::EPSILON {
|
|
214
256
|
max_x = min_x + 1.0;
|
|
215
257
|
}
|
|
216
258
|
|
|
@@ -219,7 +261,7 @@ fn render_line_chart(frame: &mut Frame, area: Rect, node: Value) -> Result<(), E
|
|
|
219
261
|
|
|
220
262
|
let mut chart = Chart::new(datasets).x_axis(x_axis).y_axis(y_axis);
|
|
221
263
|
if !block_val.is_nil() {
|
|
222
|
-
chart = chart.block(parse_block(block_val)?);
|
|
264
|
+
chart = chart.block(parse_block(block_val, &bump)?);
|
|
223
265
|
}
|
|
224
266
|
|
|
225
267
|
frame.render_widget(chart, area);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
|
+
use bumpalo::Bump;
|
|
4
5
|
use magnus::{prelude::*, Error, Value};
|
|
5
6
|
use ratatui::{layout::Rect, widgets::Widget, Frame};
|
|
6
7
|
|
|
@@ -10,7 +11,8 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
10
11
|
// If a block is provided, render it on top of the cleared area
|
|
11
12
|
if let Ok(block_val) = node.funcall::<_, _, Value>("block", ()) {
|
|
12
13
|
if !block_val.is_nil() {
|
|
13
|
-
let
|
|
14
|
+
let bump = Bump::new();
|
|
15
|
+
let block = crate::style::parse_block(block_val, &bump)?;
|
|
14
16
|
block.render(area, frame.buffer_mut());
|
|
15
17
|
}
|
|
16
18
|
}
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
4
|
use crate::style::{parse_block, parse_style};
|
|
5
|
+
use bumpalo::Bump;
|
|
5
6
|
use magnus::{prelude::*, Error, Value};
|
|
6
7
|
use ratatui::{layout::Rect, widgets::Gauge, Frame};
|
|
7
8
|
|
|
8
9
|
pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
10
|
+
let bump = Bump::new();
|
|
9
11
|
let ratio: f64 = node.funcall("ratio", ())?;
|
|
10
12
|
let label_val: Value = node.funcall("label", ())?;
|
|
11
13
|
let style_val: Value = node.funcall("style", ())?;
|
|
14
|
+
let gauge_style_val: Value = node.funcall("gauge_style", ())?;
|
|
12
15
|
let block_val: Value = node.funcall("block", ())?;
|
|
13
|
-
|
|
14
|
-
let mut gauge = Gauge::default().ratio(ratio);
|
|
16
|
+
let use_unicode: bool = node.funcall("use_unicode", ())?;
|
|
17
|
+
let mut gauge = Gauge::default().ratio(ratio).use_unicode(use_unicode);
|
|
15
18
|
|
|
16
19
|
if !label_val.is_nil() {
|
|
17
20
|
let label_str: String = label_val.funcall("to_s", ())?;
|
|
@@ -19,11 +22,15 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
if !style_val.is_nil() {
|
|
22
|
-
gauge = gauge.
|
|
25
|
+
gauge = gauge.style(parse_style(style_val)?);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if !gauge_style_val.is_nil() {
|
|
29
|
+
gauge = gauge.gauge_style(parse_style(gauge_style_val)?);
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
if !block_val.is_nil() {
|
|
26
|
-
gauge = gauge.block(parse_block(block_val)?);
|
|
33
|
+
gauge = gauge.block(parse_block(block_val, &bump)?);
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
frame.render_widget(gauge, area);
|