1use std::cell::RefCell;
4
5use cranpose_core::{CompositionLocal, compositionLocalOf};
6use cranpose_ui_graphics::EdgeInsets;
7
8use crate::Modifier;
9
10#[derive(Clone, Copy, Debug, Default, PartialEq)]
12pub struct WindowInsets {
13 pub safe_area: EdgeInsets,
14 pub ime: EdgeInsets,
15}
16
17impl WindowInsets {
18 pub fn combined(self) -> EdgeInsets {
20 EdgeInsets::from_components(
21 self.safe_area.left.max(self.ime.left),
22 self.safe_area.top.max(self.ime.top),
23 self.safe_area.right.max(self.ime.right),
24 self.safe_area.bottom.max(self.ime.bottom),
25 )
26 }
27}
28
29pub fn local_safe_area_insets() -> CompositionLocal<EdgeInsets> {
40 thread_local! {
41 static LOCAL: RefCell<Option<CompositionLocal<EdgeInsets>>> = const { RefCell::new(None) };
42 }
43 LOCAL.with(|cell| {
44 cell.borrow_mut()
45 .get_or_insert_with(|| compositionLocalOf(EdgeInsets::default))
46 .clone()
47 })
48}
49
50pub fn local_ime_insets() -> CompositionLocal<EdgeInsets> {
63 thread_local! {
64 static LOCAL: RefCell<Option<CompositionLocal<EdgeInsets>>> = const { RefCell::new(None) };
65 }
66 LOCAL.with(|cell| {
67 cell.borrow_mut()
68 .get_or_insert_with(|| compositionLocalOf(EdgeInsets::default))
69 .clone()
70 })
71}
72
73pub fn window_insets() -> WindowInsets {
75 WindowInsets {
76 safe_area: local_safe_area_insets().current(),
77 ime: local_ime_insets().current(),
78 }
79}
80
81impl Modifier {
82 pub fn window_insets_padding(self, insets: EdgeInsets) -> Self {
84 self.padding_each(insets.left, insets.top, insets.right, insets.bottom)
85 }
86
87 pub fn safe_area_padding(self) -> Self {
89 self.window_insets_padding(local_safe_area_insets().current())
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use cranpose_ui_graphics::EdgeInsets;
96
97 use super::{WindowInsets, local_ime_insets, local_safe_area_insets};
98
99 #[test]
100 fn defaults_to_zero_insets() {
101 assert_eq!(
102 local_safe_area_insets().default_value(),
103 EdgeInsets::default()
104 );
105 }
106
107 #[test]
108 fn ime_insets_default_to_zero() {
109 assert_eq!(local_ime_insets().default_value(), EdgeInsets::default());
110 }
111
112 #[test]
113 fn returns_one_shared_local_per_thread() {
114 assert!(local_safe_area_insets() == local_safe_area_insets());
116 assert!(local_ime_insets() == local_ime_insets());
117 assert!(local_ime_insets() != local_safe_area_insets());
119 }
120
121 #[test]
122 fn combined_insets_do_not_double_count_overlapping_edges() {
123 let combined = WindowInsets {
124 safe_area: EdgeInsets::from_components(2.0, 8.0, 4.0, 20.0),
125 ime: EdgeInsets::from_components(0.0, 0.0, 6.0, 100.0),
126 }
127 .combined();
128 assert_eq!(combined, EdgeInsets::from_components(2.0, 8.0, 6.0, 100.0));
129 }
130
131 #[test]
132 fn explicit_window_insets_become_padding() {
133 use crate::{Modifier, modifier::ModifierChainHandle};
134
135 let _app_context = crate::render_state::app_context_test_scope();
136 let insets = EdgeInsets::from_components(1.0, 2.0, 3.0, 4.0);
137 let mut handle = ModifierChainHandle::new();
138 let _ = handle.update(&Modifier::empty().window_insets_padding(insets));
139 assert_eq!(handle.resolved_modifiers().padding(), insets);
140 }
141}