use crcbl_core::input::ButtonState;
use super::ffi::{NSPoint, NSUInteger};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RawEvent {
Resized {
window: usize,
},
BackingChanged {
window: usize,
},
Focus {
window: usize,
focused: bool,
},
CloseRequested {
window: usize,
},
Closed {
window: usize,
},
ScreensChanged,
Key {
window: usize,
keycode: u16,
character: Option<char>,
flags: NSUInteger,
state: ButtonState,
repeat: bool,
seconds: f64,
},
FlagsChanged {
window: usize,
keycode: u16,
flags: NSUInteger,
seconds: f64,
},
TextCommitted {
window: usize,
seconds: f64,
},
Preedit {
window: usize,
seconds: f64,
},
PointerMotion {
window: usize,
location: NSPoint,
dx: f64,
dy: f64,
seconds: f64,
},
PointerFocus {
window: usize,
entered: bool,
location: NSPoint,
seconds: f64,
},
Button {
window: usize,
number: isize,
state: ButtonState,
location: NSPoint,
flags: NSUInteger,
seconds: f64,
},
FilesDropped {
window: usize,
location: NSPoint,
seconds: f64,
},
Scroll {
window: usize,
dx: f64,
dy: f64,
precise: bool,
location: NSPoint,
flags: NSUInteger,
seconds: f64,
},
}
impl RawEvent {
#[must_use]
pub const fn window(self) -> Option<usize> {
match self {
Self::Resized { window }
| Self::BackingChanged { window }
| Self::Focus { window, .. }
| Self::CloseRequested { window }
| Self::Closed { window }
| Self::Key { window, .. }
| Self::FlagsChanged { window, .. }
| Self::TextCommitted { window, .. }
| Self::Preedit { window, .. }
| Self::PointerMotion { window, .. }
| Self::PointerFocus { window, .. }
| Self::Button { window, .. }
| Self::FilesDropped { window, .. }
| Self::Scroll { window, .. } => Some(window),
Self::ScreensChanged => None,
}
}
}
pub fn enqueue(queue: &mut Vec<RawEvent>, event: RawEvent) {
match event {
RawEvent::Resized { .. } | RawEvent::BackingChanged { .. } | RawEvent::ScreensChanged
if queue.contains(&event) =>
{
return;
}
_ => {}
}
queue.push(event);
}
#[cfg(test)]
mod tests {
use super::*;
const A: usize = 0x1234;
const B: usize = 0x5678;
#[test]
fn a_live_resize_drag_collapses_to_one_marker() {
let mut queue = Vec::new();
for _ in 0..200 {
enqueue(&mut queue, RawEvent::Resized { window: A });
}
assert_eq!(queue, [RawEvent::Resized { window: A }]);
}
#[test]
fn two_windows_resizing_do_not_collapse_into_each_other() {
let mut queue = Vec::new();
enqueue(&mut queue, RawEvent::Resized { window: A });
enqueue(&mut queue, RawEvent::Resized { window: B });
enqueue(&mut queue, RawEvent::Resized { window: A });
assert_eq!(
queue,
[
RawEvent::Resized { window: A },
RawEvent::Resized { window: B }
]
);
}
#[test]
fn the_collapse_never_reorders_what_it_kept() {
let mut queue = Vec::new();
let focus = RawEvent::Focus {
window: A,
focused: true,
};
enqueue(&mut queue, RawEvent::Resized { window: A });
enqueue(&mut queue, focus);
enqueue(&mut queue, RawEvent::Resized { window: A });
assert_eq!(queue, [RawEvent::Resized { window: A }, focus]);
}
#[test]
fn moving_between_two_displays_is_one_backing_change() {
let mut queue = Vec::new();
enqueue(&mut queue, RawEvent::BackingChanged { window: A });
enqueue(&mut queue, RawEvent::BackingChanged { window: A });
enqueue(&mut queue, RawEvent::BackingChanged { window: B });
assert_eq!(
queue,
[
RawEvent::BackingChanged { window: A },
RawEvent::BackingChanged { window: B }
]
);
}
#[test]
fn one_display_reconfiguration_is_one_screens_changed() {
let mut queue = Vec::new();
enqueue(&mut queue, RawEvent::ScreensChanged);
enqueue(&mut queue, RawEvent::Resized { window: A });
enqueue(&mut queue, RawEvent::ScreensChanged);
assert_eq!(
queue,
[RawEvent::ScreensChanged, RawEvent::Resized { window: A }]
);
}
#[test]
fn every_occurrence_of_a_discrete_event_is_kept_in_order() {
let mut queue = Vec::new();
let events = [
RawEvent::Focus {
window: A,
focused: true,
},
RawEvent::CloseRequested { window: A },
RawEvent::Focus {
window: A,
focused: false,
},
RawEvent::Focus {
window: A,
focused: true,
},
RawEvent::CloseRequested { window: A },
RawEvent::Closed { window: A },
];
for event in events {
enqueue(&mut queue, event);
}
assert_eq!(queue, events);
}
fn motion(window: usize, x: f64, seconds: f64) -> RawEvent {
RawEvent::PointerMotion {
window,
location: NSPoint { x, y: 100.0 },
dx: 1.0,
dy: -1.0,
seconds,
}
}
#[test]
fn a_burst_of_input_keeps_every_sample_in_order() {
let mut queue = Vec::new();
let samples = [
motion(A, 10.0, 1.000),
motion(A, 11.0, 1.004),
RawEvent::Button {
window: A,
number: 0,
state: ButtonState::Pressed,
location: NSPoint { x: 11.0, y: 100.0 },
flags: 0,
seconds: 1.008,
},
RawEvent::Button {
window: A,
number: 0,
state: ButtonState::Released,
location: NSPoint { x: 11.0, y: 100.0 },
flags: 0,
seconds: 1.060,
},
RawEvent::Button {
window: A,
number: 0,
state: ButtonState::Pressed,
location: NSPoint { x: 11.0, y: 100.0 },
flags: 0,
seconds: 1.140,
},
RawEvent::Key {
window: A,
keycode: 0x0D,
character: Some('w'),
flags: 0,
state: ButtonState::Pressed,
repeat: false,
seconds: 1.200,
},
RawEvent::Key {
window: A,
keycode: 0x0D,
character: Some('w'),
flags: 0,
state: ButtonState::Pressed,
repeat: true,
seconds: 1.500,
},
RawEvent::Scroll {
window: A,
dx: 0.0,
dy: -3.0,
precise: true,
location: NSPoint { x: 11.0, y: 100.0 },
flags: 0,
seconds: 1.600,
},
RawEvent::TextCommitted {
window: A,
seconds: 1.200,
},
RawEvent::Preedit {
window: A,
seconds: 1.200,
},
RawEvent::Preedit {
window: A,
seconds: 1.200,
},
];
for sample in samples {
enqueue(&mut queue, sample);
}
assert_eq!(queue, samples);
}
#[test]
fn two_identical_motion_samples_are_two_samples() {
let mut queue = Vec::new();
let sample = motion(A, 10.0, 1.0);
let commit = RawEvent::TextCommitted {
window: A,
seconds: 1.0,
};
enqueue(&mut queue, sample);
enqueue(&mut queue, sample);
enqueue(&mut queue, commit);
enqueue(&mut queue, commit);
assert_eq!(queue, [sample, sample, commit, commit]);
}
#[test]
fn a_drop_keeps_its_place_behind_the_motion_that_positioned_it() {
let mut queue = Vec::new();
let dropped = RawEvent::FilesDropped {
window: A,
location: NSPoint { x: 12.0, y: 34.0 },
seconds: 2.0,
};
let sample = motion(A, 12.0, 1.9);
enqueue(&mut queue, sample);
enqueue(&mut queue, dropped);
enqueue(&mut queue, dropped);
assert_eq!(queue, [sample, dropped, dropped]);
assert_eq!(dropped.window(), Some(A));
}
#[test]
fn every_windowed_event_names_its_window_and_the_desktop_one_does_not() {
assert_eq!(RawEvent::Resized { window: A }.window(), Some(A));
assert_eq!(motion(B, 0.0, 0.0).window(), Some(B));
assert_eq!(
RawEvent::Key {
window: B,
keycode: 0x35,
character: None,
flags: 0,
state: ButtonState::Released,
repeat: false,
seconds: 0.0,
}
.window(),
Some(B)
);
assert_eq!(
RawEvent::FlagsChanged {
window: B,
keycode: 0x38,
flags: 0,
seconds: 0.0,
}
.window(),
Some(B)
);
assert_eq!(
RawEvent::TextCommitted {
window: B,
seconds: 0.0
}
.window(),
Some(B),
"a commit's marker has to name its window, because that is what the \
text is matched by"
);
assert_eq!(
RawEvent::Preedit {
window: B,
seconds: 0.0
}
.window(),
Some(B),
"so does a pre-edit's"
);
assert_eq!(
RawEvent::PointerFocus {
window: B,
entered: false,
location: NSPoint::default(),
seconds: 0.0,
}
.window(),
Some(B)
);
assert_eq!(
RawEvent::Button {
window: B,
number: 2,
state: ButtonState::Pressed,
location: NSPoint::default(),
flags: 0,
seconds: 0.0,
}
.window(),
Some(B)
);
assert_eq!(
RawEvent::Scroll {
window: B,
dx: 0.0,
dy: 1.0,
precise: false,
location: NSPoint::default(),
flags: 0,
seconds: 0.0,
}
.window(),
Some(B)
);
assert_eq!(RawEvent::BackingChanged { window: B }.window(), Some(B));
assert_eq!(
RawEvent::Focus {
window: A,
focused: false
}
.window(),
Some(A)
);
assert_eq!(RawEvent::CloseRequested { window: B }.window(), Some(B));
assert_eq!(RawEvent::Closed { window: A }.window(), Some(A));
assert_eq!(RawEvent::ScreensChanged.window(), None);
}
}