atagen

monolith

public
Star0
Watch0

Commit 846bdd33d84f

tarball

atagen <boss@atagen.co>2026-10-19 10:59 UTC

6 files changed+450-437
input: move the mouse drag code into drag

Pointer and key dispatch shared a 750 line file with the resize rings, Drag and Edges, none of which touch the dispatch beyond three calls.
Added file: crates/monolith/src/drag.rs+440 additions
@@ -0,0 +1,440 @@
1+//! Mouse-driven moves and resizes, from the rings around floating windows
2+//! 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 over
26+ /// 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.bottom
70+ }
71+
72+ /// The edges whose ring of width hw around r holds pos, a corner when
73+ /// 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 - hw
78+ || pos.x >= r.right() + hw
79+ || pos.y < r.y - hw
80+ || 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 the
146+/// 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 mouse
154+/// 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 outside
183+ /// windows, so the client's own surface is never taken over, except
184+ /// 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) = layout
196+ .outputs()
197+ .find(|(_, out)| out.area.contains(pos.x, pos.y))?;
198+ let floats = !out.floating.is_empty()
199+ || layout
200+ .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.tiled
228+ .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+ && state
245+ .input
246+ .seat
247+ .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+ layout
284+ .arrange(win.output?)
285+ .tiled
286+ .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 a
298+ /// 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+ edges
322+ };
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 = win
333+ .output
334+ .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+ true
351+}
352+
353+/// Moves the dragged edges of an axis, keeping them between lo and hi unless
354+/// the window started beyond them, and the size between `limits`, whose
355+/// 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+ true
440+}
Modified file: crates/monolith/src/input.rs+4 additions−432 deletions
@@ -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 over
351- /// 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.bottom
395- }
396-
397- /// The edges whose ring of width hw around r holds pos, a corner when
398- /// 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 - hw
403- || pos.x >= r.right() + hw
404- || pos.y < r.y - hw
405- || 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 the
471-/// 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 mouse
479-/// 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 outside
508- /// windows, so the client's own surface is never taken over, except
509- /// 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) = layout
517- .outputs()
518- .find(|(_, out)| out.area.contains(pos.x, pos.y))?;
519- let floats = !out.floating.is_empty()
520- || layout
521- .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.tiled
549- .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- && state
566- .input
567- .seat
568- .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- layout
605- .arrange(win.output?)
606- .tiled
607- .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 a
619- /// 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- edges
643- };
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 = win
654- .output
655- .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- true
672-}
673-
674-/// Moves the dragged edges of an axis, keeping them between lo and hi unless
675-/// the window started beyond them, and the size between `limits`, whose
676-/// 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- true
761-}
Modified file: crates/monolith/src/main.rs+1 additions
@@ -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;
Modified file: crates/monolith/src/state.rs+3 additions−3 deletions
@@ -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 focus
5858 /// 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
Modified file: crates/monolith/src/xdg.rs+1 additions−1 deletions
@@ -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};
Modified file: crates/monolith/src/xwayland.rs+1 additions−1 deletions
@@ -3,8 +3,8 @@
33 //! connection that woke it is served. Xwayland exits once its last client
44 //! 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;