use crate::Widget;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KeyState {
Pressed,
Released,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Key {
Tab,
Enter,
Space,
Escape,
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
Backspace,
Delete,
Character(char),
Unknown,
}
#[derive(Clone, Debug)]
pub struct KeyboardEvent {
pub key: Key,
pub state: KeyState,
pub repeat: bool,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct ModifiersState {
pub ctrl: bool,
pub shift: bool,
pub alt: bool,
pub super_key: bool,
}
#[derive(Clone, Debug)]
pub enum InputEvent {
MouseMoved {
position: (f32, f32),
},
MouseEntered,
MouseExited,
MouseInput {
state: winit::event::ElementState,
button: winit::event::MouseButton,
position: (f32, f32),
},
MouseWheel {
delta: winit::event::MouseScrollDelta,
position: (f32, f32),
},
KeyInput {
event: KeyboardEvent,
modifiers: ModifiersState,
},
ModifiersChanged(ModifiersState),
Ime(winit::event::Ime),
FocusGained,
FocusLost,
BlinkTick,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EventStatus {
Ignored,
Handled,
}
#[derive(Default)]
pub struct EventCtx {
redraw_requested: bool,
cursor_icon: Option<winit::window::CursorIcon>,
focus_requested: bool,
focus_released: bool,
pub(crate) focus_target: Option<String>,
pub(crate) clear_focus: bool,
}
impl EventCtx {
pub fn new() -> Self {
Self::default()
}
pub fn request_redraw(&mut self) {
self.redraw_requested = true;
}
pub fn redraw_requested(&self) -> bool {
self.redraw_requested
}
pub fn set_cursor_icon(&mut self, icon: winit::window::CursorIcon) {
self.cursor_icon = Some(icon);
}
pub fn take_cursor_icon(&mut self) -> Option<winit::window::CursorIcon> {
self.cursor_icon.take()
}
pub fn request_focus(&mut self) {
self.focus_requested = true;
}
pub fn release_focus(&mut self) {
self.focus_released = true;
}
fn take_focus_request(&mut self) -> bool {
std::mem::take(&mut self.focus_requested)
}
fn take_release_focus_request(&mut self) -> bool {
std::mem::take(&mut self.focus_released)
}
}
pub fn convert_keyboard_event(event: winit::event::KeyEvent) -> KeyboardEvent {
use winit::keyboard::{ KeyCode, PhysicalKey };
let key = match event.physical_key {
PhysicalKey::Code(KeyCode::Tab) => Key::Tab,
PhysicalKey::Code(KeyCode::Enter) => Key::Enter,
PhysicalKey::Code(KeyCode::Space) => Key::Space,
PhysicalKey::Code(KeyCode::Escape) => Key::Escape,
PhysicalKey::Code(KeyCode::ArrowUp) => Key::ArrowUp,
PhysicalKey::Code(KeyCode::ArrowDown) => Key::ArrowDown,
PhysicalKey::Code(KeyCode::ArrowLeft) => Key::ArrowLeft,
PhysicalKey::Code(KeyCode::ArrowRight) => Key::ArrowRight,
PhysicalKey::Code(KeyCode::Backspace) => Key::Backspace,
PhysicalKey::Code(KeyCode::Delete) => Key::Delete,
_ =>
event.text
.as_ref()
.and_then(|s| s.chars().next())
.map(Key::Character)
.unwrap_or(Key::Unknown),
};
KeyboardEvent {
key,
state: match event.state {
winit::event::ElementState::Pressed => KeyState::Pressed,
winit::event::ElementState::Released => KeyState::Released,
},
repeat: event.repeat,
}
}
fn ancestor_paths(path: &str) -> Vec<String> {
let parts: Vec<&str> = path.split('.').collect();
(1..=parts.len()).map(|n| parts[..n].join(".")).collect()
}
pub fn find_widget_mut<'a>(
tree: &'a mut [Box<dyn Widget>],
path: &str
) -> Option<&'a mut dyn Widget> {
let mut parts = path.split('.');
let root_idx: usize = parts.next()?.parse().ok()?;
let mut current: &mut dyn Widget = tree.get_mut(root_idx)?.as_mut();
for part in parts {
let idx: usize = part.parse().ok()?;
let children = current.children_mut()?;
current = children.get_mut(idx)?.as_mut();
}
Some(current)
}
pub fn hit_test_path(tree: &[Box<dyn Widget>], point: (f32, f32)) -> Option<String> {
for (i, node) in tree.iter().enumerate().rev() {
if let Some(path) = hit_test_recursive(node.as_ref(), &i.to_string(), point) {
return Some(path);
}
}
None
}
fn hit_test_recursive(widget: &dyn Widget, path: &str, point: (f32, f32)) -> Option<String> {
if !widget.hit_test(point) {
return None;
}
for (i, child) in widget.children().iter().enumerate().rev() {
let child_path = format!("{path}.{i}");
if let Some(hit) = hit_test_recursive(child.as_ref(), &child_path, point) {
return Some(hit);
}
}
Some(path.to_string())
}
pub fn path_is_within(path: &str, ancestor: &str) -> bool {
path == ancestor || path.starts_with(&format!("{ancestor}."))
}
pub fn dispatch_positional(
tree: &mut [Box<dyn Widget>],
leaf_path: &str,
event: &InputEvent,
ctx: &mut EventCtx
) -> EventStatus {
for path in ancestor_paths(leaf_path).into_iter().rev() {
let Some(widget) = find_widget_mut(tree, &path) else {
continue;
};
let status = widget.event(event, ctx);
if ctx.take_focus_request() {
ctx.focus_target = Some(path.clone());
}
if ctx.take_release_focus_request() {
ctx.clear_focus = true;
}
if status == EventStatus::Handled {
return EventStatus::Handled;
}
}
EventStatus::Ignored
}
pub fn dispatch_to_path(
tree: &mut [Box<dyn Widget>],
path: &str,
event: &InputEvent,
ctx: &mut EventCtx
) -> EventStatus {
match find_widget_mut(tree, path) {
Some(widget) => widget.event(event, ctx),
None => EventStatus::Ignored,
}
}
#[derive(Default)]
pub struct InputState {
pub cursor_pos: Option<(f32, f32)>,
pub hovered_path: Option<String>,
pub pressed_path: Option<String>,
pub focused_path: Option<String>,
pub modifiers: ModifiersState,
}