Skip to content

Commit

Permalink
add pointer location hint (#1551)
Browse files Browse the repository at this point in the history
* add pointer location hint

* describe why `PointerConstraint::commit()` returns a point

* implement pointer location hint in anvil

* set_location_hint -> set_location

* add note that you should frequently be using `PointerHandle::motion`
  • Loading branch information
sodiboo authored Sep 27, 2024
1 parent 3b0ecce commit debffed
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 9 deletions.
23 changes: 23 additions & 0 deletions anvil/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,29 @@ impl<BackendData: Backend> PointerConstraintsHandler for AnvilState<BackendData>
});
}
}

fn cursor_position_hint(
&mut self,
surface: &WlSurface,
pointer: &PointerHandle<Self>,
location: Point<f64, Logical>,
) {
if with_pointer_constraint(surface, pointer, |constraint| {
constraint.map_or(false, |c| c.is_active())
}) {
let origin = self
.space
.elements()
.find_map(|window| {
(window.wl_surface().as_deref() == Some(surface)).then(|| window.geometry())
})
.unwrap_or_default()
.loc
.to_f64();

pointer.set_location(origin + location);
}
}
}
delegate_pointer_constraints!(@<BackendData: Backend + 'static> AnvilState<BackendData>);

Expand Down
20 changes: 20 additions & 0 deletions src/input/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,26 @@ impl<D: SeatHandler + 'static> PointerHandle<D> {
self.inner.lock().unwrap().location
}

/// Update the current location of this pointer in the global space,
/// without sending any event and without updating the focus.
///
/// If you want to update the location, and update the focus,
/// and send events, use [Self::motion] instead of this.
///
/// This is useful when the pointer is only moved by relative events,
/// such as when a pointer lock is held by the focused surface.
/// The client can give us a cursor position hint, which corresponds to
/// the actual location the client may be rendering a pointer at.
/// This position hint should be used as the initial location
/// when the pointer lock is deactivated.
///
/// The next time [Self::motion] is called, the focus will be
/// updated accordingly as if this function was never called.
/// Clients will never be notified of a location hint.
pub fn set_location(&self, location: Point<f64, Logical>) {
self.inner.lock().unwrap().location = location;
}

/// Access the [`Serial`] of the last `pointer_enter` event, if that focus is still active.
///
/// In other words this will return `None` again, once a `pointer_leave` event occurred.
Expand Down
50 changes: 41 additions & 9 deletions src/wayland/pointer_constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ pub trait PointerConstraintsHandler: SeatHandler {
///
/// Use [`with_pointer_constraint`] to access the constraint.
fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<Self>);

/// The client holding a LockedPointer has commited a cursor position hint.
///
/// This is emitted upon a surface commit if the cursor position hint has been updated.
///
/// Use [`with_pointer_constraint`] to access the constraint and check if it is active.
fn cursor_position_hint(
&mut self,
surface: &WlSurface,
pointer: &PointerHandle<Self>,
location: Point<f64, Logical>,
);
}

/// Constraint confining pointer to a region of the surface
Expand Down Expand Up @@ -171,14 +183,19 @@ impl PointerConstraint {
}
}

fn commit(&mut self) {
/// Commits the pending state of the constraint, and returns the cursor position hint if it has changed.
fn commit(&mut self) -> Option<Point<f64, Logical>> {
match self {
Self::Confined(confined) => {
confined.region.clone_from(&confined.pending_region);
None
}
Self::Locked(locked) => {
locked.region.clone_from(&locked.pending_region);
locked.cursor_position_hint = locked.pending_cursor_position_hint;
locked.pending_cursor_position_hint.take().map(|hint| {
locked.cursor_position_hint = Some(hint);
hint
})
}
}
}
Expand Down Expand Up @@ -243,13 +260,28 @@ pub fn with_pointer_constraint<
})
}

fn commit_hook<D: SeatHandler + 'static>(_: &mut D, _dh: &DisplayHandle, surface: &WlSurface) {
with_constraint_data::<D, _, _>(surface, |data| {
fn commit_hook<D: SeatHandler + PointerConstraintsHandler + 'static>(
state: &mut D,
_dh: &DisplayHandle,
surface: &WlSurface,
) {
// `with_constraint_data` locks the pointer constraints,
// so we collect the hints first into a Vec, then release the mutex
// and only once the mutex is released, we call the handler method.
//
// This is to avoid deadlocks when the handler method might try to access the constraints again.
// It's not a hypothetical, it bit me while implementing the position hint functionality.
let position_hints = with_constraint_data::<D, _, _>(surface, |data| {
let data = data.unwrap();
for constraint in data.constraints.values_mut() {
constraint.commit();
}
})
data.constraints
.iter_mut()
.filter_map(|(pointer, constraint)| constraint.commit().map(|hint| (pointer.clone(), hint)))
.collect::<Vec<_>>()
});

for (pointer, hint) in position_hints {
state.cursor_position_hint(surface, &pointer, hint);
}
}

/// Get `PointerConstraintData` associated with a surface, if any.
Expand All @@ -272,7 +304,7 @@ fn with_constraint_data<
}

/// Add constraint for surface, or raise protocol error if one exists
fn add_constraint<D: SeatHandler + 'static>(
fn add_constraint<D: SeatHandler + PointerConstraintsHandler + 'static>(
pointer_constraints: &ZwpPointerConstraintsV1,
surface: &WlSurface,
pointer: &PointerHandle<D>,
Expand Down

0 comments on commit debffed

Please sign in to comment.