waterui_layout/
safe_area.rs

1//! Safe area handling for layout containers.
2//!
3//! `WaterUI` uses metadata to signal to native renderers which views should extend
4//! into unsafe screen regions (areas obscured by notches, home indicators, status bars, etc.).
5//!
6//! # Architecture
7//!
8//! Safe area is entirely handled by the **native backend**. Rust code only provides
9//! metadata hints via `IgnoreSafeArea`.
10//!
11//! # Native Backend Responsibilities
12//!
13//! The native renderer must:
14//! 1. **Default behavior**: Apply platform safe area insets to all views
15//! 2. **When encountering `IgnoreSafeArea` metadata**:
16//!    - Ignore safe area constraints on the specified edges
17//!    - Allow the view to extend edge-to-edge for those edges
18//! 3. **Handle changes**: Re-layout when safe area changes (keyboard, rotation, etc.)
19
20use waterui_core::metadata::MetadataKey;
21
22/// Specifies which edges should ignore safe area insets.
23///
24/// Used with `IgnoreSafeArea` to control which edges of a view
25/// should extend into the unsafe screen regions.
26#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
27#[allow(clippy::struct_excessive_bools)]
28pub struct EdgeSet {
29    /// Ignore safe area on the top edge.
30    pub top: bool,
31    /// Ignore safe area on the leading edge.
32    pub leading: bool,
33    /// Ignore safe area on the bottom edge.
34    pub bottom: bool,
35    /// Ignore safe area on the trailing edge.
36    pub trailing: bool,
37}
38
39impl EdgeSet {
40    /// All edges - ignore safe area on all sides.
41    pub const ALL: Self = Self {
42        top: true,
43        leading: true,
44        bottom: true,
45        trailing: true,
46    };
47
48    /// No edges - respect safe area on all sides (default).
49    pub const NONE: Self = Self {
50        top: false,
51        leading: false,
52        bottom: false,
53        trailing: false,
54    };
55
56    /// Horizontal edges only (leading and trailing).
57    pub const HORIZONTAL: Self = Self {
58        top: false,
59        leading: true,
60        bottom: false,
61        trailing: true,
62    };
63
64    /// Vertical edges only (top and bottom).
65    pub const VERTICAL: Self = Self {
66        top: true,
67        leading: false,
68        bottom: true,
69        trailing: false,
70    };
71
72    /// Top edge only.
73    pub const TOP: Self = Self {
74        top: true,
75        leading: false,
76        bottom: false,
77        trailing: false,
78    };
79
80    /// Bottom edge only.
81    pub const BOTTOM: Self = Self {
82        top: false,
83        leading: false,
84        bottom: true,
85        trailing: false,
86    };
87
88    /// Creates a custom edge set.
89    #[must_use]
90    #[allow(clippy::fn_params_excessive_bools)]
91    pub const fn new(top: bool, leading: bool, bottom: bool, trailing: bool) -> Self {
92        Self {
93            top,
94            leading,
95            bottom,
96            trailing,
97        }
98    }
99
100    /// Returns true if any edge is set to ignore safe area.
101    #[must_use]
102    pub const fn any(&self) -> bool {
103        self.top || self.leading || self.bottom || self.trailing
104    }
105
106    /// Returns true if all edges are set to ignore safe area.
107    #[must_use]
108    pub const fn all(&self) -> bool {
109        self.top && self.leading && self.bottom && self.trailing
110    }
111}
112
113/// Marker metadata indicating this view should ignore safe area insets.
114///
115/// When a native renderer encounters this metadata, it should:
116/// - In **propose phase**: Use full screen bounds (not safe bounds) for the specified edges
117/// - In **place phase**: Position the view in full screen coordinates for the specified edges
118///
119/// This allows backgrounds, images, and other visual elements to extend
120/// edge-to-edge while content remains in the safe area.
121///
122/// # Example
123///
124/// ```ignore
125/// // Extend background to fill entire screen
126/// Color::blue()
127///     .ignore_safe_area(EdgeSet::ALL)
128///
129/// // Only extend to top (under status bar)
130/// header_view
131///     .ignore_safe_area(EdgeSet::TOP)
132/// ```
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub struct IgnoreSafeArea {
135    /// Which edges should ignore the safe area.
136    pub edges: EdgeSet,
137}
138
139impl MetadataKey for IgnoreSafeArea {}
140
141impl IgnoreSafeArea {
142    /// Creates a new `IgnoreSafeArea` with the specified edges.
143    #[must_use]
144    pub const fn new(edges: EdgeSet) -> Self {
145        Self { edges }
146    }
147
148    /// Ignore safe area on all edges.
149    #[must_use]
150    pub const fn all() -> Self {
151        Self {
152            edges: EdgeSet::ALL,
153        }
154    }
155
156    /// Ignore safe area on vertical edges (top and bottom).
157    #[must_use]
158    pub const fn vertical() -> Self {
159        Self {
160            edges: EdgeSet::VERTICAL,
161        }
162    }
163
164    /// Ignore safe area on horizontal edges (leading and trailing).
165    #[must_use]
166    pub const fn horizontal() -> Self {
167        Self {
168            edges: EdgeSet::HORIZONTAL,
169        }
170    }
171}