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
}
}
|