1use alloc::string::String;
12use alloc::string::ToString;
13use crate::category;
14use crate::flag;
15use crate::Opcode;
16
17#[derive(Debug, Clone, PartialEq)]
26pub enum Event {
27 PointerDown {
29 target: u32,
30 x: f32,
31 y: f32,
32 secondary: bool,
33 },
34 PointerMove {
36 target: u32,
37 x: f32,
38 y: f32,
39 hovering: bool,
40 leaving: bool,
41 },
42 PointerUp {
44 target: u32,
45 x: f32,
46 y: f32,
47 secondary: bool,
48 },
49 KeyDown {
51 target: u32,
52 key_code: u16,
53 modifiers: u8,
54 repeat: bool,
55 },
56 KeyUp {
58 target: u32,
59 key_code: u16,
60 modifiers: u8,
61 },
62 ValueChanged {
66 target: u32,
67 value: f32,
68 },
69 TextChanged {
72 target: u32,
73 value: String,
74 },
75 FocusChanged { target: u32, focused: bool },
77 EditingChanged { target: u32, editing: bool },
79 Submit { target: u32 },
81 Scroll {
83 target: u32,
84 offset_x: f32,
85 offset_y: f32,
86 },
87 Wheel {
89 target: u32,
90 delta_x: f32,
91 delta_y: f32,
92 },
93 DateChanged {
96 target: u32,
97 days: i32,
98 millis: u32,
99 },
100 Navigate { url: Option<String> },
105 MediaPlayStateChanged { target: u32, playing: bool },
108 MediaTimeUpdated { target: u32, seconds: f32 },
112 MediaEnded { target: u32 },
115 MediaVolumeChanged { target: u32, volume: f32 },
118 FitChanged { target: u32, index: f32 },
122}
123
124impl Event {
125 pub fn encode(&self) -> Opcode {
133 match self {
134 Event::PointerDown {
135 target,
136 x,
137 y,
138 secondary,
139 } => Opcode::new(
140 category::EVENT,
141 crate::event::POINTER_DOWN,
142 flag_for(*secondary, false, false),
143 *target,
144 x.to_bits(),
145 y.to_bits(),
146 ),
147 Event::PointerMove {
148 target,
149 x,
150 y,
151 hovering,
152 leaving,
153 } => Opcode::new(
154 category::EVENT,
155 crate::event::POINTER_MOVE,
156 flag_for(false, *hovering, *leaving),
157 *target,
158 x.to_bits(),
159 y.to_bits(),
160 ),
161 Event::PointerUp {
162 target,
163 x,
164 y,
165 secondary,
166 } => Opcode::new(
167 category::EVENT,
168 crate::event::POINTER_UP,
169 flag_for(*secondary, false, false),
170 *target,
171 x.to_bits(),
172 y.to_bits(),
173 ),
174 Event::KeyDown {
175 target,
176 key_code,
177 modifiers,
178 repeat,
179 } => Opcode::new(
180 category::EVENT,
181 crate::event::KEY_DOWN,
182 if *repeat { flag::KEY_REPEAT } else { 0 },
183 *target,
184 *key_code as u32,
185 *modifiers as u32,
186 ),
187 Event::KeyUp {
188 target,
189 key_code,
190 modifiers,
191 } => Opcode::new(
192 category::EVENT,
193 crate::event::KEY_UP,
194 0,
195 *target,
196 *key_code as u32,
197 *modifiers as u32,
198 ),
199 Event::ValueChanged { target, value } => Opcode::new(
200 category::EVENT,
201 crate::event::VALUE_CHANGED,
202 0,
203 *target,
204 value.to_bits(),
205 0,
206 ),
207 Event::TextChanged { target, .. } => Opcode::new(
208 category::EVENT,
209 crate::event::TEXT_CHANGED,
210 0,
211 *target,
212 0,
213 0,
214 ),
215 Event::FocusChanged { target, focused } => Opcode::new(
216 category::EVENT,
217 crate::event::FOCUS_CHANGED,
218 0,
219 *target,
220 u32::from(*focused),
221 0,
222 ),
223 Event::EditingChanged { target, editing } => Opcode::new(
224 category::EVENT,
225 crate::event::EDITING_CHANGED,
226 0,
227 *target,
228 u32::from(*editing),
229 0,
230 ),
231 Event::Submit { target } => Opcode::new(
232 category::EVENT,
233 crate::event::SUBMIT,
234 0,
235 *target,
236 0,
237 0,
238 ),
239 Event::Scroll {
240 target,
241 offset_x,
242 offset_y,
243 } => Opcode::new(
244 category::EVENT,
245 crate::event::SCROLL,
246 0,
247 *target,
248 offset_x.to_bits(),
249 offset_y.to_bits(),
250 ),
251 Event::Wheel {
252 target,
253 delta_x,
254 delta_y,
255 } => Opcode::new(
256 category::EVENT,
257 crate::event::WHEEL,
258 0,
259 *target,
260 delta_x.to_bits(),
261 delta_y.to_bits(),
262 ),
263 Event::DateChanged {
264 target,
265 days,
266 millis,
267 } => Opcode::new(
268 category::EVENT,
269 crate::event::DATE_CHANGED,
270 0,
271 *target,
272 *days as u32,
273 *millis,
274 ),
275 Event::Navigate { url } => Opcode::new(
276 category::EVENT,
277 crate::event::NAVIGATE,
278 if url.is_some() { flag::NAVIGATE_URL } else { 0 },
279 0,
280 0,
281 0,
282 ),
283 Event::MediaPlayStateChanged { target, playing } => Opcode::new(
284 category::EVENT,
285 crate::event::MEDIA_PLAY_STATE_CHANGED,
286 0,
287 *target,
288 u32::from(*playing),
289 0,
290 ),
291 Event::MediaTimeUpdated { target, seconds } => Opcode::new(
292 category::EVENT,
293 crate::event::MEDIA_TIME_UPDATED,
294 0,
295 *target,
296 seconds.to_bits(),
297 0,
298 ),
299 Event::MediaEnded { target } => Opcode::new(
300 category::EVENT,
301 crate::event::MEDIA_ENDED,
302 0,
303 *target,
304 0,
305 0,
306 ),
307 Event::MediaVolumeChanged { target, volume } => Opcode::new(
308 category::EVENT,
309 crate::event::MEDIA_VOLUME_CHANGED,
310 0,
311 *target,
312 volume.to_bits(),
313 0,
314 ),
315 Event::FitChanged { target, index } => Opcode::new(
316 category::EVENT,
317 crate::event::FIT_CHANGED,
318 0,
319 *target,
320 index.to_bits(),
321 0,
322 ),
323 }
324 }
325}
326
327#[inline]
329fn flag_for(secondary: bool, hovering: bool, leaving: bool) -> u16 {
330 let mut flags = 0u16;
331 if secondary {
332 flags |= flag::POINTER_SECONDARY;
333 }
334 if hovering {
335 flags |= flag::HOVER_ENTER;
336 }
337 if leaving {
338 flags |= flag::HOVER_LEAVE;
339 }
340 flags
341}
342
343#[derive(Debug, Clone, Copy, PartialEq, Eq)]
345pub enum EventError {
346 NotAnEvent,
348 UnknownCommand,
350 NeedsStringSection,
354}
355
356impl TryFrom<Opcode> for Event {
357 type Error = EventError;
358
359 fn try_from(op: Opcode) -> Result<Self, Self::Error> {
360 if op.category() != category::EVENT {
361 return Err(EventError::NotAnEvent);
362 }
363 let flags = op.flags();
364 match op.command() {
365 crate::event::POINTER_DOWN => Ok(Event::PointerDown {
366 target: op.a(),
367 x: op.b_f32(),
368 y: op.c_f32(),
369 secondary: flags & flag::POINTER_SECONDARY != 0,
370 }),
371 crate::event::POINTER_MOVE => Ok(Event::PointerMove {
372 target: op.a(),
373 x: op.b_f32(),
374 y: op.c_f32(),
375 hovering: flags & flag::HOVER_ENTER != 0,
376 leaving: flags & flag::HOVER_LEAVE != 0,
377 }),
378 crate::event::POINTER_UP => Ok(Event::PointerUp {
379 target: op.a(),
380 x: op.b_f32(),
381 y: op.c_f32(),
382 secondary: flags & flag::POINTER_SECONDARY != 0,
383 }),
384 crate::event::KEY_DOWN => Ok(Event::KeyDown {
385 target: op.a(),
386 key_code: op.b() as u16,
387 modifiers: op.c() as u8,
388 repeat: flags & flag::KEY_REPEAT != 0,
389 }),
390 crate::event::KEY_UP => Ok(Event::KeyUp {
391 target: op.a(),
392 key_code: op.b() as u16,
393 modifiers: op.c() as u8,
394 }),
395 crate::event::VALUE_CHANGED => Ok(Event::ValueChanged {
396 target: op.a(),
397 value: op.b_f32(),
398 }),
399 crate::event::FOCUS_CHANGED => Ok(Event::FocusChanged {
400 target: op.a(),
401 focused: op.b() != 0,
402 }),
403 crate::event::EDITING_CHANGED => Ok(Event::EditingChanged {
404 target: op.a(),
405 editing: op.b() != 0,
406 }),
407 crate::event::SUBMIT => Ok(Event::Submit { target: op.a() }),
408 crate::event::SCROLL => Ok(Event::Scroll {
409 target: op.a(),
410 offset_x: op.b_f32(),
411 offset_y: op.c_f32(),
412 }),
413 crate::event::WHEEL => Ok(Event::Wheel {
414 target: op.a(),
415 delta_x: op.b_f32(),
416 delta_y: op.c_f32(),
417 }),
418 crate::event::DATE_CHANGED => Ok(Event::DateChanged {
419 target: op.a(),
420 days: op.b() as i32,
421 millis: op.c(),
422 }),
423 crate::event::TEXT_CHANGED => Err(EventError::NeedsStringSection),
424 crate::event::NAVIGATE => {
425 if flags & flag::NAVIGATE_URL != 0 {
426 Err(EventError::NeedsStringSection)
427 } else {
428 Ok(Event::Navigate { url: None })
429 }
430 }
431 crate::event::MEDIA_PLAY_STATE_CHANGED => Ok(Event::MediaPlayStateChanged {
432 target: op.a(),
433 playing: op.b() != 0,
434 }),
435 crate::event::MEDIA_TIME_UPDATED => Ok(Event::MediaTimeUpdated {
436 target: op.a(),
437 seconds: op.b_f32(),
438 }),
439 crate::event::MEDIA_ENDED => Ok(Event::MediaEnded { target: op.a() }),
440 crate::event::MEDIA_VOLUME_CHANGED => Ok(Event::MediaVolumeChanged {
441 target: op.a(),
442 volume: op.b_f32(),
443 }),
444 crate::event::FIT_CHANGED => Ok(Event::FitChanged {
445 target: op.a(),
446 index: op.b_f32(),
447 }),
448 _ => Err(EventError::UnknownCommand),
449 }
450 }
451}
452
453pub fn decode_event(op: &Opcode, event_arena: &[u8]) -> Option<Event> {
461 if op.category() != category::EVENT {
462 return None;
463 }
464 if op.command() == crate::event::TEXT_CHANGED {
465 let text = crate::arena::get_str(event_arena, op.b()).ok()?;
466 return Some(Event::TextChanged {
467 target: op.a(),
468 value: text.to_string(),
469 });
470 }
471 if op.command() == crate::event::NAVIGATE && op.flags() & flag::NAVIGATE_URL != 0 {
472 let url = crate::arena::get_str(event_arena, op.b()).ok()?;
473 return Some(Event::Navigate {
474 url: Some(url.to_string()),
475 });
476 }
477 Event::try_from(*op).ok()
478}
479
480#[cfg(test)]
481mod tests {
482 use super::*;
483
484 fn round_trip(e: Event) -> Event {
485 Event::try_from(Opcode::from_bytes(&e.encode().to_bytes())).unwrap()
486 }
487
488 #[test]
489 fn pointer_down_round_trips() {
490 let e = Event::PointerDown {
491 target: 5,
492 x: 10.0,
493 y: 20.0,
494 secondary: true,
495 };
496 assert_eq!(round_trip(e.clone()), e);
497 }
498
499 #[test]
500 fn pointer_move_round_trips_with_hover_flags() {
501 let e = Event::PointerMove {
502 target: 5,
503 x: 10.0,
504 y: 20.0,
505 hovering: true,
506 leaving: false,
507 };
508 assert_eq!(round_trip(e.clone()), e);
509
510 let leaving = Event::PointerMove {
511 target: 5,
512 x: 10.0,
513 y: 20.0,
514 hovering: false,
515 leaving: true,
516 };
517 assert_eq!(round_trip(leaving.clone()), leaving);
518 }
519
520 #[test]
521 fn pointer_up_round_trips() {
522 let e = Event::PointerUp {
523 target: 5,
524 x: 10.0,
525 y: 20.0,
526 secondary: false,
527 };
528 assert_eq!(round_trip(e.clone()), e);
529 }
530
531 #[test]
532 fn key_down_round_trips_with_repeat() {
533 let e = Event::KeyDown {
534 target: 5,
535 key_code: 0x41,
536 modifiers: 0x01,
537 repeat: true,
538 };
539 assert_eq!(round_trip(e.clone()), e);
540 }
541
542 #[test]
543 fn key_up_round_trips() {
544 let e = Event::KeyUp {
545 target: 5,
546 key_code: 0x41,
547 modifiers: 0x01,
548 };
549 assert_eq!(round_trip(e.clone()), e);
550 }
551
552 #[test]
553 fn value_changed_round_trips() {
554 let e = Event::ValueChanged {
555 target: 7,
556 value: 0.625,
557 };
558 assert_eq!(round_trip(e.clone()), e);
559 }
560
561 #[test]
562 fn draft_events_round_trip() {
563 for e in [
564 Event::FocusChanged { target: 1, focused: true },
565 Event::FocusChanged { target: 1, focused: false },
566 Event::EditingChanged { target: 2, editing: true },
567 Event::Submit { target: 3 },
568 Event::Scroll { target: 4, offset_x: 10.0, offset_y: 20.5 },
569 Event::Wheel { target: 5, delta_x: -1.5, delta_y: 2.0 },
570 Event::DateChanged { target: 6, days: 19_723, millis: 3_600_000 },
571 Event::DateChanged { target: 6, days: -1, millis: 0 },
572 ] {
573 assert_eq!(round_trip(e.clone()), e);
574 }
575 }
576
577 #[test]
578 fn media_events_round_trip() {
579 for e in [
580 Event::MediaPlayStateChanged { target: 7, playing: true },
581 Event::MediaPlayStateChanged { target: 7, playing: false },
582 Event::MediaTimeUpdated { target: 7, seconds: 12.5 },
583 Event::MediaEnded { target: 7 },
584 Event::MediaVolumeChanged { target: 7, volume: 0.5 },
585 Event::FitChanged { target: 42, index: 2.0 },
586 ] {
587 assert_eq!(round_trip(e.clone()), e);
588 }
589 assert_eq!(
591 Event::MediaPlayStateChanged { target: 7, playing: true }.encode().to_bytes(),
592 [0x03, 0x0F, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
593 );
594 assert_eq!(
595 Event::MediaTimeUpdated { target: 7, seconds: 12.5 }.encode().to_bytes(),
596 [0x03, 0x10, 0, 0, 7, 0, 0, 0, 0, 0, 0x48, 0x41, 0, 0, 0, 0]
597 );
598 assert_eq!(
599 Event::MediaEnded { target: 7 }.encode().to_bytes(),
600 [0x03, 0x11, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
601 );
602 assert_eq!(
603 Event::MediaVolumeChanged { target: 7, volume: 0.5 }.encode().to_bytes(),
604 [0x03, 0x12, 0, 0, 7, 0, 0, 0, 0, 0, 0x00, 0x3F, 0, 0, 0, 0]
605 );
606 assert_eq!(
607 Event::FitChanged { target: 42, index: 2.0 }.encode().to_bytes(),
608 [0x03, 0x13, 0, 0, 0x2A, 0, 0, 0, 0, 0, 0, 0x40, 0, 0, 0, 0]
609 );
610 }
611
612 #[test]
613 fn non_event_opcode_is_rejected() {
614 let op = Opcode::new(crate::category::TREE, 0x01, 0, 1, 0, 0);
615 assert_eq!(Event::try_from(op), Err(EventError::NotAnEvent));
616 }
617
618 #[test]
619 fn unknown_event_command_is_rejected() {
620 let op = Opcode::new(crate::category::EVENT, 0xFF, 0, 1, 0, 0);
622 assert_eq!(Event::try_from(op), Err(EventError::UnknownCommand));
623 }
624
625 #[test]
626 fn text_changed_needs_string_section() {
627 let op = Opcode::new(crate::category::EVENT, crate::event::TEXT_CHANGED, 0, 7, 0, 0);
630 assert_eq!(Event::try_from(op), Err(EventError::NeedsStringSection));
631 }
632
633 #[test]
634 fn navigate_back_round_trips() {
635 let e = Event::Navigate { url: None };
636 assert_eq!(round_trip(e.clone()), e);
637 }
638
639 #[test]
640 fn navigate_with_url_needs_string_section() {
641 let op = Opcode::new(
645 crate::category::EVENT,
646 crate::event::NAVIGATE,
647 flag::NAVIGATE_URL,
648 0,
649 0,
650 0,
651 );
652 assert_eq!(Event::try_from(op), Err(EventError::NeedsStringSection));
653 }
654
655 #[test]
656 fn navigate_back_matches_conformance_vector_bytes() {
657 let e = Event::Navigate { url: None };
659 assert_eq!(
660 e.encode().to_bytes(),
661 [0x03, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
662 );
663 }
664
665 #[test]
666 fn decode_event_resolves_navigate_url_from_arena() {
667 use alloc::vec;
669 let mut arena = vec![0u8; 32];
670 arena[0..4].copy_from_slice(&9u32.to_le_bytes());
672 arena[4..13].copy_from_slice(b"/users/42");
673 let op = Opcode::new(
674 crate::category::EVENT,
675 crate::event::NAVIGATE,
676 flag::NAVIGATE_URL,
677 0,
678 0,
679 0,
680 );
681 assert_eq!(
682 decode_event(&op, &arena),
683 Some(Event::Navigate {
684 url: Some("/users/42".into())
685 })
686 );
687 }
688
689 #[test]
690 fn pointer_move_round_trips_with_combined_hover_flags() {
691 let e = Event::PointerMove {
693 target: 5,
694 x: 10.0,
695 y: 20.0,
696 hovering: true,
697 leaving: true,
698 };
699 assert_eq!(round_trip(e.clone()), e);
700 assert_eq!(e.encode().flags(), flag::HOVER_ENTER | flag::HOVER_LEAVE);
702 }
703
704 #[test]
705 fn matches_conformance_vector_bytes() {
706 let down = Event::PointerDown {
708 target: 5,
709 x: 10.0,
710 y: 20.0,
711 secondary: false,
712 };
713 assert_eq!(
714 down.encode().to_bytes(),
715 [
716 0x03, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00,
717 0x00, 0xA0, 0x41,
718 ]
719 );
720 }
721}