Commit e32c28d90a3d
tarball4 files changed+140-10
@@ -55,6 +55,14 @@ impl Cursor {
5555
CursorIcon::Progress,5656
CursorIcon::EwResize,5757
CursorIcon::NsResize,58
+ CursorIcon::NResize,59
+ CursorIcon::SResize,60
+ CursorIcon::EResize,61
+ CursorIcon::WResize,62
+ CursorIcon::NwResize,63
+ CursorIcon::NeResize,64
+ CursorIcon::SwResize,65
+ CursorIcon::SeResize,5866
] {5967
cursor.image(icon);6068
}@@ -43,11 +43,9 @@ pub fn compose(
4343
if area.contains(pos.x, pos.y) {4444
let local = pos - Point::from((area.x, area.y));4545
if cursor {46
- elements.extend(ctx.input.cursor.elements(47
- ctx.renderer,48
- &ctx.input.cursor_status,49
- local,50
- ));46
+ let resize = ctx.input.resize_icon().map(CursorImageStatus::Named);47
+ let status = resize.as_ref().unwrap_or(&ctx.input.cursor_status);48
+ elements.extend(ctx.input.cursor.elements(ctx.renderer, status, local));5149
}5250
if let Some(icon) = &ctx.input.dnd_icon {5351
elements.extend(icon.elements(ctx.renderer, local));@@ -1,12 +1,12 @@
11
//! Input handling shared by every backend. Backends translate their events22
//! into these calls, with pointer positions in global logical coordinates.33
4
-use crate::state::State;4
+use crate::state::{Input, State};55
use monolith_layout::{Axis, Dir, Layout, MIN_FLOAT_SIZE, Rect, WindowId, clamp_float};66
use smithay::backend::input::{ButtonState, InputTime, KeyState};77
use smithay::backend::session::Session;88
use smithay::input::keyboard::{FilterResult, Keycode, xkb};9
-use smithay::input::pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent};9
+use smithay::input::pointer::{AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, MotionEvent};1010
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;1111
use smithay::reexports::wayland_server::Resource;1212
use smithay::utils::{Logical, Point, SERIAL_COUNTER};@@ -26,6 +26,10 @@ impl State {
2626
focus_on_hover(self, pos, self.input.warping);2727
let under = self.surface_under(pos);2828
let before = pointer.current_focus().and_then(|s| s.client());29
+ let hover = self.edge_at(pos, under.is_some()).map(|(_, e)| e);30
+ if std::mem::replace(&mut self.input.hover, hover) != hover {31
+ self.dirty.cursor = true;32
+ }2933
pointer.motion(3034
self,3135
under,@@ -49,6 +53,12 @@ impl State {
4953
.input5054
.drag5155
.take_if(|d| d.button.is_none_or(|b| b == button));56
+ if ended.is_some() {57
+ let pos = self.input.pointer_pos;58
+ let over = self.surface_under(pos).is_some();59
+ self.input.hover = self.edge_at(pos, over).map(|(_, e)| e);60
+ self.dirty.cursor = true;61
+ }5262
if ended.is_some_and(|d| d.button.is_none()) {5363
return;5464
}@@ -302,6 +312,42 @@ impl Edges {
302312
self.left || self.right || self.top || self.bottom303313
}304314
315
+ /// The edges whose ring of width hw around r holds pos, a corner when316
+ /// pos is near the end of a side. None inside r or beyond the ring.317
+ fn in_ring(r: Rect, pos: Point<f64, Logical>, hw: f64) -> Option<Self> {318
+ let (left, right) = (pos.x < r.x, pos.x >= r.right());319
+ let (top, bottom) = (pos.y < r.y, pos.y >= r.bottom());320
+ let beyond = pos.x < r.x - hw321
+ || pos.x >= r.right() + hw322
+ || pos.y < r.y - hw323
+ || pos.y >= r.bottom() + hw;324
+ if beyond || !(left || right || top || bottom) {325
+ return None;326
+ }327
+ let (cx, cy) = ((2.0 * hw).min(r.w / 2.0), (2.0 * hw).min(r.h / 2.0));328
+ let sideways = (left || right) && !(top || bottom);329
+ let lengthwise = (top || bottom) && !(left || right);330
+ Some(Self {331
+ left: left || (lengthwise && pos.x < r.x + cx),332
+ right: right || (lengthwise && pos.x >= r.right() - cx),333
+ top: top || (sideways && pos.y < r.y + cy),334
+ bottom: bottom || (sideways && pos.y >= r.bottom() - cy),335
+ })336
+ }337
+338
+ pub fn icon(self) -> CursorIcon {339
+ match (self.left, self.right, self.top, self.bottom) {340
+ (true, _, true, _) => CursorIcon::NwResize,341
+ (_, true, true, _) => CursorIcon::NeResize,342
+ (true, _, _, true) => CursorIcon::SwResize,343
+ (_, true, _, true) => CursorIcon::SeResize,344
+ (true, ..) => CursorIcon::WResize,345
+ (_, true, ..) => CursorIcon::EResize,346
+ (_, _, true, _) => CursorIcon::NResize,347
+ _ => CursorIcon::SResize,348
+ }349
+ }350
+305351
pub fn from_xdg(edge: xdg_toplevel::ResizeEdge) -> Option<Self> {306352
use xdg_toplevel::ResizeEdge as E;307353
let (left, right, top, bottom) = match edge {@@ -358,6 +404,70 @@ pub struct Drag {
358404
button: Option<u32>,359405
}360406
407
+impl Input {408
+ /// The resize cursor to show over the client's own, if any.409
+ pub fn resize_icon(&self) -> Option<CursorIcon> {410
+ let edges = match &self.drag {411
+ Some(d) => d.edges,412
+ None => self.hover,413
+ };414
+ edges.map(Edges::icon)415
+ }416
+}417
+418
+impl State {419
+ /// The window and edges of a resize grab at pos. Rings sit just outside420
+ /// windows, so the client's own surface is never taken over, except421
+ /// that a floating window's ring wins over a tile it overlaps.422
+ fn edge_at(&self, pos: Point<f64, Logical>, over_surface: bool) -> Option<(WindowId, Edges)> {423
+ if self.services.lock.active() {424
+ return None;425
+ }426
+ let layout = &self.wm.layout;427
+ let (o, out) = layout428
+ .outputs()429
+ .find(|(_, out)| out.area.contains(pos.x, pos.y))?;430
+ let floats = !out.floating.is_empty()431
+ || layout432
+ .scratchpads()433
+ .iter()434
+ .any(|s| s.shown.is_some() && s.output == Some(o));435
+ if over_surface && !floats {436
+ return None;437
+ }438
+ let arr = layout.arrange(o);439
+ if arr.fullscreen.is_some() {440
+ return None;441
+ }442
+ let hw = f64::from(self.settings.config.decoration.border.max(4) + 4);443
+ for &(w, r) in arr.floating.iter().rev() {444
+ if r.contains(pos.x, pos.y) {445
+ return None;446
+ }447
+ if let Some(e) = Edges::in_ring(r, pos, hw) {448
+ return Some((w, e));449
+ }450
+ }451
+ if over_surface {452
+ return None;453
+ }454
+ let distance = |r: Rect| {455
+ let dx = (r.x - pos.x).max(pos.x - r.right()).max(0.0);456
+ let dy = (r.y - pos.y).max(pos.y - r.bottom()).max(0.0);457
+ dx.hypot(dy)458
+ };459
+ arr.tiled460
+ .iter()461
+ .filter(|p| p.visible)462
+ .filter_map(|p| {463
+ let e = Edges::in_ring(p.rect, pos, hw)?.movable(layout, p.window);464
+ e.any().then_some((p.window, e, distance(p.rect)))465
+ })466
+ .min_by(|a, b| a.2.total_cmp(&b.2))467
+ .map(|(w, e, _)| (w, e))468
+ }469
+}470
+361471
fn start_drag(state: &mut State, button: u32) -> bool {362472
if button != BTN_LEFT && button != BTN_RIGHT {363473
return false;@@ -368,12 +478,23 @@ fn start_drag(state: &mut State, button: u32) -> bool {
368478
.get_keyboard()369479
.map(|k| k.modifier_state())370480
.is_some_and(|m| crate::commands::held(&m).has(state.settings.config.general.mod_key));481
+ if !held {482
+ let pos = state.input.pointer_pos;483
+ let over = state.surface_under(pos).is_some();484
+ let Some((id, edges)) = state.edge_at(pos, over).filter(|_| button == BTN_LEFT) else {485
+ return false;486
+ };487
+ if state.wm.layout.focused() != Some(id) {488
+ state.wm.layer_focus = None;489
+ state.input.followed = Some(id);490
+ state.wm.layout.focus(id);491
+ state.apply_layout();492
+ }493
+ return begin_drag(state, id, Some(edges), None);494
+ }371495
let Some(id) = state.wm.layout.focused() else {372496
return false;373497
};374
- if !held {375
- return false;376
- }377498
let edges = match button {378499
BTN_RIGHT => {379500
let Some(rect) = window_rect(&state.wm.layout, id) else {@@ -60,6 +60,8 @@ pub struct Input {
6060
pub cursor: crate::cursor::Cursor,6161
pub cursor_status: CursorImageStatus,6262
pub dnd_icon: Option<crate::cursor::DndIcon>,63
+ /// The edges of the window border under the pointer, for the resize cursor.64
+ pub hover: Option<crate::input::Edges>,6365
}6466
6567
/// What needs doing after this batch of events.@@ -191,6 +193,7 @@ impl State {
191193
cursor: crate::cursor::Cursor::load(),192194
cursor_status: CursorImageStatus::default_named(),193195
dnd_icon: None,196
+ hover: None,194197
},195198
dirty: Dirty {196199
redraw: true,