Skip to main content

torin/values/
position.rs

1use crate::{
2    prelude::{
3        Area,
4        AreaOf,
5        Available,
6        Parent,
7        Point2D,
8        Size2D,
9    },
10    scaled::Scaled,
11};
12
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14#[derive(Default, PartialEq, Clone, Debug)]
15pub struct PositionSides {
16    pub top: Option<f32>,
17    pub right: Option<f32>,
18    pub bottom: Option<f32>,
19    pub left: Option<f32>,
20}
21
22/// How an element is placed relative to its parent or the window.
23///
24/// Build one of the variants and set the sides you need with the chainable
25/// [`top`](Position::top), [`right`](Position::right), [`bottom`](Position::bottom) and
26/// [`left`](Position::left) methods:
27///
28/// ```
29/// # use torin::prelude::*;
30/// let stacked = Position::new_stacked(); // default, follows normal layout flow
31/// let absolute = Position::new_absolute().top(10.0).left(20.0); // offset from the parent
32/// let global = Position::new_global().bottom(0.0).right(0.0); // offset from the window
33/// ```
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35#[derive(PartialEq, Clone, Debug)]
36pub enum Position {
37    /// Placed by the normal layout flow alongside its siblings. This is the default.
38    Stacked(Box<PositionSides>),
39
40    /// Taken out of the flow and positioned by offsets relative to its parent.
41    Absolute(Box<PositionSides>),
42    /// Taken out of the flow and positioned by offsets relative to the window.
43    Global(Box<PositionSides>),
44}
45
46impl Default for Position {
47    fn default() -> Self {
48        Self::new_stacked()
49    }
50}
51
52impl Position {
53    /// Create an [`Absolute`](Position::Absolute) position, offset relative to the parent.
54    pub fn new_absolute() -> Self {
55        Self::Absolute(Box::new(PositionSides {
56            top: None,
57            right: None,
58            bottom: None,
59            left: None,
60        }))
61    }
62
63    /// Create a [`Global`](Position::Global) position, offset relative to the window.
64    pub fn new_global() -> Self {
65        Self::Global(Box::new(PositionSides {
66            top: None,
67            right: None,
68            bottom: None,
69            left: None,
70        }))
71    }
72
73    /// Create a [`Stacked`](Position::Stacked) position that follows the normal layout flow.
74    pub fn new_stacked() -> Self {
75        Self::Stacked(Box::new(PositionSides {
76            top: None,
77            right: None,
78            bottom: None,
79            left: None,
80        }))
81    }
82
83    /// Set the offset from the top edge.
84    #[must_use]
85    pub fn top(mut self, value: f32) -> Self {
86        self.position_mut().top = Some(value);
87        self
88    }
89
90    /// Set the offset from the right edge.
91    #[must_use]
92    pub fn right(mut self, value: f32) -> Self {
93        self.position_mut().right = Some(value);
94        self
95    }
96
97    /// Set the offset from the bottom edge.
98    #[must_use]
99    pub fn bottom(mut self, value: f32) -> Self {
100        self.position_mut().bottom = Some(value);
101        self
102    }
103
104    /// Set the offset from the left edge.
105    #[must_use]
106    pub fn left(mut self, value: f32) -> Self {
107        self.position_mut().left = Some(value);
108        self
109    }
110
111    fn position_mut(&mut self) -> &mut PositionSides {
112        match self {
113            Self::Absolute(position) | Self::Global(position) | Self::Stacked(position) => position,
114        }
115    }
116
117    pub fn is_stacked(&self) -> bool {
118        matches!(self, Self::Stacked { .. })
119    }
120
121    pub fn is_absolute(&self) -> bool {
122        matches!(self, Self::Absolute { .. })
123    }
124
125    pub fn is_global(&self) -> bool {
126        matches!(self, Self::Global { .. })
127    }
128
129    pub(crate) fn get_origin(
130        &self,
131        available_parent_area: &AreaOf<Available>,
132        parent_area: &AreaOf<Parent>,
133        area_size: Size2D,
134        root_area: &Area,
135    ) -> Point2D {
136        match self {
137            Self::Stacked(_) => available_parent_area.origin.cast_unit(),
138            Self::Absolute(absolute_position) => {
139                let PositionSides {
140                    top,
141                    right,
142                    bottom,
143                    left,
144                } = &**absolute_position;
145                let y = {
146                    let mut y = parent_area.min_y();
147                    if let Some(top) = top {
148                        y += top;
149                    } else if let Some(bottom) = bottom {
150                        y = parent_area.max_y() - bottom - area_size.height;
151                    }
152                    y
153                };
154                let x = {
155                    let mut x = parent_area.min_x();
156                    if let Some(left) = left {
157                        x += left;
158                    } else if let Some(right) = right {
159                        x = parent_area.max_x() - right - area_size.width;
160                    }
161                    x
162                };
163                Point2D::new(x, y)
164            }
165            Self::Global(global_position) => {
166                let PositionSides {
167                    top,
168                    right,
169                    bottom,
170                    left,
171                } = &**global_position;
172                let y = {
173                    let mut y = 0.;
174                    if let Some(top) = top {
175                        y = *top;
176                    } else if let Some(bottom) = bottom {
177                        y = root_area.max_y() - bottom - area_size.height;
178                    }
179                    y
180                };
181                let x = {
182                    let mut x = 0.;
183                    if let Some(left) = left {
184                        x = *left;
185                    } else if let Some(right) = right {
186                        x = root_area.max_x() - right - area_size.width;
187                    }
188                    x
189                };
190                Point2D::new(x, y)
191            }
192        }
193    }
194}
195
196impl Scaled for Position {
197    fn scale(&mut self, scale_factor: f32) {
198        match self {
199            Self::Absolute(position) | Self::Global(position) => {
200                if let Some(top) = &mut position.top {
201                    *top *= scale_factor;
202                }
203                if let Some(right) = &mut position.right {
204                    *right *= scale_factor;
205                }
206                if let Some(bottom) = &mut position.bottom {
207                    *bottom *= scale_factor;
208                }
209                if let Some(left) = &mut position.left {
210                    *left *= scale_factor;
211                }
212            }
213            Self::Stacked(_) => {}
214        }
215    }
216}
217
218impl Position {
219    pub fn pretty(&self) -> String {
220        match self {
221            Self::Stacked(_) => "stacked".to_string(),
222            Self::Absolute(positions) | Self::Global(positions) => format!(
223                "{}, {}, {}, {}",
224                positions.top.unwrap_or_default(),
225                positions.right.unwrap_or_default(),
226                positions.bottom.unwrap_or_default(),
227                positions.left.unwrap_or_default()
228            ),
229        }
230    }
231}