1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//! Platform safe-area insets exposed to composition.
use std::cell::RefCell;
use cranpose_core::{CompositionLocal, compositionLocalOf};
use cranpose_ui_graphics::EdgeInsets;
use crate::Modifier;
/// Insets needed to keep content clear of system UI and the on-screen keyboard.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct WindowInsets {
pub safe_area: EdgeInsets,
pub ime: EdgeInsets,
}
impl WindowInsets {
/// Combines overlapping insets by taking the largest obstruction on each edge.
pub fn combined(self) -> EdgeInsets {
EdgeInsets::from_components(
self.safe_area.left.max(self.ime.left),
self.safe_area.top.max(self.ime.top),
self.safe_area.right.max(self.ime.right),
self.safe_area.bottom.max(self.ime.bottom),
)
}
}
/// CompositionLocal carrying the platform safe-area insets in logical pixels.
///
/// Mobile backends provide the system-bar / notch / home-indicator insets here
/// so applications can keep content clear of them (for example with
/// `Modifier.padding_each`). It defaults to [`EdgeInsets::default`] (zero), so
/// desktop and web compositions are unaffected.
///
/// The same `CompositionLocal` instance is returned on every call (cached per
/// thread), so the platform that provides it and the app that reads it observe
/// one shared local.
pub fn local_safe_area_insets() -> CompositionLocal<EdgeInsets> {
thread_local! {
static LOCAL: RefCell<Option<CompositionLocal<EdgeInsets>>> = const { RefCell::new(None) };
}
LOCAL.with(|cell| {
cell.borrow_mut()
.get_or_insert_with(|| compositionLocalOf(EdgeInsets::default))
.clone()
})
}
/// CompositionLocal carrying the on-screen soft-keyboard (IME) insets in logical
/// pixels. The `bottom` field is the height the keyboard currently covers at the
/// bottom of the window (zero when it is hidden); the other edges are zero.
///
/// Mobile backends update this as the keyboard animates in and out so an
/// application can keep the focused field visible — for example by adding
/// `bottom` to a scroll container's bottom padding, or scrolling the caret into
/// view. It defaults to [`EdgeInsets::default`] (zero), so desktop and web
/// compositions (and mobile frames with no keyboard) are unaffected.
///
/// Like [`local_safe_area_insets`], the same `CompositionLocal` instance is
/// returned on every call (cached per thread).
pub fn local_ime_insets() -> CompositionLocal<EdgeInsets> {
thread_local! {
static LOCAL: RefCell<Option<CompositionLocal<EdgeInsets>>> = const { RefCell::new(None) };
}
LOCAL.with(|cell| {
cell.borrow_mut()
.get_or_insert_with(|| compositionLocalOf(EdgeInsets::default))
.clone()
})
}
/// Returns the framework-owned insets currently visible to the composition.
pub fn window_insets() -> WindowInsets {
WindowInsets {
safe_area: local_safe_area_insets().current(),
ime: local_ime_insets().current(),
}
}
impl Modifier {
/// Adds padding for explicit platform or application insets.
pub fn window_insets_padding(self, insets: EdgeInsets) -> Self {
self.padding_each(insets.left, insets.top, insets.right, insets.bottom)
}
/// Adds padding for system bars, display cutouts, and rounded display edges.
pub fn safe_area_padding(self) -> Self {
self.window_insets_padding(local_safe_area_insets().current())
}
}
#[cfg(test)]
#[path = "tests/safe_area_tests.rs"]
mod tests;