From a71edafa33a7bf66d38d14c867fe1636ebb31b05 Mon Sep 17 00:00:00 2001 From: Olivier Goffart Date: Fri, 12 Feb 2021 16:31:06 +0100 Subject: [PATCH] Refactor the way the mouse events are processed Have a function first called before the children, and then the main function called after the children if they did not accept the event. This will allow processing the Flickable gesture properly --- sixtyfps_runtime/corelib/input.rs | 110 ++++++++++++---- sixtyfps_runtime/corelib/items.rs | 124 ++++++++++++++++-- sixtyfps_runtime/corelib/items/image.rs | 22 +++- sixtyfps_runtime/corelib/items/text.rs | 20 ++- .../rendering_backends/qt/widgets.rs | 84 +++++++++++- 5 files changed, 321 insertions(+), 39 deletions(-) diff --git a/sixtyfps_runtime/corelib/input.rs b/sixtyfps_runtime/corelib/input.rs index dd16eb2bc..575d85ff4 100644 --- a/sixtyfps_runtime/corelib/input.rs +++ b/sixtyfps_runtime/corelib/input.rs @@ -45,9 +45,10 @@ pub struct MouseEvent { pub what: MouseEventType, } -/// This value is returned by the input handler of a component +/// This value is returned by the `input_event` function of an Item /// to notify the run-time about how the event was handled and /// what the next steps are. +/// See [`ItemVTable::input_event`]. #[repr(C)] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum InputEventResult { @@ -56,21 +57,8 @@ pub enum InputEventResult { EventAccepted, /// The event was ignored. EventIgnored, - /* /// Same as grab, but continue forwarding the event to children. - /// If a child grab the mouse, the grabber will be stored in the item itself. - /// Only item that have grabbed storage can return this. - /// The new_grabber is a reference to a usize to store thenext grabber - TentativeGrab { - new_grabber: &'a Cell, - }, - /// While we have a TentaztiveGrab - Forward { - to: usize, - },*/ /// All further mouse event need to be sent to this item or component GrabMouse, - /// One must send an MouseExit when the mouse leave this item - ObserveHover, } impl Default for InputEventResult { @@ -79,6 +67,32 @@ impl Default for InputEventResult { } } +/// This value is returned by the `input_event_filter_before_children` function, which +/// can specify how to further process the event. +/// See [`ItemVTable::input_event_filter_before_children`]. +#[repr(C)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum InputEventFilterResult { + /// The event is going to be forwarded to children, then the [`ItemVTable::input_event`] + /// function is called + ForwardEvent, + /// The event will be forwarded to the children, but the [`ItemVTable::input_event`] is not + /// going to be called for this item + ForwardAndIgnore, + /// Just like `ForwardEvent`, but even in the case the children grabs the mouse, this function + /// Will still be called for further event. + ForwardAndInterceptGrab, + /// The Event will not be forwarded to children, if a children already had the grab, the + /// grab will be cancelled with a [`MouseEventType::MouseExit`] event + Intercept, +} + +impl Default for InputEventFilterResult { + fn default() -> Self { + Self::ForwardEvent + } +} + /// InternalKeyCode is used to certain keys to unicode characters, since our /// public key event only exposes a string. This enum captures this mapping. #[derive(Debug, PartialEq, Clone)] @@ -238,18 +252,49 @@ pub fn process_mouse_input( component: ComponentRc, mouse_event: MouseEvent, window: &crate::window::ComponentWindow, - mouse_input_state: MouseInputState, + mut mouse_input_state: MouseInputState, ) -> MouseInputState { 'grab: loop { if !mouse_input_state.grabbed || mouse_input_state.item_stack.is_empty() { break 'grab; }; let mut event = mouse_event.clone(); - for it in mouse_input_state.item_stack.iter() { - let item = if let Some(item) = it.upgrade() { item } else { break 'grab }; + let mut intercept = false; + let mut invalid = false; + + mouse_input_state.item_stack.retain(|it| { + if invalid { + return false; + } + let item = if let Some(item) = it.upgrade() { + item + } else { + invalid = true; + return false; + }; + if intercept { + item.borrow().as_ref().input_event( + MouseEvent { pos: event.pos, what: MouseEventType::MouseExit }, + window, + &item, + ); + return false; + } let g = item.borrow().as_ref().geometry(); event.pos -= g.origin.to_vector(); + + if item.borrow().as_ref().input_event_filter_before_children(event, window, &item) + == InputEventFilterResult::Intercept + { + intercept = true; + return false; + } + true + }); + if invalid { + break 'grab; } + let grabber = mouse_input_state.item_stack.last().unwrap().upgrade().unwrap(); return match grabber.borrow().as_ref().input_event(event, window, &grabber) { InputEventResult::GrabMouse => mouse_input_state, @@ -285,16 +330,33 @@ pub fn process_mouse_input( let geom = item.as_ref().geometry(); let geom = geom.translate(*offset); + let mut mouse_grabber_stack = mouse_grabber_stack.clone(); + // FIXME: ideally we should add ourself to the stack only if InputEventFilterResult::ForwardAndInterceptGrab + // is used, but at the moment, we also use the mouse_grabber_stack to compute the offset + mouse_grabber_stack.push(item_rc.downgrade()); + let post_visit_state = if geom.contains(mouse_event.pos) { let mut event2 = mouse_event.clone(); event2.pos -= geom.origin.to_vector(); - Some((event2, mouse_grabber_stack.clone(), item_rc.clone())) + + match item.as_ref().input_event_filter_before_children( + event2.clone(), + window, + &item_rc, + ) { + InputEventFilterResult::ForwardAndIgnore => None, + InputEventFilterResult::ForwardEvent => { + Some((event2, mouse_grabber_stack.clone(), item_rc.clone())) + } + InputEventFilterResult::ForwardAndInterceptGrab => { + Some((event2, mouse_grabber_stack.clone(), item_rc.clone())) + } + InputEventFilterResult::Intercept => return (ItemVisitorResult::Abort, None), + } } else { None }; - let mut mouse_grabber_stack = mouse_grabber_stack.clone(); - mouse_grabber_stack.push(item_rc.downgrade()); ( ItemVisitorResult::Continue((geom.origin.to_vector(), mouse_grabber_stack)), post_visit_state, @@ -307,8 +369,7 @@ pub fn process_mouse_input( if let Some((event2, mouse_grabber_stack, item_rc)) = post_state { match item.as_ref().input_event(event2, window, &item_rc) { InputEventResult::EventAccepted => { - result.item_stack = mouse_grabber_stack.clone(); - result.item_stack.push(item_rc.downgrade()); + result.item_stack = mouse_grabber_stack; result.grabbed = false; return VisitChildrenResult::abort(item_rc.index(), 0); } @@ -319,11 +380,6 @@ pub fn process_mouse_input( result.grabbed = true; return VisitChildrenResult::abort(item_rc.index(), 0); } - InputEventResult::ObserveHover => { - result.item_stack = mouse_grabber_stack.clone(); - result.item_stack.push(item_rc.downgrade()); - result.grabbed = false; - } }; } r diff --git a/sixtyfps_runtime/corelib/items.rs b/sixtyfps_runtime/corelib/items.rs index 3be25bb4b..ea8eb91fd 100644 --- a/sixtyfps_runtime/corelib/items.rs +++ b/sixtyfps_runtime/corelib/items.rs @@ -28,8 +28,8 @@ use crate::component::ComponentVTable; use crate::graphics::PathDataIterator; use crate::graphics::{Brush, Color, PathData, Rect, Size}; use crate::input::{ - FocusEvent, InputEventResult, KeyEvent, KeyEventResult, KeyEventType, MouseEvent, - MouseEventType, + FocusEvent, InputEventFilterResult, InputEventResult, KeyEvent, KeyEventResult, KeyEventType, + MouseEvent, MouseEventType, }; use crate::item_rendering::CachedRenderingData; use crate::layout::LayoutInfo; @@ -80,7 +80,18 @@ pub struct ItemVTable { pub implicit_size: extern "C" fn(core::pin::Pin>, window: &ComponentWindow) -> Size, - /// input event + /// Event handler for mouse and touch event. This function is called before being called on children. + /// Then, depending on the return value, it is called for the children, and their children, then + /// [`Self::input_event`] is called on the children, and finaly [`Self::input_event`] is called + /// on this item again. + pub input_event_filter_before_children: extern "C" fn( + core::pin::Pin>, + MouseEvent, + window: &ComponentWindow, + self_rc: &ItemRc, + ) -> InputEventFilterResult, + + /// Handle input event for mouse and touch event pub input_event: extern "C" fn( core::pin::Pin>, MouseEvent, @@ -189,6 +200,15 @@ impl Item for Rectangle { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _: MouseEvent, @@ -253,6 +273,15 @@ impl Item for BorderRectangle { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _: MouseEvent, @@ -339,20 +368,33 @@ impl Item for TouchArea { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + event: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + if !self.enabled() { + return InputEventFilterResult::ForwardAndIgnore; + } + Self::FIELD_OFFSETS.mouse_x.apply_pin(self).set(event.pos.x); + Self::FIELD_OFFSETS.mouse_y.apply_pin(self).set(event.pos.y); + Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(event.what != MouseEventType::MouseExit); + InputEventFilterResult::ForwardAndInterceptGrab + } + fn input_event( self: Pin<&Self>, event: MouseEvent, _window: &ComponentWindow, _self_rc: &ItemRc, ) -> InputEventResult { + if event.what == MouseEventType::MouseExit { + Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(false) + } if !self.enabled() { return InputEventResult::EventIgnored; } - - Self::FIELD_OFFSETS.mouse_x.apply_pin(self).set(event.pos.x); - Self::FIELD_OFFSETS.mouse_y.apply_pin(self).set(event.pos.y); - Self::FIELD_OFFSETS.has_hover.apply_pin(self).set(event.what != MouseEventType::MouseExit); - let result = if matches!(event.what, MouseEventType::MouseReleased) { Self::FIELD_OFFSETS.clicked.apply_pin(self).call(&()); InputEventResult::EventAccepted @@ -371,7 +413,7 @@ impl Item for TouchArea { return if self.pressed() { InputEventResult::GrabMouse } else { - InputEventResult::ObserveHover + InputEventResult::EventAccepted } } }); @@ -446,6 +488,15 @@ impl Item for FocusScope { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardEvent + } + fn input_event( self: Pin<&Self>, event: MouseEvent, @@ -532,6 +583,15 @@ impl Item for Clip { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _: MouseEvent, @@ -592,6 +652,15 @@ impl Item for Rotate { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _: MouseEvent, @@ -673,6 +742,15 @@ impl Item for Path { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _: MouseEvent, @@ -752,6 +830,16 @@ impl Item for Flickable { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + // TODO! + InputEventFilterResult::ForwardEvent + } + fn input_event( self: Pin<&Self>, event: MouseEvent, @@ -863,6 +951,15 @@ impl Item for Window { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _event: MouseEvent, @@ -926,6 +1023,15 @@ impl Item for BoxShadow { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _event: MouseEvent, diff --git a/sixtyfps_runtime/corelib/items/image.rs b/sixtyfps_runtime/corelib/items/image.rs index 194306a4e..7c7b47e95 100644 --- a/sixtyfps_runtime/corelib/items/image.rs +++ b/sixtyfps_runtime/corelib/items/image.rs @@ -21,7 +21,9 @@ When adding an item or a property, it needs to be kept in sync with different pl */ use super::{Item, ItemConsts, ItemRc}; use crate::graphics::{Rect, Resource, Size}; -use crate::input::{FocusEvent, InputEventResult, KeyEvent, KeyEventResult, MouseEvent}; +use crate::input::{ + FocusEvent, InputEventFilterResult, InputEventResult, KeyEvent, KeyEventResult, MouseEvent, +}; use crate::item_rendering::CachedRenderingData; use crate::item_rendering::ItemRenderer; use crate::layout::LayoutInfo; @@ -78,6 +80,15 @@ impl Item for Image { window.0.image_size(Self::FIELD_OFFSETS.source.apply_pin(self)) } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _: MouseEvent, @@ -140,6 +151,15 @@ impl Item for ClippedImage { window.0.image_size(Self::FIELD_OFFSETS.source.apply_pin(self)) } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _: MouseEvent, diff --git a/sixtyfps_runtime/corelib/items/text.rs b/sixtyfps_runtime/corelib/items/text.rs index 60a92c56a..6e644e08d 100644 --- a/sixtyfps_runtime/corelib/items/text.rs +++ b/sixtyfps_runtime/corelib/items/text.rs @@ -22,11 +22,11 @@ When adding an item or a property, it needs to be kept in sync with different pl use super::{Item, ItemConsts, ItemRc, VoidArg}; use crate::graphics::{Brush, Color, Rect, Size}; -use crate::input::InternalKeyCode; use crate::input::{ FocusEvent, InputEventResult, KeyEvent, KeyEventResult, KeyEventType, KeyboardModifiers, MouseEvent, MouseEventType, }; +use crate::input::{InputEventFilterResult, InternalKeyCode}; use crate::item_rendering::{CachedRenderingData, ItemRenderer}; use crate::layout::LayoutInfo; #[cfg(feature = "rtti")] @@ -150,6 +150,15 @@ impl Item for Text { .unwrap_or_default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _: MouseEvent, @@ -260,6 +269,15 @@ impl Item for TextInput { .unwrap_or_default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardEvent + } + fn input_event( self: Pin<&Self>, event: MouseEvent, diff --git a/sixtyfps_runtime/rendering_backends/qt/widgets.rs b/sixtyfps_runtime/rendering_backends/qt/widgets.rs index 4bfbe051f..27fbff5b5 100644 --- a/sixtyfps_runtime/rendering_backends/qt/widgets.rs +++ b/sixtyfps_runtime/rendering_backends/qt/widgets.rs @@ -29,7 +29,8 @@ use core::pin::Pin; use cpp::cpp; use sixtyfps_corelib::graphics::{Rect, Size}; use sixtyfps_corelib::input::{ - FocusEvent, InputEventResult, KeyEvent, KeyEventResult, MouseEvent, MouseEventType, + FocusEvent, InputEventFilterResult, InputEventResult, KeyEvent, KeyEventResult, MouseEvent, + MouseEventType, }; use sixtyfps_corelib::item_rendering::{CachedRenderingData, ItemRenderer}; use sixtyfps_corelib::items::{Item, ItemConsts, ItemRc, ItemVTable, VoidArg}; @@ -186,6 +187,15 @@ impl Item for NativeButton { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardEvent + } + fn input_event( self: Pin<&Self>, event: MouseEvent, @@ -307,6 +317,15 @@ impl Item for NativeCheckBox { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardEvent + } + fn input_event( self: Pin<&Self>, event: MouseEvent, @@ -456,6 +475,15 @@ impl Item for NativeSpinBox { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardEvent + } + fn input_event( self: Pin<&Self>, event: MouseEvent, @@ -661,6 +689,15 @@ impl Item for NativeSlider { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardEvent + } + fn input_event( self: Pin<&Self>, event: MouseEvent, @@ -906,6 +943,15 @@ impl Item for NativeGroupBox { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardEvent + } + fn input_event( self: Pin<&Self>, _: MouseEvent, @@ -1050,6 +1096,15 @@ impl Item for NativeLineEdit { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _: MouseEvent, @@ -1204,6 +1259,15 @@ impl Item for NativeScrollView { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardEvent + } + fn input_event( self: Pin<&Self>, event: MouseEvent, @@ -1523,6 +1587,15 @@ impl Item for NativeStandardListViewItem { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardAndIgnore + } + fn input_event( self: Pin<&Self>, _event: MouseEvent, @@ -1627,6 +1700,15 @@ impl Item for NativeComboBox { Default::default() } + fn input_event_filter_before_children( + self: Pin<&Self>, + _: MouseEvent, + _window: &ComponentWindow, + _self_rc: &ItemRc, + ) -> InputEventFilterResult { + InputEventFilterResult::ForwardEvent + } + fn input_event( self: Pin<&Self>, event: MouseEvent,