diff --git a/winit/src/platform_specific/wayland/event_loop/mod.rs b/winit/src/platform_specific/wayland/event_loop/mod.rs index b4589840..56911e42 100644 --- a/winit/src/platform_specific/wayland/event_loop/mod.rs +++ b/winit/src/platform_specific/wayland/event_loop/mod.rs @@ -177,13 +177,41 @@ impl SctkEventLoop { } } crate::platform_specific::Action::SetCursor( + surface, icon, ) => { - if let Some(seat) = state.seats.get_mut(0) { + state.record_cursor(surface.clone(), |r| { + r.icon = Some(icon); + r.hidden = false; + }); + if let Some(seat) = state + .seats + .get_mut(0) + .filter(|s| s.is_over(&surface)) + { seat.icon = Some(icon); + seat.hidden = false; seat.set_cursor(&state.connection, icon); } } + crate::platform_specific::Action::SetCursorVisible( + surface, + visible, + ) => { + state.record_cursor(surface.clone(), |r| { + r.hidden = !visible; + }); + if let Some(seat) = state + .seats + .get_mut(0) + .filter(|s| s.is_over(&surface)) + { + seat.set_cursor_visible( + &state.connection, + visible, + ); + } + } crate::platform_specific::Action::RequestRedraw( id, ) => { @@ -383,6 +411,7 @@ impl SctkEventLoop { touch_points: HashMap::new(), sctk_events: Vec::new(), frame_status: HashMap::new(), + cursor_requests: HashMap::new(), fractional_scaling_manager, viewporter_state, compositor_updates: Default::default(), diff --git a/winit/src/platform_specific/wayland/event_loop/state.rs b/winit/src/platform_specific/wayland/event_loop/state.rs index f4a8c5d7..a3a41ccd 100644 --- a/winit/src/platform_specific/wayland/event_loop/state.rs +++ b/winit/src/platform_specific/wayland/event_loop/state.rs @@ -123,6 +123,13 @@ use wayland_protocols::{ pub static TOKEN_CTR: AtomicU32 = AtomicU32::new(0); +/// The requested cursor for a surface +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub(crate) struct CursorRequest { + pub(crate) icon: Option, + pub(crate) hidden: bool, +} + #[derive(Debug)] pub(crate) struct SctkSeat { pub(crate) seat: WlSeat, @@ -139,6 +146,8 @@ pub(crate) struct SctkSeat { pub(crate) active_icon: Option, // Cursor icon set by application pub(crate) icon: Option, + // Application asked for cursor to hide + pub(crate) hidden: bool, } impl SctkSeat { @@ -148,6 +157,35 @@ impl SctkSeat { self.active_icon = Some(icon); } } + + /// Is the pointer currently over `surface`. + pub(crate) fn is_over(&self, surface: &ObjectId) -> bool { + self.ptr_focus.as_ref().is_some_and(|s| &s.id() == surface) + } + + /// Set cursor visibility over this surfaces. + pub(crate) fn set_cursor_visible( + &mut self, + conn: &Connection, + visible: bool, + ) { + let Some(ptr) = self.ptr.as_ref() else { + return; + }; + + self.hidden = !visible; + + if visible { + let icon = self.icon.unwrap_or(CursorIcon::Default); + _ = ptr.set_cursor(conn, icon); + self.active_icon = Some(icon); + } else { + // Ignoring the error + // It only fails when the pointer is not on our surface (due to serial error) + _ = ptr.hide_cursor(); + self.active_icon = None; + } + } } #[derive(Debug, Clone)] @@ -471,6 +509,7 @@ pub struct SctkState { pub(crate) session_lock_state: SessionLockState, pub(crate) session_lock: Option, pub(crate) id_map: HashMap, + pub(crate) cursor_requests: HashMap, pub(crate) to_commit: HashMap, pub(crate) destroyed: HashSet, pub(crate) pending_popup: Option<(SctkPopupSettings, usize)>, @@ -573,6 +612,17 @@ pub(crate) fn receive_frame( } impl SctkState { + /// What a surface wants the pointer over it to be. + pub(crate) fn record_cursor( + &mut self, + surface: ObjectId, + amend: impl FnOnce(&mut CursorRequest), + ) { + self.cursor_requests + .retain(|id, _| id == &surface || self.id_map.contains_key(id)); + amend(self.cursor_requests.entry(surface).or_default()); + } + pub fn request_redraw(&mut self, surface: &WlSurface) { let e = self .frame_status diff --git a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs index b40912ec..ac0bd4a9 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/pointer.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/pointer.rs @@ -31,7 +31,7 @@ impl PointerHandler for SctkState { // track events, but only forward for the active seat for e in events { - if my_seat.active_icon != my_seat.icon { + if !my_seat.hidden && my_seat.active_icon != my_seat.icon { // Restore cursor that was set by appliction, or default my_seat.set_cursor( conn, @@ -60,10 +60,19 @@ impl PointerHandler for SctkState { match e.kind { PointerEventKind::Enter { .. } => { _ = my_seat.ptr_focus.replace(e.surface.clone()); + if let Some(want) = + self.cursor_requests.get(&e.surface.id()) + { + my_seat.icon = want.icon; + my_seat.set_cursor_visible(conn, !want.hidden); + } } PointerEventKind::Leave { .. } => { _ = my_seat.ptr_focus.take(); _ = my_seat.active_icon = None; + // The next surface the pointer lands on decides what it + // looks like there + my_seat.hidden = false; } PointerEventKind::Press { time, diff --git a/winit/src/platform_specific/wayland/handlers/seat/seat.rs b/winit/src/platform_specific/wayland/handlers/seat/seat.rs index ee6b0e6e..ec2b187c 100644 --- a/winit/src/platform_specific/wayland/handlers/seat/seat.rs +++ b/winit/src/platform_specific/wayland/handlers/seat/seat.rs @@ -38,6 +38,7 @@ impl SeatHandler for SctkState { last_touch_down: None, icon: None, active_icon: None, + hidden: false, }); } @@ -65,6 +66,7 @@ impl SeatHandler for SctkState { last_touch_down: None, icon: None, active_icon: None, + hidden: false, }); self.seats.last_mut().unwrap() } diff --git a/winit/src/platform_specific/wayland/mod.rs b/winit/src/platform_specific/wayland/mod.rs index e98316ce..289d410d 100644 --- a/winit/src/platform_specific/wayland/mod.rs +++ b/winit/src/platform_specific/wayland/mod.rs @@ -32,7 +32,8 @@ use winit::window::ImePurpose; pub(crate) enum Action { Action(iced_runtime::platform_specific::wayland::Action), - SetCursor(CursorIcon), + SetCursor(ObjectId, CursorIcon), + SetCursorVisible(ObjectId, bool), RequestRedraw(ObjectId), TrackWindow(Arc, window::Id), ResizeWindow(window::Id), @@ -48,9 +49,14 @@ impl std::fmt::Debug for Action { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Action(arg0) => f.debug_tuple("Action").field(arg0).finish(), - Self::SetCursor(arg0) => { - f.debug_tuple("SetCursor").field(arg0).finish() + Self::SetCursor(arg0, arg1) => { + f.debug_tuple("SetCursor").field(arg0).field(arg1).finish() } + Self::SetCursorVisible(arg0, arg1) => f + .debug_tuple("SetCursorVisible") + .field(arg0) + .field(arg1) + .finish(), Self::RequestRedraw(arg0) => { f.debug_tuple("RequestRedraw").field(arg0).finish() } diff --git a/winit/src/platform_specific/wayland/winit_window.rs b/winit/src/platform_specific/wayland/winit_window.rs index b33b7f9e..2751b31c 100644 --- a/winit/src/platform_specific/wayland/winit_window.rs +++ b/winit/src/platform_specific/wayland/winit_window.rs @@ -83,7 +83,10 @@ impl winit::window::Window for SctkWinitWindow { fn set_cursor(&self, cursor: winit_core::cursor::Cursor) { match cursor { winit_core::cursor::Cursor::Icon(icon) => { - _ = self.tx.send(Action::SetCursor(icon)); + _ = self.tx.send(Action::SetCursor( + self.surface.wl_surface().id(), + icon, + )); } winit_core::cursor::Cursor::Custom(_) => { // TODO @@ -92,7 +95,10 @@ impl winit::window::Window for SctkWinitWindow { } fn set_cursor_visible(&self, visible: bool) { - // TODO + _ = self.tx.send(Action::SetCursorVisible( + self.surface.wl_surface().id(), + visible, + )); } fn surface_size(&self) -> winit::dpi::PhysicalSize { diff --git a/winit/src/window.rs b/winit/src/window.rs index e56483f4..18f5f41f 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -291,11 +291,11 @@ where pub fn update_mouse(&mut self, interaction: mouse::Interaction) { if interaction != self.mouse_interaction { if let Some(icon) = conversion::mouse_interaction(interaction) { - self.raw.set_cursor(icon.into()); - if self.mouse_interaction == mouse::Interaction::Hidden { self.raw.set_cursor_visible(true); } + + self.raw.set_cursor(icon.into()); } else { self.raw.set_cursor_visible(false); }