winit-core: revise MouseButton type

Unify the values of `MouseButton` and thus remove `Other` variant in
limit possible buttons to 32, which was picked based on platform
capabilities, where 32 is the highest.

For the reference, SDL has identical limit.
This commit is contained in:
Diggory Hardy 2025-08-07 09:52:34 +01:00 committed by Kirill Chibisov
parent 779f52a21f
commit 9d9d21cfdb
8 changed files with 191 additions and 229 deletions

View file

@ -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

View file

@ -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<MouseButton> {
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<MouseButton> {
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<TabletToolButton> for MouseButton {
impl From<TabletToolButton> for Option<MouseButton> {
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,

View file

@ -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 <linux/input-event-codes.h>.
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)
}
}

View file

@ -27,43 +27,7 @@ bitflags::bitflags! {
const BACK = 0b001000;
const FORWARD = 0b010000;
const ERASER = 0b100000;
}
}
impl From<ButtonsState> 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<ButtonSource> for ButtonsState {
fn from(value: ButtonSource) -> Self {
match value {
ButtonSource::TabletTool { button, .. } => button.into(),
other => ButtonsState::from(other.mouse_button()),
}
}
}
impl From<MouseButton> 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<TabletToolButton> for ButtonsState {
fn from(tool: TabletToolButton) -> Self {
@ -92,14 +56,16 @@ pub fn raw_button(event: &MouseEvent) -> Option<u16> {
}
}
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),
}
}

View file

@ -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}");

View file

@ -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);
},

View file

@ -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);

View file

@ -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)]