ailloli_ui_runtime/input/
event_envelope.rs

1//! Host correlation metadata wrapped around provider-neutral runtime events.
2
3use std::time::Duration;
4
5use ailloli_ui_core::event::{Event, PointerSample};
6use ailloli_ui_core::LogicalWindowId;
7
8use crate::app::PresentationGeneration;
9
10/// Monotonic event identifier assigned by one UI host.
11///
12/// The value type does not allocate IDs or enforce ordering/uniqueness. Zero is
13/// the default and has no additional meaning unless a host defines one.
14///
15/// # Examples
16///
17/// ```
18/// use ailloli_ui_runtime::input::EventId;
19/// assert_eq!(EventId::default().get(), 0);
20/// assert!(EventId::new(2) > EventId::new(1));
21/// ```
22#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct EventId(u64);
24
25/// Provides the operations defined for EventId.
26impl EventId {
27    /// Wraps an explicit host-local event ID without validation.
28    ///
29    /// # Examples
30    ///
31    /// ```
32    /// use ailloli_ui_runtime::input::EventId;
33    /// assert_eq!(EventId::new(u64::MAX).get(), u64::MAX);
34    /// ```
35    pub const fn new(value: u64) -> Self {
36        Self(value)
37    }
38
39    /// Returns the underlying host-local integer.
40    ///
41    /// # Examples
42    ///
43    /// ```
44    /// use ailloli_ui_runtime::input::EventId;
45    /// assert_eq!(EventId::new(42).get(), 42);
46    /// ```
47    pub const fn get(self) -> u64 {
48        self.0
49    }
50}
51
52/// Monotonic duration since the host's documented time origin.
53///
54/// This is not a wall-clock timestamp and cannot represent negative durations.
55/// Hosts sharing envelopes must agree on the origin; the type does not enforce
56/// monotonic event order.
57///
58/// # Examples
59///
60/// ```
61/// use std::time::Duration;
62/// use ailloli_ui_runtime::input::EventTimestamp;
63/// assert_eq!(EventTimestamp::default().duration(), Duration::ZERO);
64/// assert!(EventTimestamp::new(Duration::from_millis(2)) > EventTimestamp::new(Duration::from_millis(1)));
65/// ```
66#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
67pub struct EventTimestamp(Duration);
68
69/// Provides the operations defined for EventTimestamp.
70impl EventTimestamp {
71    /// Wraps a duration since the host-defined origin.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use std::time::Duration;
77    /// use ailloli_ui_runtime::input::EventTimestamp;
78    /// assert_eq!(EventTimestamp::new(Duration::from_micros(7)).duration().as_micros(), 7);
79    /// ```
80    pub const fn new(value: Duration) -> Self {
81        Self(value)
82    }
83
84    /// Returns the exact stored duration.
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// use std::time::Duration;
90    /// use ailloli_ui_runtime::input::EventTimestamp;
91    /// let value = Duration::from_secs(3);
92    /// assert_eq!(EventTimestamp::new(value).duration(), value);
93    /// ```
94    pub const fn duration(self) -> Duration {
95        self.0
96    }
97}
98
99/// Provider-neutral metadata attached to one routed event.
100///
101/// The metadata correlates one host-local ID and monotonic timestamp with a
102/// logical window and native presentation generation. Optional pointer data is
103/// explicit and is not inferred from the event variant. The struct is
104/// non-exhaustive and its fields remain constructor/accessor controlled.
105///
106/// # Examples
107///
108/// ```
109/// use std::time::Duration;
110/// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventId, EventMeta, EventTimestamp}};
111/// let meta = EventMeta::new(EventId::new(1), EventTimestamp::new(Duration::from_millis(5)), "main", PresentationGeneration::new(2));
112/// assert_eq!(meta.id().get(), 1);
113/// assert_eq!(meta.logical_window_id().as_str(), "main");
114/// assert_eq!(meta.pointer(), None);
115/// ```
116#[non_exhaustive]
117#[derive(Debug, Clone, PartialEq)]
118pub struct EventMeta {
119    /// Host-local event correlation ID.
120    id: EventId,
121    /// Duration since the host-defined time origin.
122    timestamp: EventTimestamp,
123    /// Stable logical-window identity.
124    logical_window_id: LogicalWindowId,
125    /// Native presentation generation that produced the event.
126    presentation_generation: PresentationGeneration,
127    /// Optional complete pointer sample.
128    pointer: Option<PointerSample>,
129}
130
131/// Provides the operations defined for EventMeta.
132impl EventMeta {
133    /// Creates metadata without a pointer sample.
134    ///
135    /// IDs, timestamp order, logical-window existence, and presentation liveness
136    /// are not validated here. Attach pointer data with [`Self::with_pointer`].
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// use std::time::Duration;
142    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventId, EventMeta, EventTimestamp}};
143    /// let meta = EventMeta::new(EventId::new(9), EventTimestamp::new(Duration::ZERO), "editor", PresentationGeneration::INITIAL);
144    /// assert_eq!(meta.id(), EventId::new(9));
145    /// assert!(meta.pointer().is_none());
146    /// ```
147    pub fn new(
148        id: EventId,
149        timestamp: EventTimestamp,
150        logical_window_id: impl Into<LogicalWindowId>,
151        presentation_generation: PresentationGeneration,
152    ) -> Self {
153        Self {
154            id,
155            timestamp,
156            logical_window_id: logical_window_id.into(),
157            presentation_generation,
158            pointer: None,
159        }
160    }
161
162    /// Returns the host-local event identifier.
163    ///
164    /// # Examples
165    ///
166    /// ```
167    /// use std::time::Duration;
168    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventId, EventMeta, EventTimestamp}};
169    /// let meta = EventMeta::new(EventId::new(3), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL);
170    /// assert_eq!(meta.id().get(), 3);
171    /// ```
172    pub const fn id(&self) -> EventId {
173        self.id
174    }
175
176    /// Returns the duration since the host-defined origin.
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// use std::time::Duration;
182    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventId, EventMeta, EventTimestamp}};
183    /// let timestamp = EventTimestamp::new(Duration::from_millis(12));
184    /// let meta = EventMeta::new(EventId::new(1), timestamp, "main", PresentationGeneration::INITIAL);
185    /// assert_eq!(meta.timestamp(), timestamp);
186    /// ```
187    pub const fn timestamp(&self) -> EventTimestamp {
188        self.timestamp
189    }
190
191    /// Borrows the exact logical-window identity supplied at construction.
192    ///
193    /// # Examples
194    ///
195    /// ```
196    /// use std::time::Duration;
197    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventId, EventMeta, EventTimestamp}};
198    /// let meta = EventMeta::new(EventId::new(1), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL);
199    /// assert_eq!(meta.logical_window_id().as_str(), "main");
200    /// ```
201    pub fn logical_window_id(&self) -> &LogicalWindowId {
202        &self.logical_window_id
203    }
204
205    /// Returns the native presentation generation that produced the event.
206    ///
207    /// Consumers compare this against a ready [`crate::app::PresentationLifecycle`]
208    /// to reject stale native events.
209    ///
210    /// # Examples
211    ///
212    /// ```
213    /// use std::time::Duration;
214    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventId, EventMeta, EventTimestamp}};
215    /// let generation = PresentationGeneration::new(4);
216    /// let meta = EventMeta::new(EventId::new(1), EventTimestamp::new(Duration::ZERO), "main", generation);
217    /// assert_eq!(meta.presentation_generation(), generation);
218    /// ```
219    pub const fn presentation_generation(&self) -> PresentationGeneration {
220        self.presentation_generation
221    }
222
223    /// Borrows the optional pointer sample.
224    ///
225    /// `None` is the default and does not imply mouse, primary, or coordinate
226    /// values. A sample can be attached even when the eventual event variant is
227    /// not `Event::Pointer`; consistency is a host responsibility.
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// use std::time::Duration;
233    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventId, EventMeta, EventTimestamp}};
234    /// let meta = EventMeta::new(EventId::new(1), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL);
235    /// assert!(meta.pointer().is_none());
236    /// ```
237    pub const fn pointer(&self) -> Option<&PointerSample> {
238        self.pointer.as_ref()
239    }
240
241    /// Returns the primary classification carried by pointer metadata.
242    ///
243    /// Non-pointer events and legacy direct dispatches have no pointer sample
244    /// and therefore return `None` rather than inventing a classification.
245    ///
246    /// # Examples
247    ///
248    /// ```
249    /// use std::time::Duration;
250    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
251    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventId, EventMeta, EventTimestamp}};
252    /// let sample = PointerSample::new_with_primary(PointerId::new(2), PointerSource::Touch, Point::default(), false)?;
253    /// let meta = EventMeta::new(EventId::new(1), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL).with_pointer(sample);
254    /// assert_eq!(meta.pointer_is_primary(), Some(false));
255    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
256    /// ```
257    pub const fn pointer_is_primary(&self) -> Option<bool> {
258        match self.pointer.as_ref() {
259            Some(pointer) => Some(pointer.is_primary()),
260            None => None,
261        }
262    }
263
264    /// Returns this metadata with `pointer` installed or replaced.
265    ///
266    /// The sample has already validated finite logical position through its own
267    /// constructor. This builder performs no event-kind consistency check.
268    ///
269    /// # Examples
270    ///
271    /// ```
272    /// use std::time::Duration;
273    /// use ailloli_ui_core::{event::{PointerId, PointerSample, PointerSource}, Point};
274    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventId, EventMeta, EventTimestamp}};
275    /// let sample = PointerSample::new(PointerId::MOUSE, PointerSource::Mouse, Point::new(3.0, 4.0))?;
276    /// let meta = EventMeta::new(EventId::new(1), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL).with_pointer(sample);
277    /// assert_eq!(meta.pointer().unwrap().position(), Point::new(3.0, 4.0));
278    /// # Ok::<(), ailloli_ui_core::event::PointerSampleError>(())
279    /// ```
280    pub const fn with_pointer(mut self, pointer: PointerSample) -> Self {
281        self.pointer = Some(pointer);
282        self
283    }
284}
285
286/// Runtime event together with host correlation and presentation metadata.
287///
288/// The envelope does not validate that optional pointer metadata corresponds to
289/// an `Event::Pointer`, or that its logical window/generation is currently
290/// accepted. Routing/presentation orchestration owns those checks. The struct
291/// is non-exhaustive and exposes data through accessors.
292///
293/// # Examples
294///
295/// ```
296/// use std::time::Duration;
297/// use ailloli_ui_core::event::{Event, FocusEvent};
298/// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventEnvelope, EventId, EventMeta, EventTimestamp}};
299/// let meta = EventMeta::new(EventId::new(1), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL);
300/// let envelope = EventEnvelope::new(meta, Event::Focus(FocusEvent::new(true)));
301/// assert!(matches!(envelope.event(), Event::Focus(_)));
302/// ```
303#[non_exhaustive]
304#[derive(Debug, Clone, PartialEq)]
305pub struct EventEnvelope {
306    /// Host correlation and presentation metadata.
307    meta: EventMeta,
308    /// Provider-neutral runtime event payload.
309    event: Event,
310}
311
312/// Provides the operations defined for EventEnvelope.
313impl EventEnvelope {
314    /// Combines metadata and an event without cross-validating them.
315    ///
316    /// # Examples
317    ///
318    /// ```
319    /// use std::time::Duration;
320    /// use ailloli_ui_core::event::{Event, FocusEvent};
321    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventEnvelope, EventId, EventMeta, EventTimestamp}};
322    /// let meta = EventMeta::new(EventId::new(2), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL);
323    /// let envelope = EventEnvelope::new(meta, Event::Focus(FocusEvent::new(false)));
324    /// assert_eq!(envelope.meta().id(), EventId::new(2));
325    /// ```
326    pub const fn new(meta: EventMeta, event: Event) -> Self {
327        Self { meta, event }
328    }
329
330    /// Borrows host correlation and presentation metadata.
331    ///
332    /// # Examples
333    ///
334    /// ```
335    /// use std::time::Duration;
336    /// use ailloli_ui_core::event::{Event, FocusEvent};
337    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventEnvelope, EventId, EventMeta, EventTimestamp}};
338    /// let envelope = EventEnvelope::new(EventMeta::new(EventId::new(7), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL), Event::Focus(FocusEvent::new(true)));
339    /// assert_eq!(envelope.meta().id().get(), 7);
340    /// ```
341    pub const fn meta(&self) -> &EventMeta {
342        &self.meta
343    }
344
345    /// Borrows the provider-neutral event payload.
346    ///
347    /// # Examples
348    ///
349    /// ```
350    /// use std::time::Duration;
351    /// use ailloli_ui_core::event::{Event, FocusEvent};
352    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventEnvelope, EventId, EventMeta, EventTimestamp}};
353    /// let envelope = EventEnvelope::new(EventMeta::new(EventId::new(1), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL), Event::Focus(FocusEvent::new(true)));
354    /// assert!(matches!(envelope.event(), Event::Focus(event) if event.focused));
355    /// ```
356    pub const fn event(&self) -> &Event {
357        &self.event
358    }
359
360    /// Pointer sample carried by this envelope, when the event has one.
361    ///
362    /// This delegates to metadata rather than inspecting the event variant.
363    ///
364    /// # Examples
365    ///
366    /// ```
367    /// use std::time::Duration;
368    /// use ailloli_ui_core::event::{Event, FocusEvent};
369    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventEnvelope, EventId, EventMeta, EventTimestamp}};
370    /// let envelope = EventEnvelope::new(EventMeta::new(EventId::new(1), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL), Event::Focus(FocusEvent::new(true)));
371    /// assert_eq!(envelope.pointer(), None);
372    /// ```
373    pub const fn pointer(&self) -> Option<&PointerSample> {
374        self.meta.pointer()
375    }
376
377    /// Returns the envelope's explicit primary-pointer classification.
378    ///
379    /// `None` means no sample was attached, not a secondary pointer.
380    ///
381    /// # Examples
382    ///
383    /// ```
384    /// use std::time::Duration;
385    /// use ailloli_ui_core::event::{Event, FocusEvent};
386    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventEnvelope, EventId, EventMeta, EventTimestamp}};
387    /// let envelope = EventEnvelope::new(EventMeta::new(EventId::new(1), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL), Event::Focus(FocusEvent::new(true)));
388    /// assert_eq!(envelope.pointer_is_primary(), None);
389    /// ```
390    pub const fn pointer_is_primary(&self) -> Option<bool> {
391        self.meta.pointer_is_primary()
392    }
393
394    /// Consumes the envelope and returns `(metadata, event)` without cloning.
395    ///
396    /// # Examples
397    ///
398    /// ```
399    /// use std::time::Duration;
400    /// use ailloli_ui_core::event::{Event, FocusEvent};
401    /// use ailloli_ui_runtime::{app::PresentationGeneration, input::{EventEnvelope, EventId, EventMeta, EventTimestamp}};
402    /// let envelope = EventEnvelope::new(EventMeta::new(EventId::new(5), EventTimestamp::new(Duration::ZERO), "main", PresentationGeneration::INITIAL), Event::Focus(FocusEvent::new(true)));
403    /// let (meta, event) = envelope.into_parts();
404    /// assert_eq!(meta.id().get(), 5);
405    /// assert!(matches!(event, Event::Focus(_)));
406    /// ```
407    pub fn into_parts(self) -> (EventMeta, Event) {
408        (self.meta, self.event)
409    }
410}