use super::{Event, View};
use crate::{Context, Node, style::Rect};
use std::rc::Rc;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum EventKind {
Click,
HoverIn,
HoverOut,
Press,
Release,
Submit,
ThumbScroll,
FocusLost,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventResult {
Handled,
Ignored,
}
impl EventResult {
pub fn or(self, other: EventResult) -> EventResult {
match (self, other) {
(EventResult::Handled, _) | (_, EventResult::Handled) => EventResult::Handled,
_ => EventResult::Ignored,
}
}
}
pub struct EventHandler<State, V, F> {
pub(crate) inner: V,
pub(crate) kind: EventKind,
pub(crate) handler: Rc<F>,
pub(crate) _marker: std::marker::PhantomData<State>,
}
pub struct EventElement<VEl> {
pub(crate) inner_element: VEl,
pub(crate) is_hovered: bool,
pub(crate) is_pressed: bool,
}
impl<State, V: View<State>, F> View<State> for EventHandler<State, V, F>
where
F: Fn(&State) -> Option<V::Message> + 'static,
{
type Element = EventElement<V::Element>;
type Message = V::Message;
fn build(&self, ctx: &mut Context) -> Self::Element {
EventElement {
inner_element: self.inner.build(ctx),
is_hovered: false,
is_pressed: false,
}
}
fn rebuild(&self, prev: &Self, ctx: &mut Context, element: &mut Self::Element) {
self.inner
.rebuild(&prev.inner, ctx, &mut element.inner_element);
}
fn rebuild_with_parent(
&self,
prev: &Self,
ctx: &mut Context,
element: &mut Self::Element,
parent: Node,
next_sibling: Option<Node>,
) {
self.inner.rebuild_with_parent(
&prev.inner,
ctx,
&mut element.inner_element,
parent,
next_sibling,
);
}
fn teardown(&self, ctx: &mut Context, element: &mut Self::Element) {
self.inner.teardown(ctx, &mut element.inner_element);
}
fn get_node(&self, element: &Self::Element) -> Node {
self.inner.get_node(&element.inner_element)
}
fn handle_event(
&self,
element: &mut Self::Element,
state: &State,
event: Event,
ctx: &mut Context,
) -> (EventResult, Option<Self::Message>) {
let self_node = self.get_node(element);
if let Event::MouseInput {
pressed, hit_nodes, ..
} = &event
{
if *pressed && hit_nodes.contains(&self_node) {
element.is_pressed = true;
}
}
let (inner_res, inner_msg) =
self.inner
.handle_event(&mut element.inner_element, state, event.clone(), ctx);
if inner_msg.is_some() {
if let Event::MouseInput { pressed, .. } = &event {
if !*pressed {
element.is_pressed = false;
}
}
return (inner_res, inner_msg);
}
if inner_res == EventResult::Handled {
let allow_outer_processing = match &event {
Event::KeyboardInput { .. } => self.kind == EventKind::Submit,
Event::MouseInput { pressed: false, .. } => {
element.is_pressed
&& (self.kind == EventKind::Release || self.kind == EventKind::Click)
}
Event::FocusLost { .. } => self.kind == EventKind::FocusLost,
_ => false,
};
if !allow_outer_processing {
if let Event::MouseInput { pressed, .. } = &event {
if !*pressed {
element.is_pressed = false;
}
}
return (inner_res, inner_msg);
}
}
let mut handled = EventResult::Ignored;
let mut emitted_msg = None;
match &event {
Event::FocusLost { node } => {
if self.kind == EventKind::FocusLost && *node == self_node {
emitted_msg = (self.handler)(state);
handled = EventResult::Handled;
}
}
Event::CursorMoved { hit_nodes, .. } => {
let newly_hovered = hit_nodes.contains(&self_node);
if newly_hovered != element.is_hovered {
element.is_hovered = newly_hovered;
if newly_hovered && self.kind == EventKind::HoverIn {
emitted_msg = (self.handler)(state);
handled = EventResult::Handled;
} else if !newly_hovered && self.kind == EventKind::HoverOut {
emitted_msg = (self.handler)(state);
handled = EventResult::Handled;
}
}
}
Event::MouseInput {
pressed, hit_nodes, ..
} => {
let is_hit = hit_nodes.contains(&self_node);
if *pressed {
if is_hit {
element.is_pressed = true;
if self.kind == EventKind::Press {
emitted_msg = (self.handler)(state);
handled = EventResult::Handled;
}
}
} else if element.is_pressed {
element.is_pressed = false;
if is_hit {
if self.kind == EventKind::Click || self.kind == EventKind::Release {
emitted_msg = (self.handler)(state);
handled = EventResult::Handled;
}
} else if self.kind == EventKind::Release {
emitted_msg = (self.handler)(state);
handled = EventResult::Handled;
}
}
}
Event::KeyboardInput {
event: key_event, ..
} => {
if self.kind == EventKind::Submit && key_event.state.is_pressed() {
if Some(self_node) == ctx.focused_node() {
let is_enter = match key_event.logical_key.as_ref() {
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Enter) => true,
winit::keyboard::Key::Character(s) => s == "\r" || s == "\n",
_ => false,
};
if is_enter {
emitted_msg = (self.handler)(state);
handled = EventResult::Handled;
}
}
}
}
_ => {}
}
(handled.or(inner_res), inner_msg.or(emitted_msg))
}
}
pub trait ViewEventExt<State>: View<State> + Sized {
fn on_event<F>(self, event: EventKind, handler: F) -> EventHandler<State, Self, F>
where
F: Fn(&State) -> Option<Self::Message> + 'static;
fn on_drag<F>(self, handler: F) -> DragHandler<State, Self, F>
where
F: Fn(&State, DragContext) -> Option<Self::Message> + 'static;
fn on_drag_relative<F>(self, handler: F) -> DragHandler<State, Self, F>
where
F: Fn(&State, DragContext) -> Option<Self::Message> + 'static;
fn on_key_down<F>(self, handler: F) -> KeyHandler<State, Self, F>
where
F: Fn(&State, KeyEventContext) -> Option<Self::Message> + 'static;
fn on_key_up<F>(self, handler: F) -> KeyHandler<State, Self, F>
where
F: Fn(&State, KeyEventContext) -> Option<Self::Message> + 'static;
fn on_key_press<F>(self, handler: F) -> KeyHandler<State, Self, F>
where
F: Fn(&State, KeyEventContext) -> Option<Self::Message> + 'static;
fn on_global_key_down<F>(self, handler: F) -> KeyHandler<State, Self, F>
where
F: Fn(&State, KeyEventContext) -> Option<Self::Message> + 'static;
fn on_tick<F>(self, handler: F) -> TickHandler<State, Self, F>
where
F: Fn(&State, f32) -> Option<Self::Message> + 'static;
fn on_thumb_scroll<F>(self, handler: F) -> ThumbScrollHandler<State, Self, F>
where
F: Fn(&State, ThumbScrollContext) -> Option<Self::Message> + 'static;
fn on_focus_lost<F>(self, handler: F) -> EventHandler<State, Self, F>
where
F: Fn(&State) -> Option<Self::Message> + 'static;
fn on_blur<F>(self, handler: F) -> EventHandler<State, Self, F>
where
F: Fn(&State) -> Option<Self::Message> + 'static;
}
impl<State, V: View<State>> ViewEventExt<State> for V {
fn on_event<F>(self, event: EventKind, handler: F) -> EventHandler<State, Self, F>
where
F: Fn(&State) -> Option<Self::Message> + 'static,
{
EventHandler {
inner: self,
kind: event,
handler: Rc::new(handler),
_marker: std::marker::PhantomData,
}
}
fn on_drag<F>(self, handler: F) -> DragHandler<State, Self, F>
where
F: Fn(&State, DragContext) -> Option<Self::Message> + 'static,
{
DragHandler {
inner: self,
policy: crate::CursorGrabPolicy::Normal,
handler: Rc::new(handler),
_marker: std::marker::PhantomData,
}
}
fn on_drag_relative<F>(self, handler: F) -> DragHandler<State, Self, F>
where
F: Fn(&State, DragContext) -> Option<Self::Message> + 'static,
{
DragHandler {
inner: self,
policy: crate::CursorGrabPolicy::Locked,
handler: Rc::new(handler),
_marker: std::marker::PhantomData,
}
}
fn on_key_down<F>(self, handler: F) -> KeyHandler<State, Self, F>
where
F: Fn(&State, KeyEventContext) -> Option<Self::Message> + 'static,
{
KeyHandler {
inner: self,
action: KeyActionKind::Down,
scope: KeyScope::Focused,
handler: Rc::new(handler),
_marker: std::marker::PhantomData,
}
}
fn on_key_up<F>(self, handler: F) -> KeyHandler<State, Self, F>
where
F: Fn(&State, KeyEventContext) -> Option<Self::Message> + 'static,
{
KeyHandler {
inner: self,
action: KeyActionKind::Up,
scope: KeyScope::Focused,
handler: Rc::new(handler),
_marker: std::marker::PhantomData,
}
}
fn on_key_press<F>(self, handler: F) -> KeyHandler<State, Self, F>
where
F: Fn(&State, KeyEventContext) -> Option<Self::Message> + 'static,
{
KeyHandler {
inner: self,
action: KeyActionKind::Press,
scope: KeyScope::Focused,
handler: Rc::new(handler),
_marker: std::marker::PhantomData,
}
}
fn on_global_key_down<F>(self, handler: F) -> KeyHandler<State, Self, F>
where
F: Fn(&State, KeyEventContext) -> Option<Self::Message> + 'static,
{
KeyHandler {
inner: self,
action: KeyActionKind::Down,
scope: KeyScope::Global,
handler: Rc::new(handler),
_marker: std::marker::PhantomData,
}
}
fn on_tick<F>(self, handler: F) -> TickHandler<State, Self, F>
where
F: Fn(&State, f32) -> Option<Self::Message> + 'static,
{
TickHandler {
inner: self,
handler: Rc::new(handler),
_marker: std::marker::PhantomData,
}
}
fn on_thumb_scroll<F>(self, handler: F) -> ThumbScrollHandler<State, Self, F>
where
F: Fn(&State, ThumbScrollContext) -> Option<Self::Message> + 'static,
{
ThumbScrollHandler {
inner: self,
handler: Rc::new(handler),
_marker: std::marker::PhantomData,
}
}
fn on_focus_lost<F>(self, handler: F) -> EventHandler<State, Self, F>
where
F: Fn(&State) -> Option<Self::Message> + 'static,
{
self.on_event(EventKind::FocusLost, handler)
}
fn on_blur<F>(self, handler: F) -> EventHandler<State, Self, F>
where
F: Fn(&State) -> Option<Self::Message> + 'static,
{
self.on_focus_lost(handler)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ThumbScrollContext {
pub thumb: Rect,
pub scroll_pct: f32,
pub is_dragging: bool,
}
pub struct ThumbScrollHandler<State, V, F> {
pub(crate) inner: V,
pub(crate) handler: Rc<F>,
pub(crate) _marker: std::marker::PhantomData<State>,
}
impl<State, V: View<State>, F> View<State> for ThumbScrollHandler<State, V, F>
where
F: Fn(&State, ThumbScrollContext) -> Option<V::Message> + 'static,
{
type Element = V::Element;
type Message = V::Message;
fn build(&self, ctx: &mut Context) -> Self::Element {
self.inner.build(ctx)
}
fn rebuild(&self, prev: &Self, ctx: &mut Context, element: &mut Self::Element) {
self.inner.rebuild(&prev.inner, ctx, element);
}
fn rebuild_with_parent(
&self,
prev: &Self,
ctx: &mut Context,
element: &mut Self::Element,
parent: Node,
next_sibling: Option<Node>,
) {
self.inner
.rebuild_with_parent(&prev.inner, ctx, element, parent, next_sibling);
}
fn teardown(&self, ctx: &mut Context, element: &mut Self::Element) {
self.inner.teardown(ctx, element);
}
fn get_node(&self, element: &Self::Element) -> Node {
self.inner.get_node(element)
}
fn handle_event(
&self,
element: &mut Self::Element,
state: &State,
event: Event,
ctx: &mut Context,
) -> (EventResult, Option<Self::Message>) {
let (inner_res, inner_msg) = self.inner.handle_event(element, state, event.clone(), ctx);
if inner_msg.is_some() {
return (inner_res, inner_msg);
}
let self_node = self.get_node(element);
if let Event::ThumbScroll { node, context } = event {
if node == self_node {
let msg = (self.handler)(state, context);
return (EventResult::Handled, msg);
}
}
(inner_res, None)
}
}
pub struct TickHandler<State, V, F> {
pub(crate) inner: V,
pub(crate) handler: Rc<F>,
pub(crate) _marker: std::marker::PhantomData<State>,
}
impl<State, V: View<State>, F> View<State> for TickHandler<State, V, F>
where
F: Fn(&State, f32) -> Option<V::Message> + 'static,
{
type Element = V::Element;
type Message = V::Message;
fn build(&self, ctx: &mut Context) -> Self::Element {
self.inner.build(ctx)
}
fn rebuild(&self, prev: &Self, ctx: &mut Context, element: &mut Self::Element) {
self.inner.rebuild(&prev.inner, ctx, element);
}
fn rebuild_with_parent(
&self,
prev: &Self,
ctx: &mut Context,
element: &mut Self::Element,
parent: Node,
next_sibling: Option<Node>,
) {
self.inner
.rebuild_with_parent(&prev.inner, ctx, element, parent, next_sibling);
}
fn teardown(&self, ctx: &mut Context, element: &mut Self::Element) {
self.inner.teardown(ctx, element);
}
fn get_node(&self, element: &Self::Element) -> Node {
self.inner.get_node(element)
}
fn handle_event(
&self,
element: &mut Self::Element,
state: &State,
event: Event,
ctx: &mut Context,
) -> (EventResult, Option<Self::Message>) {
let (inner_res, inner_msg) = self.inner.handle_event(element, state, event.clone(), ctx);
if inner_msg.is_some() {
return (inner_res, inner_msg);
}
if let Event::Tick { dt } = event {
if let Some(msg) = (self.handler)(state, dt) {
return (EventResult::Handled, Some(msg));
}
}
(inner_res, None)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DragPhase {
Start,
Move,
End,
}
#[derive(Clone, Debug)]
pub struct DragContext {
pub phase: DragPhase,
pub start_pos: (f32, f32),
pub current_pos: (f32, f32),
pub delta: (f32, f32),
pub total_delta: (f32, f32),
pub modifiers: winit::keyboard::ModifiersState,
}
pub struct DragHandler<State, V, F> {
pub(crate) inner: V,
pub(crate) policy: crate::CursorGrabPolicy,
pub(crate) handler: Rc<F>,
pub(crate) _marker: std::marker::PhantomData<State>,
}
pub struct DragElement<VEl> {
pub(crate) inner_element: VEl,
pub(crate) is_dragging: bool,
pub(crate) start_pos: (f32, f32),
pub(crate) last_pos: (f32, f32),
pub(crate) total_delta: (f32, f32),
}
impl<State, V: View<State>, F> View<State> for DragHandler<State, V, F>
where
F: Fn(&State, DragContext) -> Option<V::Message> + 'static,
{
type Element = DragElement<V::Element>;
type Message = V::Message;
fn build(&self, ctx: &mut Context) -> Self::Element {
DragElement {
inner_element: self.inner.build(ctx),
is_dragging: false,
start_pos: (0.0, 0.0),
last_pos: (0.0, 0.0),
total_delta: (0.0, 0.0),
}
}
fn rebuild(&self, prev: &Self, ctx: &mut Context, element: &mut Self::Element) {
self.inner
.rebuild(&prev.inner, ctx, &mut element.inner_element);
}
fn rebuild_with_parent(
&self,
prev: &Self,
ctx: &mut Context,
element: &mut Self::Element,
parent: Node,
next_sibling: Option<Node>,
) {
self.inner.rebuild_with_parent(
&prev.inner,
ctx,
&mut element.inner_element,
parent,
next_sibling,
);
}
fn teardown(&self, ctx: &mut Context, element: &mut Self::Element) {
if element.is_dragging {
element.is_dragging = false;
ctx.release_pointer();
}
self.inner.teardown(ctx, &mut element.inner_element);
}
fn get_node(&self, element: &Self::Element) -> Node {
self.inner.get_node(&element.inner_element)
}
fn handle_event(
&self,
element: &mut Self::Element,
state: &State,
event: Event,
ctx: &mut Context,
) -> (EventResult, Option<Self::Message>) {
let self_node = self.get_node(element);
let (inner_res, inner_msg) =
self.inner
.handle_event(&mut element.inner_element, state, event.clone(), ctx);
if inner_msg.is_some() {
return (inner_res, inner_msg);
}
let mut handled = EventResult::Ignored;
let mut emitted_msg = None;
match &event {
Event::MouseInput {
button,
pressed,
x,
y,
hit_nodes,
} => {
if *button == winit::event::MouseButton::Left {
if *pressed {
if hit_nodes.contains(&self_node) && !element.is_dragging {
element.is_dragging = true;
element.start_pos = (*x, *y);
element.last_pos = (*x, *y);
element.total_delta = (0.0, 0.0);
ctx.capture_pointer(self_node, self.policy);
let drag_ctx = DragContext {
phase: DragPhase::Start,
start_pos: element.start_pos,
current_pos: (*x, *y),
delta: (0.0, 0.0),
total_delta: (0.0, 0.0),
modifiers: ctx.modifiers,
};
emitted_msg = (self.handler)(state, drag_ctx);
handled = EventResult::Handled;
}
} else if element.is_dragging {
element.is_dragging = false;
ctx.release_pointer();
let delta = (*x - element.last_pos.0, *y - element.last_pos.1);
element.total_delta.0 += delta.0;
element.total_delta.1 += delta.1;
let drag_ctx = DragContext {
phase: DragPhase::End,
start_pos: element.start_pos,
current_pos: (*x, *y),
delta,
total_delta: element.total_delta,
modifiers: ctx.modifiers,
};
emitted_msg = (self.handler)(state, drag_ctx);
handled = EventResult::Handled;
}
}
}
Event::CursorMoved {
x,
y,
delta_x,
delta_y,
..
} => {
if element.is_dragging {
let delta = if self.policy == crate::CursorGrabPolicy::Locked {
(*delta_x, *delta_y)
} else {
(*x - element.last_pos.0, *y - element.last_pos.1)
};
element.last_pos = (*x, *y);
element.total_delta.0 += delta.0;
element.total_delta.1 += delta.1;
let drag_ctx = DragContext {
phase: DragPhase::Move,
start_pos: element.start_pos,
current_pos: (*x, *y),
delta,
total_delta: element.total_delta,
modifiers: ctx.modifiers,
};
emitted_msg = (self.handler)(state, drag_ctx);
handled = EventResult::Handled;
}
}
_ => {}
}
(handled.or(inner_res), inner_msg.or(emitted_msg))
}
}
#[derive(Clone, Debug)]
pub struct KeyEventContext {
pub logical_key: winit::keyboard::Key,
pub physical_key: winit::keyboard::PhysicalKey,
pub text: Option<String>,
pub repeat: bool,
pub modifiers: winit::keyboard::ModifiersState,
}
impl KeyEventContext {
pub fn key_matches(&self, key_str: &str) -> bool {
match &self.logical_key {
winit::keyboard::Key::Character(c) => c.eq_ignore_ascii_case(key_str),
winit::keyboard::Key::Named(named) => {
let name = format!("{:?}", named);
name.eq_ignore_ascii_case(key_str)
}
_ => false,
}
}
pub fn is_escape(&self) -> bool {
self.logical_key == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape)
}
pub fn is_enter(&self) -> bool {
matches!(
self.logical_key,
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Enter)
) || matches!(self.text.as_deref(), Some("\r") | Some("\n"))
}
pub fn is_tab(&self) -> bool {
self.logical_key == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab)
}
pub fn with_ctrl(&self) -> bool {
self.modifiers.control_key()
}
pub fn with_shift(&self) -> bool {
self.modifiers.shift_key()
}
pub fn with_alt(&self) -> bool {
self.modifiers.alt_key()
}
pub fn with_super(&self) -> bool {
self.modifiers.super_key()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KeyActionKind {
Down,
Up,
Press,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KeyScope {
Focused,
Global,
}
pub struct KeyHandler<State, V, F> {
pub(crate) inner: V,
pub(crate) action: KeyActionKind,
pub(crate) scope: KeyScope,
pub(crate) handler: Rc<F>,
pub(crate) _marker: std::marker::PhantomData<State>,
}
impl<State, V: View<State>, F> View<State> for KeyHandler<State, V, F>
where
F: Fn(&State, KeyEventContext) -> Option<V::Message> + 'static,
{
type Element = V::Element;
type Message = V::Message;
fn build(&self, ctx: &mut Context) -> Self::Element {
self.inner.build(ctx)
}
fn rebuild(&self, prev: &Self, ctx: &mut Context, element: &mut Self::Element) {
self.inner.rebuild(&prev.inner, ctx, element);
}
fn rebuild_with_parent(
&self,
prev: &Self,
ctx: &mut Context,
element: &mut Self::Element,
parent: Node,
next_sibling: Option<Node>,
) {
self.inner
.rebuild_with_parent(&prev.inner, ctx, element, parent, next_sibling);
}
fn teardown(&self, ctx: &mut Context, element: &mut Self::Element) {
self.inner.teardown(ctx, element);
}
fn get_node(&self, element: &Self::Element) -> Node {
self.inner.get_node(element)
}
fn handle_event(
&self,
element: &mut Self::Element,
state: &State,
event: Event,
ctx: &mut Context,
) -> (EventResult, Option<Self::Message>) {
let (inner_res, inner_msg) = self.inner.handle_event(element, state, event.clone(), ctx);
if inner_msg.is_some() {
return (inner_res, inner_msg);
}
let mut handled = EventResult::Ignored;
let mut emitted_msg = None;
if let Event::KeyboardInput { event: k_event, .. } = &event {
let is_focused = Some(self.get_node(element)) == ctx.focused_node();
let scope_ok = match self.scope {
KeyScope::Focused => is_focused,
KeyScope::Global => true,
};
if scope_ok {
let matches_action = match self.action {
KeyActionKind::Down => k_event.state.is_pressed() && !k_event.repeat,
KeyActionKind::Up => !k_event.state.is_pressed(),
KeyActionKind::Press => k_event.state.is_pressed(),
};
if matches_action {
let key_ctx = KeyEventContext {
logical_key: k_event.logical_key.clone(),
physical_key: k_event.physical_key,
text: k_event.text.as_ref().map(|s| s.to_string()),
repeat: k_event.repeat,
modifiers: ctx.modifiers,
};
emitted_msg = (self.handler)(state, key_ctx);
if emitted_msg.is_some() {
handled = EventResult::Handled;
}
}
}
}
(handled.or(inner_res), inner_msg.or(emitted_msg))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::KeyEvent;
use crate::ui::widgets::{row, text};
#[derive(Clone, Debug, PartialEq)]
enum TestMsg {
ParentClick,
ChildClick,
}
#[test]
fn test_nested_event_handler_child_priority() {
let mut ctx = Context::new();
let child_view = row((text::<_, TestMsg>("Child"),))
.on_event(EventKind::Click, |_| Some(TestMsg::ChildClick));
let parent_view = row((child_view, text::<_, TestMsg>("Parent Text")))
.on_event(EventKind::Click, |_| Some(TestMsg::ParentClick));
let mut element = View::<()>::build(&parent_view, &mut ctx);
let parent_node = View::<()>::get_node(&parent_view, &element);
let child_node =
View::<()>::get_node(&parent_view.inner.children.0, &element.inner_element.1.0);
let (res_down, msg_down) = View::<()>::handle_event(
&parent_view,
&mut element,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: true,
hit_nodes: vec![child_node, parent_node],
x: 0.0,
y: 0.0,
},
&mut ctx,
);
assert_eq!(res_down, EventResult::Ignored);
assert_eq!(msg_down, None);
let (res_up, msg_up) = View::<()>::handle_event(
&parent_view,
&mut element,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: false,
hit_nodes: vec![child_node, parent_node],
x: 0.0,
y: 0.0,
},
&mut ctx,
);
assert_eq!(res_up, EventResult::Handled);
assert_eq!(msg_up, Some(TestMsg::ChildClick));
let (p_down_res, p_down_msg) = View::<()>::handle_event(
&parent_view,
&mut element,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: true,
hit_nodes: vec![parent_node],
x: 0.0,
y: 0.0,
},
&mut ctx,
);
assert_eq!(p_down_res, EventResult::Ignored);
assert_eq!(p_down_msg, None);
let (p_up_res, p_up_msg) = View::<()>::handle_event(
&parent_view,
&mut element,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: false,
hit_nodes: vec![parent_node],
x: 0.0,
y: 0.0,
},
&mut ctx,
);
assert_eq!(p_up_res, EventResult::Handled);
assert_eq!(p_up_msg, Some(TestMsg::ParentClick));
}
#[test]
fn test_chained_press_and_release() {
#[derive(Clone, Debug, PartialEq)]
enum BtnMsg {
Press,
Release,
}
let mut ctx = Context::new();
let btn_view = text::<_, BtnMsg>("7")
.on_event(EventKind::Press, |_| Some(BtnMsg::Press))
.on_event(EventKind::Release, |_| Some(BtnMsg::Release));
let mut element = View::<()>::build(&btn_view, &mut ctx);
let btn_node = View::<()>::get_node(&btn_view, &element);
let (down_res, down_msg) = View::<()>::handle_event(
&btn_view,
&mut element,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: true,
hit_nodes: vec![btn_node],
x: 0.0,
y: 0.0,
},
&mut ctx,
);
assert_eq!(down_res, EventResult::Handled);
assert_eq!(down_msg, Some(BtnMsg::Press));
let (up_res, up_msg) = View::<()>::handle_event(
&btn_view,
&mut element,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: false,
hit_nodes: vec![btn_node],
x: 0.0,
y: 0.0,
},
&mut ctx,
);
assert_eq!(up_res, EventResult::Handled);
assert_eq!(up_msg, Some(BtnMsg::Release));
}
#[test]
fn test_drag_gesture_lifecycle() {
#[derive(Clone, Debug, PartialEq)]
enum DragMsg {
Drag(DragPhase, (f32, f32), (f32, f32)),
}
let mut ctx = Context::new();
let target = text::<_, DragMsg>("Draggable")
.on_drag(|_state, d| Some(DragMsg::Drag(d.phase, d.delta, d.total_delta)));
let mut element = View::<()>::build(&target, &mut ctx);
let node = View::<()>::get_node(&target, &element);
assert!(!ctx.has_pointer_capture());
assert!(!ctx.is_pointer_captured(node));
let (start_res, start_msg) = View::<()>::handle_event(
&target,
&mut element,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: true,
hit_nodes: vec![node],
x: 50.0,
y: 50.0,
},
&mut ctx,
);
assert_eq!(start_res, EventResult::Handled);
assert_eq!(
start_msg,
Some(DragMsg::Drag(DragPhase::Start, (0.0, 0.0), (0.0, 0.0)))
);
assert!(ctx.has_pointer_capture());
assert!(ctx.is_pointer_captured(node));
assert_eq!(ctx.captured_node(), Some(node));
let (move_res, move_msg) = View::<()>::handle_event(
&target,
&mut element,
&(),
Event::CursorMoved {
x: 75.0,
y: 60.0,
delta_x: 25.0,
delta_y: 10.0,
hit_nodes: vec![],
},
&mut ctx,
);
assert_eq!(move_res, EventResult::Handled);
assert_eq!(
move_msg,
Some(DragMsg::Drag(DragPhase::Move, (25.0, 10.0), (25.0, 10.0)))
);
let (move2_res, move2_msg) = View::<()>::handle_event(
&target,
&mut element,
&(),
Event::CursorMoved {
x: 80.0,
y: 70.0,
delta_x: 5.0,
delta_y: 10.0,
hit_nodes: vec![],
},
&mut ctx,
);
assert_eq!(move2_res, EventResult::Handled);
assert_eq!(
move2_msg,
Some(DragMsg::Drag(DragPhase::Move, (5.0, 10.0), (30.0, 20.0)))
);
let (end_res, end_msg) = View::<()>::handle_event(
&target,
&mut element,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: false,
hit_nodes: vec![],
x: 80.0,
y: 70.0,
},
&mut ctx,
);
assert_eq!(end_res, EventResult::Handled);
assert_eq!(
end_msg,
Some(DragMsg::Drag(DragPhase::End, (0.0, 0.0), (30.0, 20.0)))
);
assert!(!ctx.has_pointer_capture());
assert_eq!(ctx.captured_node(), None);
}
#[test]
fn test_drag_relative_gesture() {
#[derive(Clone, Debug, PartialEq)]
enum RelMsg {
Delta(f32, f32),
}
let mut ctx = Context::new();
let dial = text::<_, RelMsg>("Dial")
.on_drag_relative(|_state, d| Some(RelMsg::Delta(d.delta.0, d.delta.1)));
let mut element = View::<()>::build(&dial, &mut ctx);
let node = View::<()>::get_node(&dial, &element);
let _ = View::<()>::handle_event(
&dial,
&mut element,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: true,
hit_nodes: vec![node],
x: 100.0,
y: 100.0,
},
&mut ctx,
);
assert!(ctx.has_pointer_capture());
assert_eq!(
ctx.captured_pointer.as_ref().map(|c| c.policy),
Some(crate::CursorGrabPolicy::Locked)
);
let (res, msg) = View::<()>::handle_event(
&dial,
&mut element,
&(),
Event::CursorMoved {
x: 100.0,
y: 100.0,
delta_x: 12.5,
delta_y: -4.0,
hit_nodes: vec![],
},
&mut ctx,
);
assert_eq!(res, EventResult::Handled);
assert_eq!(msg, Some(RelMsg::Delta(12.5, -4.0)));
let _ = View::<()>::handle_event(
&dial,
&mut element,
&(),
Event::MouseInput {
button: winit::event::MouseButton::Left,
pressed: false,
hit_nodes: vec![],
x: 100.0,
y: 100.0,
},
&mut ctx,
);
assert!(!ctx.has_pointer_capture());
}
#[test]
fn test_key_events_focused_and_global() {
#[derive(Clone, Debug, PartialEq)]
enum KeyMsg {
EscPressed,
EnterUp,
GlobalA,
}
let mut ctx = Context::new();
let editor_input = text::<_, KeyMsg>("Input")
.on_key_down(|_state, key| {
if key.is_escape() {
Some(KeyMsg::EscPressed)
} else {
None
}
})
.on_key_up(|_state, key| {
if key.is_enter() {
Some(KeyMsg::EnterUp)
} else {
None
}
})
.on_global_key_down(|_state, key| {
if key.key_matches("a") {
Some(KeyMsg::GlobalA)
} else {
None
}
});
let mut element = View::<()>::build(&editor_input, &mut ctx);
let node = View::<()>::get_node(&editor_input, &element);
let esc_event = KeyEvent::new(
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),
winit::event::ElementState::Pressed,
);
let (esc_res, esc_msg) = View::<()>::handle_event(
&editor_input,
&mut element,
&(),
Event::KeyboardInput {
event: esc_event.clone(),
is_synthetic: false,
},
&mut ctx,
);
assert_eq!(esc_res, EventResult::Ignored);
assert_eq!(esc_msg, None);
let mut a_event = KeyEvent::new(
winit::keyboard::Key::Character("a".into()),
winit::event::ElementState::Pressed,
);
a_event.text = Some("a".into());
let (a_res, a_msg) = View::<()>::handle_event(
&editor_input,
&mut element,
&(),
Event::KeyboardInput {
event: a_event,
is_synthetic: false,
},
&mut ctx,
);
assert_eq!(a_res, EventResult::Handled);
assert_eq!(a_msg, Some(KeyMsg::GlobalA));
ctx.request_focus(node);
let (f_res, f_msg) = View::<()>::handle_event(
&editor_input,
&mut element,
&(),
Event::KeyboardInput {
event: esc_event,
is_synthetic: false,
},
&mut ctx,
);
assert_eq!(f_res, EventResult::Handled);
assert_eq!(f_msg, Some(KeyMsg::EscPressed));
let enter_up_event = KeyEvent::new(
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Enter),
winit::event::ElementState::Released,
);
let (enter_res, enter_msg) = View::<()>::handle_event(
&editor_input,
&mut element,
&(),
Event::KeyboardInput {
event: enter_up_event,
is_synthetic: false,
},
&mut ctx,
);
assert_eq!(enter_res, EventResult::Handled);
assert_eq!(enter_msg, Some(KeyMsg::EnterUp));
}
#[test]
fn test_thumb_scroll_handler() {
use crate::style::Rect;
use crate::ui::widgets::button;
let mut ctx = Context::new();
#[derive(Debug, PartialEq, Clone)]
struct ScrolledData {
pct: f32,
dragging: bool,
y: f32,
}
let inner = button("test");
let handled = inner.on_thumb_scroll(|_state, ctx| {
Some(ScrolledData {
pct: ctx.scroll_pct,
dragging: ctx.is_dragging,
y: ctx.thumb.y,
})
});
let mut element = View::<()>::build(&handled, &mut ctx);
let node = View::<()>::get_node(&handled, &element);
let (res, msg) = View::<()>::handle_event(
&handled,
&mut element,
&(),
Event::ThumbScroll {
node,
context: ThumbScrollContext {
thumb: Rect {
x: 95.0,
y: 45.0,
w: 5.0,
h: 20.0,
},
scroll_pct: 0.5,
is_dragging: true,
},
},
&mut ctx,
);
assert_eq!(res, EventResult::Handled);
assert_eq!(
msg,
Some(ScrolledData {
pct: 0.5,
dragging: true,
y: 45.0,
})
);
let other_node = ctx.create_node();
let (other_res, other_msg) = View::<()>::handle_event(
&handled,
&mut element,
&(),
Event::ThumbScroll {
node: other_node,
context: ThumbScrollContext {
thumb: Rect::default(),
scroll_pct: 0.0,
is_dragging: false,
},
},
&mut ctx,
);
assert_eq!(other_res, EventResult::Ignored);
assert_eq!(other_msg, None);
}
}