diff --git a/druid-shell/src/lib.rs b/druid-shell/src/lib.rs index 82496cf1aa..2c03750e58 100644 --- a/druid-shell/src/lib.rs +++ b/druid-shell/src/lib.rs @@ -47,7 +47,7 @@ pub use hotkey::{HotKey, KeyCompare, RawMods, SysMods}; pub use keyboard::{KeyEvent, KeyModifiers}; pub use keycodes::KeyCode; pub use menu::Menu; -pub use mouse::{Cursor, MouseButton, MouseEvent}; +pub use mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; pub use window::{ IdleHandle, IdleToken, Text, TimerToken, WinHandler, WindowBuilder, WindowHandle, }; diff --git a/druid-shell/src/mouse.rs b/druid-shell/src/mouse.rs index feac321198..6ba818869e 100644 --- a/druid-shell/src/mouse.rs +++ b/druid-shell/src/mouse.rs @@ -18,50 +18,218 @@ use crate::kurbo::Point; use crate::keyboard::KeyModifiers; -/// The state of the mouse for a click, mouse-up, or move event. +/// Information about the mouse event. #[derive(Debug, Clone, PartialEq)] pub struct MouseEvent { /// The location of the mouse in the current window. /// - /// This is in px units, that is, adjusted for hi-dpi. + /// This is in px units not device pixels, that is, adjusted for hi-dpi. pub pos: Point, - /// Keyboard modifiers at the time of the mouse event. + /// Mouse buttons being held down during a move or after a click event. + /// Thus it will contain the `button` that triggered a mouse-down event, + /// and it will not contain the `button` that triggered a mouse-up event. + pub buttons: MouseButtons, + /// Keyboard modifiers at the time of the event. pub mods: KeyModifiers, /// The number of mouse clicks associated with this event. This will always - /// be `0` for a mouse-up event. - pub count: u32, - /// The currently pressed button in the case of a move or click event, - /// or the released button in the case of a mouse-up event. + /// be `0` for a mouse-up and mouse-move events. + pub count: u8, + /// The button that was pressed down in the case of mouse-down, + /// or the button that was released in the case of mouse-up. + /// This will always be `MouseButton::None` in the case of mouse-move. pub button: MouseButton, } /// An indicator of which mouse button was pressed. #[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[repr(u8)] pub enum MouseButton { /// Left mouse button. Left, - /// Middle mouse button. - Middle, /// Right mouse button. Right, + /// Middle mouse button. + Middle, /// First X button. X1, /// Second X button. X2, + /// No mouse button. + None, } impl MouseButton { - /// Returns `true` if this is the left mouse button. - #[inline(always)] + /// Returns `true` if this is [`MouseButton::Left`]. + /// + /// [`MouseButton::Left`]: #variant.Left + #[inline] pub fn is_left(self) -> bool { self == MouseButton::Left } - /// Returns `true` if this is the right mouse button. - #[inline(always)] + /// Returns `true` if this is [`MouseButton::Right`]. + /// + /// [`MouseButton::Right`]: #variant.Right + #[inline] pub fn is_right(self) -> bool { self == MouseButton::Right } + + /// Returns `true` if this is [`MouseButton::Middle`]. + /// + /// [`MouseButton::Middle`]: #variant.Middle + #[inline] + pub fn is_middle(self) -> bool { + self == MouseButton::Middle + } + + /// Returns `true` if this is [`MouseButton::X1`]. + /// + /// [`MouseButton::X1`]: #variant.X1 + #[inline] + pub fn is_x1(self) -> bool { + self == MouseButton::X1 + } + + /// Returns `true` if this is [`MouseButton::X2`]. + /// + /// [`MouseButton::X2`]: #variant.X2 + #[inline] + pub fn is_x2(self) -> bool { + self == MouseButton::X2 + } +} + +/// A set of [`MouseButton`]s. +/// +/// [`MouseButton`]: enum.MouseButton.html +#[derive(PartialEq, Eq, Clone, Copy, Default)] +pub struct MouseButtons(u8); + +impl MouseButtons { + /// Create a new empty set. + #[inline] + pub fn new() -> MouseButtons { + MouseButtons(0) + } + + /// Add the `button` to the set. + #[inline] + pub fn add(&mut self, button: MouseButton) { + self.0 |= 1 << button as u8; + } + + /// Remove the `button` from the set. + #[inline] + pub fn remove(&mut self, button: MouseButton) { + self.0 &= !(1 << button as u8); + } + + /// Builder-style method for adding the `button` to the set. + #[inline] + pub fn with(mut self, button: MouseButton) -> MouseButtons { + self.0 |= 1 << button as u8; + self + } + + /// Builder-style method for removing the `button` from the set. + #[inline] + pub fn without(mut self, button: MouseButton) -> MouseButtons { + self.0 &= !(1 << button as u8); + self + } + + /// Returns `true` if the `button` is in the set. + #[inline] + pub fn has(self, button: MouseButton) -> bool { + (self.0 & (1 << button as u8)) != 0 + } + + /// Returns `true` if any button is in the set. + #[inline] + pub fn has_any(self) -> bool { + self.0 != 0 + } + + /// Returns `true` if the set is empty. + #[inline] + pub fn has_none(self) -> bool { + self.0 == 0 + } + + /// Returns `true` if all the `buttons` are in the set. + #[inline] + pub fn has_all(self, buttons: MouseButtons) -> bool { + self.0 & buttons.0 == buttons.0 + } + + /// Returns `true` if [`MouseButton::Left`] is in the set. + /// + /// [`MouseButton::Left`]: enum.MouseButton.html#variant.Left + #[inline] + pub fn has_left(self) -> bool { + self.has(MouseButton::Left) + } + + /// Returns `true` if [`MouseButton::Right`] is in the set. + /// + /// [`MouseButton::Right`]: enum.MouseButton.html#variant.Right + #[inline] + pub fn has_right(self) -> bool { + self.has(MouseButton::Right) + } + + /// Returns `true` if [`MouseButton::Middle`] is in the set. + /// + /// [`MouseButton::Middle`]: enum.MouseButton.html#variant.Middle + #[inline] + pub fn has_middle(self) -> bool { + self.has(MouseButton::Middle) + } + + /// Returns `true` if [`MouseButton::X1`] is in the set. + /// + /// [`MouseButton::X1`]: enum.MouseButton.html#variant.X1 + #[inline] + pub fn has_x1(self) -> bool { + self.has(MouseButton::X1) + } + + /// Returns `true` if [`MouseButton::X2`] is in the set. + /// + /// [`MouseButton::X2`]: enum.MouseButton.html#variant.X2 + #[inline] + pub fn has_x2(self) -> bool { + self.has(MouseButton::X2) + } + + /// Adds all the [`MouseButton`]s in `other` to the set. + /// + /// [`MouseButton`]: enum.MouseButton.html + pub fn extend(&mut self, other: MouseButtons) { + self.0 |= other.0; + } + + /// Returns a union of the values in `self` and `other`. + /// + /// [`MouseButton`]: enum.MouseButton.html + #[inline] + pub fn union(mut self, other: MouseButtons) -> MouseButtons { + self.0 |= other.0; + self + } + + /// Clear the set. + #[inline] + pub fn clear(&mut self) { + self.0 = 0; + } +} + +impl std::fmt::Debug for MouseButtons { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "MouseButtons({:06b})", self.0) + } } //NOTE: this currently only contains cursors that are included by default on diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index cdc0de9e92..155dee1040 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -41,7 +41,7 @@ use super::util::assert_main_thread; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; use crate::keyboard; -use crate::mouse::{Cursor, MouseButton, MouseEvent}; +use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; use crate::Error; @@ -285,17 +285,21 @@ impl WindowBuilder { Inhibit(false) })); - win_state.drawing_area.connect_button_press_event(clone!(handle => move |_widget, button| { + win_state.drawing_area.connect_button_press_event(clone!(handle => move |_widget, event| { if let Some(state) = handle.state.upgrade() { if let Ok(mut handler) = state.handler.try_borrow_mut() { - handler.mouse_down( - &MouseEvent { - pos: Point::from(button.get_position()), - count: get_mouse_click_count(button.get_event_type()), - mods: get_modifiers(button.get_state()), - button: get_mouse_button(button.get_button()), - }, - ); + if let Some(button) = get_mouse_button(event.get_button()) { + let button_state = event.get_state(); + handler.mouse_down( + &MouseEvent { + pos: Point::from(event.get_position()), + buttons: get_mouse_buttons_from_modifiers(button_state).with(button), + mods: get_modifiers(button_state), + count: get_mouse_click_count(event.get_event_type()), + button, + }, + ); + } } else { log::info!("GTK event was dropped because the handler was already borrowed"); } @@ -304,17 +308,21 @@ impl WindowBuilder { Inhibit(true) })); - win_state.drawing_area.connect_button_release_event(clone!(handle => move |_widget, button| { + win_state.drawing_area.connect_button_release_event(clone!(handle => move |_widget, event| { if let Some(state) = handle.state.upgrade() { if let Ok(mut handler) = state.handler.try_borrow_mut() { - handler.mouse_up( - &MouseEvent { - pos: Point::from(button.get_position()), - mods: get_modifiers(button.get_state()), - count: 0, - button: get_mouse_button(button.get_button()), - }, - ); + if let Some(button) = get_mouse_button(event.get_button()) { + let button_state = event.get_state(); + handler.mouse_up( + &MouseEvent { + pos: Point::from(event.get_position()), + buttons: get_mouse_buttons_from_modifiers(button_state).without(button), + mods: get_modifiers(button_state), + count: 0, + button, + }, + ); + } } else { log::info!("GTK event was dropped because the handler was already borrowed"); } @@ -325,17 +333,17 @@ impl WindowBuilder { win_state.drawing_area.connect_motion_notify_event(clone!(handle => move |_widget, motion| { if let Some(state) = handle.state.upgrade() { - - let pos = Point::from(motion.get_position()); - let mouse_event = MouseEvent { - pos, - mods: get_modifiers(motion.get_state()), + let motion_state = motion.get_state(); + let move_event = MouseEvent { + pos: Point::from(motion.get_position()), + buttons: get_mouse_buttons_from_modifiers(motion_state), + mods: get_modifiers(motion_state), count: 0, - button: get_mouse_button_from_modifiers(motion.get_state()), + button: MouseButton::None, }; if let Ok(mut handler) = state.handler.try_borrow_mut() { - handler.mouse_move(&mouse_event); + handler.mouse_move(&move_event); } else { log::info!("GTK event was dropped because the handler was already borrowed"); } @@ -346,17 +354,17 @@ impl WindowBuilder { win_state.drawing_area.connect_leave_notify_event(clone!(handle => move |_widget, crossing| { if let Some(state) = handle.state.upgrade() { - - let pos = Point::from(crossing.get_position()); - let mouse_event = MouseEvent { - pos, - mods: get_modifiers(crossing.get_state()), + let crossing_state = crossing.get_state(); + let move_event = MouseEvent { + pos: Point::from(crossing.get_position()), + buttons: get_mouse_buttons_from_modifiers(crossing_state), + mods: get_modifiers(crossing_state), count: 0, - button: get_mouse_button_from_modifiers(crossing.get_state()), + button: MouseButton::None, }; if let Ok(mut handler) = state.handler.try_borrow_mut() { - handler.mouse_move(&mouse_event); + handler.mouse_move(&move_event); } else { log::info!("GTK event was dropped because the handler was already borrowed"); } @@ -764,37 +772,51 @@ fn make_gdk_cursor(cursor: &Cursor, gdk_window: &gdk::Window) -> Option MouseButton { +fn get_mouse_button(button: u32) -> Option { match button { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - 4 => MouseButton::X1, - 5 => MouseButton::X2, - _ => MouseButton::Left, + 1 => Some(MouseButton::Left), + 2 => Some(MouseButton::Middle), + 3 => Some(MouseButton::Right), + // GDK X backend interprets button press events for button 4-7 as scroll events + 8 => Some(MouseButton::X1), + 9 => Some(MouseButton::X2), + _ => None, } } -fn get_mouse_button_from_modifiers(modifiers: gdk::ModifierType) -> MouseButton { - match modifiers { - modifiers if modifiers.contains(ModifierType::BUTTON1_MASK) => MouseButton::Left, - modifiers if modifiers.contains(ModifierType::BUTTON2_MASK) => MouseButton::Middle, - modifiers if modifiers.contains(ModifierType::BUTTON3_MASK) => MouseButton::Right, - modifiers if modifiers.contains(ModifierType::BUTTON4_MASK) => MouseButton::X1, - modifiers if modifiers.contains(ModifierType::BUTTON5_MASK) => MouseButton::X2, - _ => { - //FIXME: what about when no modifiers match? - MouseButton::Left - } +fn get_mouse_buttons_from_modifiers(modifiers: gdk::ModifierType) -> MouseButtons { + let mut buttons = MouseButtons::new(); + if modifiers.contains(ModifierType::BUTTON1_MASK) { + buttons.add(MouseButton::Left); } + if modifiers.contains(ModifierType::BUTTON2_MASK) { + buttons.add(MouseButton::Middle); + } + if modifiers.contains(ModifierType::BUTTON3_MASK) { + buttons.add(MouseButton::Right); + } + // TODO: Determine X1/X2 state (do caching ourselves if needed) + // Checking for BUTTON4_MASK/BUTTON5_MASK does not work with GDK X, + // because those are wheel events instead. + if modifiers.contains(ModifierType::BUTTON4_MASK) { + buttons.add(MouseButton::X1); + } + if modifiers.contains(ModifierType::BUTTON5_MASK) { + buttons.add(MouseButton::X2); + } + buttons } -fn get_mouse_click_count(event_type: gdk::EventType) -> u32 { +fn get_mouse_click_count(event_type: gdk::EventType) -> u8 { match event_type { gdk::EventType::ButtonPress => 1, gdk::EventType::DoubleButtonPress => 2, gdk::EventType::TripleButtonPress => 3, - _ => 0, + gdk::EventType::ButtonRelease => 0, + _ => { + log::warn!("Unexpected mouse click event type: {:?}", event_type); + 0 + } } } diff --git a/druid-shell/src/platform/mac/window.rs b/druid-shell/src/platform/mac/window.rs index 3110b229d5..0a86efcc7a 100644 --- a/druid-shell/src/platform/mac/window.rs +++ b/druid-shell/src/platform/mac/window.rs @@ -28,7 +28,9 @@ use cocoa::appkit::{ NSWindowStyleMask, }; use cocoa::base::{id, nil, BOOL, NO, YES}; -use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString}; +use cocoa::foundation::{ + NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger, +}; use lazy_static::lazy_static; use objc::declare::ClassDecl; use objc::rc::WeakPtr; @@ -49,7 +51,7 @@ use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; use crate::keyboard::{KeyEvent, KeyModifiers}; use crate::keycodes::KeyCode; -use crate::mouse::{Cursor, MouseButton, MouseEvent}; +use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; use crate::Error; @@ -255,6 +257,10 @@ lazy_static! { sel!(rightMouseDown:), mouse_down_right as extern "C" fn(&mut Object, Sel, id), ); + decl.add_method( + sel!(otherMouseDown:), + mouse_down_other as extern "C" fn(&mut Object, Sel, id), + ); decl.add_method( sel!(mouseUp:), mouse_up_left as extern "C" fn(&mut Object, Sel, id), @@ -263,6 +269,10 @@ lazy_static! { sel!(rightMouseUp:), mouse_up_right as extern "C" fn(&mut Object, Sel, id), ); + decl.add_method( + sel!(otherMouseUp:), + mouse_up_other as extern "C" fn(&mut Object, Sel, id), + ); decl.add_method( sel!(mouseMoved:), mouse_move as extern "C" fn(&mut Object, Sel, id), @@ -271,6 +281,10 @@ lazy_static! { sel!(mouseDragged:), mouse_move as extern "C" fn(&mut Object, Sel, id), ); + decl.add_method( + sel!(otherMouseDragged:), + mouse_move as extern "C" fn(&mut Object, Sel, id), + ); decl.add_method( sel!(scrollWheel:), scroll_wheel as extern "C" fn(&mut Object, Sel, id), @@ -346,22 +360,16 @@ extern "C" fn set_frame_size(this: &mut Object, _: Sel, size: NSSize) { } } -// NOTE: If we know the button (because of the origin call) we pass it through, -// otherwise we get it from the event itself. -fn mouse_event(nsevent: id, view: id, button: Option) -> MouseEvent { +fn click_event(nsevent: id, view: id, count: u8, button: MouseButton) -> MouseEvent { unsafe { - let button = button.unwrap_or_else(|| { - let button = NSEvent::pressedMouseButtons(nsevent); - get_mouse_button(button as usize) - }); let point = nsevent.locationInWindow(); let view_point = view.convertPoint_fromView_(point, nil); let pos = Point::new(view_point.x as f64, view_point.y as f64); - let modifiers = nsevent.modifierFlags(); - let modifiers = make_modifiers(modifiers); - let count = nsevent.clickCount() as u32; + let buttons = get_mouse_buttons(NSEvent::pressedMouseButtons(nsevent)); + let modifiers = make_modifiers(nsevent.modifierFlags()); MouseEvent { pos, + buttons, mods: modifiers, count, button, @@ -369,52 +377,101 @@ fn mouse_event(nsevent: id, view: id, button: Option) -> MouseEvent } } -fn get_mouse_button(mask: usize) -> MouseButton { - //TODO: this doesn't correctly handle multiple buttons being pressed. - match mask { - mask if mask & 1 > 0 => MouseButton::Left, - mask if mask & 1 << 1 > 0 => MouseButton::Right, - mask if mask & 1 << 2 > 0 => MouseButton::Middle, - mask if mask & 1 << 3 > 0 => MouseButton::X1, - mask if mask & 1 << 4 > 0 => MouseButton::X2, - _ => { - //FIXME: this gets called when the mouse moves, where there - //may be no buttons down. This is mostly a problem with our API? - MouseButton::Left +fn move_event(nsevent: id, view: id) -> MouseEvent { + unsafe { + let point = nsevent.locationInWindow(); + let view_point = view.convertPoint_fromView_(point, nil); + let pos = Point::new(view_point.x as f64, view_point.y as f64); + let buttons = get_mouse_buttons(NSEvent::pressedMouseButtons(nsevent)); + let modifiers = make_modifiers(nsevent.modifierFlags()); + MouseEvent { + pos, + buttons, + mods: modifiers, + count: 0, + button: MouseButton::None, } } } +fn get_mouse_button(button: NSInteger) -> Option { + match button { + 0 => Some(MouseButton::Left), + 1 => Some(MouseButton::Right), + 2 => Some(MouseButton::Middle), + 3 => Some(MouseButton::X1), + 4 => Some(MouseButton::X2), + _ => None, + } +} + +fn get_mouse_buttons(mask: NSUInteger) -> MouseButtons { + let mut buttons = MouseButtons::new(); + if mask & 1 != 0 { + buttons.add(MouseButton::Left); + } + if mask & 1 << 1 != 0 { + buttons.add(MouseButton::Right); + } + if mask & 1 << 2 != 0 { + buttons.add(MouseButton::Middle); + } + if mask & 1 << 3 != 0 { + buttons.add(MouseButton::X1); + } + if mask & 1 << 4 != 0 { + buttons.add(MouseButton::X2); + } + buttons +} + extern "C" fn mouse_down_left(this: &mut Object, _: Sel, nsevent: id) { - mouse_down(this, nsevent, MouseButton::Left) + mouse_down(this, nsevent, MouseButton::Left); } extern "C" fn mouse_down_right(this: &mut Object, _: Sel, nsevent: id) { - mouse_down(this, nsevent, MouseButton::Right) + mouse_down(this, nsevent, MouseButton::Right); +} + +extern "C" fn mouse_down_other(this: &mut Object, _: Sel, nsevent: id) { + unsafe { + if let Some(button) = get_mouse_button(nsevent.buttonNumber()) { + mouse_down(this, nsevent, button); + } + } } fn mouse_down(this: &mut Object, nsevent: id, button: MouseButton) { unsafe { let view_state: *mut c_void = *this.get_ivar("viewState"); let view_state = &mut *(view_state as *mut ViewState); - let event = mouse_event(nsevent, this as id, Some(button)); + let count = nsevent.clickCount() as u8; + let event = click_event(nsevent, this as id, count, button); (*view_state).handler.mouse_down(&event); } } extern "C" fn mouse_up_left(this: &mut Object, _: Sel, nsevent: id) { - mouse_up(this, nsevent, MouseButton::Left) + mouse_up(this, nsevent, MouseButton::Left); } extern "C" fn mouse_up_right(this: &mut Object, _: Sel, nsevent: id) { - mouse_up(this, nsevent, MouseButton::Right) + mouse_up(this, nsevent, MouseButton::Right); +} + +extern "C" fn mouse_up_other(this: &mut Object, _: Sel, nsevent: id) { + unsafe { + if let Some(button) = get_mouse_button(nsevent.buttonNumber()) { + mouse_up(this, nsevent, button); + } + } } fn mouse_up(this: &mut Object, nsevent: id, button: MouseButton) { unsafe { let view_state: *mut c_void = *this.get_ivar("viewState"); let view_state = &mut *(view_state as *mut ViewState); - let event = mouse_event(nsevent, this as id, Some(button)); + let event = click_event(nsevent, this as id, 0, button); (*view_state).handler.mouse_up(&event); } } @@ -423,7 +480,7 @@ extern "C" fn mouse_move(this: &mut Object, _: Sel, nsevent: id) { unsafe { let view_state: *mut c_void = *this.get_ivar("viewState"); let view_state = &mut *(view_state as *mut ViewState); - let event = mouse_event(nsevent, this as id, None); + let event = move_event(nsevent, this as id); (*view_state).handler.mouse_move(&event); } } diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index 30b8eaddd3..c4823dd5fe 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -38,7 +38,7 @@ use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; use crate::keyboard; use crate::keycodes::KeyCode; -use crate::mouse::{Cursor, MouseButton, MouseEvent}; +use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; use crate::KeyModifiers; @@ -140,42 +140,49 @@ impl WindowState { fn setup_mouse_down_callback(ws: &Rc) { let state = ws.clone(); register_canvas_event_listener(ws, "mousedown", move |event: web_sys::MouseEvent| { - let button = mouse_button(event.button()).unwrap(); - let event = MouseEvent { - pos: Point::new(event.offset_x() as f64, event.offset_y() as f64), - mods: get_modifiers!(event), - button, - count: 1, - }; - state.handler.borrow_mut().mouse_down(&event); + if let Some(button) = mouse_button(event.button()) { + let buttons = mouse_buttons(event.buttons()); + let event = MouseEvent { + pos: Point::new(event.offset_x() as f64, event.offset_y() as f64), + buttons, + mods: get_modifiers!(event), + count: 1, + button, + }; + state.handler.borrow_mut().mouse_down(&event); + } }); } -fn setup_mouse_move_callback(ws: &Rc) { +fn setup_mouse_up_callback(ws: &Rc) { let state = ws.clone(); - register_canvas_event_listener(ws, "mousemove", move |event: web_sys::MouseEvent| { - let button = mouse_button(event.button()).unwrap(); - let event = MouseEvent { - pos: Point::new(event.offset_x() as f64, event.offset_y() as f64), - mods: get_modifiers!(event), - button, - count: 1, - }; - state.handler.borrow_mut().mouse_move(&event); + register_canvas_event_listener(ws, "mouseup", move |event: web_sys::MouseEvent| { + if let Some(button) = mouse_button(event.button()) { + let buttons = mouse_buttons(event.buttons()); + let event = MouseEvent { + pos: Point::new(event.offset_x() as f64, event.offset_y() as f64), + buttons, + mods: get_modifiers!(event), + count: 0, + button, + }; + state.handler.borrow_mut().mouse_up(&event); + } }); } -fn setup_mouse_up_callback(ws: &Rc) { +fn setup_mouse_move_callback(ws: &Rc) { let state = ws.clone(); - register_canvas_event_listener(ws, "mouseup", move |event: web_sys::MouseEvent| { - let button = mouse_button(event.button()).unwrap(); + register_canvas_event_listener(ws, "mousemove", move |event: web_sys::MouseEvent| { + let buttons = mouse_buttons(event.buttons()); let event = MouseEvent { pos: Point::new(event.offset_x() as f64, event.offset_y() as f64), + buttons, mods: get_modifiers!(event), - button, count: 0, + button: MouseButton::None, }; - state.handler.borrow_mut().mouse_up(&event); + state.handler.borrow_mut().mouse_move(&event); }); } @@ -614,10 +621,32 @@ fn mouse_button(button: i16) -> Option { 0 => Some(MouseButton::Left), 1 => Some(MouseButton::Middle), 2 => Some(MouseButton::Right), + 3 => Some(MouseButton::X1), + 4 => Some(MouseButton::X2), _ => None, } } +fn mouse_buttons(mask: u16) -> MouseButtons { + let mut buttons = MouseButtons::new(); + if mask & 1 != 0 { + buttons.add(MouseButton::Left); + } + if mask & 1 << 1 != 0 { + buttons.add(MouseButton::Right); + } + if mask & 1 << 2 != 0 { + buttons.add(MouseButton::Middle); + } + if mask & 1 << 3 != 0 { + buttons.add(MouseButton::X1); + } + if mask & 1 << 4 != 0 { + buttons.add(MouseButton::X2); + } + buttons +} + fn set_cursor(canvas: &web_sys::HtmlCanvasElement, cursor: &Cursor) { canvas .style() diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 9c24194489..885bd183cd 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -60,7 +60,7 @@ use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; use crate::keyboard::{KeyEvent, KeyModifiers}; use crate::keycodes::KeyCode; -use crate::mouse::{Cursor, MouseButton, MouseEvent}; +use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; extern "system" { @@ -174,10 +174,10 @@ struct WndState { /// The `char` of the last `WM_CHAR` event, if there has not already been /// a `WM_KEYUP` event. stashed_char: Option, - // Stores a bit mask of all mouse buttons that are currently holding mouse + // Stores a set of all mouse buttons that are currently holding mouse // capture. When the first mouse button is down on our window we enter // capture, and we hold it until the last mouse button is up. - captured_mouse_buttons: u32, + captured_mouse_buttons: MouseButtons, // Is this window the topmost window under the mouse cursor has_mouse_focus: bool, //TODO: track surrogate orphan @@ -220,22 +220,55 @@ impl Default for PresentStrategy { /// Must only be called while handling an input message. /// This queries the keyboard state at the time of message delivery. fn get_mod_state() -> KeyModifiers { - //FIXME: does not handle windows key - unsafe { - let mut mod_state = KeyModifiers::default(); - if GetKeyState(VK_MENU) < 0 { - mod_state.alt = true; - } - if GetKeyState(VK_CONTROL) < 0 { - mod_state.ctrl = true; - } - if GetKeyState(VK_SHIFT) < 0 { - mod_state.shift = true; - } - mod_state + KeyModifiers { + shift: get_mod_state_shift(), + alt: get_mod_state_alt(), + ctrl: get_mod_state_ctrl(), + meta: get_mod_state_win(), } } +#[inline] +fn get_mod_state_shift() -> bool { + unsafe { GetKeyState(VK_SHIFT) < 0 } +} + +#[inline] +fn get_mod_state_alt() -> bool { + unsafe { GetKeyState(VK_MENU) < 0 } +} + +#[inline] +fn get_mod_state_ctrl() -> bool { + unsafe { GetKeyState(VK_CONTROL) < 0 } +} + +#[inline] +fn get_mod_state_win() -> bool { + unsafe { GetKeyState(VK_LWIN) < 0 || GetKeyState(VK_RWIN) < 0 } +} + +/// Extract the buttons that are being held down from wparam in mouse events. +fn get_buttons(wparam: WPARAM) -> MouseButtons { + let mut buttons = MouseButtons::new(); + if wparam & MK_LBUTTON != 0 { + buttons.add(MouseButton::Left); + } + if wparam & MK_RBUTTON != 0 { + buttons.add(MouseButton::Right); + } + if wparam & MK_MBUTTON != 0 { + buttons.add(MouseButton::Middle); + } + if wparam & MK_XBUTTON1 != 0 { + buttons.add(MouseButton::X1); + } + if wparam & MK_XBUTTON2 != 0 { + buttons.add(MouseButton::X2); + } + buttons +} + fn is_point_in_client_rect(hwnd: HWND, x: i32, y: i32) -> bool { unsafe { let mut client_rect = mem::MaybeUninit::uninit(); @@ -297,17 +330,17 @@ impl WndState { } fn enter_mouse_capture(&mut self, hwnd: HWND, button: MouseButton) { - if self.captured_mouse_buttons == 0 { + if self.captured_mouse_buttons.has_none() { unsafe { SetCapture(hwnd); } } - self.captured_mouse_buttons |= 1 << (button as u32); + self.captured_mouse_buttons.add(button); } fn exit_mouse_capture(&mut self, button: MouseButton) -> bool { - self.captured_mouse_buttons &= !(1 << (button as u32)); - self.captured_mouse_buttons == 0 + self.captured_mouse_buttons.remove(button); + self.captured_mouse_buttons.has_none() } } @@ -677,22 +710,19 @@ impl WndProc for MyWndProc { let (px, py) = self.handle.borrow().pixels_to_px_xy(x, y); let pos = Point::new(px as f64, py as f64); - let mods = get_mod_state(); - let button = match wparam { - w if (w & 1) > 0 => MouseButton::Left, - w if (w & 1 << 1) > 0 => MouseButton::Right, - w if (w & 1 << 5) > 0 => MouseButton::Middle, - w if (w & 1 << 6) > 0 => MouseButton::X1, - w if (w & 1 << 7) > 0 => MouseButton::X2, - //FIXME: I guess we probably do want `MouseButton::None`? - //this feels bad, but also this gets discarded in druid anyway. - _ => MouseButton::Left, + let mods = KeyModifiers { + shift: wparam & MK_SHIFT != 0, + alt: get_mod_state_alt(), + ctrl: wparam & MK_CONTROL != 0, + meta: get_mod_state_win(), }; + let buttons = get_buttons(wparam); let event = MouseEvent { pos, + buttons, mods, - button, count: 0, + button: MouseButton::None, }; s.handler.mouse_move(&event); } else { @@ -712,55 +742,64 @@ impl WndProc for MyWndProc { } // TODO: not clear where double-click processing should happen. Currently disabled // because CS_DBLCLKS is not set - WM_LBUTTONDBLCLK | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_MBUTTONDBLCLK - | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_RBUTTONDBLCLK | WM_RBUTTONDOWN | WM_RBUTTONUP + WM_LBUTTONDBLCLK | WM_LBUTTONDOWN | WM_LBUTTONUP | WM_RBUTTONDBLCLK + | WM_RBUTTONDOWN | WM_RBUTTONUP | WM_MBUTTONDBLCLK | WM_MBUTTONDOWN | WM_MBUTTONUP | WM_XBUTTONDBLCLK | WM_XBUTTONDOWN | WM_XBUTTONUP => { let mut should_release_capture = false; - if let Ok(mut s) = self.state.try_borrow_mut() { - let s = s.as_mut().unwrap(); - let button = match msg { - WM_LBUTTONDBLCLK | WM_LBUTTONDOWN | WM_LBUTTONUP => MouseButton::Left, - WM_MBUTTONDBLCLK | WM_MBUTTONDOWN | WM_MBUTTONUP => MouseButton::Middle, - WM_RBUTTONDBLCLK | WM_RBUTTONDOWN | WM_RBUTTONUP => MouseButton::Right, - WM_XBUTTONDBLCLK | WM_XBUTTONDOWN | WM_XBUTTONUP => { - match HIWORD(wparam as u32) { - 1 => MouseButton::X1, - 2 => MouseButton::X2, - _ => { - warn!("unexpected X button event"); - return None; - } + if let Some(button) = match msg { + WM_LBUTTONDBLCLK | WM_LBUTTONDOWN | WM_LBUTTONUP => Some(MouseButton::Left), + WM_RBUTTONDBLCLK | WM_RBUTTONDOWN | WM_RBUTTONUP => Some(MouseButton::Right), + WM_MBUTTONDBLCLK | WM_MBUTTONDOWN | WM_MBUTTONUP => Some(MouseButton::Middle), + WM_XBUTTONDBLCLK | WM_XBUTTONDOWN | WM_XBUTTONUP => { + match HIWORD(wparam as u32) { + XBUTTON1 => Some(MouseButton::X1), + XBUTTON2 => Some(MouseButton::X2), + w => { + // Should never happen with current Windows + log::warn!("Received an unknown XBUTTON event ({})", w); + None } } - _ => unreachable!(), - }; - let count = match msg { - WM_LBUTTONDOWN | WM_MBUTTONDOWN | WM_RBUTTONDOWN | WM_XBUTTONDOWN => 1, - WM_LBUTTONDBLCLK | WM_MBUTTONDBLCLK | WM_RBUTTONDBLCLK - | WM_XBUTTONDBLCLK => 2, - WM_LBUTTONUP | WM_MBUTTONUP | WM_RBUTTONUP | WM_XBUTTONUP => 0, - _ => unreachable!(), - }; - let x = LOWORD(lparam as u32) as i16 as i32; - let y = HIWORD(lparam as u32) as i16 as i32; - let (px, py) = self.handle.borrow().pixels_to_px_xy(x, y); - let pos = Point::new(px as f64, py as f64); - let mods = get_mod_state(); - let event = MouseEvent { - pos, - mods, - button, - count, - }; - if count > 0 { - s.enter_mouse_capture(hwnd, button); - s.handler.mouse_down(&event); + } + _ => unreachable!(), + } { + if let Ok(mut s) = self.state.try_borrow_mut() { + let s = s.as_mut().unwrap(); + let count = match msg { + WM_LBUTTONDOWN | WM_MBUTTONDOWN | WM_RBUTTONDOWN | WM_XBUTTONDOWN => 1, + WM_LBUTTONDBLCLK | WM_MBUTTONDBLCLK | WM_RBUTTONDBLCLK + | WM_XBUTTONDBLCLK => 2, + WM_LBUTTONUP | WM_MBUTTONUP | WM_RBUTTONUP | WM_XBUTTONUP => 0, + _ => unreachable!(), + }; + let x = LOWORD(lparam as u32) as i16 as i32; + let y = HIWORD(lparam as u32) as i16 as i32; + let (px, py) = self.handle.borrow().pixels_to_px_xy(x, y); + let pos = Point::new(px as f64, py as f64); + let mods = KeyModifiers { + shift: wparam & MK_SHIFT != 0, + alt: get_mod_state_alt(), + ctrl: wparam & MK_CONTROL != 0, + meta: get_mod_state_win(), + }; + let buttons = get_buttons(wparam); + let event = MouseEvent { + pos, + buttons, + mods, + count, + button, + }; + if count > 0 { + s.enter_mouse_capture(hwnd, button); + s.handler.mouse_down(&event); + } else { + s.handler.mouse_up(&event); + should_release_capture = s.exit_mouse_capture(button); + } } else { - s.handler.mouse_up(&event); - should_release_capture = s.exit_mouse_capture(button); + self.log_dropped_msg(hwnd, msg, wparam, lparam); } - } else { - self.log_dropped_msg(hwnd, msg, wparam, lparam); } // ReleaseCapture() is deferred: it needs to be called without having a mutable @@ -810,7 +849,7 @@ impl WndProc for MyWndProc { WM_CAPTURECHANGED => { if let Ok(mut s) = self.state.try_borrow_mut() { let s = s.as_mut().unwrap(); - s.captured_mouse_buttons = 0; + s.captured_mouse_buttons.clear(); } else { self.log_dropped_msg(hwnd, msg, wparam, lparam); } @@ -942,7 +981,7 @@ impl WindowBuilder { min_size: self.min_size, stashed_key_code: KeyCode::Unknown(0), stashed_char: None, - captured_mouse_buttons: 0, + captured_mouse_buttons: MouseButtons::new(), has_mouse_focus: false, }; win.wndproc.connect(&handle, state); diff --git a/druid-shell/src/platform/x11/application.rs b/druid-shell/src/platform/x11/application.rs index 2a884cc7df..bbd1775e7f 100644 --- a/druid-shell/src/platform/x11/application.rs +++ b/druid-shell/src/platform/x11/application.rs @@ -22,7 +22,7 @@ use lazy_static::lazy_static; use crate::application::AppHandler; use crate::kurbo::{Point, Rect}; -use crate::{KeyCode, KeyModifiers, MouseButton, MouseEvent}; +use crate::{KeyCode, KeyModifiers, MouseButton, MouseButtons, MouseEvent}; use super::clipboard::Clipboard; use super::error::Error; @@ -109,11 +109,13 @@ impl Application { xcb::BUTTON_PRESS => { let button_press: &xcb::ButtonPressEvent = unsafe { xcb::cast_event(&ev) }; let window_id = button_press.event(); - let mouse_event = MouseEvent { + let click_event = MouseEvent { pos: Point::new( button_press.event_x() as f64, button_press.event_y() as f64, ), + // TODO: Fill with held down buttons + buttons: MouseButtons::new().with(MouseButton::Left), mods: KeyModifiers { shift: false, alt: false, @@ -126,7 +128,7 @@ impl Application { WINDOW_MAP.with(|map| { let mut windows = map.borrow_mut(); if let Some(w) = windows.get_mut(&window_id) { - w.mouse_down(&mouse_event); + w.mouse_down(&click_event); } }) } @@ -134,11 +136,13 @@ impl Application { let button_release: &xcb::ButtonReleaseEvent = unsafe { xcb::cast_event(&ev) }; let window_id = button_release.event(); - let mouse_event = MouseEvent { + let click_event = MouseEvent { pos: Point::new( button_release.event_x() as f64, button_release.event_y() as f64, ), + // TODO: Fill with held down buttons + buttons: MouseButtons::new(), mods: KeyModifiers { shift: false, alt: false, @@ -151,18 +155,20 @@ impl Application { WINDOW_MAP.with(|map| { let mut windows = map.borrow_mut(); if let Some(w) = windows.get_mut(&window_id) { - w.mouse_up(&mouse_event); + w.mouse_up(&click_event); } }) } xcb::MOTION_NOTIFY => { let mouse_move: &xcb::MotionNotifyEvent = unsafe { xcb::cast_event(&ev) }; let window_id = mouse_move.event(); - let mouse_event = MouseEvent { + let move_event = MouseEvent { pos: Point::new( mouse_move.event_x() as f64, mouse_move.event_y() as f64, ), + // TODO: Fill with held down buttons + buttons: MouseButtons::new(), mods: KeyModifiers { shift: false, alt: false, @@ -170,12 +176,12 @@ impl Application { meta: false, }, count: 0, - button: MouseButton::Left, + button: MouseButton::None, }; WINDOW_MAP.with(|map| { let mut windows = map.borrow_mut(); if let Some(w) = windows.get_mut(&window_id) { - w.mouse_move(&mouse_event); + w.mouse_move(&move_event); } }) } diff --git a/druid-shell/src/platform/x11/window.rs b/druid-shell/src/platform/x11/window.rs index 813883c039..3fc11bf82d 100644 --- a/druid-shell/src/platform/x11/window.rs +++ b/druid-shell/src/platform/x11/window.rs @@ -258,16 +258,16 @@ impl XWindow { )); } - pub fn mouse_down(&mut self, mouse_event: &MouseEvent) { - self.handler.mouse_down(mouse_event); + pub fn mouse_down(&mut self, click_event: &MouseEvent) { + self.handler.mouse_down(click_event); } - pub fn mouse_up(&mut self, mouse_event: &MouseEvent) { - self.handler.mouse_up(mouse_event); + pub fn mouse_up(&mut self, click_event: &MouseEvent) { + self.handler.mouse_up(click_event); } - pub fn mouse_move(&mut self, mouse_event: &MouseEvent) { - self.handler.mouse_move(mouse_event); + pub fn mouse_move(&mut self, move_event: &MouseEvent) { + self.handler.mouse_move(move_event); } fn communicate_size(&mut self, size: Size) { @@ -424,7 +424,7 @@ impl WindowHandle { pub fn get_dpi(&self) -> f32 { // TODO(x11/dpi_scaling): figure out DPI scaling - log::warn!("WindowHandle::get_dpi is currently unimplemented for X11 platforms."); + //log::warn!("WindowHandle::get_dpi is currently unimplemented for X11 platforms."); 96.0 } } diff --git a/druid/src/lib.rs b/druid/src/lib.rs index f60053aa01..543f846608 100644 --- a/druid/src/lib.rs +++ b/druid/src/lib.rs @@ -140,8 +140,8 @@ pub use piet::{Color, LinearGradient, RadialGradient, RenderContext, UnitPoint}; // these are the types from shell that we expose; others we only use internally. pub use shell::{ Application, Clipboard, ClipboardFormat, Cursor, Error as PlatformError, FileDialogOptions, - FileInfo, FileSpec, FormatId, HotKey, KeyCode, KeyEvent, KeyModifiers, MouseButton, RawMods, - SysMods, Text, TimerToken, WindowHandle, + FileInfo, FileSpec, FormatId, HotKey, KeyCode, KeyEvent, KeyModifiers, MouseButton, + MouseButtons, RawMods, SysMods, Text, TimerToken, WindowHandle, }; pub use crate::core::WidgetPod; diff --git a/druid/src/mouse.rs b/druid/src/mouse.rs index 223e8ab3c9..ef1c8fb43e 100644 --- a/druid/src/mouse.rs +++ b/druid/src/mouse.rs @@ -15,9 +15,9 @@ //! The mousey bits use crate::kurbo::Point; -use crate::{KeyModifiers, MouseButton}; +use crate::{KeyModifiers, MouseButton, MouseButtons}; -/// The state of the mouse for a click, mouse-up, or move event. +/// Information about the mouse click event. /// /// In `druid`, unlike in `druid_shell`, we treat the widget's coordinate /// space and the window's coordinate space separately. @@ -27,13 +27,18 @@ pub struct MouseEvent { pub pos: Point, /// The position of the mouse in the coordinate space of the window. pub window_pos: Point, - /// Keyboard modifiers at the time of the mouse event. + /// Mouse buttons being held down during a move or after a click event. + /// Thus it will contain the `button` that triggered a mouse-down event, + /// and it will not contain the `button` that triggered a mouse-up event. + pub buttons: MouseButtons, + /// Keyboard modifiers at the time of the event. pub mods: KeyModifiers, /// The number of mouse clicks associated with this event. This will always - /// be `0` for a mouse-up event. - pub count: u32, - /// The currently pressed button in the case of a move or click event, - /// or the released button in the case of a mouse-up event. + /// be `0` for a mouse-up and mouse-move events. + pub count: u8, + /// The button that was pressed down in the case of mouse-down, + /// or the button that was released in the case of mouse-up. + /// This will always be `MouseButton::None` in the case of mouse-move. pub button: MouseButton, } @@ -41,6 +46,7 @@ impl From for MouseEvent { fn from(src: druid_shell::MouseEvent) -> MouseEvent { let druid_shell::MouseEvent { pos, + buttons, mods, count, button, @@ -48,6 +54,7 @@ impl From for MouseEvent { MouseEvent { pos, window_pos: pos, + buttons, mods, count, button, diff --git a/druid/src/tests/mod.rs b/druid/src/tests/mod.rs index 683f79e375..a9d737baca 100644 --- a/druid/src/tests/mod.rs +++ b/druid/src/tests/mod.rs @@ -71,14 +71,15 @@ fn propogate_hot() { .record(&root_rec) .with_id(root); - fn make_mouse(x: f64, y: f64) -> MouseEvent { + fn move_mouse(x: f64, y: f64) -> MouseEvent { let pos = Point::new(x, y); MouseEvent { pos, window_pos: pos, + buttons: MouseButtons::default(), mods: KeyModifiers::default(), count: 0, - button: MouseButton::Left, + button: MouseButton::None, } } #[allow(clippy::cognitive_complexity)] @@ -97,7 +98,7 @@ fn propogate_hot() { // and verifying both the widget's `is_hot` status and also that // each widget received the expected HotChanged messages. - harness.event(Event::MouseMove(make_mouse(10., 10.))); + harness.event(Event::MouseMove(move_mouse(10., 10.))); assert!(harness.get_state(root).is_hot); assert!(harness.get_state(empty).is_hot); assert!(!harness.get_state(pad).is_hot); @@ -106,7 +107,7 @@ fn propogate_hot() { assert_matches!(root_rec.next(), Record::E(Event::MouseMove(_))); assert!(root_rec.is_empty() && padding_rec.is_empty() && button_rec.is_empty()); - harness.event(Event::MouseMove(make_mouse(210., 10.))); + harness.event(Event::MouseMove(move_mouse(210., 10.))); assert!(harness.get_state(root).is_hot); assert!(!harness.get_state(empty).is_hot); @@ -118,7 +119,7 @@ fn propogate_hot() { assert_matches!(padding_rec.next(), Record::E(Event::MouseMove(_))); assert!(root_rec.is_empty() && padding_rec.is_empty() && button_rec.is_empty()); - harness.event(Event::MouseMove(make_mouse(260., 60.))); + harness.event(Event::MouseMove(move_mouse(260., 60.))); assert!(harness.get_state(root).is_hot); assert!(!harness.get_state(empty).is_hot); assert!(harness.get_state(button).is_hot); @@ -130,7 +131,7 @@ fn propogate_hot() { assert_matches!(button_rec.next(), Record::E(Event::MouseMove(_))); assert!(root_rec.is_empty() && padding_rec.is_empty() && button_rec.is_empty()); - harness.event(Event::MouseMove(make_mouse(10., 10.))); + harness.event(Event::MouseMove(move_mouse(10., 10.))); assert!(harness.get_state(root).is_hot); assert!(harness.get_state(empty).is_hot); assert!(!harness.get_state(button).is_hot);