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:
+113
-4
@@ -10,8 +10,10 @@ use std::os::fd::AsFd;
|
||||
|
||||
use wayland_client::protocol::{
|
||||
wl_keyboard::{self, WlKeyboard},
|
||||
wl_pointer::{self, WlPointer},
|
||||
wl_seat::{self, WlSeat},
|
||||
wl_shm,
|
||||
wl_surface::WlSurface,
|
||||
};
|
||||
use wayland_client::{Connection, Dispatch, QueueHandle, WEnum};
|
||||
use wayland_protocols_wlr::layer_shell::v1::client::{
|
||||
@@ -19,6 +21,8 @@ use wayland_protocols_wlr::layer_shell::v1::client::{
|
||||
zwlr_layer_surface_v1::{self, KeyboardInteractivity, ZwlrLayerSurfaceV1},
|
||||
};
|
||||
|
||||
use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::Shape;
|
||||
|
||||
use crate::app::{App, Ending};
|
||||
use crate::shm;
|
||||
use crate::theme::{Rect, fit_centred};
|
||||
@@ -44,6 +48,18 @@ const KEY_RIGHT: u32 = 106;
|
||||
const KEY_END: u32 = 107;
|
||||
const KEY_DOWN: u32 = 108;
|
||||
|
||||
/// evdev button code, as wl_pointer reports it.
|
||||
const BTN_LEFT: u32 = 0x110;
|
||||
|
||||
/// Where the pointer is: the surface it entered, and the position within it.
|
||||
/// Tiles are subsurfaces, so the surface alone usually names a tile; the
|
||||
/// position is only needed over the chrome around them.
|
||||
pub struct Hover {
|
||||
pub surface: WlSurface,
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Map the overlay: a layer surface sized to hug the grid, plus the shm the
|
||||
/// chrome is painted into.
|
||||
@@ -185,6 +201,36 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
/// The tile under the pointer, if it is over one. A tile's own subsurface
|
||||
/// answers directly; over the parent surface — padding, labels, gaps — the
|
||||
/// layout is asked instead.
|
||||
fn tile_at_pointer(&self) -> Option<usize> {
|
||||
let hover = self.hover.as_ref()?;
|
||||
let on_tile = self
|
||||
.tiles
|
||||
.iter()
|
||||
.position(|t| t.surface.as_ref() == Some(&hover.surface));
|
||||
on_tile.or_else(|| {
|
||||
(Some(&hover.surface) == self.surface.as_ref())
|
||||
.then(|| self.layout.hit(hover.x as i32, hover.y as i32))
|
||||
.flatten()
|
||||
})
|
||||
}
|
||||
|
||||
/// Press and release on the same tile picks it. Anywhere else — the margin,
|
||||
/// a gap, an empty cell of the last row — does nothing at all.
|
||||
fn click(&mut self, pressed: bool) {
|
||||
if pressed {
|
||||
self.pressed = self.tile_at_pointer();
|
||||
return;
|
||||
}
|
||||
let released = self.tile_at_pointer();
|
||||
if let Some(i) = self.pressed.take().filter(|i| Some(*i) == released) {
|
||||
self.picked = self.tiles.get(i).map(|t| t.target.clone());
|
||||
self.ending = Ending::Picked;
|
||||
}
|
||||
}
|
||||
|
||||
fn key(&mut self, code: u32) {
|
||||
match code {
|
||||
KEY_LEFTSHIFT | KEY_RIGHTSHIFT => self.shift = true,
|
||||
@@ -235,20 +281,29 @@ impl Dispatch<ZwlrLayerSurfaceV1, ()> for App {
|
||||
|
||||
impl Dispatch<WlSeat, ()> for App {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
app: &mut Self,
|
||||
seat: &WlSeat,
|
||||
event: wl_seat::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let wl_seat::Event::Capabilities {
|
||||
let wl_seat::Event::Capabilities {
|
||||
capabilities: WEnum::Value(caps),
|
||||
} = event
|
||||
&& caps.contains(wl_seat::Capability::Keyboard)
|
||||
{
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if caps.contains(wl_seat::Capability::Keyboard) {
|
||||
seat.get_keyboard(qh, ());
|
||||
}
|
||||
if caps.contains(wl_seat::Capability::Pointer) {
|
||||
let pointer = seat.get_pointer(qh, ());
|
||||
app.cursor_device = app
|
||||
.cursor_shape
|
||||
.as_ref()
|
||||
.map(|mgr| mgr.get_pointer(&pointer, qh, ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,3 +329,57 @@ impl Dispatch<WlKeyboard, ()> for App {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hovering does not move the selection — that belongs to the keyboard — so the
|
||||
/// pointer only tracks where it is and what it clicked. Scrolling is a
|
||||
/// deliberate gesture, so that does move the selection.
|
||||
impl Dispatch<WlPointer, ()> for App {
|
||||
fn event(
|
||||
app: &mut Self,
|
||||
_: &WlPointer,
|
||||
event: wl_pointer::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
wl_pointer::Event::Enter {
|
||||
serial,
|
||||
surface,
|
||||
surface_x,
|
||||
surface_y,
|
||||
} => {
|
||||
// A client owns the cursor over its own surfaces; without this
|
||||
// the pointer keeps whatever shape the window below gave it.
|
||||
if let Some(device) = &app.cursor_device {
|
||||
device.set_shape(serial, Shape::Default);
|
||||
}
|
||||
app.hover = Some(Hover {
|
||||
surface,
|
||||
x: surface_x,
|
||||
y: surface_y,
|
||||
});
|
||||
}
|
||||
wl_pointer::Event::Motion {
|
||||
surface_x,
|
||||
surface_y,
|
||||
..
|
||||
} => {
|
||||
if let Some(hover) = app.hover.as_mut() {
|
||||
(hover.x, hover.y) = (surface_x, surface_y);
|
||||
}
|
||||
}
|
||||
wl_pointer::Event::Leave { .. } => {
|
||||
app.hover = None;
|
||||
app.pressed = None;
|
||||
}
|
||||
wl_pointer::Event::Button {
|
||||
button: BTN_LEFT,
|
||||
state: WEnum::Value(state),
|
||||
..
|
||||
} => app.click(state == wl_pointer::ButtonState::Pressed),
|
||||
wl_pointer::Event::Axis { value, .. } => app.move_sel(if value > 0.0 { 1 } else { -1 }),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user