srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/core/src/manager/workspaces.rs
blob: 6ffb21b716b5b931404e5d7f2158c9c7403cd01d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//! Workspace management: create/rename/remove/switch, and per-workspace window queries.
//! 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 {
    // ---- Workspaces -----------------------------------------------------

    pub fn add_workspace(&mut self, name: impl Into<String>, layout: impl Into<String>) -> WorkspaceId {
        let id = self.next_workspace_id;
        self.next_workspace_id += 1;
        self.workspaces.push(Workspace::new(id, name, layout));
        id
    }

    /// Sets a workspace's display name - used to apply `workspace.names`
    /// at startup (`crates/srdwm/src/main.rs`'s `apply_workspace_count`),
    /// since `WindowManager::new`/`add_workspace` otherwise leave every
    /// workspace named after its own 1-based index regardless of what a
    /// config asked for. A no-op if `id` doesn't exist.
    pub fn rename_workspace(&mut self, id: WorkspaceId, name: impl Into<String>) {
        if let Some(w) = self.workspaces.iter_mut().find(|w| w.id == id) {
            w.name = name.into();
        }
    }

    pub fn remove_workspace(&mut self, id: WorkspaceId) {
        if self.workspaces.len() <= 1 {
            return;
        }
        let fallback = self.workspaces.iter().map(|w| w.id).find(|&w| w != id).unwrap_or(0);
        for w in self.windows.values_mut().filter(|w| w.workspace == id) {
            w.workspace = fallback;
        }
        self.workspaces.retain(|w| w.id != id);
        if self.current_workspace == id {
            self.current_workspace = fallback;
        }
    }

    /// Switches to `id`, unless `auto_back_and_forth` is set and `id` is
    /// already the current workspace - in which case this jumps to
    /// `previous_workspace` instead, sway's `workspace_auto_back_and_forth`
    /// behavior. `previous_workspace` itself always tracks "whatever was
    /// current right before this call changed it", updated on every real
    /// switch regardless of the setting, so turning the setting on later
    /// (or a client-driven switch, e.g. `ext_workspace_v1`'s `activate`)
    /// doesn't need its own separate bookkeeping.
    pub fn switch_workspace(&mut self, id: WorkspaceId) {
        let target = if self.auto_back_and_forth && id == self.current_workspace { self.previous_workspace } else { id };
        if self.workspaces.iter().any(|w| w.id == target) && target != self.current_workspace {
            self.previous_workspace = self.current_workspace;
            self.current_workspace = target;
        }
    }

    pub fn current_workspace(&self) -> WorkspaceId {
        self.current_workspace
    }

    pub fn workspace(&self, id: WorkspaceId) -> Option<&Workspace> {
        self.workspaces.iter().find(|w| w.id == id)
    }

    pub fn workspaces(&self) -> &[Workspace] {
        &self.workspaces
    }

    pub fn move_window_to_workspace(&mut self, id: WindowId, workspace: WorkspaceId) {
        if let Some(w) = self.windows.get_mut(&id) {
            w.workspace = workspace;
        }
    }

    /// Windows that should currently be shown to the user: those on the
    /// current workspace, and not minimized.
    ///
    /// `current_workspace` is a single value shared by every monitor --
    /// srdwm does not have Hyprland-style independent per-monitor
    /// workspaces, so switching workspace changes what's shown on every
    /// screen at once. `w.monitor` plays no part in this filter at all.
    pub fn visible_windows(&self) -> impl Iterator<Item = &Window> {
        self.windows.values().filter(|w| w.workspace == self.current_workspace && !w.minimized)
    }

    /// Same windows as [`Self::visible_windows`], but in real front-to-back
    /// stacking order (topmost first) instead of arbitrary `HashMap`
    /// iteration order. Needed anywhere a backend composites more than one
    /// window's elements (content, decoration, border) together and their
    /// relative order across *different* windows actually matters - unlike
    /// `visible_windows`, which is fine for anything per-window in
    /// isolation (border color, geometry) where order never came up.
    /// `self.order` reversed is the same "topmost first" convention
    /// `hit_test`/`window_at` already use.
    pub fn visible_windows_front_to_back(&self) -> impl Iterator<Item = &Window> {
        self.order.iter().rev().filter_map(|id| self.windows.get(id)).filter(|w| w.workspace == self.current_workspace && !w.minimized)
    }

}