Skip to main content

freya_components/
attached.rs

1use freya_core::prelude::*;
2use torin::prelude::{
3    Area,
4    Position,
5};
6
7/// Position where the attached element will be placed relative to the inner element.
8#[derive(PartialEq, Clone, Copy, Debug, Default)]
9pub enum AttachedPosition {
10    Top,
11    #[default]
12    Bottom,
13    Left,
14    Right,
15}
16
17/// A container that attaches elements to the top, bottom, left, or right of an inner element.
18///
19/// Uses absolute positioning and measures the attached element's size
20/// to offset it correctly relative to the inner content.
21///
22/// # Example
23///
24/// ```rust
25/// # use freya::prelude::*;
26/// fn app() -> impl IntoElement {
27///     let mut open = use_state(|| false);
28///
29///     Attached::new(
30///         Button::new()
31///             .on_press(move |_| open.toggle())
32///             .child("Toggle"),
33///     )
34///     .bottom()
35///     .maybe_child(open().then(|| label().text("Attached below!")))
36/// }
37/// ```
38#[derive(PartialEq)]
39pub struct Attached {
40    inner: Element,
41    children: Vec<Element>,
42    position: AttachedPosition,
43    key: DiffKey,
44}
45
46impl KeyExt for Attached {
47    fn write_key(&mut self) -> &mut DiffKey {
48        &mut self.key
49    }
50}
51
52impl ChildrenExt for Attached {
53    fn get_children(&mut self) -> &mut Vec<Element> {
54        &mut self.children
55    }
56}
57
58impl Attached {
59    pub fn new(inner: impl IntoElement) -> Self {
60        Self {
61            inner: inner.into_element(),
62            children: vec![],
63            position: AttachedPosition::Bottom,
64            key: DiffKey::None,
65        }
66    }
67
68    pub fn position(mut self, position: AttachedPosition) -> Self {
69        self.position = position;
70        self
71    }
72
73    pub fn top(self) -> Self {
74        self.position(AttachedPosition::Top)
75    }
76
77    pub fn bottom(self) -> Self {
78        self.position(AttachedPosition::Bottom)
79    }
80
81    pub fn left(self) -> Self {
82        self.position(AttachedPosition::Left)
83    }
84
85    pub fn right(self) -> Self {
86        self.position(AttachedPosition::Right)
87    }
88}
89
90impl Component for Attached {
91    fn render(&self) -> impl IntoElement {
92        let mut inner_area: State<Option<Area>> = use_state(|| None);
93        let mut attached_area: State<Option<Area>> = use_state(|| None);
94
95        let inner = *inner_area.read();
96        let attached = *attached_area.read();
97
98        let is_measured = inner.is_some() && attached.is_some();
99
100        let inner_width = inner.map(|a| a.width()).unwrap_or_default();
101        let inner_height = inner.map(|a| a.height()).unwrap_or_default();
102        let attached_width = attached.map(|a| a.width()).unwrap_or_default();
103        let attached_height = attached.map(|a| a.height()).unwrap_or_default();
104
105        let position = match self.position {
106            AttachedPosition::Top => Position::new_absolute()
107                .top(-attached_height)
108                .left((inner_width - attached_width) / 2.),
109            AttachedPosition::Bottom => Position::new_absolute()
110                .top(inner_height)
111                .left((inner_width - attached_width) / 2.),
112            AttachedPosition::Left => Position::new_absolute()
113                .top((inner_height - attached_height) / 2.)
114                .left(-attached_width),
115            AttachedPosition::Right => Position::new_absolute()
116                .top((inner_height - attached_height) / 2.)
117                .left(inner_width),
118        };
119
120        rect()
121            .on_sized(move |e: Event<SizedEventData>| inner_area.set(Some(e.area)))
122            .child(self.inner.clone())
123            .maybe_child((!self.children.is_empty()).then(|| {
124                rect()
125                    .on_sized(move |e: Event<SizedEventData>| attached_area.set(Some(e.area)))
126                    .position(position)
127                    .layer(Layer::Overlay)
128                    .opacity(if is_measured { 1. } else { 0. })
129                    .children(self.children.clone())
130            }))
131    }
132
133    fn render_key(&self) -> DiffKey {
134        self.key.clone().or(self.default_key())
135    }
136}