Skip to main content

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;