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