tui_realm_stdlib/components/
canvas.rs1use tuirealm::command::{Cmd, CmdResult};
6use tuirealm::props::{
7 Alignment, AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, Shape, Style,
8};
9use tuirealm::ratatui::symbols::Marker;
10use tuirealm::ratatui::text::Line as Spans;
11use tuirealm::ratatui::{
12 layout::Rect,
13 text::Span,
14 widgets::canvas::{Canvas as TuiCanvas, Context, Points},
15};
16use tuirealm::{Frame, MockComponent, State};
17
18use super::props::{
20 CANVAS_MARKER, CANVAS_MARKER_BLOCK, CANVAS_MARKER_BRAILLE, CANVAS_MARKER_DOT, CANVAS_X_BOUNDS,
21 CANVAS_Y_BOUNDS,
22};
23
24#[derive(Default)]
30#[must_use]
31pub struct Canvas {
32 props: Props,
33}
34
35impl Canvas {
36 pub fn foreground(mut self, fg: Color) -> Self {
41 self.attr(Attribute::Foreground, AttrValue::Color(fg));
42 self
43 }
44
45 pub fn background(mut self, bg: Color) -> Self {
46 self.attr(Attribute::Background, AttrValue::Color(bg));
47 self
48 }
49
50 pub fn borders(mut self, b: Borders) -> Self {
51 self.attr(Attribute::Borders, AttrValue::Borders(b));
52 self
53 }
54
55 pub fn title<S: Into<String>>(mut self, t: S, a: Alignment) -> Self {
56 self.attr(Attribute::Title, AttrValue::Title((t.into(), a)));
57 self
58 }
59
60 pub fn data(mut self, data: impl IntoIterator<Item = Shape>) -> Self {
61 self.attr(
62 Attribute::Shape,
63 AttrValue::Payload(PropPayload::Vec(
64 data.into_iter().map(PropValue::Shape).collect(),
65 )),
66 );
67 self
68 }
69
70 pub fn x_bounds(mut self, bounds: (f64, f64)) -> Self {
77 self.attr(
78 Attribute::Custom(CANVAS_X_BOUNDS),
79 AttrValue::Payload(PropPayload::Tup2((
80 PropValue::F64(bounds.0),
81 PropValue::F64(bounds.1),
82 ))),
83 );
84 self
85 }
86
87 pub fn y_bounds(mut self, bounds: (f64, f64)) -> Self {
94 self.attr(
95 Attribute::Custom(CANVAS_Y_BOUNDS),
96 AttrValue::Payload(PropPayload::Tup2((
97 PropValue::F64(bounds.0),
98 PropValue::F64(bounds.1),
99 ))),
100 );
101 self
102 }
103
104 pub fn marker(mut self, marker: Marker) -> Self {
106 self.attr(
107 Attribute::Custom(CANVAS_MARKER),
108 Self::marker_to_prop(marker),
109 );
110 self
111 }
112
113 fn marker_to_prop(marker: Marker) -> AttrValue {
114 AttrValue::Number(match marker {
115 Marker::HalfBlock => crate::props::CANVAS_MARKER_HALF_BLOCK,
116 Marker::Bar => crate::props::CANVAS_MARKER_BAR,
117 Marker::Block => CANVAS_MARKER_BLOCK,
118 Marker::Braille => CANVAS_MARKER_BRAILLE,
119 Marker::Dot => CANVAS_MARKER_DOT,
120 })
121 }
122
123 fn prop_to_marker(&self) -> Marker {
124 match self
125 .props
126 .get_or(
127 Attribute::Custom(CANVAS_MARKER),
128 AttrValue::Number(CANVAS_MARKER_BRAILLE),
129 )
130 .unwrap_number()
131 {
132 CANVAS_MARKER_BLOCK => Marker::Block,
133 CANVAS_MARKER_DOT => Marker::Dot,
134 _ => Marker::Braille,
135 }
136 }
137
138 fn draw_shape(ctx: &mut Context, shape: &Shape) {
140 match shape {
141 Shape::Label((x, y, label, color)) => {
142 let span = Span::styled(label.to_string(), Style::default().fg(*color));
143 ctx.print(*x, *y, Spans::from(vec![span]));
144 }
145 Shape::Layer => ctx.layer(),
146 Shape::Line(line) => ctx.draw(line),
147 Shape::Map(map) => ctx.draw(map),
148 Shape::Points((coords, color)) => ctx.draw(&Points {
149 coords,
150 color: *color,
151 }),
152 Shape::Rectangle(rectangle) => ctx.draw(rectangle),
153 }
154 }
155}
156
157impl MockComponent for Canvas {
158 fn view(&mut self, render: &mut Frame, area: Rect) {
159 if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
160 let background = self
165 .props
166 .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
167 .unwrap_color();
168 let borders = self
169 .props
170 .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
171 .unwrap_borders();
172 let title = self
173 .props
174 .get_ref(Attribute::Title)
175 .and_then(|x| x.as_title());
176 let focus = self
177 .props
178 .get_or(Attribute::Focus, AttrValue::Flag(false))
179 .unwrap_flag();
180 let block = crate::utils::get_block(borders, title, focus, None);
181 let x_bounds: [f64; 2] = self
183 .props
184 .get(Attribute::Custom(CANVAS_X_BOUNDS))
185 .map(|x| x.unwrap_payload().unwrap_tup2())
186 .map_or([0.0, 0.0], |(a, b)| [a.unwrap_f64(), b.unwrap_f64()]);
187 let y_bounds: [f64; 2] = self
188 .props
189 .get(Attribute::Custom(CANVAS_Y_BOUNDS))
190 .map(|x| x.unwrap_payload().unwrap_tup2())
191 .map_or([0.0, 0.0], |(a, b)| [a.unwrap_f64(), b.unwrap_f64()]);
192 let shapes: Vec<Shape> = self
194 .props
195 .get(Attribute::Shape)
196 .map(|x| {
197 x.unwrap_payload()
198 .unwrap_vec()
199 .iter()
200 .cloned()
201 .map(|x| x.unwrap_shape())
202 .collect()
203 })
204 .unwrap_or_default();
205 let canvas = TuiCanvas::default()
207 .background_color(background)
208 .block(block)
209 .marker(self.prop_to_marker())
210 .x_bounds(x_bounds)
211 .y_bounds(y_bounds)
212 .paint(|ctx| shapes.iter().for_each(|x| Self::draw_shape(ctx, x)));
213 render.render_widget(canvas, area);
215 }
216 }
217
218 fn query(&self, attr: Attribute) -> Option<AttrValue> {
219 self.props.get(attr)
220 }
221
222 fn attr(&mut self, attr: Attribute, value: AttrValue) {
223 self.props.set(attr, value);
224 }
225
226 fn state(&self) -> State {
227 State::None
228 }
229
230 fn perform(&mut self, _cmd: Cmd) -> CmdResult {
231 CmdResult::None
232 }
233}
234
235#[cfg(test)]
236mod test {
237
238 use super::*;
239
240 use pretty_assertions::assert_eq;
241 use tuirealm::ratatui::widgets::canvas::{Line, Map, MapResolution, Rectangle};
242
243 #[test]
244 fn test_component_canvas_with_shapes() {
245 let component: Canvas = Canvas::default()
246 .background(Color::Black)
247 .title("playing risiko", Alignment::Center)
248 .borders(Borders::default())
249 .marker(Marker::Dot)
250 .x_bounds((-180.0, 180.0))
251 .y_bounds((-90.0, 90.0))
252 .data([
253 Shape::Map(Map {
254 resolution: MapResolution::High,
255 color: Color::Rgb(240, 240, 240),
256 }),
257 Shape::Layer,
258 Shape::Line(Line {
259 x1: 0.0,
260 y1: 10.0,
261 x2: 10.0,
262 y2: 10.0,
263 color: Color::Red,
264 }),
265 Shape::Rectangle(Rectangle {
266 x: 60.0,
267 y: 20.0,
268 width: 70.0,
269 height: 22.0,
270 color: Color::Cyan,
271 }),
272 Shape::Points((
273 vec![
274 (21.0, 13.0),
275 (66.0, 77.0),
276 (34.0, 69.0),
277 (45.0, 76.0),
278 (120.0, 55.0),
279 (-32.0, -50.0),
280 (-4.0, 2.0),
281 (-32.0, -48.0),
282 ],
283 Color::Green,
284 )),
285 ]);
286 assert_eq!(component.state(), State::None);
287 }
288}