srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/core/src/manager/mod.rs
blob: 609b87f8deebd185834d25adb23a8895cbddc730 (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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use crate::geometry::Rect;
use crate::layout::{Layout, MasterStackLayout, NoOpLayout, TilingConfig};
use crate::monitor::{Monitor, MonitorId};
use crate::placement::{PlacementConfig, SmartPlacement, MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH};
use crate::rules::WindowRule;
use crate::theme::ThemeConfig;
use crate::window::{ResizeEdge, TitlebarHit, Window, WindowId, RESIZE_MARGIN};
use crate::workspace::{Workspace, WorkspaceId};
use std::collections::HashMap;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
    Left,
    Right,
    Up,
    Down,
}

struct DragState {
    window: WindowId,
    start_x: i32,
    start_y: i32,
    orig: Rect,
}

struct ResizeState {
    window: WindowId,
    edge: ResizeEdge,
    start_x: i32,
    start_y: i32,
    orig: Rect,
}

/// The platform-independent core of srdwm: owns window/workspace/monitor
/// state and layout policy. Backends (X11, Wayland, ...) drive this via
/// `add_window`/`remove_window`/input events, and apply the `Rect`s it
/// computes back onto real surfaces.
pub struct WindowManager {
    windows: HashMap<WindowId, Window>,
    order: Vec<WindowId>,
    focused: Option<WindowId>,
    monitors: Vec<Monitor>,
    workspaces: Vec<Workspace>,
    /// One flat value shared by every monitor - not per-output. Unlike
    /// Hyprland, srdwm has no notion of an independent workspace set per
    /// monitor; switching workspace changes what's visible on every screen
    /// at once. See `visible_windows`'s doc comment for the filter this
    /// actually drives.
    current_workspace: WorkspaceId,
    /// Whichever workspace was current immediately before the current one
    /// became current - see `switch_workspace`'s doc comment.
    previous_workspace: WorkspaceId,
    /// Read from `workspace.auto_back_and_forth`. When set, switching to
    /// the workspace that's already active switches to `previous_workspace`
    /// instead - sway's `workspace_auto_back_and_forth` behavior, a quick
    /// "jump back to whatever I was just on" toggle on a single keybinding.
    pub auto_back_and_forth: bool,
    next_workspace_id: WorkspaceId,
    next_window_id: WindowId,
    layouts: HashMap<String, Box<dyn Layout>>,
    pub tiling: TilingConfig,
    pub placement: PlacementConfig,
    /// Whether geometry changes made via `toggle_maximize`/`toggle_fullscreen`
    /// should be animated. Read from `general.animations`; a backend's open
    /// animation is gated on this too, since core has no notion of "open".
    pub animations_enabled: bool,
    /// Tween duration in milliseconds, read from `general.animation_duration`.
    pub animation_duration_ms: u32,
    /// Whether windows get a drop shadow. Read from `general.shadows`. A
    /// maximized or fullscreen window never gets one regardless of this --
    /// see the Wayland backend's shadow render call site - so this only
    /// ever turns it off entirely, not on for those.
    pub shadows_enabled: bool,
    /// Width, in pixels, of the resize grab band along a window's edges,
    /// read from `general.resize_margin`. See [`crate::window::RESIZE_MARGIN`]'s
    /// doc comment for the default and why it's what it is.
    pub resize_margin: i32,
    /// Whether a decorated window's content rounds its bottom two corners
    /// to match the titlebar's own curve (an undecorated/CSD window rounds
    /// all four). Read from `general.rounded_corners` - `None` when the
    /// user's config never touched that key at all (deliberately *not*
    /// defaulted in `crates/config`, unlike every other `general.*` key),
    /// so each backend can fall back to its own default rather than one
    /// baked in here: GLES/winit defaults on, udev/Pixman defaults off
    /// (an untested-on-real-hardware per-frame CPU cost for content that
    /// redraws constantly - see `crates/wayland/src/rounded_corners.rs`).
    /// `Some(_)` only when the user explicitly set it, and wins either way.
    pub rounded_corners_enabled: Option<bool>,
    /// Default decoration colours and border width, read from `theme.colors.*`/
    /// `theme.decorations.*`. See `ThemeConfig`'s own doc comment.
    pub theme: ThemeConfig,
    drag: Option<DragState>,
    resize: Option<ResizeState>,
    rules: Vec<WindowRule>,
    /// Windows a client-close was requested for, drained once per tick by
    /// `main.rs`'s event loop and forwarded to `Platform::close`. Needed
    /// because `WindowManager` is platform-agnostic and has no way to send
    /// a client its close request directly - see `close_window`.
    close_requests: Vec<WindowId>,
}

impl Default for WindowManager {
    fn default() -> Self {
        Self::new()
    }
}

impl WindowManager {
    pub fn new() -> Self {
        let mut layouts: HashMap<String, Box<dyn Layout>> = HashMap::new();
        layouts.insert("tiling".into(), Box::new(MasterStackLayout));
        layouts.insert("dynamic".into(), Box::new(NoOpLayout("dynamic")));
        layouts.insert("floating".into(), Box::new(NoOpLayout("floating")));

        Self {
            windows: HashMap::new(),
            order: Vec::new(),
            focused: None,
            monitors: Vec::new(),
            workspaces: vec![Workspace::new(0, "1", "dynamic")],
            current_workspace: 0,
            previous_workspace: 0,
            auto_back_and_forth: false,
            next_workspace_id: 1,
            next_window_id: 1,
            layouts,
            tiling: TilingConfig::default(),
            placement: PlacementConfig::default(),
            animations_enabled: true,
            animation_duration_ms: 200,
            shadows_enabled: true,
            resize_margin: RESIZE_MARGIN,
            rounded_corners_enabled: None,
            theme: ThemeConfig::default(),
            drag: None,
            resize: None,
            rules: Vec::new(),
            close_requests: Vec::new(),
        }
    }

    /// Registers a window rule; on every subsequent `add_window`, the first
    /// rule whose matcher matches the new window has its actions applied.
    pub fn add_rule(&mut self, rule: WindowRule) {
        self.rules.push(rule);
    }

    pub fn register_layout(&mut self, name: impl Into<String>, layout: Box<dyn Layout>) {
        self.layouts.insert(name.into(), layout);
    }

    pub fn available_layouts(&self) -> Vec<&str> {
        self.layouts.keys().map(String::as_str).collect()
    }

}

mod dragresize;
mod focus;
mod hittest;
mod layout;
mod monitors;
mod windows;
mod winops;
mod workspaces;

#[cfg(test)]
mod tests;