Skip to main content

oxframe_components/
callout.rs

1//! An arrow and a caption aimed at another node.
2
3use oxframe_2d::{
4    Accent, BaseProps, Group, Len, Line, Md, Muted, Node, NodeBuilder, NodeId, Side, Txt,
5};
6use oxframe_core::Tween;
7
8use crate::host::{self, Host};
9
10/// A caption above `target`, with an arrow pointing down at it.
11/// `next` reveals the caption; the arrow comes in with `enter`.
12pub struct Callout {
13    host: Host,
14}
15
16impl Callout {
17    /// The caption stays hidden until `next`.
18    #[allow(clippy::new_ret_no_self)]
19    pub fn new(target: impl Into<Node>) -> CalloutBuilder {
20        CalloutBuilder {
21            base: BaseProps::default(),
22            target: target.into(),
23            text: String::new(),
24        }
25    }
26
27    /// Fades the arrow in.
28    pub fn enter(&self) -> Tween<f64> {
29        self.host.enter()
30    }
31    /// Fades the caption in.
32    pub fn next(&self) -> Tween<f64> {
33        self.host.next()
34    }
35    /// Fades the callout out.
36    pub fn exit(&self) -> Tween<f64> {
37        self.host.exit()
38    }
39}
40
41/// Builder for [`Callout`].
42#[must_use = "a builder does nothing until it is passed to `add`"]
43pub struct CalloutBuilder {
44    base: BaseProps,
45    target: Node,
46    text: String,
47}
48
49impl CalloutBuilder {
50    /// Caption text.
51    pub fn text(mut self, text: impl Into<String>) -> Self {
52        self.text = text.into();
53        self
54    }
55}
56
57impl NodeBuilder for CalloutBuilder {
58    type Handle = Callout;
59    fn base(&mut self) -> &mut BaseProps {
60        &mut self.base
61    }
62    fn build(self, _: &oxframe_2d::SceneGraph, parent: NodeId) -> Callout {
63        let root: Group = host::root(parent, self.base);
64        let root = root.next_to(self.target, Side::Top);
65        let gap = Md.px();
66        root.add(
67            Line::new()
68                .points([(0.0, 28.0), (0.0, 28.0 + gap)])
69                .end_arrow(true)
70                .stroke(Accent)
71                .line_width(4.0)
72                .name("arrow"),
73        );
74        let caption = root.add(
75            Txt::new()
76                .text(self.text)
77                .caption()
78                .fill(Muted)
79                .opacity(0.0)
80                .name("caption"),
81        );
82        Callout {
83            host: Host::new(root, vec![caption.node()]),
84        }
85    }
86}