diff --git a/CHANGELOG.md b/CHANGELOG.md index ddd29de16..ad1b1c130 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ## [@Unreleased] - @ReleaseDate +### Features + +- **core**: Added `grab_pointer` to grabs all the pointer input. (#pr @wjian23) + ### Fixed - **core**: fix mismatch of providers (#pr @wjian23) diff --git a/core/src/builtin_widgets.rs b/core/src/builtin_widgets.rs index 5f8c3c9d4..cd40a16f5 100644 --- a/core/src/builtin_widgets.rs +++ b/core/src/builtin_widgets.rs @@ -959,13 +959,13 @@ impl<'a> FatObj> { text_style, scrollable, layout_box, - mix_builtin, - request_focus, class, cursor, constrained_box, tooltips, margin, + mix_builtin, + request_focus, transform, opacity, visibility, diff --git a/core/src/events.rs b/core/src/events.rs index 7e015c3ff..f9e261d59 100644 --- a/core/src/events.rs +++ b/core/src/events.rs @@ -8,6 +8,7 @@ use crate::{ }; pub(crate) mod dispatcher; +pub use dispatcher::{GrabPointerHandle, GrabPointerHandleGuard}; mod pointers; pub use pointers::*; use ribir_geom::Point; @@ -166,6 +167,8 @@ pub enum Event { /// The main difference between this event and focusout is that focusout emit /// in bubbles phase but this event emit in capture phase. FocusOutCapture(FocusEvent), + /// The dragstart event is fired when a drag operation is started. + Drag(DragEvent, DragEventType), } impl std::ops::Deref for Event { @@ -197,6 +200,7 @@ impl std::ops::Deref for Event { Event::Wheel(e) | Event::WheelCapture(e) => e, Event::Chars(e) | Event::CharsCapture(e) => e, Event::KeyDown(e) | Event::KeyDownCapture(e) | Event::KeyUp(e) | Event::KeyUpCapture(e) => e, + Event::Drag(e, _) => e, } } } @@ -228,6 +232,7 @@ impl std::ops::DerefMut for Event { Event::Wheel(e) | Event::WheelCapture(e) => e, Event::Chars(e) | Event::CharsCapture(e) => e, Event::KeyDown(e) | Event::KeyDownCapture(e) | Event::KeyUp(e) | Event::KeyUpCapture(e) => e, + Event::Drag(e, _) => e, } } } @@ -246,7 +251,8 @@ impl Event { | Event::PointerEnter(_) | Event::PointerLeave(_) | Event::Tap(_) - | Event::TapCapture(_) => MixFlags::Pointer, + | Event::TapCapture(_) + | Event::Drag(_, _) => MixFlags::Pointer, Event::Wheel(_) | Event::WheelCapture(_) => MixFlags::Wheel, Event::ImePreEdit(_) | Event::ImePreEditCapture(_) diff --git a/core/src/events/dispatcher.rs b/core/src/events/dispatcher.rs index 16f758fb4..22853f44e 100644 --- a/core/src/events/dispatcher.rs +++ b/core/src/events/dispatcher.rs @@ -1,3 +1,5 @@ +use std::{cell::RefCell, rc::Rc}; + use winit::event::{DeviceId, ElementState, MouseButton, MouseScrollDelta, WindowEvent}; use crate::{ @@ -5,16 +7,49 @@ use crate::{ window::{DelayEvent, WindowId}, }; +pub struct GrabPointerHandle(Rc>>); +impl GrabPointerHandle { + pub fn release(self) { self.0.borrow_mut().take(); } + + pub fn guard(self) -> GrabPointerHandleGuard { GrabPointerHandleGuard(Some(self)) } +} + +pub struct GrabPointerHandleGuard(Option); + +impl Drop for GrabPointerHandleGuard { + fn drop(&mut self) { + if let Some(h) = self.0.take() { + h.release(); + } + } +} + pub(crate) struct Dispatcher { wnd_id: WindowId, pub(crate) info: DispatchInfo, pub(crate) entered_widgets: Vec, - pub(crate) pointer_down_uid: Option, + grab_mouse_wid: Rc>>, + pointer_down_wid: Option, } impl Dispatcher { pub fn new(wnd_id: WindowId) -> Self { - Self { wnd_id, info: <_>::default(), entered_widgets: vec![], pointer_down_uid: None } + Self { + wnd_id, + info: <_>::default(), + entered_widgets: vec![], + grab_mouse_wid: Rc::new(RefCell::new(None)), + pointer_down_wid: None, + } + } + + pub(crate) fn grab_pointer(&self, wid: WidgetId) -> Option { + if self.grab_mouse_wid.borrow().is_none() { + *self.grab_mouse_wid.borrow_mut() = Some(wid); + Some(GrabPointerHandle(self.grab_mouse_wid.clone())) + } else { + None + } } fn window(&self) -> Sc { @@ -77,19 +112,63 @@ impl Dispatcher { } } + fn cursor_press_down(&mut self, hit: Option) { + let grab_pointer = *self.grab_mouse_wid.borrow(); + if let Some(grab_pointer) = grab_pointer { + self + .window() + .add_delay_event(DelayEvent::GrabPointerDown(grab_pointer)); + } else { + self.pointer_down_wid = None; + if let Some(hit) = hit { + self.pointer_down_wid = Some(hit); + self + .window() + .add_delay_event(DelayEvent::PointerDown(hit)); + } + } + } + + fn cursor_press_up(&mut self, hit: Option) { + let wnd = self.window(); + let grab_pointer = *self.grab_mouse_wid.borrow(); + if let Some(grab_pointer) = grab_pointer { + wnd.add_delay_event(DelayEvent::GrabPointerUp(grab_pointer)); + } else { + if let Some(hit) = hit { + wnd.add_delay_event(DelayEvent::PointerUp(hit)); + if let Some(wid) = self.pointer_down_wid { + if let Some(p) = wid.lowest_common_ancestor(hit, wnd.tree()) { + wnd.add_delay_event(DelayEvent::Tap(p)); + } + } + } + self.pointer_down_wid = None; + } + } + pub fn cursor_move_to(&mut self, position: Point) { self.info.cursor_pos = position; - self.pointer_enter_leave_dispatch(); - if let Some(hit) = self.hit_widget() { + let grab_pointer = *self.grab_mouse_wid.borrow(); + if let Some(grab_pointer) = grab_pointer { self .window() - .add_delay_event(DelayEvent::PointerMove(hit)); + .add_delay_event(DelayEvent::GrabPointerMove(grab_pointer)); + } else { + self.pointer_enter_leave_dispatch(); + if let Some(hit) = self.hit_widget() { + self + .window() + .add_delay_event(DelayEvent::PointerMove(hit)); + } } } pub fn on_cursor_left(&mut self) { - self.info.cursor_pos = Point::new(-1., -1.); - self.pointer_enter_leave_dispatch(); + if self.grab_mouse_wid.borrow().is_none() { + self.info.cursor_pos = Point::new(-1., -1.); + self.pointer_enter_leave_dispatch(); + } } pub fn dispatch_mouse_input( @@ -110,20 +189,9 @@ impl Dispatcher { // only the last button release emit event. if self.info.mouse_button.1.is_empty() { self.info.mouse_button.0 = None; - let wnd = self.window(); - let mut dispatch = |tree: &WidgetTree| { - let hit = self.hit_widget()?; - wnd.add_delay_event(DelayEvent::PointerUp(hit)); - - let tap_on = self - .pointer_down_uid - .take()? - .lowest_common_ancestor(hit, tree)?; - wnd.add_delay_event(DelayEvent::Tap(tap_on)); - Some(()) - }; - - dispatch(wnd.tree()); + let hit = self.hit_widget(); + + self.cursor_press_up(hit); } } }; @@ -148,11 +216,10 @@ impl Dispatcher { fn bubble_pointer_down(&mut self) { let hit = self.hit_widget(); - self.pointer_down_uid = hit; let wnd = self.window(); let tree = wnd.tree(); - let nearest_focus = self.pointer_down_uid.and_then(|wid| { + let nearest_focus = hit.and_then(|wid| { wid.ancestors(tree).find(|id| { id.query_all_iter::(tree) .any(|m| m.contain_flag(MixFlags::Focus)) @@ -163,9 +230,8 @@ impl Dispatcher { } else { wnd.focus_mgr.borrow_mut().blur(tree); } - if let Some(hit) = hit { - wnd.add_delay_event(DelayEvent::PointerDown(hit)); - } + + self.cursor_press_down(hit); } fn pointer_enter_leave_dispatch(&mut self) { @@ -438,10 +504,10 @@ mod tests { let w = fn_widget! { @MockBox { size: INFINITY_SIZE, + padding: EdgeInsets::all(4.), on_pointer_enter: move |_| { $e_writer.write().push(2); }, on_pointer_leave: move |_| { $l_writer.write().push(2); }, @MockBox { - margin: EdgeInsets::all(4.), size: INFINITY_SIZE, on_pointer_enter: move |_| { $e_writer.write().push(1); }, on_pointer_leave: move |_| { $l_writer.write().push(1); } diff --git a/core/src/events/pointers.rs b/core/src/events/pointers.rs index 80ce85e1d..d4ba64dfd 100644 --- a/core/src/events/pointers.rs +++ b/core/src/events/pointers.rs @@ -49,6 +49,21 @@ pub struct PointerEvent { pub common: CommonEvent, } +/// The drag event type +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DragEventType { + /// Indicates the start of drag + Start, + + /// Indicates the drag is moving + Move, + + /// Indicates the end of drag + End, +} + +pub type DragEvent = PointerEvent; + bitflags! { #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] pub struct MouseButtons: u8 { diff --git a/core/src/window.rs b/core/src/window.rs index a62cea807..0f93528b2 100644 --- a/core/src/window.rs +++ b/core/src/window.rs @@ -5,6 +5,7 @@ use std::{ ptr::NonNull, }; +use dispatcher::GrabPointerHandle; use futures::{Future, task::LocalSpawnExt}; use ribir_algo::Sc; use widget_id::TrackId; @@ -194,6 +195,15 @@ impl Window { .set(self.running_animates.get() - 1); } + /// Grabs the pointer input. + /// + /// The widget corresponding to the wid will receives all pointer events + /// (on_pointer_down, on_pointer_move, and on_pointer_up) until the handle's + /// release() is called; other widgets get no pointer events at all. + pub fn grab_pointer(&self, wid: WidgetId) -> Option { + self.dispatcher.borrow().grab_pointer(wid) + } + /// Draw an image what current render tree represent. #[track_caller] pub fn draw_frame(&self) -> bool { @@ -550,6 +560,19 @@ impl Window { let Event::ImePreEditCapture(e) = e else { unreachable!() }; self.bottom_up_emit(&mut Event::ImePreEdit(e), wid, None); } + + DelayEvent::GrabPointerDown(wid) => { + let mut e = Event::PointerDown(PointerEvent::from_mouse(wid, self)); + self.emit(wid, &mut e); + } + DelayEvent::GrabPointerMove(wid) => { + let mut e = Event::PointerMove(PointerEvent::from_mouse(wid, self)); + self.emit(wid, &mut e); + } + DelayEvent::GrabPointerUp(wid) => { + let mut e = Event::PointerUp(PointerEvent::from_mouse(wid, self)); + self.emit(wid, &mut e); + } } } } @@ -774,6 +797,9 @@ pub(crate) enum DelayEvent { PointerLeave { bottom: WidgetId, up: Option }, Tap(WidgetId), ImePreEdit { wid: WidgetId, pre_edit: ImePreEdit }, + GrabPointerDown(WidgetId), + GrabPointerMove(WidgetId), + GrabPointerUp(WidgetId), } impl From for WindowId { diff --git a/macros/src/declare_derive.rs b/macros/src/declare_derive.rs index b86f92161..7dc1d4d88 100644 --- a/macros/src/declare_derive.rs +++ b/macros/src/declare_derive.rs @@ -593,6 +593,8 @@ pub(crate) fn declare_derive(input: &mut syn::DeriveInput) -> syn::Result