azul_layout/managers/
geolocation.rs1use alloc::collections::btree_map::BTreeMap;
24use alloc::vec::Vec;
25
26pub use azul_core::geolocation::{GeolocationProbeConfig, LocationFix};
32
33#[derive(Debug, Clone, Copy, PartialEq)]
37#[repr(C, u8)]
38pub enum GeolocationDiffEvent {
39 Subscribe { config: GeolocationProbeConfig },
42 Release,
44 Reconfigure { config: GeolocationProbeConfig },
48}
49
50#[derive(Debug, Clone, PartialEq, Default)]
53pub struct GeolocationManager {
54 pub latest_fix: Option<LocationFix>,
57 pub active_config: Option<GeolocationProbeConfig>,
60 pending_events: Vec<GeolocationDiffEvent>,
62 refcount: u32,
64}
65
66impl GeolocationManager {
67 pub fn new() -> Self {
68 Self::default()
69 }
70
71 pub fn latest_fix(&self) -> Option<LocationFix> {
72 self.latest_fix
73 }
74
75 pub fn refcount(&self) -> u32 {
76 self.refcount
77 }
78
79 pub fn set_latest_fix(&mut self, fix: LocationFix) -> bool {
88 let changed = match self.latest_fix {
89 Some(prev) => !Self::location_fix_bitwise_eq(&prev, &fix),
90 None => true,
91 };
92 self.latest_fix = Some(fix);
93 changed
94 }
95
96 fn location_fix_bitwise_eq(a: &LocationFix, b: &LocationFix) -> bool {
97 a.latitude_deg.to_bits() == b.latitude_deg.to_bits()
98 && a.longitude_deg.to_bits() == b.longitude_deg.to_bits()
99 && a.accuracy_m.to_bits() == b.accuracy_m.to_bits()
100 && a.altitude_m.to_bits() == b.altitude_m.to_bits()
101 && a.altitude_accuracy_m.to_bits() == b.altitude_accuracy_m.to_bits()
102 && a.heading_deg.to_bits() == b.heading_deg.to_bits()
103 && a.speed_mps.to_bits() == b.speed_mps.to_bits()
104 && a.timestamp_ms == b.timestamp_ms
105 }
106
107 pub fn take_pending_events(&mut self) -> Vec<GeolocationDiffEvent> {
110 core::mem::take(&mut self.pending_events)
111 }
112
113 pub fn diff_layout<F>(&mut self, mut for_each_probe: F)
119 where
120 F: FnMut(&mut dyn FnMut(GeolocationProbeConfig)),
121 {
122 let mut new_count: u32 = 0;
123 let mut next_config: Option<GeolocationProbeConfig> = None;
124 for_each_probe(&mut |cfg| {
125 new_count += 1;
126 if next_config.is_none() {
131 next_config = Some(cfg);
132 }
133 });
134
135 let old_count = self.refcount;
136 self.refcount = new_count;
137
138 match (old_count, new_count) {
139 (0, n) if n > 0 => {
140 let config = next_config.unwrap_or_default();
141 self.active_config = Some(config);
142 self.pending_events
143 .push(GeolocationDiffEvent::Subscribe { config });
144 }
145 (m, 0) if m > 0 => {
146 self.active_config = None;
147 self.latest_fix = None;
148 self.pending_events.push(GeolocationDiffEvent::Release);
149 }
150 (m, n) if m > 0 && n > 0 => {
151 let new_config = next_config.unwrap_or_default();
154 if Some(new_config) != self.active_config {
155 self.active_config = Some(new_config);
156 self.pending_events
157 .push(GeolocationDiffEvent::Reconfigure { config: new_config });
158 }
159 }
160 _ => {
161 }
163 }
164 }
165}
166
167static PENDING_FIXES: std::sync::Mutex<Vec<LocationFix>> =
180 std::sync::Mutex::new(Vec::new());
181
182pub fn push_location_fix(fix: LocationFix) {
186 let mut q = PENDING_FIXES.lock().unwrap_or_else(|e| e.into_inner());
187 q.push(fix);
188}
189
190pub fn drain_location_fixes() -> Vec<LocationFix> {
194 let mut q = PENDING_FIXES.lock().unwrap_or_else(|e| e.into_inner());
195 core::mem::take(&mut *q)
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 fn cfg() -> GeolocationProbeConfig {
203 GeolocationProbeConfig::default()
204 }
205
206 fn high_accuracy_cfg() -> GeolocationProbeConfig {
207 GeolocationProbeConfig {
208 high_accuracy: true,
209 ..GeolocationProbeConfig::default()
210 }
211 }
212
213 fn fix(lat: f64, lon: f64) -> LocationFix {
214 LocationFix {
215 latitude_deg: lat,
216 longitude_deg: lon,
217 accuracy_m: 10.0,
218 altitude_m: f32::NAN,
219 altitude_accuracy_m: f32::NAN,
220 heading_deg: f32::NAN,
221 speed_mps: f32::NAN,
222 timestamp_ms: 0,
223 }
224 }
225
226 #[test]
227 fn first_probe_emits_subscribe_with_config() {
228 let mut mgr = GeolocationManager::new();
229 mgr.diff_layout(|emit| emit(cfg()));
230 assert_eq!(mgr.refcount(), 1);
231 let events = mgr.take_pending_events();
232 assert_eq!(events.len(), 1);
233 assert!(matches!(events[0], GeolocationDiffEvent::Subscribe { .. }));
234 }
235
236 #[test]
237 fn last_probe_drop_emits_release_and_clears_fix() {
238 let mut mgr = GeolocationManager::new();
239 mgr.diff_layout(|emit| emit(cfg()));
240 mgr.set_latest_fix(fix(37.0, -122.0));
241 let _ = mgr.take_pending_events();
242
243 mgr.diff_layout(|_emit| {});
244 assert_eq!(mgr.refcount(), 0);
245 assert_eq!(mgr.latest_fix(), None);
246 let events = mgr.take_pending_events();
247 assert_eq!(events.len(), 1);
248 assert!(matches!(events[0], GeolocationDiffEvent::Release));
249 }
250
251 #[test]
252 fn config_drift_emits_reconfigure() {
253 let mut mgr = GeolocationManager::new();
254 mgr.diff_layout(|emit| emit(cfg()));
255 let _ = mgr.take_pending_events();
256
257 mgr.diff_layout(|emit| emit(high_accuracy_cfg()));
258 let events = mgr.take_pending_events();
259 assert_eq!(events.len(), 1);
260 let ev = &events[0];
261 match ev {
262 GeolocationDiffEvent::Reconfigure { config } => {
263 assert!(config.high_accuracy);
264 }
265 _ => panic!("expected Reconfigure, got {:?}", ev),
266 }
267 }
268
269 #[test]
270 fn stable_config_does_not_re_emit() {
271 let mut mgr = GeolocationManager::new();
272 mgr.diff_layout(|emit| emit(cfg()));
273 let _ = mgr.take_pending_events();
274
275 mgr.diff_layout(|emit| emit(cfg()));
277 assert!(mgr.take_pending_events().is_empty());
278 }
279
280 #[test]
281 fn set_latest_fix_returns_change_flag() {
282 let mut mgr = GeolocationManager::new();
283 assert!(mgr.set_latest_fix(fix(37.0, -122.0)));
284 assert!(!mgr.set_latest_fix(fix(37.0, -122.0)));
285 assert!(mgr.set_latest_fix(fix(37.7749, -122.4194)));
286 }
287
288 #[test]
289 fn missing_fields_decode_to_none() {
290 let f = fix(0.0, 0.0);
291 assert_eq!(f.altitude(), None);
292 assert_eq!(f.heading(), None);
293 assert_eq!(f.speed(), None);
294 }
295
296 #[test]
297 fn async_fixes_round_trip_through_manager() {
298 let _ = drain_location_fixes();
300
301 push_location_fix(fix(37.0, -122.0));
302 push_location_fix(fix(48.8566, 2.3522)); let drained = drain_location_fixes();
304 assert_eq!(drained.len(), 2, "both parked fixes drain in order");
305 assert_eq!(drained[0].latitude_deg, 37.0);
306 assert_eq!(drained[1].latitude_deg, 48.8566);
307
308 let mut mgr = GeolocationManager::new();
310 for f in &drained {
311 mgr.set_latest_fix(*f);
312 }
313 let got = mgr.latest_fix().expect("a fix was applied");
314 assert_eq!(got.latitude_deg, 48.8566, "the last applied fix wins");
315
316 assert!(drain_location_fixes().is_empty());
318 }
319}