srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/core/src/manager/winops.rs
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2024-07-11 14:17:00 +0200
committersrdusr <[email protected]>2024-07-11 14:17:00 +0200
commit86b557a6c0baa4146b30712d8b7633f1b9b46d43 (patch)
tree96ab749c728ac11c2851ea361c6fda63990e588f /crates/core/src/manager/winops.rs
parent68b645b43e19330474ff106ce5c88dab520d7417 (diff)
downloadsrdwm-86b557a6c0baa4146b30712d8b7633f1b9b46d43.tar.gz
srdwm-86b557a6c0baa4146b30712d8b7633f1b9b46d43.zip
Split crates/core/src/manager.rs (2048 lines) into manager/
Pure reorganization, no behavior change - verified by diffing the function-name set before/after (identical 130 functions) plus a full cargo test pass. WindowManager's struct/field definitions, Default, new(), and the three trivial constructors (add_rule/register_layout/ available_layouts) stay in mod.rs; the rest of the single ~950-line impl block is split into one file per the section comments the file already had (monitors, windows, focus, winops, hittest, dragresize, workspaces, layout). Three methods called across section boundaries (monitor_for, windows_on_workspace, cycle_focus) went from private to pub(super) - Rust's privacy model doesn't let sibling submodules see each other's private items, only a defining module's own descendants. The ~1000-line test module moves to manager/tests.rs unsplit: its helpers (wm_with_monitor, two_monitors, monitor_with_dock) are shared across tests for every section, so splitting further would mean duplicating them or adding another shared-support file for little benefit.
Diffstat (limited to 'crates/core/src/manager/winops.rs')
-rw-r--r--crates/core/src/manager/winops.rs200
1 files changed, 200 insertions, 0 deletions
diff --git a/crates/core/src/manager/winops.rs b/crates/core/src/manager/winops.rs
new file mode 100644
index 0000000..e656298
--- /dev/null
+++ b/crates/core/src/manager/winops.rs
@@ -0,0 +1,200 @@
+//! Per-window lifecycle: close, minimize, restore, scratchpad, maximize, fullscreen, floating, and plain move/resize.
+//! Split out of the original single `manager.rs` - see `super` (`mod.rs`) for
+//! `WindowManager`'s field definitions; everything here is plain `impl WindowManager`
+//! methods, unchanged from before the split.
+
+use super::*;
+
+impl WindowManager {
+ // ---- Window operations ----------------------------------------------
+
+ pub fn close_window(&mut self, id: WindowId) {
+ log::info!("close_window({id})");
+ self.close_requests.push(id);
+ }
+
+ /// Drains windows queued by `close_window` since the last call. Core
+ /// has no way to reach a client itself - the caller (`main.rs`) is
+ /// expected to forward each id to `Platform::close`.
+ pub fn take_close_requests(&mut self) -> Vec<WindowId> {
+ std::mem::take(&mut self.close_requests)
+ }
+
+ pub fn minimize_window(&mut self, id: WindowId) {
+ if let Some(w) = self.windows.get_mut(&id) {
+ w.minimized = true;
+ }
+ if self.focused == Some(id) {
+ self.cycle_focus(true);
+ }
+ }
+
+ pub fn restore_window(&mut self, id: WindowId) {
+ if let Some(w) = self.windows.get_mut(&id) {
+ w.minimized = false;
+ }
+ }
+
+ /// Moves a window into the scratchpad pool, hiding it immediately --
+ /// sway's `move scratchpad`. The single most-used "quick terminal"
+ /// pattern in tiling window managers, and srdwm had no equivalent at
+ /// all before this.
+ ///
+ /// Also floats the window: tiling something that's meant to pop in and
+ /// out on demand doesn't make sense, and would otherwise fight
+ /// `arrange_workspace` every time it's shown. Reuses `minimized` for
+ /// the actual show/hide gating rather than introducing a second
+ /// visibility flag - `scratchpad` here is purely a marker of *pool
+ /// membership*, kept separate so `scratchpad_show` knows which hidden
+ /// windows are its own to bring back, as opposed to an ordinarily
+ /// minimized one.
+ pub fn scratchpad_add(&mut self, id: WindowId) {
+ if let Some(w) = self.windows.get_mut(&id) {
+ w.scratchpad = true;
+ w.floating = true;
+ }
+ self.minimize_window(id);
+ }
+
+ /// Removes a window from the scratchpad pool without changing its
+ /// current visibility - for a rule or script that wants to opt a
+ /// window back into ordinary window management.
+ pub fn scratchpad_remove(&mut self, id: WindowId) {
+ if let Some(w) = self.windows.get_mut(&id) {
+ w.scratchpad = false;
+ }
+ }
+
+ /// Toggles the scratchpad - sway's `scratchpad show`, meant for one
+ /// keybinding a user presses repeatedly. If the focused window is
+ /// itself a currently-shown scratchpad window, hides it; otherwise
+ /// shows (and focuses) the most recently added hidden scratchpad
+ /// window, if any, moving it onto whichever workspace is current so it
+ /// follows the user rather than staying pinned to wherever it was
+ /// added from - sway's own behavior. "Most recently added" is `id`
+ /// order, since ids are allocated monotonically and no separate
+ /// timestamp is tracked; only ever one window is shown/hidden per
+ /// call, deliberately not sway's full multi-window cycling, which
+ /// needs its own remembered order and is a rarer need than a single
+ /// scratchpad window covers.
+ pub fn scratchpad_show(&mut self) {
+ if let Some(id) = self.focused {
+ if self.windows.get(&id).is_some_and(|w| w.scratchpad && !w.minimized) {
+ self.minimize_window(id);
+ return;
+ }
+ }
+ let Some(id) = self.windows.values().filter(|w| w.scratchpad && w.minimized).map(|w| w.id).max() else { return };
+ if let Some(w) = self.windows.get_mut(&id) {
+ w.workspace = self.current_workspace;
+ }
+ self.restore_window(id);
+ self.focus_window(id);
+ }
+
+ pub fn toggle_maximize(&mut self, id: WindowId) {
+ let monitor_geom = self.windows.get(&id).and_then(|w| self.monitor_for(w.monitor)).map(|m| m.geometry);
+ let animations_enabled = self.animations_enabled;
+ let Some(w) = self.windows.get_mut(&id) else { return };
+ let from = w.geometry;
+ if w.maximized {
+ if let Some(restore) = w.restore_geometry.take() {
+ w.geometry = restore;
+ }
+ w.maximized = false;
+ } else if let Some(geom) = monitor_geom {
+ w.restore_geometry = Some(w.geometry);
+ w.geometry = geom;
+ w.maximized = true;
+ }
+ if animations_enabled && w.geometry != from {
+ w.anim_from = Some(from);
+ }
+ }
+
+ /// Fullscreen: the window covers its whole monitor with no decoration.
+ ///
+ /// Distinct from [`Self::toggle_maximize`], which keeps the titlebar (and
+ /// is what a maximise button does). Both share `restore_geometry`, so
+ /// they are mutually exclusive - toggling one off restores whatever the
+ /// window's geometry was before *either* was applied, and entering
+ /// fullscreen from a maximised window doesn't lose the original size.
+ ///
+ /// `decorated` is saved and restored the same way, via
+ /// `restore_decorated` - exiting used to hardcode `w.decorated = true`
+ /// unconditionally, which is only correct for a window that was
+ /// decorated to begin with. Any window a rule sets `decorated = false`
+ /// for (client-side-decorated apps like Firefox, matched via
+ /// `srd.rule({ class = "firefox" }, { decorated = false })`) that ever
+ /// goes fullscreen - an HTML5 video, a PDF presentation, plain F11 --
+ /// came back from it permanently `decorated = true`, with no further
+ /// event to ever set it back. Since border/titlebar redraw fresh from
+ /// live `Window.decorated` every frame but the *hit-testing* band this
+ /// wrongly turned on doesn't correspond to anything the client is
+ /// actually drawing there, every click in what srdwm now (incorrectly)
+ /// treats as the titlebar band got swallowed as a drag/button hit
+ /// instead of ever reaching the client - reported live as a click on
+ /// Firefox's back button minimizing the window instead.
+ pub fn toggle_fullscreen(&mut self, id: WindowId) {
+ // Unlike `toggle_maximize`, fullscreen uses the monitor's true
+ // full rect, not the exclusive-zone-shrunk usable area - a
+ // fullscreen window should cover (or go under) a bar/dock like
+ // everywhere else, not stop short of it. See `Monitor::
+ // full_geometry`'s doc comment.
+ let monitor_geom = self.windows.get(&id).and_then(|w| self.monitor_for(w.monitor)).map(|m| m.full_geometry);
+ let animations_enabled = self.animations_enabled;
+ let Some(w) = self.windows.get_mut(&id) else { return };
+ let from = w.geometry;
+ if w.fullscreen {
+ if let Some(restore) = w.restore_geometry.take() {
+ w.geometry = restore;
+ }
+ w.fullscreen = false;
+ w.decorated = w.restore_decorated.take().unwrap_or(true);
+ } else if let Some(geom) = monitor_geom {
+ // Only remember the pre-fullscreen geometry if we aren't already
+ // maximised, otherwise the monitor rect would overwrite the real
+ // restore point and the window could never get its size back.
+ if !w.maximized {
+ w.restore_geometry = Some(w.geometry);
+ }
+ w.maximized = false;
+ w.geometry = geom;
+ w.fullscreen = true;
+ w.restore_decorated = Some(w.decorated);
+ w.decorated = false;
+ }
+ if animations_enabled && w.geometry != from {
+ w.anim_from = Some(from);
+ }
+ }
+
+ pub fn is_fullscreen(&self, id: WindowId) -> bool {
+ self.windows.get(&id).map(|w| w.fullscreen).unwrap_or(false)
+ }
+
+ pub fn toggle_floating(&mut self, id: WindowId) {
+ if let Some(w) = self.windows.get_mut(&id) {
+ w.floating = !w.floating;
+ }
+ }
+
+ pub fn is_floating(&self, id: WindowId) -> bool {
+ self.windows.get(&id).map(|w| w.floating).unwrap_or(false)
+ }
+
+ pub fn move_window(&mut self, id: WindowId, x: i32, y: i32) {
+ if let Some(w) = self.windows.get_mut(&id) {
+ w.geometry.x = x;
+ w.geometry.y = y;
+ }
+ }
+
+ pub fn resize_window(&mut self, id: WindowId, width: u32, height: u32) {
+ if let Some(w) = self.windows.get_mut(&id) {
+ w.geometry.width = width.max(MIN_WINDOW_WIDTH);
+ w.geometry.height = height.max(MIN_WINDOW_HEIGHT);
+ }
+ }
+
+}