Pick with the mouse

Click a tile to pick it, scroll to move the selection. Hovering does not
move the selection: the keyboard keeps that, and a click acts on whatever
is under the cursor instead. A click on the margin, on a gap, or on an
empty cell of a ragged last row does nothing.

Tiles are already subsurfaces, so a click on a thumbnail identifies its
tile by which surface the event arrived on — no hit-testing needed. Only
the chrome around them (padding, labels, gaps) needs Layout::hit, which
inverts the same maths elem() lays out with, and is tested against it.

Picking waits for press and release on the same tile, so sliding off a
tile before letting go is not a pick.

wp_cursor_shape_v1 sets the pointer shape, which spares us a cursor theme
and libwayland-cursor; without it the cursor keeps whatever shape the
window underneath gave it. It is optional — a compositor without it just
gets whatever shape was already there.
This commit is contained in:
Milad Alizadeh
2026-08-23 20:25:57 +01:00
parent 8d8a27c4c1
commit 25d90bdcec
6 changed files with 209 additions and 9 deletions
+21
View File
@@ -33,12 +33,17 @@ use wayland_protocols::ext::image_capture_source::v1::client::{
ext_output_image_capture_source_manager_v1::ExtOutputImageCaptureSourceManagerV1,
};
use wayland_protocols::ext::image_copy_capture::v1::client::ext_image_copy_capture_manager_v1::ExtImageCopyCaptureManagerV1;
use wayland_protocols::wp::cursor_shape::v1::client::{
wp_cursor_shape_device_v1::WpCursorShapeDeviceV1,
wp_cursor_shape_manager_v1::WpCursorShapeManagerV1,
};
use wayland_protocols::wp::viewporter::client::{
wp_viewport::WpViewport, wp_viewporter::WpViewporter,
};
use wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::ZwlrLayerShellV1;
use crate::capture::{Live, Tile};
use crate::overlay;
use crate::shm;
use crate::target::Target;
use crate::text;
@@ -80,6 +85,16 @@ pub struct App {
pub(crate) sel: usize,
pub(crate) shift: bool,
/// Where the pointer is, and which tile it pressed. Hovering deliberately
/// does not move the keyboard selection; a click acts on what is under the
/// cursor instead.
pub(crate) hover: Option<overlay::Hover>,
pub(crate) pressed: Option<usize>,
/// Set the cursor shape without shipping a cursor theme. Optional: without
/// it the pointer keeps whatever shape it had over the window below.
pub(crate) cursor_shape: Option<WpCursorShapeManagerV1>,
pub(crate) cursor_device: Option<WpCursorShapeDeviceV1>,
pub(crate) labels: Option<text::Labels>,
pub(crate) surface: Option<WlSurface>,
pub(crate) chrome: Option<shm::Chrome>,
@@ -161,6 +176,10 @@ impl App {
scale,
sel: 0,
shift: false,
hover: None,
pressed: None,
cursor_shape: globals.bind(qh, 1..=2, ()).ok(),
cursor_device: None,
labels: None,
surface: None,
chrome: None,
@@ -323,4 +342,6 @@ delegate_noop!(App: ExtOutputImageCaptureSourceManagerV1);
delegate_noop!(App: ignore WlSurface);
// The chrome's own buffers: two slots alternating on keypresses, so their
// release timing does not matter.
delegate_noop!(App: WpCursorShapeManagerV1);
delegate_noop!(App: WpCursorShapeDeviceV1);
delegate_noop!(App: ignore WlBuffer);