use crcbl_core::input::{ButtonState, PointerButton, TouchPhase};
use crate::PhysicalSize;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RawEvent {
Resized {
hwnd: isize,
size: PhysicalSize,
},
Minimized {
hwnd: isize,
},
DpiChanged {
hwnd: isize,
dpi: u32,
},
Focus {
hwnd: isize,
focused: bool,
},
CloseRequested {
hwnd: isize,
},
Destroyed {
hwnd: isize,
},
Key {
hwnd: isize,
scancode: u32,
virtual_key: u16,
state: ButtonState,
repeat: bool,
millis: u32,
},
Char {
hwnd: isize,
unit: u16,
millis: u32,
},
PointerMotion {
hwnd: isize,
x: i32,
y: i32,
millis: u32,
},
PointerFocus {
hwnd: isize,
entered: bool,
x: i32,
y: i32,
millis: u32,
},
Button {
hwnd: isize,
button: PointerButton,
state: ButtonState,
x: i32,
y: i32,
millis: u32,
},
Wheel {
hwnd: isize,
horizontal: bool,
ticks: i16,
x: i32,
y: i32,
millis: u32,
},
Touch {
hwnd: isize,
pointer_id: u32,
phase: TouchPhase,
position: Option<(i32, i32)>,
millis: u32,
},
RawMotion {
hwnd: isize,
device: isize,
flags: u16,
buttons: u16,
x: i32,
y: i32,
millis: u32,
},
RawKey {
hwnd: isize,
device: isize,
make_code: u16,
flags: u16,
millis: u32,
},
DeviceRemoved {
device: isize,
},
FilesDropped {
hwnd: isize,
millis: u32,
},
Preedit {
hwnd: isize,
millis: u32,
},
MonitorsChanged,
AppBar {
hwnd: isize,
notification: u32,
param: isize,
},
}
impl RawEvent {
#[must_use]
pub const fn hwnd(self) -> Option<isize> {
match self {
Self::Resized { hwnd, .. }
| Self::Minimized { hwnd }
| Self::DpiChanged { hwnd, .. }
| Self::Focus { hwnd, .. }
| Self::CloseRequested { hwnd }
| Self::Destroyed { hwnd }
| Self::Key { hwnd, .. }
| Self::Char { hwnd, .. }
| Self::PointerMotion { hwnd, .. }
| Self::PointerFocus { hwnd, .. }
| Self::Button { hwnd, .. }
| Self::Wheel { hwnd, .. }
| Self::Touch { hwnd, .. }
| Self::RawMotion { hwnd, .. }
| Self::RawKey { hwnd, .. }
| Self::Preedit { hwnd, .. }
| Self::AppBar { hwnd, .. }
| Self::FilesDropped { hwnd, .. } => Some(hwnd),
Self::MonitorsChanged | Self::DeviceRemoved { .. } => None,
}
}
}
pub fn enqueue(queue: &mut Vec<RawEvent>, event: RawEvent) {
match event {
RawEvent::Resized { hwnd, .. } => {
if let Some(pending) = queue.iter_mut().find(
|queued| matches!(queued, RawEvent::Resized { hwnd: queued, .. } if *queued == hwnd),
) {
*pending = event;
return;
}
}
RawEvent::MonitorsChanged if queue.contains(&RawEvent::MonitorsChanged) => return,
_ => {}
}
queue.push(event);
}
#[cfg(test)]
mod tests {
use super::*;
const A: isize = 0x1234;
const B: isize = 0x5678;
fn resize(hwnd: isize, width: u32) -> RawEvent {
RawEvent::Resized {
hwnd,
size: PhysicalSize::new(width, 720),
}
}
#[test]
fn a_drag_resize_storm_collapses_to_the_size_the_window_ended_at() {
let mut queue = Vec::new();
for width in [1280, 1281, 1290, 1400, 1401] {
enqueue(&mut queue, resize(A, width));
}
assert_eq!(queue.len(), 1, "{queue:?}");
assert_eq!(
queue[0],
RawEvent::Resized {
hwnd: A,
size: PhysicalSize::new(1401, 720)
}
);
}
#[test]
fn two_windows_resizing_do_not_collapse_into_each_other() {
let mut queue = Vec::new();
enqueue(&mut queue, resize(A, 800));
enqueue(&mut queue, resize(B, 1024));
enqueue(&mut queue, resize(A, 900));
assert_eq!(queue.len(), 2);
assert_eq!(queue[0], resize(A, 900));
assert_eq!(queue[1], resize(B, 1024));
}
#[test]
fn everything_that_is_not_a_resize_keeps_every_occurrence_in_order() {
let mut queue = Vec::new();
let events = [
RawEvent::Focus {
hwnd: A,
focused: true,
},
RawEvent::CloseRequested { hwnd: A },
RawEvent::Focus {
hwnd: A,
focused: false,
},
RawEvent::CloseRequested { hwnd: A },
RawEvent::DpiChanged { hwnd: A, dpi: 144 },
RawEvent::DpiChanged { hwnd: A, dpi: 96 },
RawEvent::FilesDropped {
hwnd: A,
millis: 1_000,
},
RawEvent::FilesDropped {
hwnd: A,
millis: 1_200,
},
RawEvent::Minimized { hwnd: A },
RawEvent::Destroyed { hwnd: A },
RawEvent::DeviceRemoved { device: 0x100 },
RawEvent::DeviceRemoved { device: 0x200 },
RawEvent::Preedit {
hwnd: A,
millis: 1_300,
},
RawEvent::Preedit {
hwnd: A,
millis: 1_301,
},
];
for event in events {
enqueue(&mut queue, event);
}
assert_eq!(queue, events);
}
#[test]
fn one_display_reconfiguration_is_one_monitors_changed() {
let mut queue = Vec::new();
enqueue(&mut queue, RawEvent::MonitorsChanged);
enqueue(&mut queue, resize(A, 1024));
enqueue(&mut queue, RawEvent::MonitorsChanged);
assert_eq!(queue, [RawEvent::MonitorsChanged, resize(A, 1024)]);
}
#[test]
fn a_burst_of_input_keeps_every_sample_in_order() {
let mut queue = Vec::new();
let samples = [
RawEvent::PointerMotion {
hwnd: A,
x: 10,
y: 10,
millis: 1_000,
},
RawEvent::PointerMotion {
hwnd: A,
x: 11,
y: 12,
millis: 1_004,
},
RawEvent::Button {
hwnd: A,
button: PointerButton::Left,
state: ButtonState::Pressed,
x: 11,
y: 12,
millis: 1_008,
},
RawEvent::Button {
hwnd: A,
button: PointerButton::Left,
state: ButtonState::Released,
x: 11,
y: 12,
millis: 1_060,
},
RawEvent::Button {
hwnd: A,
button: PointerButton::Left,
state: ButtonState::Pressed,
x: 11,
y: 12,
millis: 1_140,
},
RawEvent::Key {
hwnd: A,
scancode: 0x0011,
virtual_key: 0x57,
state: ButtonState::Pressed,
repeat: false,
millis: 1_200,
},
RawEvent::Key {
hwnd: A,
scancode: 0x0011,
virtual_key: 0x57,
state: ButtonState::Pressed,
repeat: true,
millis: 1_500,
},
RawEvent::RawMotion {
hwnd: A,
device: 0,
flags: 0,
buttons: 0,
x: 3,
y: -2,
millis: 1_501,
},
RawEvent::RawMotion {
hwnd: A,
device: 0,
flags: 0,
buttons: 0,
x: 4,
y: -1,
millis: 1_502,
},
RawEvent::Char {
hwnd: A,
unit: 0xD83C,
millis: 1_600,
},
RawEvent::Char {
hwnd: A,
unit: 0xDFAE,
millis: 1_600,
},
RawEvent::RawKey {
hwnd: A,
device: 0x100,
make_code: 0x1E,
flags: 0,
millis: 1_650,
},
RawEvent::RawKey {
hwnd: A,
device: 0x200,
make_code: 0x1E,
flags: 0,
millis: 1_651,
},
RawEvent::Touch {
hwnd: A,
pointer_id: 5,
phase: TouchPhase::Began,
position: Some((20, 30)),
millis: 1_700,
},
RawEvent::Touch {
hwnd: A,
pointer_id: 5,
phase: TouchPhase::Moved,
position: Some((21, 30)),
millis: 1_708,
},
RawEvent::Touch {
hwnd: A,
pointer_id: 5,
phase: TouchPhase::Ended,
position: Some((21, 30)),
millis: 1_716,
},
];
for sample in samples {
enqueue(&mut queue, sample);
}
assert_eq!(queue, samples);
}
#[test]
fn a_resize_still_collapses_past_the_input_that_arrived_with_it() {
let mut queue = Vec::new();
let click = RawEvent::Button {
hwnd: A,
button: PointerButton::Right,
state: ButtonState::Pressed,
x: 4,
y: 4,
millis: 9,
};
enqueue(&mut queue, resize(A, 800));
enqueue(&mut queue, click);
enqueue(&mut queue, resize(A, 900));
assert_eq!(queue, [resize(A, 900), click]);
}
#[test]
fn every_windowed_event_names_its_window_and_the_desktop_one_does_not() {
assert_eq!(resize(A, 640).hwnd(), Some(A));
assert_eq!(RawEvent::Minimized { hwnd: B }.hwnd(), Some(B));
assert_eq!(RawEvent::DpiChanged { hwnd: A, dpi: 96 }.hwnd(), Some(A));
assert_eq!(
RawEvent::Focus {
hwnd: A,
focused: true
}
.hwnd(),
Some(A)
);
assert_eq!(RawEvent::CloseRequested { hwnd: A }.hwnd(), Some(A));
assert_eq!(RawEvent::Destroyed { hwnd: A }.hwnd(), Some(A));
assert_eq!(
RawEvent::Key {
hwnd: B,
scancode: 1,
virtual_key: 0x1B,
state: ButtonState::Released,
repeat: false,
millis: 0,
}
.hwnd(),
Some(B)
);
assert_eq!(
RawEvent::Char {
hwnd: B,
unit: 0x61,
millis: 0
}
.hwnd(),
Some(B)
);
assert_eq!(
RawEvent::PointerMotion {
hwnd: B,
x: 0,
y: 0,
millis: 0
}
.hwnd(),
Some(B)
);
assert_eq!(
RawEvent::PointerFocus {
hwnd: B,
entered: false,
x: 0,
y: 0,
millis: 0,
}
.hwnd(),
Some(B)
);
assert_eq!(
RawEvent::Button {
hwnd: B,
button: PointerButton::Middle,
state: ButtonState::Pressed,
x: 0,
y: 0,
millis: 0,
}
.hwnd(),
Some(B)
);
assert_eq!(
RawEvent::Wheel {
hwnd: B,
horizontal: false,
ticks: 120,
x: 0,
y: 0,
millis: 0,
}
.hwnd(),
Some(B)
);
assert_eq!(
RawEvent::RawMotion {
hwnd: B,
device: 0,
flags: 0,
buttons: 0,
x: 1,
y: 1,
millis: 0
}
.hwnd(),
Some(B)
);
assert_eq!(
RawEvent::FilesDropped { hwnd: B, millis: 0 }.hwnd(),
Some(B),
"a drop's marker has to name its window, because that is what the \
payload is matched by"
);
assert_eq!(
RawEvent::AppBar {
hwnd: B,
notification: 1,
param: 0,
}
.hwnd(),
Some(B),
"an appbar notification is placed against its own window"
);
assert_eq!(RawEvent::MonitorsChanged.hwnd(), None);
}
}