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                Option::None,
156                self.clip_handle,
157            );
158        }
159        fn handle_text(
160            &mut self,
161            loc: Point,
162            _size: Point,
163            label: &str,
164            _port: &Option<String>,
165        ) {
166            self.canvas.draw_text(loc, label, &self.look);
167        }
168    }
169
170    let mut visitor = Renderer {
171        look: look.clone(),
172        clip_handle,
173        canvas,
174    };
175    // Make the internal record boxes square and not round.
176    visitor.look.rounded = 0;
177    visit_record(rec, dir, loc, size, look, &mut visitor);
178
179    let mut look = look.clone();
180    look.fill_color = Option::None;
181    canvas.draw_rect(
182        Point::new(loc.x - size.x / 2., loc.y - size.y / 2.),
183        Point::new(size.x, size.y),
184        &look,
185        Option::None,
186        Option::None,
187    );
188}
189
190pub trait RecordVisitor {
191    fn handle_box(&mut self, loc: Point, size: Point);
192    fn handle_text(
193        &mut self,
194        loc: Point,
195        size: Point,
196        label: &str,
197        port: &Option<String>,
198    );
199}
200
201fn visit_record(
202    rec: &RecordDef,
203    dir: Orientation,
204    loc: Point,
205    size: Point,
206    look: &StyleAttr,
207    visitor: &mut dyn RecordVisitor,
208) {
209    visitor.handle_box(loc, size);
210    match rec {
211        RecordDef::Text(text, port) => {
212            visitor.handle_text(loc, size, text, port);
213        }
214        RecordDef::Array(arr) => {
215            let mut sizes: Vec<Point> = Vec::new();
216            let mut sum = Point::zero();
217            let mut mx = Point::zero();
218            // Figure out the recursive size of each element, and the largest
219            // element.
220            for elem in arr {
221                let sz = get_record_size(elem, dir, look.font_size);
222                sizes.push(sz);
223                sum = Point::new(sum.x + sz.x, sum.y + sz.y);
224                mx = Point::new(mx.x.max(sz.x), mx.y.max(sz.y));
225            }
226            // Normalize the size of each element on the x axis, and the maximum
227            // width of the y axis to render something like: [...][..][.][...]
228            for sz in &mut sizes {
229                if dir.is_left_right() {
230                    *sz = Point::new(size.x * sz.x / sum.x, size.y);
231                } else {
232                    *sz = Point::new(size.x, size.y * sz.y / sum.y);
233                }
234            }
235
236            if dir.is_left_right() {
237                // Start placing blocks from the left edge of the box.
238                // Use the edge as reference point.
239                let mut startx = loc.x - size.x / 2.;
240                for i in 0..sizes.len() {
241                    let element = &arr[i];
242                    let loc2 = Point::new(startx + sizes[i].x / 2., loc.y);
243                    visit_record(
244                        element,
245                        dir.flip(),
246                        loc2,
247                        sizes[i],
248                        look,
249                        visitor,
250                    );
251                    startx += sizes[i].x;
252                }
253            } else {
254                // Start placing blocks from the top edge of the box.
255                // Use the edge as reference point.
256                let mut starty = loc.y - size.y / 2.;
257                for i in 0..sizes.len() {
258                    let element = &arr[i];
259                    let loc2 = Point::new(loc.x, starty + sizes[i].y / 2.);
260                    visit_record(
261                        element,
262                        dir.flip(),
263                        loc2,
264                        sizes[i],
265                        look,
266                        visitor,
267                    );
268                    starty += sizes[i].y;
269                }
270            }
271        }
272    }
273}
274
275impl Renderable for Element {
276    fn render(&self, debug: bool, canvas: &mut dyn RenderBackend) {
277        if debug {
278            // Draw the pink bounding box.
279            let debug_look = StyleAttr::debug0();
280            let bb = self.pos.bbox(true);
281            canvas.draw_rect(
282                bb.0,
283                self.pos.size(true),
284                &debug_look,
285                self.properties.clone(),
286                Option::None,
287            );
288        }
289
290        match &self.shape {
291            ShapeKind::None => {}
292            ShapeKind::Record(rec) => {
293                render_record(
294                    rec,
295                    self.orientation,
296                    self.pos.center(),
297                    self.pos.size(false),
298                    &self.look,
299                    canvas,
300                );
301            }
302            ShapeKind::Box(text) => {
303                canvas.draw_rect(
304                    self.pos.bbox(false).0,
305                    self.pos.size(false),
306                    &self.look,
307                    self.properties.clone(),
308                    Option::None,
309                );
310                canvas.draw_text(self.pos.center(), text.as_str(), &self.look);
311            }
312            ShapeKind::Circle(text) => {
313                canvas.draw_circle(
314                    self.pos.center(),
315                    self.pos.size(false),
316                    &self.look,
317                    self.properties.clone(),
318                );
319                canvas.draw_text(self.pos.center(), text.as_str(), &self.look);
320            }
321            ShapeKind::DoubleCircle(text) => {
322                canvas.draw_circle(
323                    self.pos.center(),
324                    self.pos.size(false),
325                    &self.look,
326                    self.properties.clone(),
327                );
328                let outer_circle_style = {
329                    let mut x = self.look.clone();
330                    x.fill_color = None;
331                    x
332                };
333                canvas.draw_circle(
334                    self.pos.center(),
335                    self.pos.size(false).add(Point::splat(8.)),
336                    &outer_circle_style,
337                    None,
338                );
339                canvas.draw_text(self.pos.center(), text.as_str(), &self.look);
340            }
341            ShapeKind::Connector(label) => {
342                if debug {
343                    canvas.draw_rect(
344                        self.pos.bbox(true).0,
345                        self.pos.size(true),
346                        &StyleAttr::debug0(),
347                        Option::None,
348                        Option::None,
349                    );
350
351                    canvas.draw_rect(
352                        self.pos.bbox(false).0,
353                        self.pos.size(false),
354                        &StyleAttr::debug1(),
355                        Option::None,
356                        Option::None,
357                    );
358                }
359                if let Option::Some(label) = label {
360                    canvas.draw_text(self.pos.middle(), label, &self.look);
361                }
362            }
363        }
364        if debug {
365            canvas.draw_circle(
366                self.pos.center(),
367                Point::new(6., 6.),
368                &StyleAttr::debug2(),
369                Option::None,
370            );
371        }
372    }
373
374    fn get_connector_location(
375        &self,
376        from: Point,
377        force: f64,
378        port: &Option<String>,
379    ) -> (Point, Point) {
380        match &self.shape {
381            ShapeKind::None => (Point::zero(), Point::zero()),
382            ShapeKind::Record(rec) => {
383                let mut loc = self.pos.center();
384                let mut size = self.pos.size(false);
385                // Find the region that represents the inner box in the record.
386                if let Option::Some(port_name) = port {
387                    let r = get_record_port_location(
388                        rec,
389                        self.orientation,
390                        loc,
391                        size,
392                        &self.look,
393                        port_name,
394                    );
395                    loc = r.0;
396                    size = r.1;
397                }
398
399                get_connection_point_for_box(loc, size, from, force)
400            }
401            ShapeKind::Box(_) => {
402                let loc = self.pos.center();
403                let size = self.pos.size(false);
404                get_connection_point_for_box(loc, size, from, force)
405            }
406            ShapeKind::Circle(_) => {
407                let loc = self.pos.center();
408                let size = self.pos.size(false);
409                get_connection_point_for_circle(loc, size, from, force)
410            }
411            ShapeKind::DoubleCircle(_) => {
412                let loc = self.pos.center();
413                let size = self.pos.size(false);
414                get_connection_point_for_circle(loc, size, from, force)
415            }
416            _ => {
417                unreachable!();
418            }
419        }
420    }
421
422    fn get_passthrough_path(
423        &self,
424        _from: Point,
425        _to: Point,
426        _force: f64,
427    ) -> (Point, Point) {
428        let loc = self.pos.center();
429        let size = self.pos.size(false);
430        if let ShapeKind::Connector(_) = self.shape {
431            get_passthrough_path_invisible(size, loc, _from, _to, _force)
432        } else {
433            panic!("We don't pass edges through this kind of shape");
434        }
435    }
436}
437
438pub fn generate_curve_for_elements(
439    elements: &[Element],
440    arrow: &Arrow,
441    force: f64,
442) -> Vec<(Point, Point)> {
443    let mut path: Vec<(Point, Point)> = Vec::new();
444    let to_loc = elements[1].position().center();
445    let from_con =
446        elements[0].get_connector_location(to_loc, force, &arrow.src_port);
447
448    let mut prev_exit_loc = from_con.0;
449
450    path.push((from_con.0, from_con.1));
451
452    for i in 1..elements.len() {
453        let to_con;
454        let is_last: bool = i == elements.len() - 1;
455
456        if is_last {
457            let to = &elements[i];
458            to_con = to.get_connector_location(
459                prev_exit_loc,
460                force,
461                &arrow.dst_port,
462            );
463            prev_exit_loc = to_con.0;
464        } else {
465            let center = &elements[i];
466            let to = &elements[i + 1];
467            let to_loc = to.position().center();
468            to_con = center.get_passthrough_path(prev_exit_loc, to_loc, force);
469            prev_exit_loc = to_con.0;
470        }
471
472        path.push((to_con.1, to_con.0));
473    }
474
475    path
476}
477
478pub fn render_arrow(
479    canvas: &mut dyn RenderBackend,
480    debug: bool,
481    elements: &[Element],
482    arrow: &Arrow,
483) {
484    let path = generate_curve_for_elements(elements, arrow, 30.);
485
486    if debug {
487        for seg in &path {
488            canvas.draw_line(seg.0, seg.1, &StyleAttr::debug2(), Option::None);
489            canvas.draw_circle(
490                seg.0,
491                Point::new(6., 6.),
492                &StyleAttr::debug1(),
493                Option::None,
494            );
495            canvas.draw_circle(
496                seg.1,
497                Point::new(6., 6.),
498                &StyleAttr::debug1(),
499                Option::None,
500            );
501        }
502    }
503
504    let dash = match arrow.line_style {
505        LineStyleKind::None => {
506            return;
507        }
508        LineStyleKind::Normal => false,
509        LineStyleKind::Dashed => true,
510        LineStyleKind::Dotted => true,
511    };
512
513    let start = matches!(arrow.start, LineEndKind::Arrow);
514    let end = matches!(arrow.end, LineEndKind::Arrow);
515
516    canvas.draw_arrow(
517        &path,
518        dash,
519        (start, end),
520        &arrow.look,
521        arrow.properties.clone(),
522        &arrow.text,
523    );
524}