use serde::Serialize;
use serde_json::json;
use skia_safe::Matrix;
use winit::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition},
event::{ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, WindowEvent},
keyboard::{
Key::{Character, Named},
KeyCode, KeyLocation, ModifiersState, NamedKey,
PhysicalKey::Code,
},
};
use super::window::WindowSpec;
use crate::context::page::Page;
#[derive(Debug, Clone)]
pub enum AppEvent {
Open(WindowSpec, Page),
Close(u32),
FrameRate(u64),
Quit,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum UiEvent {
#[allow(non_snake_case)]
Wheel {
deltaX: f32,
deltaY: f32,
},
Move {
left: f32,
top: f32,
},
Keyboard {
event: String,
key: String,
code: KeyCode,
location: u32,
modifiers: ModifierKeys,
repeat: bool,
},
Composition {
event: String,
data: String,
},
Mouse {
event: String,
button: Option<u16>,
buttons: u16,
point: LogicalPosition<f32>,
page_point: LogicalPosition<f32>,
modifiers: ModifierKeys,
},
Input(Option<String>, String),
Focus(bool),
Resize(LogicalSize<u32>),
Fullscreen(bool),
}
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModifierKeys {
shift_key: bool,
ctrl_key: bool,
alt_key: bool,
meta_key: bool,
}
impl From<ModifiersState> for ModifierKeys {
fn from(state: ModifiersState) -> Self {
ModifierKeys {
shift_key: state.shift_key(),
ctrl_key: state.control_key(),
alt_key: state.alt_key(),
meta_key: state.super_key(),
}
}
}
#[derive(Debug)]
pub struct Sieve {
dpr: f64,
queue: Vec<UiEvent>,
key_modifiers: ModifierKeys,
mouse_point: PhysicalPosition<f64>,
mouse_button: Option<u16>,
mouse_buttons: u16,
mouse_transform: Matrix,
compose_begun: bool,
compose_ongoing: bool,
}
impl Sieve {
pub fn new(dpr: f64) -> Self {
Sieve {
dpr,
queue: vec![],
key_modifiers: Modifiers::default().state().into(),
mouse_point: PhysicalPosition::default(),
mouse_button: None,
mouse_buttons: 0,
mouse_transform: Matrix::new_identity(),
compose_begun: false,
compose_ongoing: false,
}
}
pub fn use_transform(&mut self, matrix: Matrix) {
self.mouse_transform = matrix;
}
pub fn go_fullscreen(&mut self, is_full: bool) {
self.queue.push(UiEvent::Fullscreen(is_full));
}
fn add_mouse_event(&mut self, event: &str) {
let raw_position = LogicalPosition::<f32>::from_physical(self.mouse_point, self.dpr);
let canvas_point = self
.mouse_transform
.map_point((raw_position.x, raw_position.y));
let canvas_position = LogicalPosition::<f32>::new(canvas_point.x, canvas_point.y);
self.queue.push(UiEvent::Mouse {
event: event.to_string(),
point: canvas_position,
page_point: raw_position,
button: self.mouse_button,
buttons: self.mouse_buttons,
modifiers: self.key_modifiers,
})
}
pub fn capture(&mut self, event: &WindowEvent) {
match event {
WindowEvent::Moved(physical_pt) => {
let LogicalPosition { x, y } = physical_pt.to_logical(self.dpr);
self.queue.push(UiEvent::Move { left: x, top: y });
}
WindowEvent::Resized(physical_size) => {
let logical_size = LogicalSize::from_physical(*physical_size, self.dpr);
self.queue.push(UiEvent::Resize(logical_size));
}
WindowEvent::Focused(in_focus) => {
self.queue.push(UiEvent::Focus(*in_focus));
}
WindowEvent::ModifiersChanged(modifiers) => {
self.key_modifiers = modifiers.state().into();
}
WindowEvent::CursorEntered { .. } => {
self.add_mouse_event("mouseenter");
}
WindowEvent::CursorLeft { .. } => {
self.add_mouse_event("mouseleave");
}
WindowEvent::CursorMoved { position, .. } if *position != self.mouse_point => {
self.mouse_point = *position;
self.add_mouse_event("mousemove");
}
WindowEvent::MouseWheel { delta, .. } => {
let LogicalPosition { x, y } = match delta {
MouseScrollDelta::PixelDelta(physical_pt) => {
LogicalPosition::from_physical(*physical_pt, self.dpr)
}
MouseScrollDelta::LineDelta(h, v) => LogicalPosition { x: *h, y: *v },
};
self.queue.push(UiEvent::Wheel {
deltaX: x,
deltaY: y,
});
}
WindowEvent::MouseInput { state, button, .. } => {
let (button_id, button_bits) = match button {
MouseButton::Left => (0, 1),
MouseButton::Middle => (1, 4),
MouseButton::Right => (2, 2),
MouseButton::Back => (3, 8),
MouseButton::Forward => (4, 16),
MouseButton::Other(num) => (*num, 0),
};
self.mouse_button = Some(button_id);
match state {
ElementState::Pressed => {
self.mouse_buttons |= button_bits;
self.add_mouse_event("mousedown");
}
ElementState::Released => {
self.mouse_buttons &= !button_bits;
self.add_mouse_event("mouseup");
self.mouse_button = None;
}
}
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key: Code(key_code),
logical_key,
state,
repeat,
location,
..
},
..
} => {
let event_type = match state {
ElementState::Pressed => "keydown",
ElementState::Released => "keyup",
}
.to_string();
let key_text = match logical_key {
Named(n) => {
serde_json::from_value(json!(n)).unwrap_or_else(|_| format!("{:?}", n))
}
Character(c) => c.to_string(),
_ => String::new(),
};
let key_location = match location {
KeyLocation::Standard => 0,
KeyLocation::Left => 1,
KeyLocation::Right => 2,
KeyLocation::Numpad => 3,
};
self.queue.push(UiEvent::Keyboard {
event: event_type,
key: key_text.clone(),
code: *key_code,
location: key_location,
modifiers: self.key_modifiers,
repeat: *repeat,
});
if self.compose_ongoing {
self.compose_ongoing = !matches!(state, ElementState::Released);
} else if *state == ElementState::Pressed {
let key_char = match &logical_key {
Character(c) => Some(c.to_string()),
Named(NamedKey::Tab) => Some("\t".to_string()),
Named(NamedKey::Space) => Some(" ".to_string()),
Named(NamedKey::Backspace | NamedKey::Delete | NamedKey::Enter) => {
Some("".to_string())
}
_ => None,
};
let input_type = match &logical_key {
Named(NamedKey::Backspace) => "deleteContentBackward",
Named(NamedKey::Delete) => "deleteContentForward",
Named(NamedKey::Enter) => "insertLineBreak",
_ => "insertText",
}
.to_string();
if let Some(string) = key_char {
let data = if !string.is_empty() {
Some(string)
} else {
None
};
self.queue.push(UiEvent::Input(data, input_type));
};
}
}
WindowEvent::Ime(event, ..) => {
match &event {
Ime::Preedit(string, Some(_range)) => {
if !self.compose_begun {
self.queue.push(UiEvent::Composition {
event: "compositionstart".to_string(),
data: "".to_string(),
});
self.compose_begun = true; }
self.queue.push(UiEvent::Composition {
event: "compositionupdate".to_string(),
data: string.clone(),
});
self.compose_ongoing = true; }
Ime::Commit(string) => {
self.queue.push(UiEvent::Composition {
event: "compositionend".to_string(),
data: string.clone(),
});
self.queue.push(UiEvent::Input(
Some(string.clone()),
"insertCompositionText".to_string(),
)); self.compose_begun = false;
}
_ => {}
};
}
_ => {}
}
}
pub fn collect(&mut self) -> serde_json::Value {
let payload = json!(self.queue);
self.queue.clear();
payload
}
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
}