use crate::core::ElementId;
use crate::event::{
Event, EventContext, EventEffects, EventPhase, HitTestResult, Key, Modifiers, MouseButton,
};
use crate::geometry::Point;
use crate::runtime::element::ElementTree;
use std::collections::HashSet;
struct DragState {
source: ElementId,
start: Point,
last: Point,
threshold: f32,
started: bool,
button: MouseButton,
modifiers: Modifiers,
}
pub struct EventManager {
focused: Option<ElementId>,
mouse_capture: Option<ElementId>,
mouse_down: bool,
drag: Option<DragState>,
hovered: Option<ElementId>,
hovered_listeners: Vec<ElementId>,
pressed_node: Option<ElementId>,
pressed_listeners: Vec<ElementId>,
focused_ime: bool,
last_click_at: Option<std::time::Instant>,
last_click_pos: Point,
last_click_button: Option<MouseButton>,
click_count: u8,
}
impl EventManager {
pub fn new() -> Self {
Self {
focused: None,
mouse_capture: None,
mouse_down: false,
drag: None,
hovered: None,
hovered_listeners: Vec::new(),
pressed_node: None,
pressed_listeners: Vec::new(),
focused_ime: false,
last_click_at: None,
last_click_pos: Point::zero(),
last_click_button: None,
click_count: 0,
}
}
pub fn focused(&self) -> Option<ElementId> {
self.focused
}
pub fn focused_ime(&self) -> bool {
self.focused_ime
}
pub fn hovered(&self) -> Option<ElementId> {
self.hovered
}
pub fn mouse_capture(&self) -> Option<ElementId> {
self.mouse_capture
}
pub fn set_mouse_capture(&mut self, id: Option<ElementId>) {
self.mouse_capture = id;
}
fn set_hovered_state(tree: &ElementTree, id: Option<ElementId>, hovered: bool) {
let Some(id) = id else { return };
if tree.contains(id) {
let s = tree.state(id);
if s.hovered != hovered {
let mut s = s;
s.hovered = hovered;
tree.set_state(id, s);
}
}
}
fn set_pressed_state(tree: &ElementTree, id: Option<ElementId>, pressed: bool) {
let Some(id) = id else { return };
if tree.contains(id) {
let s = tree.state(id);
if s.pressed != pressed {
let mut s = s;
s.pressed = pressed;
tree.set_state(id, s);
}
}
}
fn find_focusable_ancestor(tree: &ElementTree, hit: &HitTestResult) -> Option<ElementId> {
for &id in hit.path.iter().rev() {
if tree.has_any_listener(id) {
let listeners = tree.listeners(id);
if listeners.iter().any(|l| {
matches!(
l.event,
crate::event::EventType::FocusIn
| crate::event::EventType::ImePreedit
| crate::event::EventType::ImeCommit
| crate::event::EventType::ImeDisabled
)
}) {
return Some(id);
}
}
}
None
}
pub fn handle_mouse_down(
&mut self,
point: Point,
button: MouseButton,
modifiers: Modifiers,
hit: &HitTestResult,
tree: &ElementTree,
mut handler: impl FnMut(ElementId, &Event, &mut EventContext),
) -> EventEffects {
self.drag = None;
self.mouse_down = true;
let mut effects = EventEffects::default();
let now = std::time::Instant::now();
let is_multi = self
.last_click_at
.is_some_and(|t| now.duration_since(t).as_millis() <= 500)
&& self.last_click_button == Some(button)
&& (point.x - self.last_click_pos.x).abs() <= 4.0
&& (point.y - self.last_click_pos.y).abs() <= 4.0;
self.click_count = if is_multi {
self.click_count % 3 + 1
} else {
1
};
self.last_click_at = Some(now);
self.last_click_pos = point;
self.last_click_button = Some(button);
let focus_target = Self::find_focusable_ancestor(tree, hit);
let target = hit.target;
if self.focused != focus_target {
let focus_effects = self.handle_focus_change(focus_target, tree, &mut handler);
effects.merge(&focus_effects);
}
Self::set_pressed_state(tree, self.pressed_node, false);
for &id in &self.pressed_listeners {
Self::set_pressed_state(tree, Some(id), false);
}
Self::set_pressed_state(tree, Some(target), true);
self.pressed_node = Some(target);
self.pressed_listeners = hit
.path
.iter()
.copied()
.filter(|&id| tree.has_any_listener(id) || tree.is_interactive(id))
.collect();
for &id in &self.pressed_listeners {
Self::set_pressed_state(tree, Some(id), true);
}
if !self.pressed_listeners.is_empty() {
effects.request_render();
}
let mut ctx = EventContext::with_target(hit.target);
let event = Event::MouseDown {
x: point.x,
y: point.y,
button,
modifiers,
click_count: self.click_count,
};
dispatch_three_phase(&event, hit, &mut handler, &mut ctx);
if let Some(cap) = ctx.take_capture_request() {
self.mouse_capture = Some(cap);
}
if let Some((source, threshold)) = ctx.take_drag_request() {
self.mouse_capture = Some(source);
self.drag = Some(DragState {
source,
start: point,
last: point,
threshold,
started: false,
button,
modifiers,
});
}
if ctx.is_stopped() {
effects.stop_propagation();
}
effects.merge(&ctx.take_effects());
effects
}
pub fn handle_mouse_up(
&mut self,
point: Point,
button: MouseButton,
hit: &HitTestResult,
tree: &ElementTree,
mut handler: impl FnMut(ElementId, &Event, &mut EventContext),
) -> EventEffects {
let mut effects = EventEffects::default();
let dragged = self.drag.as_ref().is_some_and(|d| d.started);
if let Some(drag) = self.drag.take()
&& drag.started
{
let mut ctx = EventContext::with_target(drag.source);
let event = Event::DragEnd {
x: point.x,
y: point.y,
dx: point.x - drag.last.x,
dy: point.y - drag.last.y,
offset_x: point.x - drag.start.x,
offset_y: point.y - drag.start.y,
button: drag.button,
modifiers: drag.modifiers,
};
handler(drag.source, &event, &mut ctx);
effects.merge(&ctx.take_effects());
}
Self::set_pressed_state(tree, self.mouse_capture, false);
Self::set_pressed_state(tree, self.pressed_node, false);
for &id in &self.pressed_listeners {
Self::set_pressed_state(tree, Some(id), false);
}
if self.pressed_node.is_some() || !self.pressed_listeners.is_empty() {
effects.request_render();
}
self.pressed_node = None;
self.pressed_listeners.clear();
if let Some(capture) = self.mouse_capture.take() {
let mut ctx = EventContext::with_target(capture);
let event = Event::MouseUp {
x: point.x,
y: point.y,
button,
};
handler(capture, &event, &mut ctx);
effects.merge(&ctx.take_effects());
if hit.target == capture && !dragged {
let mut ctx = EventContext::with_target(capture);
handler(capture, &Event::Click { button }, &mut ctx);
effects.merge(&ctx.take_effects());
}
} else {
let mut ctx = EventContext::with_target(hit.target);
dispatch_three_phase(
&Event::MouseUp {
x: point.x,
y: point.y,
button,
},
hit,
&mut handler,
&mut ctx,
);
effects.merge(&ctx.take_effects());
let mut ctx = EventContext::with_target(hit.target);
dispatch_three_phase(&Event::Click { button }, hit, &mut handler, &mut ctx);
effects.merge(&ctx.take_effects());
}
self.mouse_down = false;
effects
}
pub fn handle_mouse_move(
&mut self,
point: Point,
hit: Option<&HitTestResult>,
tree: &ElementTree,
mut handler: impl FnMut(ElementId, &Event, &mut EventContext),
) -> EventEffects {
let mut effects = EventEffects::default();
if let Some(capture) = self.mouse_capture {
let mut drag_started_now = false;
let mut prev = None;
if let Some(drag) = &mut self.drag {
prev = Some(drag.last);
drag.last = point;
if !drag.started {
let dist = ((point.x - drag.start.x).powi(2)
+ (point.y - drag.start.y).powi(2))
.sqrt();
if dist >= drag.threshold {
drag.started = true;
drag_started_now = true;
}
}
}
let event = if let Some(drag) = &self.drag {
if drag.started {
if drag_started_now {
Event::DragStart {
x: point.x,
y: point.y,
offset_x: 0.0,
offset_y: 0.0,
button: drag.button,
modifiers: drag.modifiers,
}
} else {
let prev = prev.expect("drag last known");
Event::DragMove {
x: point.x,
y: point.y,
dx: point.x - prev.x,
dy: point.y - prev.y,
offset_x: point.x - drag.start.x,
offset_y: point.y - drag.start.y,
button: drag.button,
modifiers: drag.modifiers,
}
}
} else {
Event::MouseMove {
x: point.x,
y: point.y,
}
}
} else {
Event::MouseMove {
x: point.x,
y: point.y,
}
};
let mut ctx = EventContext::with_target(capture);
handler(capture, &event, &mut ctx);
effects.merge(&ctx.take_effects());
effects.request_render();
} else if let Some(hit) = hit {
let event = Event::MouseMove {
x: point.x,
y: point.y,
};
let mut ctx = EventContext::with_target(hit.target);
dispatch_three_phase(&event, hit, &mut handler, &mut ctx);
effects.merge(&ctx.take_effects());
}
let new_target = hit.map(|h| h.target);
let new_listeners: Vec<ElementId> = hit
.map(|h| {
h.path
.iter()
.copied()
.filter(|&id| tree.has_any_listener(id) || tree.is_interactive(id))
.collect()
})
.unwrap_or_default();
if new_target != self.hovered {
Self::set_hovered_state(tree, self.hovered, false);
Self::set_hovered_state(tree, new_target, true);
self.hovered = new_target;
effects.request_render();
}
let old_set: HashSet<ElementId> = self.hovered_listeners.iter().copied().collect();
let new_set: HashSet<ElementId> = new_listeners.iter().copied().collect();
for &id in &self.hovered_listeners {
if !new_set.contains(&id) {
Self::set_hovered_state(tree, Some(id), false);
let mut ctx = EventContext::with_target(id);
handler(id, &Event::MouseLeave, &mut ctx);
effects.merge(&ctx.take_effects());
effects.request_render();
}
}
for &id in &new_listeners {
if !old_set.contains(&id) {
Self::set_hovered_state(tree, Some(id), true);
let mut ctx = EventContext::with_target(id);
handler(id, &Event::MouseEnter, &mut ctx);
effects.merge(&ctx.take_effects());
effects.request_render();
}
}
self.hovered_listeners = new_listeners;
effects
}
pub fn handle_wheel(
&mut self,
point: Point,
delta_x: f32,
delta_y: f32,
hit: &HitTestResult,
mut handler: impl FnMut(ElementId, &Event, &mut EventContext),
) -> EventEffects {
let mut ctx = EventContext::with_target(hit.target);
dispatch_three_phase(
&Event::MouseWheel {
delta_x,
delta_y,
x: point.x,
y: point.y,
},
hit,
&mut handler,
&mut ctx,
);
ctx.take_effects()
}
pub fn handle_focus_change(
&mut self,
new_focus: Option<ElementId>,
tree: &ElementTree,
handler: &mut impl FnMut(ElementId, &Event, &mut EventContext),
) -> EventEffects {
let old_focus = self.focused;
let mut effects = EventEffects::default();
if old_focus == new_focus {
return effects;
}
self.focused_ime = new_focus.is_some_and(|id| tree.has_ime_listener(id));
if let Some(old_id) = old_focus {
let mut ctx = EventContext::with_target(old_id);
handler(old_id, &Event::FocusOut, &mut ctx);
effects.merge(&ctx.take_effects());
if tree.contains(old_id) {
let mut s = tree.state(old_id);
s.focused = false;
tree.set_state(old_id, s);
}
}
if let Some(new_id) = new_focus {
let mut ctx = EventContext::with_target(new_id);
handler(new_id, &Event::FocusIn, &mut ctx);
effects.merge(&ctx.take_effects());
if tree.contains(new_id) {
let mut s = tree.state(new_id);
s.focused = true;
tree.set_state(new_id, s);
}
}
self.focused = new_focus;
effects
}
pub fn handle_key_down(
&mut self,
key: Key,
modifiers: Modifiers,
mut handler: impl FnMut(ElementId, &Event, &mut EventContext),
) -> EventEffects {
if let Some(target) = self.focused {
let mut ctx = EventContext::with_target(target);
handler(target, &Event::KeyDown { key, modifiers }, &mut ctx);
ctx.take_effects()
} else {
EventEffects::default()
}
}
pub fn handle_key_up(
&mut self,
key: Key,
modifiers: Modifiers,
mut handler: impl FnMut(ElementId, &Event, &mut EventContext),
) -> EventEffects {
if let Some(target) = self.focused {
let mut ctx = EventContext::with_target(target);
handler(target, &Event::KeyUp { key, modifiers }, &mut ctx);
ctx.take_effects()
} else {
EventEffects::default()
}
}
pub fn handle_ime_preedit(
&mut self,
text: String,
cursor_start: Option<usize>,
cursor_end: Option<usize>,
mut handler: impl FnMut(ElementId, &Event, &mut EventContext),
) -> EventEffects {
if let Some(target) = self.focused {
let mut ctx = EventContext::with_target(target);
handler(
target,
&Event::ImePreedit {
text,
cursor_start,
cursor_end,
},
&mut ctx,
);
ctx.take_effects()
} else {
EventEffects::default()
}
}
pub fn handle_ime_commit(
&mut self,
text: String,
mut handler: impl FnMut(ElementId, &Event, &mut EventContext),
) -> EventEffects {
if let Some(target) = self.focused {
let mut ctx = EventContext::with_target(target);
handler(target, &Event::ImeCommit { text }, &mut ctx);
ctx.take_effects()
} else {
EventEffects::default()
}
}
pub fn handle_ime_disabled(
&mut self,
mut handler: impl FnMut(ElementId, &Event, &mut EventContext),
) -> EventEffects {
if let Some(target) = self.focused {
let mut ctx = EventContext::with_target(target);
handler(target, &Event::ImeDisabled, &mut ctx);
ctx.take_effects()
} else {
EventEffects::default()
}
}
}
impl Default for EventManager {
fn default() -> Self {
Self::new()
}
}
pub fn dispatch_node_listeners(
tree: &ElementTree,
id: ElementId,
event: &Event,
ctx: &mut EventContext,
) {
use crate::view::node::ListenerKind as LK;
let Some(node) = tree.get_node_ref(id) else {
return;
};
let event_type = event.to_type();
for pass in [LK::BuiltIn, LK::User] {
for listener in node.listeners().iter().filter(|l| l.kind == pass) {
if listener.event != event_type {
continue;
}
match ctx.phase() {
EventPhase::Capture => {
if let crate::view::node::Callback::WithCtx(cb) = &listener.callback {
cb(ctx);
}
}
EventPhase::Target => match &listener.callback {
crate::view::node::Callback::Simple(cb) => {
cb();
ctx.stop_propagation();
}
crate::view::node::Callback::WithCtx(cb) => {
cb(ctx);
}
},
EventPhase::Bubble => {
if let crate::view::node::Callback::Simple(cb) = &listener.callback {
cb();
}
}
}
if ctx.is_stopped() {
break;
}
}
}
}
pub fn dispatch_three_phase(
event: &Event,
hit: &HitTestResult,
handler: &mut impl FnMut(ElementId, &Event, &mut EventContext),
ctx: &mut EventContext,
) {
for &id in hit.path.iter().take(hit.path.len().saturating_sub(1)) {
ctx.set_phase(EventPhase::Capture);
handler(id, event, ctx);
if ctx.is_stopped() {
return;
}
}
ctx.set_phase(EventPhase::Target);
handler(hit.target, event, ctx);
if ctx.is_stopped() {
return;
}
for &id in hit.path.iter().take(hit.path.len().saturating_sub(1)).rev() {
ctx.set_phase(EventPhase::Bubble);
handler(id, event, ctx);
if ctx.is_stopped() {
return;
}
}
}