Skip to main content

oxframe_components/
lower.rs

1//! A name plate at the bottom of the frame, and a chapter label at the top.
2
3use oxframe_2d::{
4    BaseProps, Edge, Group, Lg, Md, Muted, NodeBuilder, NodeId, Rect, Surface, Txt, pct,
5};
6use oxframe_core::Tween;
7
8use crate::host::{self, Host};
9
10/// Name and role pinned to the bottom safe area. `next` reveals the role.
11pub struct LowerThird {
12    host: Host,
13}
14
15impl LowerThird {
16    /// The name is visible after `enter`. The role waits for `next`.
17    #[allow(clippy::new_ret_no_self)]
18    pub fn new(name: impl Into<String>) -> LowerThirdBuilder {
19        LowerThirdBuilder {
20            base: BaseProps::default(),
21            name: name.into(),
22            role: None,
23        }
24    }
25
26    /// Fades the plate in.
27    pub fn enter(&self) -> Tween<f64> {
28        self.host.enter()
29    }
30    /// Fades the role in.
31    pub fn next(&self) -> Tween<f64> {
32        self.host.next()
33    }
34    /// Fades the plate out.
35    pub fn exit(&self) -> Tween<f64> {
36        self.host.exit()
37    }
38}
39
40/// Builder for [`LowerThird`].
41#[must_use = "a builder does nothing until it is passed to `add`"]
42pub struct LowerThirdBuilder {
43    base: BaseProps,
44    name: String,
45    role: Option<String>,
46}
47
48impl LowerThirdBuilder {
49    /// Second line, revealed by `next`.
50    pub fn role(mut self, text: impl Into<String>) -> Self {
51        self.role = Some(text.into());
52        self
53    }
54}
55
56impl NodeBuilder for LowerThirdBuilder {
57    type Handle = LowerThird;
58    fn base(&mut self) -> &mut BaseProps {
59        &mut self.base
60    }
61    fn build(self, _: &oxframe_2d::SceneGraph, parent: NodeId) -> LowerThird {
62        let root: Group = host::root(parent, self.base);
63        let root = root.to_edge(Edge::Bottom);
64        root.add(
65            Rect::new()
66                .width(pct(70))
67                .height(140.0)
68                .radius(Lg)
69                .fill(Surface)
70                .elevation(Md)
71                .name("plate"),
72        );
73        root.add(Txt::new().text(self.name).h2().y(-24.0).name("name"));
74        let mut reveals = Vec::new();
75        if let Some(role) = self.role {
76            let line = root.add(
77                Txt::new()
78                    .text(role)
79                    .caption()
80                    .fill(Muted)
81                    .y(36.0)
82                    .opacity(0.0)
83                    .name("role"),
84            );
85            reveals.push(line.node());
86        }
87        LowerThird {
88            host: Host::new(root, reveals),
89        }
90    }
91}
92
93/// A short label pinned to the top safe area.
94pub struct ChapterMarker {
95    host: Host,
96}
97
98impl ChapterMarker {
99    /// The label is the whole marker. `next` holds it.
100    #[allow(clippy::new_ret_no_self)]
101    pub fn new(label: impl Into<String>) -> ChapterMarkerBuilder {
102        ChapterMarkerBuilder {
103            base: BaseProps::default(),
104            label: label.into(),
105        }
106    }
107
108    /// Fades the label in.
109    pub fn enter(&self) -> Tween<f64> {
110        self.host.enter()
111    }
112    /// Holds the label.
113    pub fn next(&self) -> Tween<f64> {
114        self.host.next()
115    }
116    /// Fades the label out.
117    pub fn exit(&self) -> Tween<f64> {
118        self.host.exit()
119    }
120}
121
122/// Builder for [`ChapterMarker`].
123#[must_use = "a builder does nothing until it is passed to `add`"]
124pub struct ChapterMarkerBuilder {
125    base: BaseProps,
126    label: String,
127}
128
129impl NodeBuilder for ChapterMarkerBuilder {
130    type Handle = ChapterMarker;
131    fn base(&mut self) -> &mut BaseProps {
132        &mut self.base
133    }
134    fn build(self, _: &oxframe_2d::SceneGraph, parent: NodeId) -> ChapterMarker {
135        let root: Group = host::root(parent, self.base);
136        let root = root.to_edge(Edge::Top);
137        root.add(Txt::new().text(self.label).h2().name("chapter"));
138        ChapterMarker {
139            host: Host::new(root, Vec::new()),
140        }
141    }
142}