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