Skip to main content

azul_css/props/layout/
position.rs

1//! CSS properties for positioning elements.
2
3use alloc::string::{String, ToString};
4
5#[cfg(feature = "parser")]
6use crate::props::basic::pixel::parse_pixel_value;
7use crate::props::{
8    basic::pixel::{CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue},
9    formatter::PrintAsCssValue,
10    macros::PixelValueTaker,
11};
12
13// --- LayoutPosition ---
14
15/// Represents a `position` attribute - default: `Static`
16#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[repr(C)]
18pub enum LayoutPosition {
19    Static,
20    Relative,
21    Absolute,
22    Fixed,
23    Sticky,
24}
25
26impl LayoutPosition {
27    pub fn is_positioned(&self) -> bool {
28        *self != LayoutPosition::Static
29    }
30}
31
32impl Default for LayoutPosition {
33    fn default() -> Self {
34        LayoutPosition::Static
35    }
36}
37
38impl PrintAsCssValue for LayoutPosition {
39    fn print_as_css_value(&self) -> String {
40        String::from(match self {
41            LayoutPosition::Static => "static",
42            LayoutPosition::Relative => "relative",
43            LayoutPosition::Absolute => "absolute",
44            LayoutPosition::Fixed => "fixed",
45            LayoutPosition::Sticky => "sticky",
46        })
47    }
48}
49
50impl_enum_fmt!(LayoutPosition, Static, Fixed, Absolute, Relative, Sticky);
51
52// -- Parser for LayoutPosition
53
54#[derive(Clone, PartialEq)]
55pub enum LayoutPositionParseError<'a> {
56    InvalidValue(&'a str),
57}
58
59impl_debug_as_display!(LayoutPositionParseError<'a>);
60impl_display! { LayoutPositionParseError<'a>, {
61    InvalidValue(val) => format!("Invalid position value: \"{}\"", val),
62}}
63
64#[derive(Debug, Clone, PartialEq)]
65pub enum LayoutPositionParseErrorOwned {
66    InvalidValue(String),
67}
68
69impl<'a> LayoutPositionParseError<'a> {
70    pub fn to_contained(&self) -> LayoutPositionParseErrorOwned {
71        match self {
72            LayoutPositionParseError::InvalidValue(s) => {
73                LayoutPositionParseErrorOwned::InvalidValue(s.to_string())
74            }
75        }
76    }
77}
78
79impl LayoutPositionParseErrorOwned {
80    pub fn to_shared<'a>(&'a self) -> LayoutPositionParseError<'a> {
81        match self {
82            LayoutPositionParseErrorOwned::InvalidValue(s) => {
83                LayoutPositionParseError::InvalidValue(s.as_str())
84            }
85        }
86    }
87}
88
89#[cfg(feature = "parser")]
90pub fn parse_layout_position<'a>(
91    input: &'a str,
92) -> Result<LayoutPosition, LayoutPositionParseError<'a>> {
93    let input = input.trim();
94    match input {
95        "static" => Ok(LayoutPosition::Static),
96        "relative" => Ok(LayoutPosition::Relative),
97        "absolute" => Ok(LayoutPosition::Absolute),
98        "fixed" => Ok(LayoutPosition::Fixed),
99        _ => Err(LayoutPositionParseError::InvalidValue(input)),
100    }
101}
102
103// --- Offset Properties (top, right, bottom, left) ---
104
105macro_rules! define_position_property {
106    ($struct_name:ident) => {
107        #[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
108        #[repr(C)]
109        pub struct $struct_name {
110            pub inner: PixelValue,
111        }
112
113        impl ::core::fmt::Debug for $struct_name {
114            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
115                write!(f, "{}", self.inner)
116            }
117        }
118
119        impl PixelValueTaker for $struct_name {
120            fn from_pixel_value(inner: PixelValue) -> Self {
121                Self { inner }
122            }
123        }
124
125        impl_pixel_value!($struct_name);
126
127        impl PrintAsCssValue for $struct_name {
128            fn print_as_css_value(&self) -> String {
129                format!("{}", self.inner)
130            }
131        }
132    };
133}
134
135define_position_property!(LayoutTop);
136define_position_property!(LayoutRight);
137define_position_property!(LayoutInsetBottom);
138define_position_property!(LayoutLeft);
139
140// -- Parser for LayoutTop
141
142#[derive(Clone, PartialEq)]
143pub enum LayoutTopParseError<'a> {
144    PixelValue(CssPixelValueParseError<'a>),
145}
146impl_debug_as_display!(LayoutTopParseError<'a>);
147impl_display! { LayoutTopParseError<'a>, { PixelValue(e) => format!("{}", e), }}
148impl_from!(CssPixelValueParseError<'a>, LayoutTopParseError::PixelValue);
149
150#[derive(Debug, Clone, PartialEq)]
151pub enum LayoutTopParseErrorOwned {
152    PixelValue(CssPixelValueParseErrorOwned),
153}
154impl<'a> LayoutTopParseError<'a> {
155    pub fn to_contained(&self) -> LayoutTopParseErrorOwned {
156        match self {
157            LayoutTopParseError::PixelValue(e) => {
158                LayoutTopParseErrorOwned::PixelValue(e.to_contained())
159            }
160        }
161    }
162}
163impl LayoutTopParseErrorOwned {
164    pub fn to_shared<'a>(&'a self) -> LayoutTopParseError<'a> {
165        match self {
166            LayoutTopParseErrorOwned::PixelValue(e) => {
167                LayoutTopParseError::PixelValue(e.to_shared())
168            }
169        }
170    }
171}
172
173#[cfg(feature = "parser")]
174pub fn parse_layout_top<'a>(input: &'a str) -> Result<LayoutTop, LayoutTopParseError<'a>> {
175    parse_pixel_value(input)
176        .map(|v| LayoutTop { inner: v })
177        .map_err(Into::into)
178}
179
180// -- Parser for LayoutRight
181
182#[derive(Clone, PartialEq)]
183pub enum LayoutRightParseError<'a> {
184    PixelValue(CssPixelValueParseError<'a>),
185}
186impl_debug_as_display!(LayoutRightParseError<'a>);
187impl_display! { LayoutRightParseError<'a>, { PixelValue(e) => format!("{}", e), }}
188impl_from!(
189    CssPixelValueParseError<'a>,
190    LayoutRightParseError::PixelValue
191);
192
193#[derive(Debug, Clone, PartialEq)]
194pub enum LayoutRightParseErrorOwned {
195    PixelValue(CssPixelValueParseErrorOwned),
196}
197impl<'a> LayoutRightParseError<'a> {
198    pub fn to_contained(&self) -> LayoutRightParseErrorOwned {
199        match self {
200            LayoutRightParseError::PixelValue(e) => {
201                LayoutRightParseErrorOwned::PixelValue(e.to_contained())
202            }
203        }
204    }
205}
206impl LayoutRightParseErrorOwned {
207    pub fn to_shared<'a>(&'a self) -> LayoutRightParseError<'a> {
208        match self {
209            LayoutRightParseErrorOwned::PixelValue(e) => {
210                LayoutRightParseError::PixelValue(e.to_shared())
211            }
212        }
213    }
214}
215
216#[cfg(feature = "parser")]
217pub fn parse_layout_right<'a>(input: &'a str) -> Result<LayoutRight, LayoutRightParseError<'a>> {
218    parse_pixel_value(input)
219        .map(|v| LayoutRight { inner: v })
220        .map_err(Into::into)
221}
222
223// -- Parser for LayoutInsetBottom
224
225#[derive(Clone, PartialEq)]
226pub enum LayoutInsetBottomParseError<'a> {
227    PixelValue(CssPixelValueParseError<'a>),
228}
229impl_debug_as_display!(LayoutInsetBottomParseError<'a>);
230impl_display! { LayoutInsetBottomParseError<'a>, { PixelValue(e) => format!("{}", e), }}
231impl_from!(
232    CssPixelValueParseError<'a>,
233    LayoutInsetBottomParseError::PixelValue
234);
235
236#[derive(Debug, Clone, PartialEq)]
237pub enum LayoutInsetBottomParseErrorOwned {
238    PixelValue(CssPixelValueParseErrorOwned),
239}
240impl<'a> LayoutInsetBottomParseError<'a> {
241    pub fn to_contained(&self) -> LayoutInsetBottomParseErrorOwned {
242        match self {
243            LayoutInsetBottomParseError::PixelValue(e) => {
244                LayoutInsetBottomParseErrorOwned::PixelValue(e.to_contained())
245            }
246        }
247    }
248}
249impl LayoutInsetBottomParseErrorOwned {
250    pub fn to_shared<'a>(&'a self) -> LayoutInsetBottomParseError<'a> {
251        match self {
252            LayoutInsetBottomParseErrorOwned::PixelValue(e) => {
253                LayoutInsetBottomParseError::PixelValue(e.to_shared())
254            }
255        }
256    }
257}
258
259#[cfg(feature = "parser")]
260pub fn parse_layout_bottom<'a>(
261    input: &'a str,
262) -> Result<LayoutInsetBottom, LayoutInsetBottomParseError<'a>> {
263    parse_pixel_value(input)
264        .map(|v| LayoutInsetBottom { inner: v })
265        .map_err(Into::into)
266}
267
268// -- Parser for LayoutLeft
269
270#[derive(Clone, PartialEq)]
271pub enum LayoutLeftParseError<'a> {
272    PixelValue(CssPixelValueParseError<'a>),
273}
274impl_debug_as_display!(LayoutLeftParseError<'a>);
275impl_display! { LayoutLeftParseError<'a>, { PixelValue(e) => format!("{}", e), }}
276impl_from!(
277    CssPixelValueParseError<'a>,
278    LayoutLeftParseError::PixelValue
279);
280
281#[derive(Debug, Clone, PartialEq)]
282pub enum LayoutLeftParseErrorOwned {
283    PixelValue(CssPixelValueParseErrorOwned),
284}
285impl<'a> LayoutLeftParseError<'a> {
286    pub fn to_contained(&self) -> LayoutLeftParseErrorOwned {
287        match self {
288            LayoutLeftParseError::PixelValue(e) => {
289                LayoutLeftParseErrorOwned::PixelValue(e.to_contained())
290            }
291        }
292    }
293}
294impl LayoutLeftParseErrorOwned {
295    pub fn to_shared<'a>(&'a self) -> LayoutLeftParseError<'a> {
296        match self {
297            LayoutLeftParseErrorOwned::PixelValue(e) => {
298                LayoutLeftParseError::PixelValue(e.to_shared())
299            }
300        }
301    }
302}
303
304#[cfg(feature = "parser")]
305pub fn parse_layout_left<'a>(input: &'a str) -> Result<LayoutLeft, LayoutLeftParseError<'a>> {
306    parse_pixel_value(input)
307        .map(|v| LayoutLeft { inner: v })
308        .map_err(Into::into)
309}
310
311// --- LayoutZIndex ---
312
313/// Represents a `z-index` attribute - controls stacking order of positioned elements
314#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
315#[repr(C, u8)]
316pub enum LayoutZIndex {
317    Auto,
318    Integer(i32),
319}
320
321// Formatting to Rust code
322impl crate::format_rust_code::FormatAsRustCode for LayoutZIndex {
323    fn format_as_rust_code(&self, _tabs: usize) -> String {
324        match self {
325            LayoutZIndex::Auto => String::from("LayoutZIndex::Auto"),
326            LayoutZIndex::Integer(val) => {
327                format!("LayoutZIndex::Integer({})", val)
328            }
329        }
330    }
331}
332
333impl Default for LayoutZIndex {
334    fn default() -> Self {
335        LayoutZIndex::Auto
336    }
337}
338
339impl PrintAsCssValue for LayoutZIndex {
340    fn print_as_css_value(&self) -> String {
341        match self {
342            LayoutZIndex::Auto => String::from("auto"),
343            LayoutZIndex::Integer(val) => val.to_string(),
344        }
345    }
346}
347
348// -- Parser for LayoutZIndex
349
350#[derive(Clone, PartialEq)]
351pub enum LayoutZIndexParseError<'a> {
352    InvalidValue(&'a str),
353    ParseInt(::core::num::ParseIntError, &'a str),
354}
355impl_debug_as_display!(LayoutZIndexParseError<'a>);
356impl_display! { LayoutZIndexParseError<'a>, {
357    InvalidValue(val) => format!("Invalid z-index value: \"{}\"", val),
358    ParseInt(e, s) => format!("Invalid z-index integer \"{}\": {}", s, e),
359}}
360
361#[derive(Debug, Clone, PartialEq)]
362pub enum LayoutZIndexParseErrorOwned {
363    InvalidValue(String),
364    ParseInt(String, String),
365}
366
367impl<'a> LayoutZIndexParseError<'a> {
368    pub fn to_contained(&self) -> LayoutZIndexParseErrorOwned {
369        match self {
370            LayoutZIndexParseError::InvalidValue(s) => {
371                LayoutZIndexParseErrorOwned::InvalidValue(s.to_string())
372            }
373            LayoutZIndexParseError::ParseInt(e, s) => {
374                LayoutZIndexParseErrorOwned::ParseInt(e.to_string(), s.to_string())
375            }
376        }
377    }
378}
379
380impl LayoutZIndexParseErrorOwned {
381    pub fn to_shared<'a>(&'a self) -> LayoutZIndexParseError<'a> {
382        match self {
383            LayoutZIndexParseErrorOwned::InvalidValue(s) => {
384                LayoutZIndexParseError::InvalidValue(s.as_str())
385            }
386            LayoutZIndexParseErrorOwned::ParseInt(_e, s) => {
387                // We can't reconstruct ParseIntError, so use InvalidValue
388                LayoutZIndexParseError::InvalidValue(s.as_str())
389            }
390        }
391    }
392}
393
394#[cfg(feature = "parser")]
395pub fn parse_layout_z_index<'a>(
396    input: &'a str,
397) -> Result<LayoutZIndex, LayoutZIndexParseError<'a>> {
398    let input = input.trim();
399    if input == "auto" {
400        return Ok(LayoutZIndex::Auto);
401    }
402
403    match input.parse::<i32>() {
404        Ok(val) => Ok(LayoutZIndex::Integer(val)),
405        Err(e) => Err(LayoutZIndexParseError::ParseInt(e, input)),
406    }
407}
408
409#[cfg(all(test, feature = "parser"))]
410mod tests {
411    use super::*;
412
413    #[test]
414    fn test_parse_layout_position() {
415        assert_eq!(
416            parse_layout_position("static").unwrap(),
417            LayoutPosition::Static
418        );
419        assert_eq!(
420            parse_layout_position("relative").unwrap(),
421            LayoutPosition::Relative
422        );
423        assert_eq!(
424            parse_layout_position("absolute").unwrap(),
425            LayoutPosition::Absolute
426        );
427        assert_eq!(
428            parse_layout_position("fixed").unwrap(),
429            LayoutPosition::Fixed
430        );
431    }
432
433    #[test]
434    fn test_parse_layout_position_whitespace() {
435        assert_eq!(
436            parse_layout_position("  absolute  ").unwrap(),
437            LayoutPosition::Absolute
438        );
439    }
440
441    #[test]
442    fn test_parse_layout_position_invalid() {
443        assert!(parse_layout_position("sticky").is_err());
444        assert!(parse_layout_position("").is_err());
445        assert!(parse_layout_position("absolutely").is_err());
446    }
447
448    #[test]
449    fn test_parse_layout_z_index() {
450        assert_eq!(parse_layout_z_index("auto").unwrap(), LayoutZIndex::Auto);
451        assert_eq!(
452            parse_layout_z_index("10").unwrap(),
453            LayoutZIndex::Integer(10)
454        );
455        assert_eq!(parse_layout_z_index("0").unwrap(), LayoutZIndex::Integer(0));
456        assert_eq!(
457            parse_layout_z_index("-5").unwrap(),
458            LayoutZIndex::Integer(-5)
459        );
460        assert_eq!(
461            parse_layout_z_index("  999  ").unwrap(),
462            LayoutZIndex::Integer(999)
463        );
464    }
465
466    #[test]
467    fn test_parse_layout_z_index_invalid() {
468        assert!(parse_layout_z_index("10px").is_err());
469        assert!(parse_layout_z_index("1.5").is_err());
470        assert!(parse_layout_z_index("none").is_err());
471        assert!(parse_layout_z_index("").is_err());
472    }
473
474    #[test]
475    fn test_parse_offsets() {
476        assert_eq!(
477            parse_layout_top("10px").unwrap(),
478            LayoutTop {
479                inner: PixelValue::px(10.0)
480            }
481        );
482        assert_eq!(
483            parse_layout_right("5%").unwrap(),
484            LayoutRight {
485                inner: PixelValue::percent(5.0)
486            }
487        );
488        assert_eq!(
489            parse_layout_bottom("2.5em").unwrap(),
490            LayoutInsetBottom {
491                inner: PixelValue::em(2.5)
492            }
493        );
494        assert_eq!(
495            parse_layout_left("0").unwrap(),
496            LayoutLeft {
497                inner: PixelValue::px(0.0)
498            }
499        );
500    }
501
502    #[test]
503    fn test_parse_offsets_invalid() {
504        // The simple `parse_pixel_value` does not handle `auto`.
505        assert!(parse_layout_top("auto").is_err());
506        assert!(parse_layout_right("").is_err());
507        // Liberal parsing accepts whitespace between number and unit
508        assert!(parse_layout_bottom("10 px").is_ok());
509        assert!(parse_layout_left("ten pixels").is_err());
510    }
511}