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}