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}