cursor: implement implicit grabs

When a button is held down and the cursor leaves a surface, events now
continue to be sent to the client. This allows e.g. dragging a scroll
bar from outside the surface.
This commit is contained in:
Isaac Freund 2020-08-07 13:54:58 +02:00
parent 91052f7477
commit 0c4e3295b1
No known key found for this signature in database
GPG key ID: 86DED400DDFD7A11
2 changed files with 55 additions and 29 deletions

View file

@ -42,52 +42,66 @@ const ResizeData = struct {
const Mode = union(enum) {
passthrough: void,
down: *View,
move: *View,
resize: ResizeData,
/// Enter move or resize mode
fn enter(self: *Self, mode: @TagType(Mode), event: *c.wlr_event_pointer_button, view: *View) void {
std.debug.assert(self.mode == .passthrough);
log.debug(.cursor, "enter {} mode", .{@tagName(mode)});
const cur_box = &view.current.box;
self.mode = switch (mode) {
switch (mode) {
.passthrough => unreachable,
.move => .{ .move = view },
.resize => .{
.resize = .{
.view = view,
.x_offset = cur_box.x + @intCast(i32, cur_box.width) - @floatToInt(i32, self.wlr_cursor.x),
.y_offset = cur_box.y + @intCast(i32, cur_box.height) - @floatToInt(i32, self.wlr_cursor.y),
},
.down => self.mode = .{ .down = view },
.move, .resize => {
const cur_box = &view.current.box;
self.mode = switch (mode) {
.passthrough, .down => unreachable,
.move => .{ .move = view },
.resize => .{
.resize = .{
.view = view,
.x_offset = cur_box.x + @intCast(i32, cur_box.width) - @floatToInt(i32, self.wlr_cursor.x),
.y_offset = cur_box.y + @intCast(i32, cur_box.height) - @floatToInt(i32, self.wlr_cursor.y),
},
},
};
// Automatically float all views being moved by the pointer
if (!view.current.float) {
view.pending.float = true;
// Start a transaction to apply the pending state of the grabbed
// view and rearrange the layout to fill the hole.
view.output.root.arrange();
}
// Clear cursor focus, so that the surface does not receive events
c.wlr_seat_pointer_clear_focus(self.seat.wlr_seat);
c.wlr_xcursor_manager_set_cursor_image(
self.wlr_xcursor_manager,
if (mode == .move) "move" else "se-resize",
self.wlr_cursor,
);
},
};
// Automatically float all views being moved by the pointer
if (!view.current.float) {
view.pending.float = true;
// Start a transaction to apply the pending state of the grabbed
// view and rearrange the layout to fill the hole.
view.output.root.arrange();
}
// Clear cursor focus, so that the surface does not receive events
c.wlr_seat_pointer_clear_focus(self.seat.wlr_seat);
c.wlr_xcursor_manager_set_cursor_image(
self.wlr_xcursor_manager,
if (mode == .move) "move" else "se-resize",
self.wlr_cursor,
);
}
/// Return from move/resize to passthrough
/// Return from down/move/resize to passthrough
fn leave(self: *Self, event: *c.wlr_event_pointer_button) void {
std.debug.assert(self.mode != .passthrough);
log.debug(.cursor, "leave {} mode", .{@tagName(self.mode)});
// If we were in down mode, we need pass along the release event
if (self.mode == .down)
_ = c.wlr_seat_pointer_notify_button(
self.seat.wlr_seat,
event.time_msec,
event.button,
event.state,
);
self.mode = .passthrough;
passthrough(self, event.time_msec);
}
@ -100,6 +114,15 @@ const Mode = union(enum) {
c.wlr_cursor_move(self.wlr_cursor, device, delta_x, delta_y);
passthrough(self, time);
},
.down => |view| {
c.wlr_cursor_move(self.wlr_cursor, device, delta_x, delta_y);
c.wlr_seat_pointer_notify_motion(
self.seat.wlr_seat,
time,
self.wlr_cursor.x - @intToFloat(f64, view.current.box.x),
self.wlr_cursor.y - @intToFloat(f64, view.current.box.y),
);
},
.move => |view| {
var output_width: c_int = undefined;
var output_height: c_int = undefined;
@ -364,6 +387,8 @@ fn handleButton(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
else => {},
}
return;
} else {
Mode.enter(self, .down, event, view);
}
}
}

View file

@ -108,6 +108,7 @@ pub fn isCursorActionTarget(self: Self, view: *View) bool {
const seat = &node.data;
switch (seat.cursor.mode) {
.passthrough => {},
.down => |target_view| if (target_view == view) break true,
.move => |target_view| if (target_view == view) break true,
.resize => |data| if (data.view == view) break true,
}