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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
use crate::rendering::render_node;
|
|
5
5
|
use magnus::{prelude::*, Error, Symbol, Value};
|
|
6
6
|
use ratatui::{
|
|
7
|
-
layout::{Constraint, Direction, Layout, Rect},
|
|
7
|
+
layout::{Constraint, Direction, Flex, Layout, Rect},
|
|
8
8
|
Frame,
|
|
9
9
|
};
|
|
10
10
|
|
|
@@ -18,27 +18,34 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
18
18
|
let constraints_val: Value = node.funcall("constraints", ())?;
|
|
19
19
|
let constraints_array = magnus::RArray::from_value(constraints_val);
|
|
20
20
|
|
|
21
|
+
let flex_sym: Symbol = node.funcall("flex", ())?;
|
|
22
|
+
|
|
21
23
|
let direction = if direction_sym.to_string() == "vertical" {
|
|
22
24
|
Direction::Vertical
|
|
23
25
|
} else {
|
|
24
26
|
Direction::Horizontal
|
|
25
27
|
};
|
|
26
28
|
|
|
29
|
+
let flex = match flex_sym.to_string().as_str() {
|
|
30
|
+
"start" => Flex::Start,
|
|
31
|
+
"center" => Flex::Center,
|
|
32
|
+
"end" => Flex::End,
|
|
33
|
+
"space_between" => Flex::SpaceBetween,
|
|
34
|
+
"space_around" => Flex::SpaceAround,
|
|
35
|
+
"space_evenly" => Flex::SpaceEvenly,
|
|
36
|
+
_ => Flex::Legacy,
|
|
37
|
+
};
|
|
38
|
+
|
|
27
39
|
let len = children_array.len();
|
|
28
40
|
if len > 0 {
|
|
29
41
|
let mut ratatui_constraints = Vec::new();
|
|
30
42
|
|
|
31
43
|
if let Some(arr) = constraints_array {
|
|
32
44
|
for i in 0..arr.len() {
|
|
33
|
-
let
|
|
34
|
-
let
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
match type_sym.to_string().as_str() {
|
|
38
|
-
"length" => ratatui_constraints.push(Constraint::Length(value)),
|
|
39
|
-
"percentage" => ratatui_constraints.push(Constraint::Percentage(value)),
|
|
40
|
-
"min" => ratatui_constraints.push(Constraint::Min(value)),
|
|
41
|
-
_ => {}
|
|
45
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
46
|
+
let constraint_obj: Value = arr.entry(index)?;
|
|
47
|
+
if let Ok(constraint) = parse_constraint(constraint_obj) {
|
|
48
|
+
ratatui_constraints.push(constraint);
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
}
|
|
@@ -46,29 +53,152 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
46
53
|
// If constraints don't match children, adjust or default
|
|
47
54
|
if ratatui_constraints.len() != len {
|
|
48
55
|
ratatui_constraints = (0..len)
|
|
49
|
-
.map(|_| Constraint::Percentage(100 / (len
|
|
56
|
+
.map(|_| Constraint::Percentage(100 / u16::try_from(len).unwrap_or(u16::MAX).max(1)))
|
|
50
57
|
.collect();
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
let chunks = Layout::default()
|
|
54
61
|
.direction(direction)
|
|
62
|
+
.flex(flex)
|
|
55
63
|
.constraints(ratatui_constraints)
|
|
56
64
|
.split(area);
|
|
57
65
|
|
|
58
66
|
for i in 0..len {
|
|
59
|
-
let
|
|
67
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
68
|
+
let child: Value = children_array.entry(index)?;
|
|
60
69
|
if let Err(e) = render_node(frame, chunks[i], child) {
|
|
61
|
-
eprintln!("Error rendering child {}: {:?}"
|
|
70
|
+
eprintln!("Error rendering child {i}: {e:?}");
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
}
|
|
65
74
|
Ok(())
|
|
66
75
|
}
|
|
67
76
|
|
|
77
|
+
pub fn parse_constraint(value: Value) -> Result<Constraint, Error> {
|
|
78
|
+
let type_sym: Symbol = value.funcall("type", ())?;
|
|
79
|
+
let value_obj: Value = value.funcall("value", ())?;
|
|
80
|
+
|
|
81
|
+
match type_sym.to_string().as_str() {
|
|
82
|
+
"length" => {
|
|
83
|
+
let val = u16::try_convert(value_obj)?;
|
|
84
|
+
Ok(Constraint::Length(val))
|
|
85
|
+
}
|
|
86
|
+
"percentage" => {
|
|
87
|
+
let val = u16::try_convert(value_obj)?;
|
|
88
|
+
Ok(Constraint::Percentage(val))
|
|
89
|
+
}
|
|
90
|
+
"min" => {
|
|
91
|
+
let val = u16::try_convert(value_obj)?;
|
|
92
|
+
Ok(Constraint::Min(val))
|
|
93
|
+
}
|
|
94
|
+
"max" => {
|
|
95
|
+
let val = u16::try_convert(value_obj)?;
|
|
96
|
+
Ok(Constraint::Max(val))
|
|
97
|
+
}
|
|
98
|
+
"fill" => {
|
|
99
|
+
let val = u16::try_convert(value_obj)?;
|
|
100
|
+
Ok(Constraint::Fill(val))
|
|
101
|
+
}
|
|
102
|
+
"ratio" => {
|
|
103
|
+
if let Some(arr) = magnus::RArray::from_value(value_obj) {
|
|
104
|
+
if arr.len() == 2 {
|
|
105
|
+
let n = u32::try_convert(arr.entry(0)?)?;
|
|
106
|
+
let d = u32::try_convert(arr.entry(1)?)?;
|
|
107
|
+
return Ok(Constraint::Ratio(n, d));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Fallback or error for invalid ratio?
|
|
111
|
+
// For now, let's treat it as Min(0) or similar, or error.
|
|
112
|
+
// But to match previous behavior (which ignored invalid), we just return 0 length or something?
|
|
113
|
+
// Check previous logic: it ignored it (`_ => {}`).
|
|
114
|
+
// But we need to return a Constraint. Use Length(0) as safe fallback if unmatched?
|
|
115
|
+
// Actually, let's error if strictly required, but existing logic pushed nothing if mismatched.
|
|
116
|
+
// If we push nothing, we can't return a Constraint.
|
|
117
|
+
// Let's assume input is valid for now or return a default.
|
|
118
|
+
Ok(Constraint::Length(0))
|
|
119
|
+
}
|
|
120
|
+
_ => Ok(Constraint::Length(0)), // Default fallback
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Splits an area into multiple rectangles based on constraints.
|
|
125
|
+
/// This is a pure calculation helper for hit testing.
|
|
126
|
+
///
|
|
127
|
+
/// # Arguments
|
|
128
|
+
/// * `area` - A Ruby Hash or Rect with :x, :y, :width, :height keys
|
|
129
|
+
/// * `direction` - Symbol :vertical or :horizontal
|
|
130
|
+
/// * `constraints` - Array of Constraint objects
|
|
131
|
+
/// * `flex` - Symbol for flex mode
|
|
132
|
+
///
|
|
133
|
+
/// # Returns
|
|
134
|
+
/// An array of Ruby Hashes representing Rect objects
|
|
135
|
+
pub fn split_layout(
|
|
136
|
+
area: Value,
|
|
137
|
+
direction: Symbol,
|
|
138
|
+
constraints: magnus::RArray,
|
|
139
|
+
flex: Symbol,
|
|
140
|
+
) -> Result<magnus::RArray, Error> {
|
|
141
|
+
let ruby = magnus::Ruby::get().unwrap();
|
|
142
|
+
|
|
143
|
+
// Parse area from Hash or Rect-like object
|
|
144
|
+
let x: u16 = area.funcall("x", ())?;
|
|
145
|
+
let y: u16 = area.funcall("y", ())?;
|
|
146
|
+
let width: u16 = area.funcall("width", ())?;
|
|
147
|
+
let height: u16 = area.funcall("height", ())?;
|
|
148
|
+
let rect = Rect::new(x, y, width, height);
|
|
149
|
+
|
|
150
|
+
// Parse direction
|
|
151
|
+
let dir = if direction.to_string() == "vertical" {
|
|
152
|
+
Direction::Vertical
|
|
153
|
+
} else {
|
|
154
|
+
Direction::Horizontal
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Parse flex
|
|
158
|
+
let flex_mode = match flex.to_string().as_str() {
|
|
159
|
+
"start" => Flex::Start,
|
|
160
|
+
"center" => Flex::Center,
|
|
161
|
+
"end" => Flex::End,
|
|
162
|
+
"space_between" => Flex::SpaceBetween,
|
|
163
|
+
"space_around" => Flex::SpaceAround,
|
|
164
|
+
"space_evenly" => Flex::SpaceEvenly,
|
|
165
|
+
_ => Flex::Legacy,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Parse constraints
|
|
169
|
+
let mut ratatui_constraints = Vec::new();
|
|
170
|
+
for i in 0..constraints.len() {
|
|
171
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
172
|
+
let constraint_obj: Value = constraints.entry(index)?;
|
|
173
|
+
if let Ok(constraint) = parse_constraint(constraint_obj) {
|
|
174
|
+
ratatui_constraints.push(constraint);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Compute layout
|
|
179
|
+
let chunks = Layout::default()
|
|
180
|
+
.direction(dir)
|
|
181
|
+
.flex(flex_mode)
|
|
182
|
+
.constraints(ratatui_constraints)
|
|
183
|
+
.split(rect);
|
|
184
|
+
|
|
185
|
+
// Convert to Ruby array of Hashes
|
|
186
|
+
let result = ruby.ary_new_capa(chunks.len());
|
|
187
|
+
for chunk in chunks.iter() {
|
|
188
|
+
let hash = ruby.hash_new();
|
|
189
|
+
hash.aset(ruby.sym_new("x"), chunk.x)?;
|
|
190
|
+
hash.aset(ruby.sym_new("y"), chunk.y)?;
|
|
191
|
+
hash.aset(ruby.sym_new("width"), chunk.width)?;
|
|
192
|
+
hash.aset(ruby.sym_new("height"), chunk.height)?;
|
|
193
|
+
result.push(hash)?;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
Ok(result)
|
|
197
|
+
}
|
|
198
|
+
|
|
68
199
|
#[cfg(test)]
|
|
69
200
|
mod tests {
|
|
70
|
-
|
|
71
|
-
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
|
201
|
+
use ratatui::layout::{Constraint, Direction, Flex, Layout, Rect};
|
|
72
202
|
|
|
73
203
|
#[test]
|
|
74
204
|
fn test_layout_logic() {
|
|
@@ -80,4 +210,77 @@ mod tests {
|
|
|
80
210
|
assert_eq!(chunks.len(), 2);
|
|
81
211
|
assert_eq!(chunks[0].height, 50);
|
|
82
212
|
}
|
|
213
|
+
|
|
214
|
+
#[test]
|
|
215
|
+
fn test_fill_constraint() {
|
|
216
|
+
let area = Rect::new(0, 0, 100, 10);
|
|
217
|
+
let chunks = Layout::default()
|
|
218
|
+
.direction(Direction::Horizontal)
|
|
219
|
+
.constraints([Constraint::Fill(1), Constraint::Fill(3)])
|
|
220
|
+
.split(area);
|
|
221
|
+
assert_eq!(chunks.len(), 2);
|
|
222
|
+
assert_eq!(chunks[0].width, 25);
|
|
223
|
+
assert_eq!(chunks[1].width, 75);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#[test]
|
|
227
|
+
fn test_flex_space_between() {
|
|
228
|
+
let area = Rect::new(0, 0, 100, 10);
|
|
229
|
+
let chunks = Layout::default()
|
|
230
|
+
.direction(Direction::Horizontal)
|
|
231
|
+
.flex(Flex::SpaceBetween)
|
|
232
|
+
.constraints([
|
|
233
|
+
Constraint::Length(10),
|
|
234
|
+
Constraint::Length(10),
|
|
235
|
+
Constraint::Length(10),
|
|
236
|
+
])
|
|
237
|
+
.split(area);
|
|
238
|
+
assert_eq!(chunks.len(), 3);
|
|
239
|
+
assert_eq!(chunks[0].x, 0);
|
|
240
|
+
assert_eq!(chunks[1].x, 45);
|
|
241
|
+
assert_eq!(chunks[2].x, 90);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
#[test]
|
|
245
|
+
fn test_flex_space_evenly() {
|
|
246
|
+
let area = Rect::new(0, 0, 100, 10);
|
|
247
|
+
let chunks = Layout::default()
|
|
248
|
+
.direction(Direction::Horizontal)
|
|
249
|
+
.flex(Flex::SpaceEvenly)
|
|
250
|
+
.constraints([
|
|
251
|
+
Constraint::Length(10),
|
|
252
|
+
Constraint::Length(10),
|
|
253
|
+
Constraint::Length(10),
|
|
254
|
+
])
|
|
255
|
+
.split(area);
|
|
256
|
+
assert_eq!(chunks.len(), 3);
|
|
257
|
+
assert_eq!(chunks[0].x, 18);
|
|
258
|
+
assert_eq!(chunks[1].x, 45);
|
|
259
|
+
assert_eq!(chunks[2].x, 73);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
#[test]
|
|
263
|
+
fn test_flex_center() {
|
|
264
|
+
let area = Rect::new(0, 0, 100, 10);
|
|
265
|
+
let chunks = Layout::default()
|
|
266
|
+
.direction(Direction::Horizontal)
|
|
267
|
+
.flex(Flex::Center)
|
|
268
|
+
.constraints([Constraint::Length(20)])
|
|
269
|
+
.split(area);
|
|
270
|
+
assert_eq!(chunks.len(), 1);
|
|
271
|
+
assert_eq!(chunks[0].x, 40);
|
|
272
|
+
assert_eq!(chunks[0].width, 20);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
#[test]
|
|
276
|
+
fn test_max_constraint() {
|
|
277
|
+
let area = Rect::new(0, 0, 100, 10);
|
|
278
|
+
let chunks = Layout::default()
|
|
279
|
+
.direction(Direction::Horizontal)
|
|
280
|
+
.constraints([Constraint::Max(30), Constraint::Fill(1)])
|
|
281
|
+
.split(area);
|
|
282
|
+
assert_eq!(chunks.len(), 2);
|
|
283
|
+
assert_eq!(chunks[0].width, 30);
|
|
284
|
+
assert_eq!(chunks[1].width, 70);
|
|
285
|
+
}
|
|
83
286
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
|
|
2
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
use crate::style::{parse_block, parse_style};
|
|
5
|
+
use bumpalo::Bump;
|
|
6
|
+
use magnus::{prelude::*, Error, Value};
|
|
7
|
+
use ratatui::{layout::Rect, widgets::LineGauge, Frame};
|
|
8
|
+
|
|
9
|
+
pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
10
|
+
let bump = Bump::new();
|
|
11
|
+
let ratio: f64 = node.funcall("ratio", ())?;
|
|
12
|
+
let label_val: Value = node.funcall("label", ())?;
|
|
13
|
+
let style_val: Value = node.funcall("style", ())?;
|
|
14
|
+
let filled_style_val: Value = node.funcall("filled_style", ())?;
|
|
15
|
+
let unfilled_style_val: Value = node.funcall("unfilled_style", ())?;
|
|
16
|
+
let block_val: Value = node.funcall("block", ())?;
|
|
17
|
+
let filled_symbol_val: String = node.funcall("filled_symbol", ())?;
|
|
18
|
+
let unfilled_symbol_val: String = node.funcall("unfilled_symbol", ())?;
|
|
19
|
+
|
|
20
|
+
let mut gauge = LineGauge::default()
|
|
21
|
+
.ratio(ratio)
|
|
22
|
+
.filled_symbol(&filled_symbol_val)
|
|
23
|
+
.unfilled_symbol(&unfilled_symbol_val);
|
|
24
|
+
|
|
25
|
+
if !label_val.is_nil() {
|
|
26
|
+
let label_str: String = label_val.funcall("to_s", ())?;
|
|
27
|
+
gauge = gauge.label(label_str);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if !style_val.is_nil() {
|
|
31
|
+
gauge = gauge.style(parse_style(style_val)?);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if !filled_style_val.is_nil() {
|
|
35
|
+
let parsed_style = parse_style(filled_style_val)?;
|
|
36
|
+
gauge = gauge.filled_style(parsed_style);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if !unfilled_style_val.is_nil() {
|
|
40
|
+
let parsed_style = parse_style(unfilled_style_val)?;
|
|
41
|
+
gauge = gauge.unfilled_style(parsed_style);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if !block_val.is_nil() {
|
|
45
|
+
gauge = gauge.block(parse_block(block_val, &bump)?);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
frame.render_widget(gauge, area);
|
|
49
|
+
Ok(())
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[cfg(test)]
|
|
53
|
+
mod tests {
|
|
54
|
+
use super::*;
|
|
55
|
+
use ratatui::buffer::Buffer;
|
|
56
|
+
use ratatui::widgets::{LineGauge, Widget};
|
|
57
|
+
|
|
58
|
+
#[test]
|
|
59
|
+
fn test_line_gauge_rendering() {
|
|
60
|
+
let gauge = LineGauge::default().ratio(0.5).label("50%");
|
|
61
|
+
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
|
|
62
|
+
gauge.render(Rect::new(0, 0, 20, 1), &mut buf);
|
|
63
|
+
// LineGauge renders filled and unfilled characters
|
|
64
|
+
assert!(buf.content().iter().any(|c| c.symbol() != " "));
|
|
65
|
+
// Should contain label
|
|
66
|
+
assert!(buf.content().iter().any(|c| c.symbol() == "5"));
|
|
67
|
+
assert!(buf.content().iter().any(|c| c.symbol() == "%"));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[test]
|
|
71
|
+
fn test_line_gauge_zero_ratio() {
|
|
72
|
+
let gauge = LineGauge::default().ratio(0.0);
|
|
73
|
+
let mut buf = Buffer::empty(Rect::new(0, 0, 10, 1));
|
|
74
|
+
gauge.render(Rect::new(0, 0, 10, 1), &mut buf);
|
|
75
|
+
// At zero ratio, should mostly show unfilled character
|
|
76
|
+
let unfilled_count = buf
|
|
77
|
+
.content()
|
|
78
|
+
.iter()
|
|
79
|
+
.filter(|c| c.symbol() == "░" || c.symbol() == " ")
|
|
80
|
+
.count();
|
|
81
|
+
assert!(unfilled_count > 0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#[test]
|
|
85
|
+
fn test_line_gauge_full_ratio() {
|
|
86
|
+
let gauge = LineGauge::default().ratio(1.0).label("100%");
|
|
87
|
+
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
|
|
88
|
+
gauge.render(Rect::new(0, 0, 20, 1), &mut buf);
|
|
89
|
+
// At full ratio, should show some non-space characters (filled or partial)
|
|
90
|
+
assert!(buf.content().iter().any(|c| c.symbol() != " "));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
4
|
use crate::style::{parse_block, parse_style};
|
|
5
|
-
use
|
|
5
|
+
use bumpalo::Bump;
|
|
6
|
+
use magnus::{prelude::*, Error, Symbol, TryConvert, Value};
|
|
6
7
|
use ratatui::{
|
|
7
8
|
layout::Rect,
|
|
8
|
-
|
|
9
|
+
text::Line,
|
|
10
|
+
widgets::{HighlightSpacing, List, ListState},
|
|
9
11
|
Frame,
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
15
|
+
let bump = Bump::new();
|
|
13
16
|
let ruby = magnus::Ruby::get().unwrap();
|
|
14
17
|
let items_val: Value = node.funcall("items", ())?;
|
|
15
18
|
let items_array = magnus::RArray::from_value(items_val)
|
|
@@ -18,19 +21,23 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
18
21
|
let style_val: Value = node.funcall("style", ())?;
|
|
19
22
|
let highlight_style_val: Value = node.funcall("highlight_style", ())?;
|
|
20
23
|
let highlight_symbol_val: Value = node.funcall("highlight_symbol", ())?;
|
|
24
|
+
let repeat_highlight_symbol_val: Value = node.funcall("repeat_highlight_symbol", ())?;
|
|
25
|
+
let highlight_spacing_sym: Symbol = node.funcall("highlight_spacing", ())?;
|
|
26
|
+
let direction_val: Value = node.funcall("direction", ())?;
|
|
27
|
+
let scroll_padding_val: Value = node.funcall("scroll_padding", ())?;
|
|
21
28
|
let block_val: Value = node.funcall("block", ())?;
|
|
22
29
|
|
|
23
|
-
let mut items = Vec::new();
|
|
30
|
+
let mut items: Vec<String> = Vec::new();
|
|
24
31
|
for i in 0..items_array.len() {
|
|
25
|
-
let
|
|
32
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
33
|
+
let item: String = items_array.entry(index)?;
|
|
26
34
|
items.push(item);
|
|
27
35
|
}
|
|
28
36
|
|
|
29
|
-
let symbol: String = if
|
|
30
|
-
let s: String = String::try_convert(highlight_symbol_val)?;
|
|
31
|
-
s
|
|
32
|
-
} else {
|
|
37
|
+
let symbol: String = if highlight_symbol_val.is_nil() {
|
|
33
38
|
String::new()
|
|
39
|
+
} else {
|
|
40
|
+
String::try_convert(highlight_symbol_val)?
|
|
34
41
|
};
|
|
35
42
|
|
|
36
43
|
let mut state = ListState::default();
|
|
@@ -41,8 +48,40 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
41
48
|
|
|
42
49
|
let mut list = List::new(items);
|
|
43
50
|
|
|
51
|
+
let highlight_spacing = match highlight_spacing_sym.to_string().as_str() {
|
|
52
|
+
"always" => HighlightSpacing::Always,
|
|
53
|
+
"never" => HighlightSpacing::Never,
|
|
54
|
+
_ => HighlightSpacing::WhenSelected,
|
|
55
|
+
};
|
|
56
|
+
list = list.highlight_spacing(highlight_spacing);
|
|
57
|
+
|
|
44
58
|
if !highlight_symbol_val.is_nil() {
|
|
45
|
-
list = list.highlight_symbol(
|
|
59
|
+
list = list.highlight_symbol(Line::from(symbol));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if !repeat_highlight_symbol_val.is_nil() {
|
|
63
|
+
let repeat: bool = TryConvert::try_convert(repeat_highlight_symbol_val)?;
|
|
64
|
+
list = list.repeat_highlight_symbol(repeat);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if !direction_val.is_nil() {
|
|
68
|
+
let direction_sym: magnus::Symbol = TryConvert::try_convert(direction_val)?;
|
|
69
|
+
let direction_str = direction_sym.name().unwrap();
|
|
70
|
+
match direction_str.as_ref() {
|
|
71
|
+
"top_to_bottom" => list = list.direction(ratatui::widgets::ListDirection::TopToBottom),
|
|
72
|
+
"bottom_to_top" => list = list.direction(ratatui::widgets::ListDirection::BottomToTop),
|
|
73
|
+
_ => {
|
|
74
|
+
return Err(Error::new(
|
|
75
|
+
ruby.exception_arg_error(),
|
|
76
|
+
"direction must be :top_to_bottom or :bottom_to_top",
|
|
77
|
+
))
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if !scroll_padding_val.is_nil() {
|
|
83
|
+
let padding: usize = TryConvert::try_convert(scroll_padding_val)?;
|
|
84
|
+
list = list.scroll_padding(padding);
|
|
46
85
|
}
|
|
47
86
|
|
|
48
87
|
if !style_val.is_nil() {
|
|
@@ -54,7 +93,7 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
54
93
|
}
|
|
55
94
|
|
|
56
95
|
if !block_val.is_nil() {
|
|
57
|
-
list = list.block(parse_block(block_val)?);
|
|
96
|
+
list = list.block(parse_block(block_val, &bump)?);
|
|
58
97
|
}
|
|
59
98
|
|
|
60
99
|
frame.render_stateful_widget(list, area, &mut state);
|
|
@@ -71,7 +110,7 @@ mod tests {
|
|
|
71
110
|
fn test_list_rendering() {
|
|
72
111
|
let items = vec!["Item 1", "Item 2"];
|
|
73
112
|
let list = List::new(items)
|
|
74
|
-
.highlight_symbol(">> ")
|
|
113
|
+
.highlight_symbol(Line::from(">> "))
|
|
75
114
|
.style(ratatui::style::Style::default().fg(ratatui::style::Color::White))
|
|
76
115
|
.highlight_style(
|
|
77
116
|
ratatui::style::Style::default()
|
|
@@ -98,4 +137,45 @@ mod tests {
|
|
|
98
137
|
.modifier
|
|
99
138
|
.contains(ratatui::style::Modifier::BOLD));
|
|
100
139
|
}
|
|
140
|
+
|
|
141
|
+
#[test]
|
|
142
|
+
fn test_repeat_highlight_symbol() {
|
|
143
|
+
let items = vec!["Item 1", "Item 2"];
|
|
144
|
+
let list_without_repeat = List::new(items.clone()).highlight_symbol(Line::from(">> "));
|
|
145
|
+
let list_with_repeat = List::new(items).highlight_symbol(Line::from(">> ")).repeat_highlight_symbol(true);
|
|
146
|
+
|
|
147
|
+
let mut state = ListState::default();
|
|
148
|
+
state.select(Some(0));
|
|
149
|
+
|
|
150
|
+
let mut buf1 = Buffer::empty(Rect::new(0, 0, 10, 2));
|
|
151
|
+
use ratatui::widgets::StatefulWidget;
|
|
152
|
+
StatefulWidget::render(list_without_repeat, Rect::new(0, 0, 10, 2), &mut buf1, &mut state);
|
|
153
|
+
|
|
154
|
+
let mut buf2 = Buffer::empty(Rect::new(0, 0, 10, 2));
|
|
155
|
+
StatefulWidget::render(list_with_repeat, Rect::new(0, 0, 10, 2), &mut buf2, &mut state);
|
|
156
|
+
|
|
157
|
+
// Both should render, but the behavior might differ based on content width
|
|
158
|
+
let content1 = buf1.content().iter().map(|c| c.symbol()).collect::<String>();
|
|
159
|
+
let content2 = buf2.content().iter().map(|c| c.symbol()).collect::<String>();
|
|
160
|
+
assert!(!content1.is_empty());
|
|
161
|
+
assert!(!content2.is_empty());
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#[test]
|
|
165
|
+
fn test_scroll_padding() {
|
|
166
|
+
let items = vec!["Item 1", "Item 2", "Item 3", "Item 4"];
|
|
167
|
+
let list = List::new(items).scroll_padding(1).highlight_symbol(Line::from(">> "));
|
|
168
|
+
|
|
169
|
+
let mut state = ListState::default();
|
|
170
|
+
state.select(Some(1));
|
|
171
|
+
|
|
172
|
+
let mut buf = Buffer::empty(Rect::new(0, 0, 15, 4));
|
|
173
|
+
use ratatui::widgets::StatefulWidget;
|
|
174
|
+
StatefulWidget::render(list, Rect::new(0, 0, 15, 4), &mut buf, &mut state);
|
|
175
|
+
|
|
176
|
+
let content = buf.content().iter().map(|c| c.symbol()).collect::<String>();
|
|
177
|
+
// With scroll padding, it should render but the exact behavior is handled by ratatui
|
|
178
|
+
assert!(!content.is_empty());
|
|
179
|
+
assert!(content.contains("Item"));
|
|
180
|
+
}
|
|
101
181
|
}
|
|
@@ -11,9 +11,12 @@ pub mod clear;
|
|
|
11
11
|
pub mod cursor;
|
|
12
12
|
pub mod gauge;
|
|
13
13
|
pub mod layout;
|
|
14
|
+
pub mod line_gauge;
|
|
14
15
|
pub mod list;
|
|
15
16
|
pub mod overlay;
|
|
16
17
|
pub mod paragraph;
|
|
18
|
+
pub mod ratatui_logo;
|
|
19
|
+
pub mod ratatui_mascot;
|
|
17
20
|
pub mod scrollbar;
|
|
18
21
|
pub mod sparkline;
|
|
19
22
|
pub mod table;
|
|
@@ -12,9 +12,10 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
12
12
|
.ok_or_else(|| Error::new(ruby.exception_type_error(), "expected array for layers"))?;
|
|
13
13
|
|
|
14
14
|
for i in 0..layers_array.len() {
|
|
15
|
-
let
|
|
15
|
+
let index = isize::try_from(i).map_err(|e| Error::new(ruby.exception_range_error(), e.to_string()))?;
|
|
16
|
+
let layer: Value = layers_array.entry(index)?;
|
|
16
17
|
if let Err(e) = render_node(frame, area, layer) {
|
|
17
|
-
eprintln!("Error rendering overlay layer {}: {:?}"
|
|
18
|
+
eprintln!("Error rendering overlay layer {i}: {e:?}");
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
Ok(())
|
|
@@ -2,40 +2,44 @@
|
|
|
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
|
-
layout::{
|
|
8
|
+
layout::{HorizontalAlignment, Rect},
|
|
8
9
|
widgets::{Paragraph, Wrap},
|
|
9
10
|
Frame,
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
use crate::text::parse_text;
|
|
14
|
+
|
|
15
|
+
fn create_paragraph<'a>(node: Value, bump: &'a Bump) -> Result<Paragraph<'a>, Error> {
|
|
16
|
+
let text_val: Value = node.funcall("text", ())?;
|
|
14
17
|
let style_val: Value = node.funcall("style", ())?;
|
|
15
18
|
let block_val: Value = node.funcall("block", ())?;
|
|
16
19
|
let wrap: bool = node.funcall("wrap", ())?;
|
|
17
|
-
let
|
|
20
|
+
let alignment_opt: Option<Symbol> = node.funcall("alignment", ())?;
|
|
18
21
|
let scroll_val: Value = node.funcall("scroll", ())?;
|
|
19
22
|
|
|
23
|
+
let lines = parse_text(text_val)?;
|
|
20
24
|
let style = parse_style(style_val)?;
|
|
21
|
-
let mut paragraph = Paragraph::new(
|
|
25
|
+
let mut paragraph = Paragraph::new(lines).style(style);
|
|
22
26
|
|
|
23
27
|
if !block_val.is_nil() {
|
|
24
|
-
paragraph = paragraph.block(parse_block(block_val)?);
|
|
28
|
+
paragraph = paragraph.block(parse_block(block_val, bump)?);
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
if wrap {
|
|
28
32
|
paragraph = paragraph.wrap(Wrap { trim: true });
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
if let Some(alignment) = alignment_opt {
|
|
36
|
+
match alignment.to_string().as_str() {
|
|
37
|
+
"center" => paragraph = paragraph.alignment(HorizontalAlignment::Center),
|
|
38
|
+
"right" => paragraph = paragraph.alignment(HorizontalAlignment::Right),
|
|
39
|
+
_ => {}
|
|
40
|
+
}
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
// Apply scroll offset if provided
|
|
38
|
-
// Ruby passes (y, x) array matching ratatui's convention
|
|
39
43
|
if !scroll_val.is_nil() {
|
|
40
44
|
let scroll_array: Vec<u16> = Vec::<u16>::try_convert(scroll_val)?;
|
|
41
45
|
if scroll_array.len() >= 2 {
|
|
@@ -43,10 +47,28 @@ pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
Ok(paragraph)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub fn render(frame: &mut Frame, area: Rect, node: Value) -> Result<(), Error> {
|
|
54
|
+
let bump = Bump::new();
|
|
55
|
+
let paragraph = create_paragraph(node, &bump)?;
|
|
46
56
|
frame.render_widget(paragraph, area);
|
|
47
57
|
Ok(())
|
|
48
58
|
}
|
|
49
59
|
|
|
60
|
+
pub fn line_count(node: Value, width: u16) -> Result<usize, Error> {
|
|
61
|
+
let bump = Bump::new();
|
|
62
|
+
let paragraph = create_paragraph(node, &bump)?;
|
|
63
|
+
Ok(paragraph.line_count(width))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub fn line_width(node: Value) -> Result<usize, Error> {
|
|
67
|
+
let bump = Bump::new();
|
|
68
|
+
let paragraph = create_paragraph(node, &bump)?;
|
|
69
|
+
Ok(paragraph.line_width())
|
|
70
|
+
}
|
|
71
|
+
|
|
50
72
|
#[cfg(test)]
|
|
51
73
|
mod tests {
|
|
52
74
|
use super::*;
|
|
@@ -54,7 +76,7 @@ mod tests {
|
|
|
54
76
|
|
|
55
77
|
#[test]
|
|
56
78
|
fn test_paragraph_rendering() {
|
|
57
|
-
let p = Paragraph::new("test content").alignment(
|
|
79
|
+
let p = Paragraph::new("test content").alignment(HorizontalAlignment::Center);
|
|
58
80
|
let mut buf = Buffer::empty(Rect::new(0, 0, 20, 1));
|
|
59
81
|
use ratatui::widgets::Widget;
|
|
60
82
|
p.render(Rect::new(0, 0, 20, 1), &mut buf);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
use magnus::{Error, Value};
|
|
2
|
+
use ratatui::{layout::Rect, widgets::{RatatuiLogo, RatatuiLogoSize}, Frame};
|
|
3
|
+
|
|
4
|
+
pub fn render(frame: &mut Frame, area: Rect, _node: Value) -> Result<(), Error> {
|
|
5
|
+
// RatatuiLogo does not support custom styling (it has fixed colors).
|
|
6
|
+
// It requires a size argument.
|
|
7
|
+
let widget = RatatuiLogo::new(RatatuiLogoSize::Small);
|
|
8
|
+
frame.render_widget(widget, area);
|
|
9
|
+
Ok(())
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#[cfg(test)]
|
|
13
|
+
mod tests {
|
|
14
|
+
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
|
15
|
+
use super::*;
|
|
16
|
+
|
|
17
|
+
#[test]
|
|
18
|
+
fn test_render() {
|
|
19
|
+
let mut buffer = Buffer::empty(Rect::new(0, 0, 50, 20));
|
|
20
|
+
let widget = RatatuiLogo::new(RatatuiLogoSize::Small);
|
|
21
|
+
widget.render(Rect::new(0, 0, 50, 20), &mut buffer);
|
|
22
|
+
|
|
23
|
+
let content = buffer.content().iter().map(|c| c.symbol()).collect::<String>();
|
|
24
|
+
|
|
25
|
+
// The logo uses block characters for rendering
|
|
26
|
+
assert!(content.contains('█'));
|
|
27
|
+
assert!(!content.trim().is_empty());
|
|
28
|
+
}
|
|
29
|
+
}
|