sctk_adwaita/
pointer.rs

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}