layout/std_shapes/
render.rs

1//! Implements the drawing of elements and arrows on the backing canvas.
2
3use crate::core::base::Orientation;
4use crate::core::format::{ClipHandle, RenderBackend, Renderable, Visible};
5use crate::core::geometry::*;
6use crate::core::style::{LineStyleKind, StyleAttr};
7use crate::std_shapes::shapes::*;
8
9/// Return the height and width of the record, depending on the geometry and
10/// internal text.
11fn get_record_size(
12    rec: &RecordDef,
13    dir: Orientation,
14    font_size: usize,
15) -> Point {
16    match rec {
17        RecordDef::Text(label, _) => pad_shape_scalar(
18            get_size_for_str(label, font_size),
19            BOX_SHAPE_PADDING,
20        ),
21        RecordDef::Array(arr) => {
22            let mut x: f64 = 0.;
23            let mut y: f64 = 0.;
24            for elem in arr {
25                let ret = get_record_size(elem, dir.flip(), font_size);
26                if dir.is_left_right() {
27                    x += ret.x;
28                    y = y.max(ret.y);
29                } else {
30                    x = x.max(ret.x);
31                    y += ret.y;
32                }
33            }
34            Point::new(x, y)
35        }
36    }
37}
38
39const BOX_SHAPE_PADDING: f64 = 10.;
40const CIRCLE_SHAPE_PADDING: f64 = 20.;
41
42/// Return the size of the shape. If \p make_xy_same is set then make the
43/// X and the Y of the shape the same. This will turn ellipses into circles and
44/// rectangles into boxes. The parameter \p dir specifies the direction of the
45/// graph. This tells us if we need to draw records left to right or top down.
46pub fn get_shape_size(
47    dir: Orientation,
48    s: &ShapeKind,
49    font: usize,
50    make_xy_same: bool,
51) -> Point {
52    let mut res = match s {
53        ShapeKind::Box(text) => {
54            pad_shape_scalar(get_size_for_str(text, font), BOX_SHAPE_PADDING)
55        }
56        ShapeKind::Circle(text) => {
57            pad_shape_scalar(get_size_for_str(text, font), CIRCLE_SHAPE_PADDING)
58        }
59        ShapeKind::DoubleCircle(text) => {
60            pad_shape_scalar(get_size_for_str(text, font), CIRCLE_SHAPE_PADDING)
61        }
62        ShapeKind::Record(sr) => {
63            pad_shape_scalar(get_record_size(sr, dir, font), BOX_SHAPE_PADDING)
64        }
65        ShapeKind::Connector(text) => {
66            if let Option::Some(text) = text {
67                pad_shape_scalar(
68                    get_size_for_str(text, font),
69                    BOX_SHAPE_PADDING,
70                )
71            } else {
72                Point::new(1., 1.)
73            }
74        }
75        _ => Point::new(1., 1.),
76    };
77    if make_xy_same {
78        res = make_size_square(res);
79    }
80    res
81}
82
83// Returns the innermost shape that the record describes, or the location and
84// size of the outer shape.
85fn get_record_port_location(
86    rec: &RecordDef,
87    dir: Orientation,
88    loc: Point,
89    size: Point,
90    look: &StyleAttr,
91    port_name: &str,
92) -> (Point, Point) {
93    struct Locator {
94        port_name: String,
95        loc: Point,
96        size: Point,
97    }
98
99    impl RecordVisitor for Locator {
100        fn handle_box(&mut self, _loc: Point, _size: Point) {}
101        fn handle_text(
102            &mut self,
103            loc: Point,
104            size: Point,
105            _label: &str,
106            port: &Option<String>,
107        ) {
108            if let Option::Some(port_name) = port {
109                if *port_name == self.port_name {
110                    self.loc = loc;
111                    self.size = size;
112                }
113            }
114        }
115    }
116
117    let mut visitor = Locator {
118        port_name: port_name.to_string(),
119        loc,
120        size,
121    };
122    visit_record(rec, dir, loc, size, look, &mut visitor);
123    (visitor.loc, visitor.size)
124}
125
126fn render_record(
127    rec: &RecordDef,
128    dir: Orientation,
129    loc: Point,
130    size: Point,
131    look: &StyleAttr,
132    canvas: &mut dyn RenderBackend,
133) {
134    struct Renderer<'a> {
135        look: StyleAttr,
136        clip_handle: Option<ClipHandle>,
137        canvas: &'a mut dyn RenderBackend,
138    }
139
140    // A reference to the clip region.
141    let mut clip_handle: Option<ClipHandle> = Option::None;
142
143    if look.rounded > 0 {
144        let xy = Point::new(loc.x - size.x / 2., loc.y - size.y / 2.);
145        let ch = canvas.create_clip(xy, size, 15);
146        clip_handle = Option::Some(ch);
147    }
148
149    impl<'a> RecordVisitor for Renderer<'a> {
150        fn handle_box(&mut self, loc: Point, size: Point) {
151            self.canvas.draw_rect(
152                Point::new(loc.x - size.x / 2., loc.y - size.y / 2.),
153                Point::new(size.x, size.y),
154                &self.look,
155                self.clip_handle,
156            );
157        }
158        fn handle_text(
159            &mut self,
160            loc: Point,
161            _size: Point,
162            label: &str,
163            _port: &Option<String>,
164        ) {
165            self.canvas.draw_text(loc, label, &self.look);
166        }
167    }
168
169    let mut visitor = Renderer {
170        look: look.clone(),
171        clip_handle,
172        canvas,
173    };
174    // Make the internal record boxes square and not round.
175    visitor.look.rounded = 0;
176    visit_record(rec, dir, loc, size, look, &mut visitor);
177
178    let mut look = look.clone();
179    look.fill_color = Option::None;
180    canvas.draw_rect(
181        Point::new(loc.x - size.x / 2., loc.y - size.y / 2.),
182        Point::new(size.x, size.y),
183        &look,
184        Option::None,
185    );
186}
187
188pub trait RecordVisitor {
189    fn handle_box(&mut self, loc: Point, size: Point);
190    fn handle_text(
191        &mut self,
192        loc: Point,
193        size: Point,
194        label: &str,
195        port: &Option<String>,
196    );
197}
198
199fn visit_record(
200    rec: &RecordDef,
201    dir: Orientation,
202    loc: Point,
203    size: Point,
204    look: &StyleAttr,
205    visitor: &mut dyn RecordVisitor,
206) {
207    visitor.handle_box(loc, size);
208    match rec {
209        RecordDef::Text(text, port) => {
210            visitor.handle_text(loc, size, text, port);
211        }
212        RecordDef::Array(arr) => {
213            let mut sizes: Vec<Point> = Vec::new();
214            let mut sum = Point::zero();
215            let mut mx = Point::zero();
216            // Figure out the recursive size of each element, and the largest
217            // element.
218            for elem in arr {
219                let sz = get_record_size(elem, dir, look.font_size);
220                sizes.push(sz);
221                sum = Point::new(sum.x + sz.x, sum.y + sz.y);
222                mx = Point::new(mx.x.max(sz.x), mx.y.max(sz.y));
223            }
224            // Normalize the size of each element on the x axis, and the maximum
225            // width of the y axis to render something like: [...][..][.][...]
226            for sz in &mut sizes {
227                if dir.is_left_right() {
228                    *sz = Point::new(size.x * sz.x / sum.x, size.y);
229                } else {
230                    *sz = Point::new(size.x, size.y * sz.y / sum.y);
231                }
232            }
233
234            if dir.is_left_right() {
235                // Start placing blocks from the left edge of the box.
236                // Use the edge as reference point.
237                let mut startx = loc.x - size.x / 2.;
238                for i in 0..sizes.len() {
239                    let element = &arr[i];
240                    let loc2 = Point::new(startx + sizes[i].x / 2., loc.y);
241                    visit_record(
242                        element,
243                        dir.flip(),
244                        loc2,
245                        sizes[i],
246                        look,
247                        visitor,
248                    );
249                    startx += sizes[i].x;
250                }
251            } else {
252                // Start placing blocks from the top edge of the box.
253                // Use the edge as reference point.
254                let mut starty = loc.y - size.y / 2.;
255                for i in 0..sizes.len() {
256                    let element = &arr[i];
257                    let loc2 = Point::new(loc.x, starty + sizes[i].y / 2.);
258                    visit_record(
259                        element,
260                        dir.flip(),
261                        loc2,
262                        sizes[i],
263                        look,
264                        visitor,
265                    );
266                    starty += sizes[i].y;
267                }
268            }
269        }
270    }
271}
272
273impl Renderable for Element {
274    fn render(&self, debug: bool, canvas: &mut dyn RenderBackend) {
275        if debug {
276            // Draw the pink bounding box.
277            let debug_look = StyleAttr::debug0();
278            let bb = self.pos.bbox(true);
279            canvas.draw_rect(
280                bb.0,
281                self.pos.size(true),
282                &debug_look,
283                Option::None,
284            );
285        }
286
287        match &self.shape {
288            ShapeKind::None => {}
289            ShapeKind::Record(rec) => {
290                render_record(
291                    rec,
292                    self.orientation,
293                    self.pos.center(),
294                    self.pos.size(false),
295                    &self.look,
296                    canvas,
297                );
298            }
299            ShapeKind::Box(text) => {
300                canvas.draw_rect(
301                    self.pos.bbox(false).0,
302                    self.pos.size(false),
303                    &self.look,
304                    Option::None,
305                );
306                canvas.draw_text(self.pos.center(), text.as_str(), &self.look);
307            }
308            ShapeKind::Circle(text) => {
309                canvas.draw_circle(
310                    self.pos.center(),
311                    self.pos.size(false),
312                    &self.look,
313                );
314                canvas.draw_text(self.pos.center(), text.as_str(), &self.look);
315            }
316            ShapeKind::DoubleCircle(text) => {
317                canvas.draw_circle(
318                    self.pos.center(),
319                    self.pos.size(false),
320                    &self.look,
321                );
322                canvas.draw_circle(
323                    self.pos.center(),
324                    self.pos.size(false).sub(Point::splat(15.)),
325                    &self.look,
326                );
327                canvas.draw_text(self.pos.center(), text.as_str(), &self.look);
328            }
329            ShapeKind::Connector(label) => {
330                if debug {
331                    canvas.draw_rect(
332                        self.pos.bbox(true).0,
333                        self.pos.size(true),
334                        &StyleAttr::debug0(),
335                        Option::None,
336                    );
337
338                    canvas.draw_rect(
339                        self.pos.bbox(false).0,
340                        self.pos.size(false),
341                        &StyleAttr::debug1(),
342                        Option::None,
343                    );
344                }
345                if let Option::Some(label) = label {
346                    canvas.draw_text(self.pos.middle(), label, &self.look);
347                }
348            }
349        }
350        if debug {
351            canvas.draw_circle(
352                self.pos.center(),
353                Point::new(6., 6.),
354                &StyleAttr::debug2(),
355            );
356        }
357    }
358
359    fn get_connector_location(
360        &self,
361        from: Point,
362        force: f64,
363        port: &Option<String>,
364    ) -> (Point, Point) {
365        match &self.shape {
366            ShapeKind::None => (Point::zero(), Point::zero()),
367            ShapeKind::Record(rec) => {
368                let mut loc = self.pos.center();
369                let mut size = self.pos.size(false);
370                // Find the region that represents the inner box in the record.
371                if let Option::Some(port_name) = port {
372                    let r = get_record_port_location(
373                        rec,
374                        self.orientation,
375                        loc,
376                        size,
377                        &self.look,
378                        port_name,
379                    );
380                    loc = r.0;
381                    size = r.1;
382                }
383
384                get_connection_point_for_box(loc, size, from, force)
385            }
386            ShapeKind::Box(_) => {
387                let loc = self.pos.center();
388                let size = self.pos.size(false);
389                get_connection_point_for_box(loc, size, from, force)
390            }
391            ShapeKind::Circle(_) => {
392                let loc = self.pos.center();
393                let size = self.pos.size(false);
394                get_connection_point_for_circle(loc, size, from, force)
395            }
396            ShapeKind::DoubleCircle(_) => {
397                let loc = self.pos.center();
398                let size = self.pos.size(false);
399                get_connection_point_for_circle(loc, size, from, force)
400            }
401            _ => {
402                unreachable!();
403            }
404        }
405    }
406
407    fn get_passthrough_path(
408        &self,
409        _from: Point,
410        _to: Point,
411        _force: f64,
412    ) -> (Point, Point) {
413        let loc = self.pos.center();
414        let size = self.pos.size(false);
415        if let ShapeKind::Connector(_) = self.shape {
416            get_passthrough_path_invisible(size, loc, _from, _to, _force)
417        } else {
418            panic!("We don't pass edges through this kind of shape");
419        }
420    }
421}
422
423pub fn generate_curve_for_elements(
424    elements: &[Element],
425    arrow: &Arrow,
426    force: f64,
427) -> Vec<(Point, Point)> {
428    let mut path: Vec<(Point, Point)> = Vec::new();
429    let to_loc = elements[1].position().center();
430    let from_con =
431        elements[0].get_connector_location(to_loc, force, &arrow.src_port);
432
433    let mut prev_exit_loc = from_con.0;
434
435    path.push((from_con.0, from_con.1));
436
437    for i in 1..elements.len() {
438        let to_con;
439        let is_last: bool = i == elements.len() - 1;
440
441        if is_last {
442            let to = &elements[i];
443            to_con = to.get_connector_location(
444                prev_exit_loc,
445                force,
446                &arrow.dst_port,
447            );
448            prev_exit_loc = to_con.0;
449        } else {
450            let center = &elements[i];
451            let to = &elements[i + 1];
452            let to_loc = to.position().center();
453            to_con = center.get_passthrough_path(prev_exit_loc, to_loc, force);
454            prev_exit_loc = to_con.0;
455        }
456
457        path.push((to_con.1, to_con.0));
458    }
459
460    path
461}
462
463pub fn render_arrow(
464    canvas: &mut dyn RenderBackend,
465    debug: bool,
466    elements: &[Element],
467    arrow: &Arrow,
468) {
469    let path = generate_curve_for_elements(elements, arrow, 30.);
470
471    if debug {
472        for seg in &path {
473            canvas.draw_line(seg.0, seg.1, &StyleAttr::debug2());
474            canvas.draw_circle(seg.0, Point::new(6., 6.), &StyleAttr::debug1());
475            canvas.draw_circle(seg.1, Point::new(6., 6.), &StyleAttr::debug1());
476        }
477    }
478
479    let dash = match arrow.line_style {
480        LineStyleKind::None => {
481            return;
482        }
483        LineStyleKind::Normal => false,
484        LineStyleKind::Dashed => true,
485        LineStyleKind::Dotted => true,
486    };
487
488    let start = matches!(arrow.start, LineEndKind::Arrow);
489    let end = matches!(arrow.end, LineEndKind::Arrow);
490
491    canvas.draw_arrow(&path, dash, (start, end), &arrow.look, &arrow.text);
492}