srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src/state/menu.rs
blob: b8cf094ee654528b054af99002ad673cac7a95e6 (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
use super::*;

impl CompState {

    /// Opens the right-click titlebar window menu for `window`, top-left
    /// corner at `pos` (global space, wherever the click landed). Rebuilds
    /// and caches the rasterised buffer once here rather than per frame --
    /// same reasoning as `redraw_decoration_buffer`.
    pub(crate) fn open_context_menu(&mut self, window: WindowId, pos: (i32, i32)) {
        let Some(menu) = ({
            let wm = self.wm.borrow();
            crate::context_menu::ContextMenu::open(&wm, window, pos)
        }) else {
            return;
        };
        let theme = self.wm.borrow().theme;
        let items: Vec<(&str, bool)> = menu.items.iter().map(|&(label, _)| (label, false)).collect();
        let data = decoration::render_context_menu(menu.width, menu.row_height, &items, theme.titlebar_bg, theme.titlebar_fg_focused, theme.titlebar_fg_unfocused, theme.default_border_color);
        let buffer = MemoryRenderBuffer::from_slice(&data, Fourcc::Argb8888, (menu.width as i32, menu.height()), 1, Transform::Normal, None);
        self.context_menu_buffer = Some(buffer);
        self.context_menu = Some(menu);
    }

    pub(crate) fn close_context_menu(&mut self) {
        self.context_menu = None;
        self.context_menu_buffer = None;
    }

    /// Runs whichever action a click on `row` of the currently-open context
    /// menu selected. Takes the menu's `window`/action rather than reading
    /// `self.context_menu` itself so the caller can close the menu first
    /// (clearing the borrow) before this runs - several of these actions
    /// (`sync_geometry`, `redraw_decoration_buffer`) need `&mut self` in
    /// ways that would otherwise conflict with an active `self.context_menu`
    /// borrow.
    pub(crate) fn run_context_menu_action(&mut self, window: WindowId, action: crate::context_menu::MenuAction) {
        use crate::context_menu::MenuAction;
        match action {
            MenuAction::Minimize => {
                self.wm.borrow_mut().minimize_window(window);
                foreign_toplevel::send_state(self, window);
            }
            MenuAction::ToggleMaximize => {
                self.wm.borrow_mut().toggle_maximize(window);
                self.sync_geometry(window);
                foreign_toplevel::send_state(self, window);
            }
            MenuAction::ToggleFullscreen => {
                self.wm.borrow_mut().toggle_fullscreen(window);
                self.sync_geometry(window);
                foreign_toplevel::send_state(self, window);
            }
            MenuAction::ToggleFloating => {
                self.wm.borrow_mut().toggle_floating(window);
                self.sync_geometry(window);
            }
            MenuAction::ToggleAlwaysOnTop => {
                self.wm.borrow_mut().toggle_always_on_top(window);
            }
            MenuAction::MoveToWorkspace(workspace) => {
                self.wm.borrow_mut().move_window_to_workspace(window, workspace);
            }
            MenuAction::Close => {
                if let Some(w) = self.id_to_window.get(&window) {
                    crate::input::close_dwindow(w);
                }
            }
            // Never actually reached: the click-dispatch site
            // (`input/pointer.rs`) intercepts `Separator` before calling
            // this function at all. Handled here too so this match stays
            // exhaustive without a catch-all that would silently swallow a
            // real future variant added without updating this function.
            MenuAction::Separator => {}
        }
    }

    /// Opens the Snap-Layouts flyout for `window`, top-left corner at `pos`
    /// (global space, by convention the maximize button's own titlebar
    /// position). Same build-once-on-open pattern as `open_context_menu`.
    pub(crate) fn open_snap_flyout(&mut self, window: WindowId, pos: (i32, i32)) {
        let flyout = crate::snap_flyout::SnapFlyout::open(window, pos);
        let theme = self.wm.borrow().theme;
        let labels: Vec<&str> = flyout.cells().iter().map(|z| z.label()).collect();
        let data = decoration::render_snap_flyout(3, flyout.cell_width, flyout.cell_height, &labels, theme.titlebar_bg, theme.titlebar_fg_focused, theme.default_border_color);
        let buffer = MemoryRenderBuffer::from_slice(&data, Fourcc::Argb8888, (flyout.width() as i32, flyout.height() as i32), 1, Transform::Normal, None);
        self.snap_flyout_buffer = Some(buffer);
        self.snap_flyout = Some(flyout);
    }

    pub(crate) fn close_snap_flyout(&mut self) {
        self.snap_flyout = None;
        self.snap_flyout_buffer = None;
    }

    /// Applies `zone` to `window` - the flyout's own equivalent of
    /// `run_context_menu_action`, taking the target explicitly rather than
    /// reading `self.snap_flyout` for the same borrow-conflict reason
    /// documented on that function.
    pub(crate) fn run_snap_flyout_action(&mut self, window: WindowId, zone: SnapZoneKind) {
        self.wm.borrow_mut().apply_snap_zone(window, zone);
        self.sync_geometry(window);
        foreign_toplevel::send_state(self, window);
    }

    /// True when this titlebar press is the second of a double-click on the
    /// same window. Threshold is the usual 400ms.
    pub(crate) fn is_double_click(&mut self, id: WindowId, time: u32) -> bool {
        const DOUBLE_CLICK_MS: u32 = 400;
        let doubled = match self.last_titlebar_click {
            Some((last_id, last_time)) => last_id == id && time.saturating_sub(last_time) <= DOUBLE_CLICK_MS,
            None => false,
        };
        // Reset after a double, so a third click starts a fresh pair rather
        // than counting as another double.
        self.last_titlebar_click = if doubled { None } else { Some((id, time)) };
        doubled
    }
}