1use std::time::Duration;
2
3use smithay_client_toolkit::reexports::csd_frame::{
4 CursorIcon, FrameAction, ResizeEdge, WindowManagerCapabilities, WindowState,
5};
6
7use 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.
15const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(400);
16
17/// The state of the mouse input inside the decorations frame.
18#[derive(Debug, Default)]
19pub(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
29impl 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)]
129pub 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