1 | use std::time::Duration; |
2 | |
3 | use smithay_client_toolkit::reexports::csd_frame::{ |
4 | CursorIcon, FrameAction, ResizeEdge, WindowManagerCapabilities, WindowState, |
5 | }; |
6 | |
7 | use crate::{ |
8 | buttons::ButtonKind, |
9 | theme::{BORDER_SIZE, HEADER_SIZE}, |
10 | }; |
11 | |
12 | /// Time to register the next click as a double click. |
13 | /// |
14 | /// The value is the same as the default in gtk4. |
15 | const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(400); |
16 | |
17 | /// The state of the mouse input inside the decorations frame. |
18 | #[derive (Debug, Default)] |
19 | pub(crate) struct MouseState { |
20 | pub location: Location, |
21 | |
22 | /// The surface local location inside the surface. |
23 | position: (f64, f64), |
24 | |
25 | /// The instant of the last click. |
26 | last_normal_click: Option<Duration>, |
27 | } |
28 | |
29 | impl MouseState { |
30 | /// The normal click on decorations frame was made. |
31 | pub fn click( |
32 | &mut self, |
33 | timestamp: Duration, |
34 | pressed: bool, |
35 | resizable: bool, |
36 | state: &WindowState, |
37 | wm_capabilities: &WindowManagerCapabilities, |
38 | ) -> Option<FrameAction> { |
39 | let maximized = state.contains(WindowState::MAXIMIZED); |
40 | let action = match self.location { |
41 | Location::Top if resizable => FrameAction::Resize(ResizeEdge::Top), |
42 | Location::TopLeft if resizable => FrameAction::Resize(ResizeEdge::TopLeft), |
43 | Location::Left if resizable => FrameAction::Resize(ResizeEdge::Left), |
44 | Location::BottomLeft if resizable => FrameAction::Resize(ResizeEdge::BottomLeft), |
45 | Location::Bottom if resizable => FrameAction::Resize(ResizeEdge::Bottom), |
46 | Location::BottomRight if resizable => FrameAction::Resize(ResizeEdge::BottomRight), |
47 | Location::Right if resizable => FrameAction::Resize(ResizeEdge::Right), |
48 | Location::TopRight if resizable => FrameAction::Resize(ResizeEdge::TopRight), |
49 | Location::Button(ButtonKind::Close) if !pressed => FrameAction::Close, |
50 | Location::Button(ButtonKind::Maximize) if !pressed && !maximized => { |
51 | FrameAction::Maximize |
52 | } |
53 | Location::Button(ButtonKind::Maximize) if !pressed && maximized => { |
54 | FrameAction::UnMaximize |
55 | } |
56 | Location::Button(ButtonKind::Minimize) if !pressed => FrameAction::Minimize, |
57 | Location::Head |
58 | if pressed && wm_capabilities.contains(WindowManagerCapabilities::MAXIMIZE) => |
59 | { |
60 | match self.last_normal_click.replace(timestamp) { |
61 | Some(last) if timestamp.saturating_sub(last) < DOUBLE_CLICK_DURATION => { |
62 | if maximized { |
63 | FrameAction::UnMaximize |
64 | } else { |
65 | FrameAction::Maximize |
66 | } |
67 | } |
68 | _ => FrameAction::Move, |
69 | } |
70 | } |
71 | Location::Head if pressed => FrameAction::Move, |
72 | _ => return None, |
73 | }; |
74 | |
75 | Some(action) |
76 | } |
77 | |
78 | /// Alternative click on decorations frame was made. |
79 | pub fn alternate_click( |
80 | &mut self, |
81 | pressed: bool, |
82 | wm_capabilities: &WindowManagerCapabilities, |
83 | ) -> Option<FrameAction> { |
84 | // Invalidate the normal click. |
85 | self.last_normal_click = None; |
86 | |
87 | match self.location { |
88 | Location::Head | Location::Button(_) |
89 | if pressed && wm_capabilities.contains(WindowManagerCapabilities::WINDOW_MENU) => |
90 | { |
91 | Some(FrameAction::ShowMenu( |
92 | // XXX this could be one 1pt off when the frame is not maximized, but it's not |
93 | // like it really matters in the end. |
94 | self.position.0 as i32 - BORDER_SIZE as i32, |
95 | // We must offset it by header size for precise position. |
96 | self.position.1 as i32 - HEADER_SIZE as i32, |
97 | )) |
98 | } |
99 | _ => None, |
100 | } |
101 | } |
102 | |
103 | /// The mouse moved inside the decorations frame. |
104 | pub fn moved(&mut self, location: Location, x: f64, y: f64, resizable: bool) -> CursorIcon { |
105 | self.location = location; |
106 | self.position = (x, y); |
107 | match self.location { |
108 | _ if !resizable => CursorIcon::Default, |
109 | Location::Top => CursorIcon::NResize, |
110 | Location::TopRight => CursorIcon::NeResize, |
111 | Location::Right => CursorIcon::EResize, |
112 | Location::BottomRight => CursorIcon::SeResize, |
113 | Location::Bottom => CursorIcon::SResize, |
114 | Location::BottomLeft => CursorIcon::SwResize, |
115 | Location::Left => CursorIcon::WResize, |
116 | Location::TopLeft => CursorIcon::NwResize, |
117 | _ => CursorIcon::Default, |
118 | } |
119 | } |
120 | |
121 | /// The mouse left the decorations frame. |
122 | pub fn left(&mut self) { |
123 | // Reset only the location. |
124 | self.location = Location::None; |
125 | } |
126 | } |
127 | |
128 | #[derive (Debug, Copy, Clone, PartialEq, Eq, Default)] |
129 | pub enum Location { |
130 | #[default] |
131 | None, |
132 | Head, |
133 | Top, |
134 | TopRight, |
135 | Right, |
136 | BottomRight, |
137 | Bottom, |
138 | BottomLeft, |
139 | Left, |
140 | TopLeft, |
141 | Button(ButtonKind), |
142 | } |
143 | |