1use std::{
2 cmp,
3 collections::{self, HashMap},
4 fmt,
5};
6
7use crate::{
8 ARC_RADIUS, Empty, HDir, Node, NodeGeometry, RenderBackend, draw_group_with_geometry,
9 render_group_with_geometry, svg,
10};
11
12#[derive(Debug, Default, Clone, Copy)]
16pub enum LinkTarget {
17 #[default]
19 Blank,
20 Parent,
22 Top,
24}
25
26#[derive(Debug, Clone)]
28pub struct Link<N> {
29 inner: N,
30 uri: String,
31 target: Option<LinkTarget>,
32 attributes: HashMap<String, String>,
33}
34
35impl<N> Link<N> {
36 pub fn new(inner: N, uri: String) -> Self {
49 let mut l = Self {
50 inner,
51 uri,
52 target: None,
53 attributes: HashMap::default(),
54 };
55 l.attributes.insert("class".to_owned(), "link".to_owned());
56 l
57 }
58
59 pub fn set_target(&mut self, target: Option<LinkTarget>) {
63 self.target = target;
64 }
65
66 pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
68 self.attributes.entry(key)
69 }
70
71 fn emit_with_geometry<B: RenderBackend>(
73 &self,
74 backend: &mut B,
75 x: i64,
76 y: i64,
77 h_dir: HDir,
78 geo: &NodeGeometry,
79 ) -> fmt::Result
80 where
81 N: Node,
82 {
83 backend.push_child(&self.inner, x, y, h_dir, &geo.children[0])
84 }
85}
86
87impl<N> Node for Link<N>
88where
89 N: Node,
90{
91 fn entry_height(&self) -> i64 {
92 self.inner.entry_height()
93 }
94 fn height(&self) -> i64 {
95 self.inner.height()
96 }
97 fn width(&self) -> i64 {
98 self.inner.width()
99 }
100
101 fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
102 let mut a = svg::Element::new("a")
103 .debug("Link", x, y, self)
104 .set("xlink:href", &self.uri);
105 a = match self.target {
106 Some(LinkTarget::Blank) => a.set("target", "_blank"),
107 Some(LinkTarget::Parent) => a.set("target", "_parent"),
108 Some(LinkTarget::Top) => a.set("target", "_top"),
109 None => a,
110 };
111 a.set_all(self.attributes.iter())
112 .add(self.inner.draw(x, y, h_dir))
113 }
114
115 fn compute_geometry(&self) -> NodeGeometry {
116 let inner_geo = self.inner.compute_geometry();
117 let entry_height = inner_geo.entry_height;
118 let height = inner_geo.height;
119 let width = inner_geo.width;
120 NodeGeometry {
121 entry_height,
122 height,
123 width,
124 children: vec![inner_geo],
125 }
126 }
127
128 fn draw_with_geometry(&self, x: i64, y: i64, h_dir: HDir, geo: &NodeGeometry) -> svg::Element {
129 let mut backend = crate::ElementBackend::default();
130 self.emit_with_geometry(&mut backend, x, y, h_dir, geo)
131 .expect("element backend is infallible");
132 let mut a = svg::Element::new("a")
133 .debug_with_geometry("Link", x, y, geo)
134 .set("xlink:href", &self.uri);
135 a = match self.target {
136 Some(LinkTarget::Blank) => a.set("target", "_blank"),
137 Some(LinkTarget::Parent) => a.set("target", "_parent"),
138 Some(LinkTarget::Top) => a.set("target", "_top"),
139 None => a,
140 };
141 let mut a = a.set_all(self.attributes.iter());
142 for child in backend.children {
143 a.push(child);
144 }
145 a
146 }
147
148 fn render_with_geometry(
149 &self,
150 out: &mut svg::Renderer<'_>,
151 x: i64,
152 y: i64,
153 h_dir: HDir,
154 geo: &NodeGeometry,
155 ) -> fmt::Result {
156 let mut a = out.start_element("a")?;
157 a.attr("xlink:href", &self.uri)?;
158 match self.target {
159 Some(LinkTarget::Blank) => a.attr("target", "_blank")?,
160 Some(LinkTarget::Parent) => a.attr("target", "_parent")?,
161 Some(LinkTarget::Top) => a.attr("target", "_top")?,
162 None => {}
163 }
164 a.attr_hashmap(&self.attributes)?;
165 crate::add_debug_attrs(&mut a, "Link", x, y, geo)?;
166 a.finish()?;
167 self.emit_with_geometry(&mut crate::RendererBackend { out }, x, y, h_dir, geo)?;
168 crate::write_debug_overlay(out, x, y, geo)?;
169 out.end_element("a")
170 }
171}
172
173#[derive(Debug, Clone, Default)]
177pub struct Optional<N> {
178 inner: N,
179 attributes: HashMap<String, String>,
180}
181
182impl<N> Optional<N> {
183 pub fn new(inner: N) -> Self {
193 let mut o = Self {
194 inner,
195 attributes: HashMap::default(),
196 };
197 o.attributes
198 .insert("class".to_owned(), "optional".to_owned());
199 o
200 }
201
202 pub fn into_inner(self) -> N {
204 self.inner
205 }
206
207 pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
209 self.attributes.entry(key)
210 }
211
212 fn emit_with_geometry<B: RenderBackend>(
214 &self,
215 backend: &mut B,
216 x: i64,
217 y: i64,
218 h_dir: HDir,
219 geo: &NodeGeometry,
220 ) -> fmt::Result
221 where
222 N: Node,
223 {
224 let inner_geo = &geo.children[0];
225 backend.push_path(
226 svg::PathData::new(h_dir)
227 .move_to(x, y + geo.entry_height)
228 .horizontal(ARC_RADIUS * 2)
229 .move_rel(-ARC_RADIUS * 2, 0)
230 .arc(ARC_RADIUS, svg::Arc::WestToNorth)
231 .vertical(cmp::min(0, -inner_geo.entry_height + ARC_RADIUS))
232 .arc(ARC_RADIUS, svg::Arc::SouthToEast)
233 .horizontal(inner_geo.width)
234 .arc(ARC_RADIUS, svg::Arc::WestToSouth)
235 .vertical(cmp::max(0, inner_geo.entry_height - ARC_RADIUS))
236 .arc(ARC_RADIUS, svg::Arc::NorthToEast)
237 .horizontal(-ARC_RADIUS * 2),
238 )?;
239 backend.push_child(
240 &self.inner,
241 x + ARC_RADIUS * 2,
242 y + geo.entry_height - inner_geo.entry_height,
243 h_dir,
244 inner_geo,
245 )
246 }
247}
248
249impl<N> Node for Optional<N>
250where
251 N: Node,
252{
253 fn entry_height(&self) -> i64 {
254 ARC_RADIUS + cmp::max(ARC_RADIUS, self.inner.entry_height())
255 }
256
257 fn height(&self) -> i64 {
258 self.entry_height() + self.inner.height_below_entry()
259 }
260
261 fn width(&self) -> i64 {
262 ARC_RADIUS * 2 + self.inner.width() + ARC_RADIUS * 2
263 }
264
265 fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
266 let i = self.inner.draw(
267 x + ARC_RADIUS * 2,
268 y + self.entry_height() - self.inner.entry_height(),
269 h_dir,
270 );
271
272 let v = svg::PathData::new(h_dir)
273 .move_to(x, y + self.entry_height())
274 .horizontal(ARC_RADIUS * 2)
275 .move_rel(-ARC_RADIUS * 2, 0)
276 .arc(ARC_RADIUS, svg::Arc::WestToNorth)
277 .vertical(cmp::min(0, -self.inner.entry_height() + ARC_RADIUS))
278 .arc(ARC_RADIUS, svg::Arc::SouthToEast)
279 .horizontal(self.inner.width())
280 .arc(ARC_RADIUS, svg::Arc::WestToSouth)
281 .vertical(cmp::max(0, self.inner.entry_height() - ARC_RADIUS))
282 .arc(ARC_RADIUS, svg::Arc::NorthToEast)
283 .horizontal(-ARC_RADIUS * 2)
284 .into_path();
285
286 svg::Element::new("g")
287 .debug("Optional", x, y, self)
288 .set_all(self.attributes.iter())
289 .add(v)
290 .add(i)
291 }
292
293 fn compute_geometry(&self) -> NodeGeometry {
294 let inner_geo = self.inner.compute_geometry();
295 let entry_height = ARC_RADIUS + cmp::max(ARC_RADIUS, inner_geo.entry_height);
296 let height = entry_height + inner_geo.height_below_entry();
297 let width = ARC_RADIUS * 2 + inner_geo.width + ARC_RADIUS * 2;
298 NodeGeometry {
299 entry_height,
300 height,
301 width,
302 children: vec![inner_geo],
303 }
304 }
305
306 fn draw_with_geometry(&self, x: i64, y: i64, h_dir: HDir, geo: &NodeGeometry) -> svg::Element {
307 draw_group_with_geometry(&self.attributes, "Optional", x, y, geo, |backend| {
308 self.emit_with_geometry(backend, x, y, h_dir, geo)
309 })
310 }
311
312 fn render_with_geometry(
313 &self,
314 out: &mut svg::Renderer<'_>,
315 x: i64,
316 y: i64,
317 h_dir: HDir,
318 geo: &NodeGeometry,
319 ) -> fmt::Result {
320 render_group_with_geometry(out, &self.attributes, "Optional", x, y, geo, |backend| {
321 self.emit_with_geometry(backend, x, y, h_dir, geo)
322 })
323 }
324}
325
326#[derive(Debug, Clone)]
333pub struct Repeat<I, R> {
334 inner: I,
335 repeat: R,
336 spacing: i64,
337 attributes: HashMap<String, String>,
338}
339
340impl<I, R> Repeat<I, R> {
341 pub fn new(inner: I, repeat: R) -> Self {
352 let mut r = Self {
353 inner,
354 repeat,
355 spacing: 10,
356 attributes: HashMap::default(),
357 };
358 r.attributes.insert("class".to_owned(), "repeat".to_owned());
359 r
360 }
361
362 pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
364 self.attributes.entry(key)
365 }
366}
367
368impl<I, R> Repeat<I, R>
369where
370 I: Node,
371 R: Node,
372{
373 fn height_between_entries(&self) -> i64 {
374 cmp::max(
375 ARC_RADIUS * 2,
376 self.inner.height_below_entry() + self.spacing + self.repeat.entry_height(),
377 )
378 }
379
380 fn emit_with_geometry<B: RenderBackend>(
382 &self,
383 backend: &mut B,
384 x: i64,
385 y: i64,
386 h_dir: HDir,
387 geo: &NodeGeometry,
388 ) -> fmt::Result {
389 let inner_geo = &geo.children[0];
390 let repeat_geo = &geo.children[1];
391 let height_between = cmp::max(
392 ARC_RADIUS * 2,
393 inner_geo.height_below_entry() + self.spacing + repeat_geo.entry_height,
394 );
395
396 backend.push_path(
397 svg::PathData::new(h_dir)
398 .move_to(x, y + geo.entry_height)
399 .horizontal(ARC_RADIUS)
400 .move_rel(inner_geo.width, 0)
401 .horizontal(cmp::max(
402 ARC_RADIUS,
403 repeat_geo.width - inner_geo.width + ARC_RADIUS,
404 ))
405 .move_rel(-ARC_RADIUS, 0)
406 .arc(ARC_RADIUS, svg::Arc::WestToSouth)
407 .vertical(height_between - ARC_RADIUS * 2)
408 .arc(ARC_RADIUS, svg::Arc::NorthToWest)
409 .move_rel(-repeat_geo.width, 0)
410 .horizontal(cmp::min(0, repeat_geo.width - inner_geo.width))
411 .arc(ARC_RADIUS, svg::Arc::EastToNorth)
412 .vertical(-height_between + ARC_RADIUS * 2)
413 .arc(ARC_RADIUS, svg::Arc::SouthToEast),
414 )?;
415 backend.push_child(
416 &self.repeat,
417 x + geo.width - repeat_geo.width - ARC_RADIUS,
418 y + geo.height - repeat_geo.height_below_entry() - repeat_geo.entry_height,
419 h_dir.invert(),
420 repeat_geo,
421 )?;
422 backend.push_child(&self.inner, x + ARC_RADIUS, y, h_dir, inner_geo)
423 }
424}
425
426impl<I, R> Default for Repeat<I, R>
427where
428 I: Default,
429 R: Default,
430{
431 fn default() -> Self {
432 Self {
433 inner: Default::default(),
434 repeat: Default::default(),
435 spacing: 10,
436 attributes: HashMap::default(),
437 }
438 }
439}
440
441impl<I, R> Node for Repeat<I, R>
442where
443 I: Node,
444 R: Node,
445{
446 fn entry_height(&self) -> i64 {
447 self.inner.entry_height()
448 }
449
450 fn height(&self) -> i64 {
451 self.inner.entry_height() + self.height_between_entries() + self.repeat.height_below_entry()
452 }
453
454 fn width(&self) -> i64 {
455 ARC_RADIUS + cmp::max(self.repeat.width(), self.inner.width()) + ARC_RADIUS
456 }
457
458 fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
459 let mut g = svg::Element::new("g").set_all(self.attributes.iter());
460
461 g.push(
462 svg::PathData::new(h_dir)
463 .move_to(x, y + self.entry_height())
464 .horizontal(ARC_RADIUS)
465 .move_rel(self.inner.width(), 0)
466 .horizontal(cmp::max(
467 ARC_RADIUS,
468 self.repeat.width() - self.inner.width() + ARC_RADIUS,
469 ))
470 .move_rel(-ARC_RADIUS, 0)
471 .arc(ARC_RADIUS, svg::Arc::WestToSouth)
472 .vertical(self.height_between_entries() - ARC_RADIUS * 2)
473 .arc(ARC_RADIUS, svg::Arc::NorthToWest)
474 .move_rel(-self.repeat.width(), 0)
475 .horizontal(cmp::min(0, self.repeat.width() - self.inner.width()))
476 .arc(ARC_RADIUS, svg::Arc::EastToNorth)
477 .vertical(-self.height_between_entries() + ARC_RADIUS * 2)
478 .arc(ARC_RADIUS, svg::Arc::SouthToEast)
479 .into_path(),
480 )
481 .push(self.repeat.draw(
482 x + self.width() - self.repeat.width() - ARC_RADIUS,
483 y + self.height() - self.repeat.height_below_entry() - self.repeat.entry_height(),
484 h_dir.invert(),
485 ));
486 g.push(self.inner.draw(x + ARC_RADIUS, y, h_dir));
487 g.debug("Repeat", x, y, self)
488 }
489
490 fn compute_geometry(&self) -> NodeGeometry {
491 let inner_geo = self.inner.compute_geometry();
492 let repeat_geo = self.repeat.compute_geometry();
493 let height_between = cmp::max(
494 ARC_RADIUS * 2,
495 inner_geo.height_below_entry() + self.spacing + repeat_geo.entry_height,
496 );
497 let entry_height = inner_geo.entry_height;
498 let height = inner_geo.entry_height + height_between + repeat_geo.height_below_entry();
499 let width = ARC_RADIUS + cmp::max(repeat_geo.width, inner_geo.width) + ARC_RADIUS;
500 NodeGeometry {
501 entry_height,
502 height,
503 width,
504 children: vec![inner_geo, repeat_geo],
505 }
506 }
507
508 fn draw_with_geometry(&self, x: i64, y: i64, h_dir: HDir, geo: &NodeGeometry) -> svg::Element {
509 draw_group_with_geometry(&self.attributes, "Repeat", x, y, geo, |backend| {
510 self.emit_with_geometry(backend, x, y, h_dir, geo)
511 })
512 }
513
514 fn render_with_geometry(
515 &self,
516 out: &mut svg::Renderer<'_>,
517 x: i64,
518 y: i64,
519 h_dir: HDir,
520 geo: &NodeGeometry,
521 ) -> fmt::Result {
522 render_group_with_geometry(out, &self.attributes, "Repeat", x, y, geo, |backend| {
523 self.emit_with_geometry(backend, x, y, h_dir, geo)
524 })
525 }
526}
527
528#[derive(Debug, Clone)]
532pub struct LabeledBox<T, U> {
533 inner: T,
534 label: U,
535 spacing: i64,
536 padding: i64,
537 attributes: HashMap<String, String>,
538}
539
540impl<T> LabeledBox<T, Empty> {
541 pub fn without_label(inner: T) -> Self {
545 Self::new(inner, Empty)
546 }
547}
548
549impl<T, U> LabeledBox<T, U> {
550 pub fn new(inner: T, label: U) -> Self {
564 let mut l = Self {
565 inner,
566 label,
567 spacing: 8,
568 padding: 8,
569 attributes: HashMap::default(),
570 };
571 l.attributes
572 .insert("class".to_owned(), "labeledbox".to_owned());
573 l
574 }
575
576 pub fn attr(&mut self, key: String) -> collections::hash_map::Entry<'_, String, String> {
578 self.attributes.entry(key)
579 }
580}
581
582impl<T, U> Default for LabeledBox<T, U>
583where
584 T: Default,
585 U: Default,
586{
587 fn default() -> Self {
588 Self {
589 inner: Default::default(),
590 label: Default::default(),
591 spacing: 8,
592 padding: 8,
593 attributes: HashMap::default(),
594 }
595 }
596}
597
598impl<T, U> LabeledBox<T, U>
599where
600 T: Node,
601 U: Node,
602{
603 fn spacing(&self) -> i64 {
604 if self.label.height() > 0 {
605 self.spacing
606 } else {
607 0
608 }
609 }
610
611 fn padding(&self) -> i64 {
612 if self.label.height() + self.inner.height() + self.label.width() + self.inner.width() > 0 {
613 self.padding
614 } else {
615 0
616 }
617 }
618
619 fn emit_with_geometry<B: RenderBackend>(
621 &self,
622 backend: &mut B,
623 x: i64,
624 y: i64,
625 h_dir: HDir,
626 geo: &NodeGeometry,
627 ) -> fmt::Result {
628 let inner_geo = &geo.children[0];
629 let label_geo = &geo.children[1];
630 let padding = if label_geo.height + inner_geo.height + label_geo.width + inner_geo.width > 0
631 {
632 self.padding
633 } else {
634 0
635 };
636 let spacing = if label_geo.height > 0 {
637 self.spacing
638 } else {
639 0
640 };
641
642 backend.push_rect(x, y, geo.width, geo.height)?;
643 backend.push_path(
644 svg::PathData::new(h_dir)
645 .move_to(x, y + geo.entry_height)
646 .horizontal(padding)
647 .move_rel(inner_geo.width, 0)
648 .horizontal(geo.width - inner_geo.width - padding),
649 )?;
650 backend.push_child(&self.label, x + padding, y + padding, h_dir, label_geo)?;
651 backend.push_child(
652 &self.inner,
653 x + padding,
654 y + padding + label_geo.height + spacing,
655 h_dir,
656 inner_geo,
657 )
658 }
659}
660
661impl<T, U> Node for LabeledBox<T, U>
662where
663 T: Node,
664 U: Node,
665{
666 fn entry_height(&self) -> i64 {
667 self.padding() + self.label.height() + self.spacing() + self.inner.entry_height()
668 }
669
670 fn height(&self) -> i64 {
671 self.padding() + self.label.height() + self.spacing() + self.inner.height() + self.padding()
672 }
673
674 fn width(&self) -> i64 {
675 self.padding() + cmp::max(self.inner.width(), self.label.width()) + self.padding()
676 }
677
678 fn draw(&self, x: i64, y: i64, h_dir: HDir) -> svg::Element {
679 svg::Element::new("g")
680 .add(
681 svg::Element::new("rect")
682 .set("x", &x)
683 .set("y", &y)
684 .set("height", &self.height())
685 .set("width", &self.width()),
686 )
687 .add(
688 svg::PathData::new(h_dir)
689 .move_to(x, y + self.entry_height())
690 .horizontal(self.padding())
691 .move_rel(self.inner.width(), 0)
692 .horizontal(self.width() - self.inner.width() - self.padding())
693 .into_path(),
694 )
695 .add(
696 self.label
697 .draw(x + self.padding(), y + self.padding(), h_dir),
698 )
699 .add(self.inner.draw(
700 x + self.padding(),
701 y + self.padding() + self.label.height() + self.spacing(),
702 h_dir,
703 ))
704 .set_all(self.attributes.iter())
705 .debug("LabeledBox", x, y, self)
706 }
707
708 fn compute_geometry(&self) -> NodeGeometry {
709 let inner_geo = self.inner.compute_geometry();
710 let label_geo = self.label.compute_geometry();
711 let padding = if label_geo.height + inner_geo.height + label_geo.width + inner_geo.width > 0
712 {
713 self.padding
714 } else {
715 0
716 };
717 let spacing = if label_geo.height > 0 {
718 self.spacing
719 } else {
720 0
721 };
722 let entry_height = padding + label_geo.height + spacing + inner_geo.entry_height;
723 let height = padding + label_geo.height + spacing + inner_geo.height + padding;
724 let width = padding + cmp::max(inner_geo.width, label_geo.width) + padding;
725 NodeGeometry {
726 entry_height,
727 height,
728 width,
729 children: vec![inner_geo, label_geo],
730 }
731 }
732
733 fn draw_with_geometry(&self, x: i64, y: i64, h_dir: HDir, geo: &NodeGeometry) -> svg::Element {
734 draw_group_with_geometry(&self.attributes, "LabeledBox", x, y, geo, |backend| {
735 self.emit_with_geometry(backend, x, y, h_dir, geo)
736 })
737 }
738
739 fn render_with_geometry(
740 &self,
741 out: &mut svg::Renderer<'_>,
742 x: i64,
743 y: i64,
744 h_dir: HDir,
745 geo: &NodeGeometry,
746 ) -> fmt::Result {
747 render_group_with_geometry(out, &self.attributes, "LabeledBox", x, y, geo, |backend| {
748 self.emit_with_geometry(backend, x, y, h_dir, geo)
749 })
750 }
751}