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