azul_core/geolocation.rs
1//! POD types for the geolocation surface. Defined here in `azul-core`
2//! so `NodeType::GeolocationProbe(GeolocationProbeConfig)` can carry the
3//! config without `azul-layout` having to be a `azul-core` dependency.
4//!
5//! The stateful side (refcount, diff queue, latest-fix storage) lives
6//! in `azul_layout::managers::geolocation::GeolocationManager` and
7//! re-exports these types for the existing import paths.
8
9/// One GPS / network-located fix. Mirrors the W3C
10/// [`GeolocationPosition`](https://www.w3.org/TR/geolocation/#position_interface)
11/// shape so the future web backend lands without API churn.
12///
13/// `accuracy_m` is the 1-sigma radius in metres. `altitude_m` /
14/// `altitude_accuracy_m` / `heading_deg` / `speed_mps` are reported as
15/// `f32::NAN` when the platform doesn't supply them — iOS / Android
16/// always supply lat/lon but the other fields depend on hardware.
17#[derive(Debug, Clone, Copy, PartialEq)]
18#[repr(C)]
19pub struct LocationFix {
20 /// Latitude in WGS-84 degrees (positive = north, negative = south).
21 pub latitude_deg: f64,
22 /// Longitude in WGS-84 degrees (positive = east, negative = west).
23 pub longitude_deg: f64,
24 /// 1-sigma horizontal accuracy radius in metres.
25 pub accuracy_m: f32,
26 /// Altitude above the WGS-84 ellipsoid in metres. `NaN` if not
27 /// reported (the platform couldn't measure it).
28 pub altitude_m: f32,
29 /// 1-sigma altitude accuracy in metres. `NaN` if `altitude_m` is
30 /// `NaN` or the platform doesn't report it.
31 pub altitude_accuracy_m: f32,
32 /// Bearing in degrees clockwise from true north, `0..360`. `NaN`
33 /// if the device is stationary or the platform doesn't report it.
34 pub heading_deg: f32,
35 /// Ground speed in metres per second. `NaN` if not reported.
36 pub speed_mps: f32,
37 /// Monotonic timestamp in milliseconds since program start. Lets
38 /// callers detect stale fixes without depending on wall-clock time.
39 pub timestamp_ms: u64,
40}
41
42// FFI Option wrapper (mirrors OptionPenState). Lets `CallbackInfo::
43// get_location_fix() -> Option<LocationFix>` cross the C ABI once the
44// matching api.json type entry + getter are registered via the autofix
45// workflow. Unused internally today; this is the no-codegen prerequisite
46// for that exposure (see MOBILE_SESSION_LOG P3.1h).
47impl_option!(
48 LocationFix,
49 OptionLocationFix,
50 [Debug, Clone, Copy, PartialEq]
51);
52
53impl LocationFix {
54 #[must_use]
55 pub const fn altitude(&self) -> Option<f32> {
56 if self.altitude_m.is_nan() {
57 None
58 } else {
59 Some(self.altitude_m)
60 }
61 }
62
63 #[must_use]
64 pub const fn altitude_accuracy(&self) -> Option<f32> {
65 if self.altitude_accuracy_m.is_nan() {
66 None
67 } else {
68 Some(self.altitude_accuracy_m)
69 }
70 }
71
72 #[must_use]
73 pub const fn heading(&self) -> Option<f32> {
74 if self.heading_deg.is_nan() {
75 None
76 } else {
77 Some(self.heading_deg)
78 }
79 }
80
81 #[must_use]
82 pub const fn speed(&self) -> Option<f32> {
83 if self.speed_mps.is_nan() {
84 None
85 } else {
86 Some(self.speed_mps)
87 }
88 }
89}
90
91/// Configuration the user attaches to a `NodeType::GeolocationProbe`
92/// to tune the platform subscription. Maps to W3C `PositionOptions`
93/// (`enableHighAccuracy` + `maximumAge` + `timeout`).
94#[derive(Debug, Clone, Copy)]
95#[repr(C)]
96pub struct GeolocationProbeConfig {
97 /// `true` requests precise (GPS-driven) location. iOS maps this to
98 /// `CLLocationManager.desiredAccuracy = kCLLocationAccuracyBest`;
99 /// Android to `LocationRequest.PRIORITY_HIGH_ACCURACY`. Costs
100 /// battery — leave `false` for city-block-level apps.
101 pub high_accuracy: bool,
102 /// Subscribe to *background* location updates. Requires extra
103 /// per-platform manifest declarations and a separate
104 /// `Capability::GeolocationBackground` permission grant. `false`
105 /// is the safe default.
106 pub background: bool,
107 /// Reject any fix whose `accuracy_m` exceeds this radius. `0`
108 /// disables the filter — every native sample is delivered.
109 pub max_accuracy_m: f32,
110 /// Minimum time between delivered updates, in milliseconds. `0`
111 /// disables throttling (every native sample is delivered;
112 /// expensive when the platform fires at 10 Hz indoors).
113 pub min_interval_ms: u32,
114}
115
116impl Default for GeolocationProbeConfig {
117 fn default() -> Self {
118 Self {
119 high_accuracy: false,
120 background: false,
121 max_accuracy_m: 0.0,
122 min_interval_ms: 0,
123 }
124 }
125}
126
127/// Canonical bit pattern for hashing / total-ordering / equality of an f32 config
128/// field: -0.0 and +0.0 collapse to the same value (they compare numerically
129/// equal), and every NaN maps to one canonical NaN (so a NaN is equal to — and
130/// hashes like — itself). Used by `PartialEq`, `Ord` and `Hash` so all three agree.
131const fn canon_bits(f: f32) -> u32 {
132 let bits = f.to_bits();
133 if bits.trailing_zeros() >= 31 {
134 0 // +0.0 and -0.0 collapse to +0.0
135 } else if f.is_nan() {
136 f32::NAN.to_bits() // all NaN payloads -> one canonical NaN
137 } else {
138 bits
139 }
140}
141
142// PartialEq / Ord / Hash are hand-written to compare `max_accuracy_m` via
143// `canon_bits` so all three agree: a derived PartialEq's raw float `==` makes a
144// NaN unequal to itself, and raw `to_bits` makes -0.0 != +0.0 — either way
145// breaking the Eq/Hash/Ord contracts that NodeType (which embeds this) relies on.
146impl PartialEq for GeolocationProbeConfig {
147 fn eq(&self, other: &Self) -> bool {
148 self.high_accuracy == other.high_accuracy
149 && self.background == other.background
150 && canon_bits(self.max_accuracy_m) == canon_bits(other.max_accuracy_m)
151 && self.min_interval_ms == other.min_interval_ms
152 }
153}
154
155impl Eq for GeolocationProbeConfig {}
156
157impl PartialOrd for GeolocationProbeConfig {
158 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
159 Some(self.cmp(other))
160 }
161}
162
163impl Ord for GeolocationProbeConfig {
164 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
165 // f32 comparison via to_bits — gives a total order even with
166 // NaNs and matches NodeType::Eq + Hash requirements.
167 (
168 self.high_accuracy,
169 self.background,
170 canon_bits(self.max_accuracy_m),
171 self.min_interval_ms,
172 )
173 .cmp(&(
174 other.high_accuracy,
175 other.background,
176 canon_bits(other.max_accuracy_m),
177 other.min_interval_ms,
178 ))
179 }
180}
181
182impl core::hash::Hash for GeolocationProbeConfig {
183 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
184 self.high_accuracy.hash(state);
185 self.background.hash(state);
186 canon_bits(self.max_accuracy_m).hash(state);
187 self.min_interval_ms.hash(state);
188 }
189}
190
191#[cfg(test)]
192#[path = "geolocation_test.rs"]
193mod geolocation_test;