Commit 846bdd33d84f
tarball6 files changed+450-437
@@ -0,0 +1,440 @@
1
+//! Mouse-driven moves and resizes, from the rings around floating windows2
+//! to the splits between tiles.3
+4
+use crate::state::{Input, State};5
+use crate::wm::Resizing;6
+use monolith_layout::{Dir, Layout, MIN_FLOAT_SIZE, Rect, WindowId, clamp_float};7
+use smithay::input::pointer::CursorIcon;8
+use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;9
+use smithay::utils::{Logical, Point};10
+use smithay::xwayland::xwm::ResizeEdge;11
+12
+const BTN_LEFT: u32 = 0x110;13
+const BTN_RIGHT: u32 = 0x111;14
+15
+/// The sides of a window a resize moves.16
+#[derive(Clone, Copy, Default, PartialEq, Eq)]17
+pub struct Edges {18
+ left: bool,19
+ right: bool,20
+ top: bool,21
+ bottom: bool,22
+}23
+24
+impl Edges {25
+ /// The edges a drag from pos resizes, from the cell of a 3x3 grid over26
+ /// the rect. The centre cell takes the nearest corner.27
+ fn grid(r: Rect, pos: Point<f64, Logical>) -> Self {28
+ let (fx, fy) = ((pos.x - r.x) / r.w, (pos.y - r.y) / r.h);29
+ let third = |f: f64| match f {30
+ f if f < 1.0 / 3.0 => -1,31
+ f if f > 2.0 / 3.0 => 1,32
+ _ => 0,33
+ };34
+ let (col, row) = match (third(fx), third(fy)) {35
+ (0, 0) => (if fx < 0.5 { -1 } else { 1 }, if fy < 0.5 { -1 } else { 1 }),36
+ cell => cell,37
+ };38
+ Self {39
+ left: col < 0,40
+ right: col > 0,41
+ top: row < 0,42
+ bottom: row > 0,43
+ }44
+ }45
+46
+ fn dirs(self) -> impl Iterator<Item = Dir> {47
+ [48
+ (Dir::Left, self.left),49
+ (Dir::Right, self.right),50
+ (Dir::Up, self.top),51
+ (Dir::Down, self.bottom),52
+ ]53
+ .into_iter()54
+ .filter_map(|(dir, on)| on.then_some(dir))55
+ }56
+57
+ /// Drops the edges of a tiled window that have no split to move.58
+ fn movable(self, layout: &Layout, id: WindowId) -> Self {59
+ let has = |dir, on: bool| on && layout.has_split(id, dir);60
+ Self {61
+ left: has(Dir::Left, self.left),62
+ right: has(Dir::Right, self.right),63
+ top: has(Dir::Up, self.top),64
+ bottom: has(Dir::Down, self.bottom),65
+ }66
+ }67
+68
+ fn any(self) -> bool {69
+ self.left || self.right || self.top || self.bottom70
+ }71
+72
+ /// The edges whose ring of width hw around r holds pos, a corner when73
+ /// pos is near the end of a side. None inside r or beyond the ring.74
+ fn in_ring(r: Rect, pos: Point<f64, Logical>, hw: f64) -> Option<Self> {75
+ let (left, right) = (pos.x < r.x, pos.x >= r.right());76
+ let (top, bottom) = (pos.y < r.y, pos.y >= r.bottom());77
+ let beyond = pos.x < r.x - hw78
+ || pos.x >= r.right() + hw79
+ || pos.y < r.y - hw80
+ || pos.y >= r.bottom() + hw;81
+ if beyond || !(left || right || top || bottom) {82
+ return None;83
+ }84
+ let (cx, cy) = ((2.0 * hw).min(r.w / 2.0), (2.0 * hw).min(r.h / 2.0));85
+ let sideways = (left || right) && !(top || bottom);86
+ let lengthwise = (top || bottom) && !(left || right);87
+ Some(Self {88
+ left: left || (lengthwise && pos.x < r.x + cx),89
+ right: right || (lengthwise && pos.x >= r.right() - cx),90
+ top: top || (sideways && pos.y < r.y + cy),91
+ bottom: bottom || (sideways && pos.y >= r.bottom() - cy),92
+ })93
+ }94
+95
+ pub fn icon(self) -> CursorIcon {96
+ match (self.left, self.right, self.top, self.bottom) {97
+ (true, _, true, _) => CursorIcon::NwResize,98
+ (_, true, true, _) => CursorIcon::NeResize,99
+ (true, _, _, true) => CursorIcon::SwResize,100
+ (_, true, _, true) => CursorIcon::SeResize,101
+ (true, ..) => CursorIcon::WResize,102
+ (_, true, ..) => CursorIcon::EResize,103
+ (_, _, true, _) => CursorIcon::NResize,104
+ _ => CursorIcon::SResize,105
+ }106
+ }107
+108
+ pub fn from_xdg(edge: xdg_toplevel::ResizeEdge) -> Option<Self> {109
+ use xdg_toplevel::ResizeEdge as E;110
+ let (left, right, top, bottom) = match edge {111
+ E::Top => (false, false, true, false),112
+ E::Bottom => (false, false, false, true),113
+ E::Left => (true, false, false, false),114
+ E::Right => (false, true, false, false),115
+ E::TopLeft => (true, false, true, false),116
+ E::TopRight => (false, true, true, false),117
+ E::BottomLeft => (true, false, false, true),118
+ E::BottomRight => (false, true, false, true),119
+ _ => return None,120
+ };121
+ Some(Self {122
+ left,123
+ right,124
+ top,125
+ bottom,126
+ })127
+ }128
+129
+ pub fn from_x11(edge: ResizeEdge) -> Self {130
+ use xdg_toplevel::ResizeEdge as E;131
+ Self::from_xdg(match edge {132
+ ResizeEdge::Top => E::Top,133
+ ResizeEdge::Bottom => E::Bottom,134
+ ResizeEdge::Left => E::Left,135
+ ResizeEdge::Right => E::Right,136
+ ResizeEdge::TopLeft => E::TopLeft,137
+ ResizeEdge::TopRight => E::TopRight,138
+ ResizeEdge::BottomLeft => E::BottomLeft,139
+ ResizeEdge::BottomRight => E::BottomRight,140
+ })141
+ .unwrap_or_default()142
+ }143
+}144
+145
+/// What the pointer is over. Layer surfaces, popups and X11 menus keep the146
+/// resize rings around windows from taking their clicks.147
+#[derive(Clone, Copy, PartialEq, Eq)]148
+pub enum Cover {149
+ Window,150
+ Other,151
+}152
+153
+/// A floating window being moved or resized, either with Mod and a mouse154
+/// button or because its client asked while holding `button`.155
+pub struct Drag {156
+ window: WindowId,157
+ from: Point<f64, Logical>,158
+ rect: Rect,159
+ bounds: Rect,160
+ /// A tiled window, whose splits follow the pointer instead of a rect.161
+ tiled: bool,162
+ /// None moves the window.163
+ edges: Option<Edges>,164
+ /// The button the drag started with, whose release ends it.165
+ pub(crate) button: u32,166
+ /// Whether the client asked for the drag and so still sees the release.167
+ pub(crate) client: bool,168
+}169
+170
+impl Input {171
+ /// The resize cursor to show over the client's own, if any.172
+ pub fn resize_icon(&self) -> Option<CursorIcon> {173
+ let edges = match &self.drag {174
+ Some(d) => d.edges,175
+ None => self.hover,176
+ };177
+ edges.map(Edges::icon)178
+ }179
+}180
+181
+impl State {182
+ /// The window and edges of a resize grab at pos. Rings sit just outside183
+ /// windows, so the client's own surface is never taken over, except184
+ /// that a floating window's ring wins over a tile it overlaps.185
+ pub(crate) fn edge_at(186
+ &self,187
+ pos: Point<f64, Logical>,188
+ cover: Option<Cover>,189
+ ) -> Option<(WindowId, Edges)> {190
+ if self.services.lock.active() || cover == Some(Cover::Other) {191
+ return None;192
+ }193
+ let over_surface = cover.is_some();194
+ let layout = &self.wm.layout;195
+ let (o, out) = layout196
+ .outputs()197
+ .find(|(_, out)| out.area.contains(pos.x, pos.y))?;198
+ let floats = !out.floating.is_empty()199
+ || layout200
+ .scratchpads()201
+ .iter()202
+ .any(|s| s.shown.is_some() && s.output == Some(o));203
+ if over_surface && !floats {204
+ return None;205
+ }206
+ let arr = layout.arrange(o);207
+ if arr.fullscreen.is_some() {208
+ return None;209
+ }210
+ let hw = f64::from(self.settings.config.decoration.border.max(4) + 4);211
+ for &(w, r) in arr.floating.iter().rev() {212
+ if r.contains(pos.x, pos.y) {213
+ return None;214
+ }215
+ if let Some(e) = Edges::in_ring(r, pos, hw) {216
+ return Some((w, e));217
+ }218
+ }219
+ if over_surface {220
+ return None;221
+ }222
+ let distance = |r: Rect| {223
+ let dx = (r.x - pos.x).max(pos.x - r.right()).max(0.0);224
+ let dy = (r.y - pos.y).max(pos.y - r.bottom()).max(0.0);225
+ dx.hypot(dy)226
+ };227
+ arr.tiled228
+ .iter()229
+ .filter(|p| p.visible)230
+ .filter_map(|p| {231
+ let e = Edges::in_ring(p.rect, pos, hw)?.movable(layout, p.window);232
+ e.any().then_some((p.window, e, distance(p.rect)))233
+ })234
+ .min_by(|a, b| a.2.total_cmp(&b.2))235
+ .map(|(w, e, _)| (w, e))236
+ }237
+}238
+239
+pub(crate) fn start_drag(state: &mut State, button: u32) -> bool {240
+ if button != BTN_LEFT && button != BTN_RIGHT {241
+ return false;242
+ }243
+ let held = !state.shortcuts_inhibited()244
+ && state245
+ .input246
+ .seat247
+ .get_keyboard()248
+ .map(|k| k.modifier_state())249
+ .is_some_and(|m| crate::commands::held(&m).has(state.settings.config.general.mod_key));250
+ if !held {251
+ let pos = state.input.pointer_pos;252
+ let cover = state.cover_under(pos).map(|c| c.2);253
+ let Some((id, edges)) = state.edge_at(pos, cover).filter(|_| button == BTN_LEFT) else {254
+ return false;255
+ };256
+ if !state.wm.layout.is_focused(id) {257
+ state.wm.layer_focus = None;258
+ state.input.followed = state.wm.layout.focus(id);259
+ state.apply_layout();260
+ }261
+ return begin_drag(state, id, Some(edges), button, false);262
+ }263
+ let Some(id) = state.wm.layout.focused() else {264
+ return false;265
+ };266
+ let edges = match button {267
+ BTN_RIGHT => {268
+ let Some(rect) = window_rect(&state.wm.layout, id) else {269
+ return false;270
+ };271
+ Some(Edges::grid(rect, state.input.pointer_pos))272
+ }273
+ _ => None,274
+ };275
+ begin_drag(state, id, edges, button, false)276
+}277
+278
+fn window_rect(layout: &Layout, id: WindowId) -> Option<Rect> {279
+ let win = layout.window(id)?;280
+ if win.floating {281
+ return win.float_rect;282
+ }283
+ layout284
+ .arrange(win.output?)285
+ .tiled286
+ .iter()287
+ .find(|p| p.window == id && p.visible)288
+ .map(|p| p.rect)289
+}290
+291
+impl State {292
+ pub fn forget_drag(&mut self, id: WindowId) {293
+ self.input.drag.take_if(|d| d.window == id);294
+ self.wm.resizing.take_if(|r| r.window == id);295
+ }296
+297
+ /// Starts moving, or with `edges` resizing, a floating window at a298
+ /// client's request. Tiled windows are left alone.299
+ pub fn client_drag(&mut self, id: WindowId, edges: Option<Edges>, button: u32) {300
+ if self.wm.layout.window(id).is_some_and(|w| w.floating) {301
+ begin_drag(self, id, edges, button, true);302
+ }303
+ }304
+}305
+306
+fn begin_drag(307
+ state: &mut State,308
+ id: WindowId,309
+ edges: Option<Edges>,310
+ button: u32,311
+ client: bool,312
+) -> bool {313
+ let layout = &state.wm.layout;314
+ let Some(win) = layout.window(id).filter(|w| !w.fullscreen) else {315
+ return false;316
+ };317
+ let tiled = !win.floating;318
+ let edges = if tiled {319
+ edges.map(|e| e.movable(layout, id)).filter(|e| e.any())320
+ } else {321
+ edges322
+ };323
+ if tiled && edges.is_none() {324
+ return false;325
+ }326
+ let (Some(rect), Some(out)) = (327
+ window_rect(layout, id),328
+ win.output.and_then(|o| layout.output(o)),329
+ ) else {330
+ return false;331
+ };332
+ state.wm.resizing = win333
+ .output334
+ .filter(|_| edges.is_some())335
+ .map(|output| Resizing {336
+ output,337
+ window: id,338
+ tiled,339
+ });340
+ state.input.drag = Some(Drag {341
+ window: id,342
+ from: state.input.pointer_pos,343
+ rect,344
+ bounds: out.usable(),345
+ tiled,346
+ edges,347
+ button,348
+ client,349
+ });350
+ true351
+}352
+353
+/// Moves the dragged edges of an axis, keeping them between lo and hi unless354
+/// the window started beyond them, and the size between `limits`, whose355
+/// minimum wins over its maximum.356
+fn resized(357
+ start: f64,358
+ len: f64,359
+ delta: f64,360
+ edges: (bool, bool),361
+ (lo, hi): (f64, f64),362
+ (min, max): (f64, f64),363
+) -> (f64, f64) {364
+ let end = start + len;365
+ match edges {366
+ (true, _) => {367
+ let edge = (start + delta)368
+ .max(lo.min(start))369
+ .max(end - max)370
+ .min(end - min);371
+ (edge, end - edge)372
+ }373
+ (_, true) => {374
+ let edge = (end + delta)375
+ .min(hi.max(end))376
+ .min(start + max)377
+ .max(start + min);378
+ (start, edge - start)379
+ }380
+ _ => (start, len),381
+ }382
+}383
+384
+pub(crate) fn drag_to(state: &mut State, pos: Point<f64, Logical>) -> bool {385
+ let Some(d) = &state.input.drag else {386
+ return false;387
+ };388
+ let (dx, dy) = (pos.x - d.from.x, pos.y - d.from.y);389
+ if let (true, Some(e)) = (d.tiled, d.edges) {390
+ let (id, start) = (d.window, d.rect);391
+ for dir in e.dirs() {392
+ let Some(now) = window_rect(&state.wm.layout, id) else {393
+ continue;394
+ };395
+ let (at, was, offset) = match dir {396
+ Dir::Left => (now.x, start.x, dx),397
+ Dir::Right => (now.right(), start.right(), dx),398
+ Dir::Up => (now.y, start.y, dy),399
+ Dir::Down => (now.bottom(), start.bottom(), dy),400
+ };401
+ state.wm.layout.move_split(id, dir, was + offset - at);402
+ }403
+ state.apply_layout();404
+ return true;405
+ }406
+ let r = d.rect;407
+ let rect = match d.edges {408
+ Some(e) => {409
+ let b = d.bounds;410
+ let rule = state.wm.get(d.window).map(|s| &s.rule);411
+ let (min_w, min_h) = rule.map_or((0.0, 0.0), |r| r.clamp_size(0.0, 0.0));412
+ let (max_w, max_h) = rule.map_or((f64::INFINITY, f64::INFINITY), |r| {413
+ r.clamp_size(f64::INFINITY, f64::INFINITY)414
+ });415
+ let min = (min_w.max(MIN_FLOAT_SIZE), min_h.max(MIN_FLOAT_SIZE));416
+ let (x, w) = resized(417
+ r.x,418
+ r.w,419
+ dx,420
+ (e.left, e.right),421
+ (b.x, b.right()),422
+ (min.0, max_w),423
+ );424
+ let (y, h) = resized(425
+ r.y,426
+ r.h,427
+ dy,428
+ (e.top, e.bottom),429
+ (b.y, b.bottom()),430
+ (min.1, max_h),431
+ );432
+ Rect::new(x, y, w, h)433
+ }434
+ None => clamp_float(Rect::new(r.x + dx, r.y + dy, r.w, r.h), d.bounds),435
+ };436
+ let id = d.window;437
+ state.wm.layout.set_float_rect(id, rect);438
+ state.apply_layout();439
+ true440
+}@@ -2,21 +2,19 @@
22
//! into these calls, with pointer positions in global logical coordinates.33
44
use crate::constraints::Constraint;5
-use crate::state::{Input, State};6
-use crate::wm::Resizing;7
-use monolith_layout::{Dir, Layout, MIN_FLOAT_SIZE, Rect, WindowId, clamp_float};5
+use crate::drag::{drag_to, start_drag};6
+use crate::state::State;7
+use monolith_layout::WindowId;88
use smithay::backend::input::{ButtonState, InputTime, KeyState};99
use smithay::backend::session::Session;1010
use smithay::input::keyboard::{FilterResult, Keycode, xkb};1111
use smithay::input::pointer::{12
- AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, MotionEvent, RelativeMotionEvent,12
+ AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent, RelativeMotionEvent,1313
};14
-use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;1514
use smithay::reexports::wayland_server::Resource;1615
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;1716
use smithay::utils::{Logical, Point, SERIAL_COUNTER};1817
use smithay::wayland::shell::wlr_layer::Layer;19
-use smithay::xwayland::xwm::ResizeEdge;2018
2119
/// How the pointer got to where a motion wants it.2220
pub enum Motion {@@ -333,429 +331,3 @@ impl State {
333331
);334332
}335333
}336
-337
-const BTN_LEFT: u32 = 0x110;338
-const BTN_RIGHT: u32 = 0x111;339
-340
-/// The sides of a window a resize moves.341
-#[derive(Clone, Copy, Default, PartialEq, Eq)]342
-pub struct Edges {343
- left: bool,344
- right: bool,345
- top: bool,346
- bottom: bool,347
-}348
-349
-impl Edges {350
- /// The edges a drag from pos resizes, from the cell of a 3x3 grid over351
- /// the rect. The centre cell takes the nearest corner.352
- fn grid(r: Rect, pos: Point<f64, Logical>) -> Self {353
- let (fx, fy) = ((pos.x - r.x) / r.w, (pos.y - r.y) / r.h);354
- let third = |f: f64| match f {355
- f if f < 1.0 / 3.0 => -1,356
- f if f > 2.0 / 3.0 => 1,357
- _ => 0,358
- };359
- let (col, row) = match (third(fx), third(fy)) {360
- (0, 0) => (if fx < 0.5 { -1 } else { 1 }, if fy < 0.5 { -1 } else { 1 }),361
- cell => cell,362
- };363
- Self {364
- left: col < 0,365
- right: col > 0,366
- top: row < 0,367
- bottom: row > 0,368
- }369
- }370
-371
- fn dirs(self) -> impl Iterator<Item = Dir> {372
- [373
- (Dir::Left, self.left),374
- (Dir::Right, self.right),375
- (Dir::Up, self.top),376
- (Dir::Down, self.bottom),377
- ]378
- .into_iter()379
- .filter_map(|(dir, on)| on.then_some(dir))380
- }381
-382
- /// Drops the edges of a tiled window that have no split to move.383
- fn movable(self, layout: &Layout, id: WindowId) -> Self {384
- let has = |dir, on: bool| on && layout.has_split(id, dir);385
- Self {386
- left: has(Dir::Left, self.left),387
- right: has(Dir::Right, self.right),388
- top: has(Dir::Up, self.top),389
- bottom: has(Dir::Down, self.bottom),390
- }391
- }392
-393
- fn any(self) -> bool {394
- self.left || self.right || self.top || self.bottom395
- }396
-397
- /// The edges whose ring of width hw around r holds pos, a corner when398
- /// pos is near the end of a side. None inside r or beyond the ring.399
- fn in_ring(r: Rect, pos: Point<f64, Logical>, hw: f64) -> Option<Self> {400
- let (left, right) = (pos.x < r.x, pos.x >= r.right());401
- let (top, bottom) = (pos.y < r.y, pos.y >= r.bottom());402
- let beyond = pos.x < r.x - hw403
- || pos.x >= r.right() + hw404
- || pos.y < r.y - hw405
- || pos.y >= r.bottom() + hw;406
- if beyond || !(left || right || top || bottom) {407
- return None;408
- }409
- let (cx, cy) = ((2.0 * hw).min(r.w / 2.0), (2.0 * hw).min(r.h / 2.0));410
- let sideways = (left || right) && !(top || bottom);411
- let lengthwise = (top || bottom) && !(left || right);412
- Some(Self {413
- left: left || (lengthwise && pos.x < r.x + cx),414
- right: right || (lengthwise && pos.x >= r.right() - cx),415
- top: top || (sideways && pos.y < r.y + cy),416
- bottom: bottom || (sideways && pos.y >= r.bottom() - cy),417
- })418
- }419
-420
- pub fn icon(self) -> CursorIcon {421
- match (self.left, self.right, self.top, self.bottom) {422
- (true, _, true, _) => CursorIcon::NwResize,423
- (_, true, true, _) => CursorIcon::NeResize,424
- (true, _, _, true) => CursorIcon::SwResize,425
- (_, true, _, true) => CursorIcon::SeResize,426
- (true, ..) => CursorIcon::WResize,427
- (_, true, ..) => CursorIcon::EResize,428
- (_, _, true, _) => CursorIcon::NResize,429
- _ => CursorIcon::SResize,430
- }431
- }432
-433
- pub fn from_xdg(edge: xdg_toplevel::ResizeEdge) -> Option<Self> {434
- use xdg_toplevel::ResizeEdge as E;435
- let (left, right, top, bottom) = match edge {436
- E::Top => (false, false, true, false),437
- E::Bottom => (false, false, false, true),438
- E::Left => (true, false, false, false),439
- E::Right => (false, true, false, false),440
- E::TopLeft => (true, false, true, false),441
- E::TopRight => (false, true, true, false),442
- E::BottomLeft => (true, false, false, true),443
- E::BottomRight => (false, true, false, true),444
- _ => return None,445
- };446
- Some(Self {447
- left,448
- right,449
- top,450
- bottom,451
- })452
- }453
-454
- pub fn from_x11(edge: ResizeEdge) -> Self {455
- use xdg_toplevel::ResizeEdge as E;456
- Self::from_xdg(match edge {457
- ResizeEdge::Top => E::Top,458
- ResizeEdge::Bottom => E::Bottom,459
- ResizeEdge::Left => E::Left,460
- ResizeEdge::Right => E::Right,461
- ResizeEdge::TopLeft => E::TopLeft,462
- ResizeEdge::TopRight => E::TopRight,463
- ResizeEdge::BottomLeft => E::BottomLeft,464
- ResizeEdge::BottomRight => E::BottomRight,465
- })466
- .unwrap_or_default()467
- }468
-}469
-470
-/// What the pointer is over. Layer surfaces, popups and X11 menus keep the471
-/// resize rings around windows from taking their clicks.472
-#[derive(Clone, Copy, PartialEq, Eq)]473
-pub enum Cover {474
- Window,475
- Other,476
-}477
-478
-/// A floating window being moved or resized, either with Mod and a mouse479
-/// button or because its client asked while holding `button`.480
-pub struct Drag {481
- window: WindowId,482
- from: Point<f64, Logical>,483
- rect: Rect,484
- bounds: Rect,485
- /// A tiled window, whose splits follow the pointer instead of a rect.486
- tiled: bool,487
- /// None moves the window.488
- edges: Option<Edges>,489
- /// The button the drag started with, whose release ends it.490
- button: u32,491
- /// Whether the client asked for the drag and so still sees the release.492
- client: bool,493
-}494
-495
-impl Input {496
- /// The resize cursor to show over the client's own, if any.497
- pub fn resize_icon(&self) -> Option<CursorIcon> {498
- let edges = match &self.drag {499
- Some(d) => d.edges,500
- None => self.hover,501
- };502
- edges.map(Edges::icon)503
- }504
-}505
-506
-impl State {507
- /// The window and edges of a resize grab at pos. Rings sit just outside508
- /// windows, so the client's own surface is never taken over, except509
- /// that a floating window's ring wins over a tile it overlaps.510
- fn edge_at(&self, pos: Point<f64, Logical>, cover: Option<Cover>) -> Option<(WindowId, Edges)> {511
- if self.services.lock.active() || cover == Some(Cover::Other) {512
- return None;513
- }514
- let over_surface = cover.is_some();515
- let layout = &self.wm.layout;516
- let (o, out) = layout517
- .outputs()518
- .find(|(_, out)| out.area.contains(pos.x, pos.y))?;519
- let floats = !out.floating.is_empty()520
- || layout521
- .scratchpads()522
- .iter()523
- .any(|s| s.shown.is_some() && s.output == Some(o));524
- if over_surface && !floats {525
- return None;526
- }527
- let arr = layout.arrange(o);528
- if arr.fullscreen.is_some() {529
- return None;530
- }531
- let hw = f64::from(self.settings.config.decoration.border.max(4) + 4);532
- for &(w, r) in arr.floating.iter().rev() {533
- if r.contains(pos.x, pos.y) {534
- return None;535
- }536
- if let Some(e) = Edges::in_ring(r, pos, hw) {537
- return Some((w, e));538
- }539
- }540
- if over_surface {541
- return None;542
- }543
- let distance = |r: Rect| {544
- let dx = (r.x - pos.x).max(pos.x - r.right()).max(0.0);545
- let dy = (r.y - pos.y).max(pos.y - r.bottom()).max(0.0);546
- dx.hypot(dy)547
- };548
- arr.tiled549
- .iter()550
- .filter(|p| p.visible)551
- .filter_map(|p| {552
- let e = Edges::in_ring(p.rect, pos, hw)?.movable(layout, p.window);553
- e.any().then_some((p.window, e, distance(p.rect)))554
- })555
- .min_by(|a, b| a.2.total_cmp(&b.2))556
- .map(|(w, e, _)| (w, e))557
- }558
-}559
-560
-fn start_drag(state: &mut State, button: u32) -> bool {561
- if button != BTN_LEFT && button != BTN_RIGHT {562
- return false;563
- }564
- let held = !state.shortcuts_inhibited()565
- && state566
- .input567
- .seat568
- .get_keyboard()569
- .map(|k| k.modifier_state())570
- .is_some_and(|m| crate::commands::held(&m).has(state.settings.config.general.mod_key));571
- if !held {572
- let pos = state.input.pointer_pos;573
- let cover = state.cover_under(pos).map(|c| c.2);574
- let Some((id, edges)) = state.edge_at(pos, cover).filter(|_| button == BTN_LEFT) else {575
- return false;576
- };577
- if !state.wm.layout.is_focused(id) {578
- state.wm.layer_focus = None;579
- state.input.followed = state.wm.layout.focus(id);580
- state.apply_layout();581
- }582
- return begin_drag(state, id, Some(edges), button, false);583
- }584
- let Some(id) = state.wm.layout.focused() else {585
- return false;586
- };587
- let edges = match button {588
- BTN_RIGHT => {589
- let Some(rect) = window_rect(&state.wm.layout, id) else {590
- return false;591
- };592
- Some(Edges::grid(rect, state.input.pointer_pos))593
- }594
- _ => None,595
- };596
- begin_drag(state, id, edges, button, false)597
-}598
-599
-fn window_rect(layout: &Layout, id: WindowId) -> Option<Rect> {600
- let win = layout.window(id)?;601
- if win.floating {602
- return win.float_rect;603
- }604
- layout605
- .arrange(win.output?)606
- .tiled607
- .iter()608
- .find(|p| p.window == id && p.visible)609
- .map(|p| p.rect)610
-}611
-612
-impl State {613
- pub fn forget_drag(&mut self, id: WindowId) {614
- self.input.drag.take_if(|d| d.window == id);615
- self.wm.resizing.take_if(|r| r.window == id);616
- }617
-618
- /// Starts moving, or with `edges` resizing, a floating window at a619
- /// client's request. Tiled windows are left alone.620
- pub fn client_drag(&mut self, id: WindowId, edges: Option<Edges>, button: u32) {621
- if self.wm.layout.window(id).is_some_and(|w| w.floating) {622
- begin_drag(self, id, edges, button, true);623
- }624
- }625
-}626
-627
-fn begin_drag(628
- state: &mut State,629
- id: WindowId,630
- edges: Option<Edges>,631
- button: u32,632
- client: bool,633
-) -> bool {634
- let layout = &state.wm.layout;635
- let Some(win) = layout.window(id).filter(|w| !w.fullscreen) else {636
- return false;637
- };638
- let tiled = !win.floating;639
- let edges = if tiled {640
- edges.map(|e| e.movable(layout, id)).filter(|e| e.any())641
- } else {642
- edges643
- };644
- if tiled && edges.is_none() {645
- return false;646
- }647
- let (Some(rect), Some(out)) = (648
- window_rect(layout, id),649
- win.output.and_then(|o| layout.output(o)),650
- ) else {651
- return false;652
- };653
- state.wm.resizing = win654
- .output655
- .filter(|_| edges.is_some())656
- .map(|output| Resizing {657
- output,658
- window: id,659
- tiled,660
- });661
- state.input.drag = Some(Drag {662
- window: id,663
- from: state.input.pointer_pos,664
- rect,665
- bounds: out.usable(),666
- tiled,667
- edges,668
- button,669
- client,670
- });671
- true672
-}673
-674
-/// Moves the dragged edges of an axis, keeping them between lo and hi unless675
-/// the window started beyond them, and the size between `limits`, whose676
-/// minimum wins over its maximum.677
-fn resized(678
- start: f64,679
- len: f64,680
- delta: f64,681
- edges: (bool, bool),682
- (lo, hi): (f64, f64),683
- (min, max): (f64, f64),684
-) -> (f64, f64) {685
- let end = start + len;686
- match edges {687
- (true, _) => {688
- let edge = (start + delta)689
- .max(lo.min(start))690
- .max(end - max)691
- .min(end - min);692
- (edge, end - edge)693
- }694
- (_, true) => {695
- let edge = (end + delta)696
- .min(hi.max(end))697
- .min(start + max)698
- .max(start + min);699
- (start, edge - start)700
- }701
- _ => (start, len),702
- }703
-}704
-705
-fn drag_to(state: &mut State, pos: Point<f64, Logical>) -> bool {706
- let Some(d) = &state.input.drag else {707
- return false;708
- };709
- let (dx, dy) = (pos.x - d.from.x, pos.y - d.from.y);710
- if let (true, Some(e)) = (d.tiled, d.edges) {711
- let (id, start) = (d.window, d.rect);712
- for dir in e.dirs() {713
- let Some(now) = window_rect(&state.wm.layout, id) else {714
- continue;715
- };716
- let (at, was, offset) = match dir {717
- Dir::Left => (now.x, start.x, dx),718
- Dir::Right => (now.right(), start.right(), dx),719
- Dir::Up => (now.y, start.y, dy),720
- Dir::Down => (now.bottom(), start.bottom(), dy),721
- };722
- state.wm.layout.move_split(id, dir, was + offset - at);723
- }724
- state.apply_layout();725
- return true;726
- }727
- let r = d.rect;728
- let rect = match d.edges {729
- Some(e) => {730
- let b = d.bounds;731
- let rule = state.wm.get(d.window).map(|s| &s.rule);732
- let (min_w, min_h) = rule.map_or((0.0, 0.0), |r| r.clamp_size(0.0, 0.0));733
- let (max_w, max_h) = rule.map_or((f64::INFINITY, f64::INFINITY), |r| {734
- r.clamp_size(f64::INFINITY, f64::INFINITY)735
- });736
- let min = (min_w.max(MIN_FLOAT_SIZE), min_h.max(MIN_FLOAT_SIZE));737
- let (x, w) = resized(738
- r.x,739
- r.w,740
- dx,741
- (e.left, e.right),742
- (b.x, b.right()),743
- (min.0, max_w),744
- );745
- let (y, h) = resized(746
- r.y,747
- r.h,748
- dy,749
- (e.top, e.bottom),750
- (b.y, b.bottom()),751
- (min.1, max_h),752
- );753
- Rect::new(x, y, w, h)754
- }755
- None => clamp_float(Rect::new(r.x + dx, r.y + dy, r.w, r.h), d.bounds),756
- };757
- let id = d.window;758
- state.wm.layout.set_float_rect(id, rect);759
- state.apply_layout();760
- true761
-}@@ -7,6 +7,7 @@ mod commit;
77
mod constraints;88
mod cursor;99
mod devices;10
+mod drag;1011
mod drm;1112
mod fifo;1213
mod focus;@@ -1,9 +1,9 @@
11
use crate::anim::Animator;22
use crate::backend::logical_area;33
use crate::commands::Binds;4
+use crate::drag::Cover;45
use crate::focus::Focus;56
use crate::globals::Protocols;6
-use crate::input::Cover;77
use crate::wm::{Kind, Wm, configure};88
use monolith_config::Config;99
use monolith_layout::{Layout, OutputId, Rect, WindowId};@@ -52,7 +52,7 @@ pub struct Input {
5252
pub pointer_pos: Point<f64, Logical>,5353
/// Keys whose press ran a bind, so their release is swallowed too.5454
pub suppressed: Vec<Keycode>,55
- pub drag: Option<crate::input::Drag>,55
+ pub drag: Option<crate::drag::Drag>,5656
pub move_inverted: bool,5757
/// The focus mouse-follows-focus last saw, so pointer-made focus5858
/// changes can mark themselves as already followed.@@ -65,7 +65,7 @@ pub struct Input {
6565
pub dnd_icon: Option<crate::cursor::DndIcon>,6666
pub dnd: bool,6767
/// The edges of the window border under the pointer, for the resize cursor.68
- pub hover: Option<crate::input::Edges>,68
+ pub hover: Option<crate::drag::Edges>,6969
/// The output the pointer was last on, whose scale cursor and drag icon surfaces follow.7070
pub pointer_output: Option<smithay::output::Output>,7171
/// Where the last absolute device event put the pointer, so the next@@ -1,4 +1,4 @@
1
-use crate::input::Edges;1
+use crate::drag::Edges;22
use crate::state::State;33
use monolith_layout::OutputId;44
use smithay::desktop::{PopupKind, Window};@@ -3,8 +3,8 @@
33
//! connection that woke it is served. Xwayland exits once its last client44
//! leaves and the socket is watched again.55
6
+use crate::drag::Edges;67
use crate::focus::Focus;7
-use crate::input::Edges;88
use crate::state::State;99
use monolith_config::SelectionAccess;1010
use smithay::desktop::Window;