diff --git a/winit-appkit/src/view.rs b/winit-appkit/src/view.rs index 249489f4..6fce2bdb 100644 --- a/winit-appkit/src/view.rs +++ b/winit-appkit/src/view.rs @@ -1095,14 +1095,11 @@ fn mouse_button(event: &NSEvent) -> MouseButton { // For the other events, it's always set to 0. // MacOS only defines the left, right and middle buttons, 3..=31 are left as generic buttons, // but 3 and 4 are very commonly used as Back and Forward by hardware vendors and applications. - match event.buttonNumber() { - 0 => MouseButton::Left, - 1 => MouseButton::Right, - 2 => MouseButton::Middle, - 3 => MouseButton::Back, - 4 => MouseButton::Forward, - n => MouseButton::Other(n as u16), - } + let b: isize = event.buttonNumber(); + b.try_into() + .ok() + .and_then(MouseButton::try_from_u8) + .expect("expected MacOS button number in the range 0..=31") } // NOTE: to get option as alt working we need to rewrite events diff --git a/winit-core/src/event.rs b/winit-core/src/event.rs index 17c7cfd6..24a80990 100644 --- a/winit-core/src/event.rs +++ b/winit-core/src/event.rs @@ -531,26 +531,22 @@ pub enum ButtonSource { button: TabletToolButton, data: TabletToolData, }, + /// A pointer button of unknown source. + /// + /// Codes are undefined and may not be reproducible across platforms or winit versions. Unknown(u16), } impl ButtonSource { - /// Convert any [`ButtonSource`] to an equivalent [`MouseButton`]. If a pointer type has no + /// Try to convert a [`ButtonSource`] to an equivalent [`MouseButton`]. If a pointer type has no /// special handling in an application, this method can be used to handle it like any generic /// mouse input. - pub fn mouse_button(self) -> MouseButton { + pub fn mouse_button(self) -> Option { match self { - ButtonSource::Mouse(mouse) => mouse, - ButtonSource::Touch { .. } => MouseButton::Left, + ButtonSource::Mouse(mouse) => Some(mouse), + ButtonSource::Touch { .. } => Some(MouseButton::Left), ButtonSource::TabletTool { button, .. } => button.into(), - ButtonSource::Unknown(button) => match button { - 0 => MouseButton::Left, - 1 => MouseButton::Middle, - 2 => MouseButton::Right, - 3 => MouseButton::Back, - 4 => MouseButton::Forward, - _ => MouseButton::Other(button), - }, + ButtonSource::Unknown(_) => None, } } } @@ -1323,21 +1319,114 @@ impl ElementState { } } -/// Describes a button of a mouse controller. +/// Identifies a button of a mouse controller. /// /// ## Platform-specific /// -/// **macOS:** `Back` and `Forward` might not work with all hardware. -/// **Orbital:** `Back` and `Forward` are unsupported due to orbital not supporting them. +/// The first three buttons should be supported on all platforms. +/// [`Self::Back`] and [`Self::Forward`] are supported on most platforms +/// (when using a compatible mouse). +/// +/// - **Android, iOS:** Currently not supported. +/// - **Orbital:** Only left/right/middle buttons are supported at this time. +/// - **Web, Windows:** Supports left/right/middle/back/forward buttons. +/// - **Wayland:** Supports buttons 0..=15. +/// - **macOS:** Supports all button variants. +/// - **X11:** Technically supports further buttons than this (0..=250), these are emitted in +/// `ButtonSource::Unknown`. #[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[repr(u8)] pub enum MouseButton { - Left, - Right, - Middle, - Back, - Forward, - Other(u16), + /// The primary (usually left) button + Left = 0, + /// The secondary (usually right) button + Right = 1, + /// The tertiary (usually middle) button + Middle = 2, + /// The first side button, frequently assigned a back function + Back = 3, + /// The second side button, frequently assigned a forward function + Forward = 4, + /// The sixth button + Button6 = 5, + /// The seventh button + Button7 = 6, + /// The eighth button + Button8 = 7, + /// The ninth button + Button9 = 8, + /// The tenth button + Button10 = 9, + /// The eleventh button + Button11 = 10, + /// The twelfth button + Button12 = 11, + /// The thirteenth button + Button13 = 12, + /// The fourteenth button + Button14 = 13, + /// The fifteenth button + Button15 = 14, + /// The sixteenth button + Button16 = 15, + Button17 = 16, + Button18 = 17, + Button19 = 18, + Button20 = 19, + Button21 = 20, + Button22 = 21, + Button23 = 22, + Button24 = 23, + Button25 = 24, + Button26 = 25, + Button27 = 26, + Button28 = 27, + Button29 = 28, + Button30 = 29, + Button31 = 30, + Button32 = 31, +} + +impl MouseButton { + /// Construct from a `u8` if within the range `0..=31` + pub fn try_from_u8(b: u8) -> Option { + Some(match b { + 0 => MouseButton::Left, + 1 => MouseButton::Right, + 2 => MouseButton::Middle, + 3 => MouseButton::Back, + 4 => MouseButton::Forward, + 5 => MouseButton::Button6, + 6 => MouseButton::Button7, + 7 => MouseButton::Button8, + 8 => MouseButton::Button9, + 9 => MouseButton::Button10, + 10 => MouseButton::Button11, + 11 => MouseButton::Button12, + 12 => MouseButton::Button13, + 13 => MouseButton::Button14, + 14 => MouseButton::Button15, + 15 => MouseButton::Button16, + 16 => MouseButton::Button17, + 17 => MouseButton::Button18, + 18 => MouseButton::Button19, + 19 => MouseButton::Button20, + 20 => MouseButton::Button21, + 21 => MouseButton::Button22, + 22 => MouseButton::Button23, + 23 => MouseButton::Button24, + 24 => MouseButton::Button25, + 25 => MouseButton::Button26, + 26 => MouseButton::Button27, + 27 => MouseButton::Button28, + 28 => MouseButton::Button29, + 29 => MouseButton::Button30, + 30 => MouseButton::Button31, + 31 => MouseButton::Button32, + _ => return None, + }) + } } /// Describes a button of a tool, e.g. a pen. @@ -1349,16 +1438,16 @@ pub enum TabletToolButton { Other(u16), } -impl From for MouseButton { +impl From for Option { fn from(tool: TabletToolButton) -> Self { - match tool { + Some(match tool { TabletToolButton::Contact => MouseButton::Left, TabletToolButton::Barrel => MouseButton::Right, TabletToolButton::Other(1) => MouseButton::Middle, TabletToolButton::Other(3) => MouseButton::Back, TabletToolButton::Other(4) => MouseButton::Forward, - TabletToolButton::Other(other) => MouseButton::Other(other), - } + TabletToolButton::Other(_) => return None, + }) } } @@ -1492,7 +1581,7 @@ mod tests { primary: true, state: event::ElementState::Pressed, position: (0, 0).into(), - button: event::MouseButton::Other(0).into(), + button: event::ButtonSource::Unknown(0), }); with_window_event(PointerButton { device_id: None, diff --git a/winit-wayland/src/seat/pointer/mod.rs b/winit-wayland/src/seat/pointer/mod.rs index f266b4d0..11970d70 100644 --- a/winit-wayland/src/seat/pointer/mod.rs +++ b/winit-wayland/src/seat/pointer/mod.rs @@ -30,7 +30,7 @@ use sctk::seat::SeatState; use dpi::{LogicalPosition, PhysicalPosition}; use winit_core::event::{ ElementState, MouseButton, MouseScrollDelta, PointerKind, PointerSource, TouchPhase, - WindowEvent, + WindowEvent, ButtonSource, }; use crate::state::WinitState; @@ -108,8 +108,8 @@ impl PointerHandler for WinitState { if parent_surface != surface => { let click = match wayland_button_to_winit(button) { - MouseButton::Left => FrameClick::Normal, - MouseButton::Right => FrameClick::Alternate, + ButtonSource::Mouse(MouseButton::Left) => FrameClick::Normal, + ButtonSource::Mouse(MouseButton::Right) => FrameClick::Alternate, _ => continue, }; let pressed = matches!(kind, PointerEventKind::Press { .. }); @@ -186,7 +186,7 @@ impl PointerHandler for WinitState { device_id: None, state, position, - button: button.into(), + button, }, window_id, ); @@ -409,23 +409,16 @@ impl Default for WinitPointerDataInner { } /// Convert the Wayland button into winit. -fn wayland_button_to_winit(button: u32) -> MouseButton { +fn wayland_button_to_winit(button: u32) -> ButtonSource { // These values are coming from . - const BTN_LEFT: u32 = 0x110; - const BTN_RIGHT: u32 = 0x111; - const BTN_MIDDLE: u32 = 0x112; - const BTN_SIDE: u32 = 0x113; - const BTN_EXTRA: u32 = 0x114; - const BTN_FORWARD: u32 = 0x115; - const BTN_BACK: u32 = 0x116; + const BTN_MOUSE: u32 = 0x110; + const BTN_JOYSTICK: u32 = 0x120; - match button { - BTN_LEFT => MouseButton::Left, - BTN_RIGHT => MouseButton::Right, - BTN_MIDDLE => MouseButton::Middle, - BTN_BACK | BTN_SIDE => MouseButton::Back, - BTN_FORWARD | BTN_EXTRA => MouseButton::Forward, - button => MouseButton::Other(button as u16), + if (BTN_MOUSE..BTN_JOYSTICK).contains(&button) { + // Mapping orders match + MouseButton::try_from_u8((button - BTN_MOUSE) as u8).unwrap().into() + } else { + ButtonSource::Unknown(button as u16) } } diff --git a/winit-web/src/web_sys/event.rs b/winit-web/src/web_sys/event.rs index ad654314..076500d4 100644 --- a/winit-web/src/web_sys/event.rs +++ b/winit-web/src/web_sys/event.rs @@ -27,43 +27,7 @@ bitflags::bitflags! { const BACK = 0b001000; const FORWARD = 0b010000; const ERASER = 0b100000; - } -} - -impl From for MouseButton { - fn from(value: ButtonsState) -> Self { - match value { - ButtonsState::LEFT => MouseButton::Left, - ButtonsState::RIGHT => MouseButton::Right, - ButtonsState::MIDDLE => MouseButton::Middle, - ButtonsState::BACK => MouseButton::Back, - ButtonsState::FORWARD => MouseButton::Forward, - _ => MouseButton::Other(value.bits()), - } - } -} - -impl From for ButtonsState { - fn from(value: ButtonSource) -> Self { - match value { - ButtonSource::TabletTool { button, .. } => button.into(), - other => ButtonsState::from(other.mouse_button()), - } - } -} - -impl From for ButtonsState { - fn from(value: MouseButton) -> Self { - match value { - MouseButton::Left => ButtonsState::LEFT, - MouseButton::Right => ButtonsState::RIGHT, - MouseButton::Middle => ButtonsState::MIDDLE, - MouseButton::Back => ButtonsState::BACK, - MouseButton::Forward => ButtonsState::FORWARD, - MouseButton::Other(value) => ButtonsState::from_bits_retain(value), - } - } -} +}} impl From for ButtonsState { fn from(tool: TabletToolButton) -> Self { @@ -92,14 +56,16 @@ pub fn raw_button(event: &MouseEvent) -> Option { } } -pub fn mouse_button(button: u16) -> MouseButton { +pub fn mouse_button(button: u16) -> ButtonSource { match button { - 0 => MouseButton::Left, - 1 => MouseButton::Middle, - 2 => MouseButton::Right, - 3 => MouseButton::Back, - 4 => MouseButton::Forward, - other => MouseButton::Other(other), + 0 => MouseButton::Left.into(), + 1 => MouseButton::Middle.into(), + 2 => MouseButton::Right.into(), + 3 => MouseButton::Back.into(), + 4 => MouseButton::Forward.into(), + // Codes above 4 are not observed on Firefox or Chromium. 5 is defined as an eraser, + // which is not a mouse button. No other codes are defined. + i => ButtonSource::Unknown(i), } } diff --git a/winit-web/src/web_sys/pointer.rs b/winit-web/src/web_sys/pointer.rs index fe609cfa..4cade1e9 100644 --- a/winit-web/src/web_sys/pointer.rs +++ b/winit-web/src/web_sys/pointer.rs @@ -7,10 +7,9 @@ use winit_core::event::{ButtonSource, DeviceId, ElementState, PointerKind, Point use winit_core::keyboard::ModifiersState; use super::canvas::Common; -use super::event; +use super::event::{self, ButtonsState}; use super::event_handle::EventListenerHandle; use crate::event::mkdid; -use crate::web_sys::event::ButtonsState; #[allow(dead_code)] pub(super) struct PointerHandler { @@ -84,7 +83,7 @@ impl PointerHandler { let button = event::raw_button(&event).expect("no button pressed"); let source = match event::pointer_source(&event, kind) { - PointerSource::Mouse => ButtonSource::Mouse(event::mouse_button(button)), + PointerSource::Mouse => event::mouse_button(button), PointerSource::Touch { finger_id, force } => { ButtonSource::Touch { finger_id, force } }, @@ -138,7 +137,7 @@ impl PointerHandler { // care if it fails. let _e = canvas.set_pointer_capture(pointer_id); - ButtonSource::Mouse(event::mouse_button(button)) + event::mouse_button(button) }, PointerSource::Touch { finger_id, force } => { ButtonSource::Touch { finger_id, force } @@ -217,7 +216,7 @@ impl PointerHandler { }; let button = match event::pointer_source(&event, kind) { - PointerSource::Mouse => ButtonSource::Mouse(event::mouse_button(button)), + PointerSource::Mouse => event::mouse_button(button), PointerSource::Touch { finger_id, force } => { if button != 0 { tracing::error!("unexpected touch button id: {button}"); diff --git a/winit-win32/src/event_loop.rs b/winit-win32/src/event_loop.rs index fb137889..7a3a5ecb 100644 --- a/winit-win32/src/event_loop.rs +++ b/winit-win32/src/event_loop.rs @@ -1705,9 +1705,9 @@ unsafe fn public_window_callback_inner( } }, - WM_LBUTTONDOWN => { + WM_LBUTTONDOWN | WM_RBUTTONDOWN | WM_MBUTTONDOWN => { use winit_core::event::ElementState::Pressed; - use winit_core::event::MouseButton::Left; + use winit_core::event::MouseButton; use winit_core::event::WindowEvent::PointerButton; unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; @@ -1723,14 +1723,20 @@ unsafe fn public_window_callback_inner( primary: true, state: Pressed, position, - button: Left.into(), + button: match msg { + WM_LBUTTONDOWN => MouseButton::Left, + WM_RBUTTONDOWN => MouseButton::Right, + WM_MBUTTONDOWN => MouseButton::Middle, + _ => unreachable!(), + } + .into(), }); result = ProcResult::Value(0); }, - WM_LBUTTONUP => { + WM_LBUTTONUP | WM_RBUTTONUP | WM_MBUTTONUP => { use winit_core::event::ElementState::Released; - use winit_core::event::MouseButton::Left; + use winit_core::event::MouseButton; use winit_core::event::WindowEvent::PointerButton; unsafe { release_mouse(userdata.window_state_lock()) }; @@ -1746,106 +1752,20 @@ unsafe fn public_window_callback_inner( primary: true, state: Released, position, - button: Left.into(), - }); - result = ProcResult::Value(0); - }, - - WM_RBUTTONDOWN => { - use winit_core::event::ElementState::Pressed; - use winit_core::event::MouseButton::Right; - use winit_core::event::WindowEvent::PointerButton; - - unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; - - update_modifiers(window, userdata); - - let x = util::get_x_lparam(lparam as u32) as i32; - let y = util::get_y_lparam(lparam as u32) as i32; - let position = PhysicalPosition::new(x as f64, y as f64); - - userdata.send_window_event(window, PointerButton { - device_id: None, - primary: true, - state: Pressed, - position, - button: Right.into(), - }); - result = ProcResult::Value(0); - }, - - WM_RBUTTONUP => { - use winit_core::event::ElementState::Released; - use winit_core::event::MouseButton::Right; - use winit_core::event::WindowEvent::PointerButton; - - unsafe { release_mouse(userdata.window_state_lock()) }; - - update_modifiers(window, userdata); - - let x = util::get_x_lparam(lparam as u32) as i32; - let y = util::get_y_lparam(lparam as u32) as i32; - let position = PhysicalPosition::new(x as f64, y as f64); - - userdata.send_window_event(window, PointerButton { - device_id: None, - primary: true, - state: Released, - position, - button: Right.into(), - }); - result = ProcResult::Value(0); - }, - - WM_MBUTTONDOWN => { - use winit_core::event::ElementState::Pressed; - use winit_core::event::MouseButton::Middle; - use winit_core::event::WindowEvent::PointerButton; - - unsafe { capture_mouse(window, &mut userdata.window_state_lock()) }; - - update_modifiers(window, userdata); - - let x = util::get_x_lparam(lparam as u32) as i32; - let y = util::get_y_lparam(lparam as u32) as i32; - let position = PhysicalPosition::new(x as f64, y as f64); - - userdata.send_window_event(window, PointerButton { - device_id: None, - primary: true, - state: Pressed, - position, - button: Middle.into(), - }); - result = ProcResult::Value(0); - }, - - WM_MBUTTONUP => { - use winit_core::event::ElementState::Released; - use winit_core::event::MouseButton::Middle; - use winit_core::event::WindowEvent::PointerButton; - - unsafe { release_mouse(userdata.window_state_lock()) }; - - update_modifiers(window, userdata); - - let x = util::get_x_lparam(lparam as u32) as i32; - let y = util::get_y_lparam(lparam as u32) as i32; - let position = PhysicalPosition::new(x as f64, y as f64); - - userdata.send_window_event(window, PointerButton { - device_id: None, - primary: true, - state: Released, - position, - button: Middle.into(), + button: match msg { + WM_LBUTTONUP => MouseButton::Left, + WM_RBUTTONUP => MouseButton::Right, + WM_MBUTTONUP => MouseButton::Middle, + _ => unreachable!(), + } + .into(), }); result = ProcResult::Value(0); }, WM_XBUTTONDOWN => { use winit_core::event::ElementState::Pressed; - use winit_core::event::MouseButton::{Back, Forward, Other}; + use winit_core::event::MouseButton; use winit_core::event::WindowEvent::PointerButton; let xbutton = util::get_xbutton_wparam(wparam as u32); @@ -1857,25 +1777,25 @@ unsafe fn public_window_callback_inner( let y = util::get_y_lparam(lparam as u32) as i32; let position = PhysicalPosition::new(x as f64, y as f64); + // 1 is defined as back, 2 as forward; other codes are unexpected. + let b = xbutton as u8 + MouseButton::Back as u8 - 1; + userdata.send_window_event(window, PointerButton { device_id: None, primary: true, state: Pressed, position, - button: match xbutton { - 1 => Back, - 2 => Forward, - _ => Other(xbutton), - } - .into(), + // 1 is defined as back, 2 as forward; other codes are unexpected. + button: MouseButton::try_from_u8(b).unwrap().into(), }); result = ProcResult::Value(0); }, WM_XBUTTONUP => { use winit_core::event::ElementState::Released; - use winit_core::event::MouseButton::{Back, Forward, Other}; + use winit_core::event::MouseButton; use winit_core::event::WindowEvent::PointerButton; + let xbutton = util::get_xbutton_wparam(wparam as u32); unsafe { release_mouse(userdata.window_state_lock()) }; @@ -1886,17 +1806,16 @@ unsafe fn public_window_callback_inner( let y = util::get_y_lparam(lparam as u32) as i32; let position = PhysicalPosition::new(x as f64, y as f64); + // 1 is defined as back, 2 as forward; other codes are unexpected. + let b = xbutton as u8 + MouseButton::Back as u8 - 1; + userdata.send_window_event(window, PointerButton { device_id: None, primary: true, state: Released, position, - button: match xbutton { - 1 => Back, - 2 => Forward, - _ => Other(xbutton), - } - .into(), + // 1 is defined as back, 2 as forward; other codes are unexpected. + button: MouseButton::try_from_u8(b).unwrap().into(), }); result = ProcResult::Value(0); }, diff --git a/winit-x11/src/event_processor.rs b/winit-x11/src/event_processor.rs index 068e3f6b..72c1c295 100644 --- a/winit-x11/src/event_processor.rs +++ b/winit-x11/src/event_processor.rs @@ -1007,7 +1007,6 @@ impl EventProcessor { position, button: MouseButton::Middle.into(), }, - xlib::Button3 => WindowEvent::PointerButton { device_id, primary: true, @@ -1034,28 +1033,25 @@ impl EventProcessor { }, ElementState::Released => return, }, - 8 => WindowEvent::PointerButton { - device_id, - primary: true, - state, - position, - button: MouseButton::Back.into(), - }, - 9 => WindowEvent::PointerButton { + x @ 8..37 => WindowEvent::PointerButton { device_id, primary: true, state, position, - button: MouseButton::Forward.into(), + // Button 8 maps to MouseButton::BACK = 3; 36 maps to MouseButton::Button32. + // 255 is the largest code yielded on X11 (tested). + button: MouseButton::try_from_u8((x - 5) as u8).unwrap().into(), }, - x => WindowEvent::PointerButton { + x @ 37..=0xff => WindowEvent::PointerButton { device_id, primary: true, state, position, - button: MouseButton::Other(x as u16).into(), + // 255 is the largest code yielded on X11 (tested). + button: ButtonSource::Unknown(x as u16), }, + _ => return, }; app.window_event(&self.target, window_id, event); diff --git a/winit/examples/application.rs b/winit/examples/application.rs index 8947f1e9..4842eaa4 100644 --- a/winit/examples/application.rs +++ b/winit/examples/application.rs @@ -3,6 +3,7 @@ //! Note that a real application accepting text input **should** support //! the IME interface. See the `ime` example. +use std::borrow::Cow; use std::collections::HashMap; use std::error::Error; use std::fmt::Debug; @@ -485,8 +486,9 @@ impl ApplicationHandler for Application { let mods = window.modifiers; if let Some(action) = state .is_pressed() - .then(|| Self::process_mouse_binding(button.mouse_button(), &mods)) + .then(|| button.mouse_button()) .flatten() + .and_then(|button| Self::process_mouse_binding(button, &mods)) { self.handle_action_with_window(event_loop, window_id, action); } @@ -1145,15 +1147,16 @@ fn modifiers_to_string(mods: ModifiersState) -> String { mods_line } -fn mouse_button_to_string(button: MouseButton) -> &'static str { +fn mouse_button_to_string(button: MouseButton) -> Cow<'static, str> { match button { MouseButton::Left => "LMB", MouseButton::Right => "RMB", MouseButton::Middle => "MMB", MouseButton::Back => "Back", MouseButton::Forward => "Forward", - MouseButton::Other(_) => "", + other => return format!("button {}", other as u8 + 1).into(), } + .into() } #[cfg(web_platform)]