diff options
Diffstat (limited to 'src/input/move_grab.rs')
| -rw-r--r-- | src/input/move_grab.rs | 392 |
1 files changed, 333 insertions, 59 deletions
diff --git a/src/input/move_grab.rs b/src/input/move_grab.rs index e939696b..c78b796e 100644 --- a/src/input/move_grab.rs +++ b/src/input/move_grab.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use smithay::backend::input::ButtonState; use smithay::desktop::Window; use smithay::input::pointer::{ @@ -7,107 +9,279 @@ use smithay::input::pointer::{ GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent, }; +use smithay::input::touch::{ + self, GrabStartData as TouchGrabStartData, TouchGrab, TouchInnerHandle, +}; use smithay::input::SeatHandler; -use smithay::utils::{IsAlive, Logical, Point}; +use smithay::output::Output; +use smithay::utils::{IsAlive, Logical, Point, Serial}; +use crate::input::PointerOrTouchStartData; use crate::niri::State; pub struct MoveGrab { - start_data: PointerGrabStartData<State>, + start_data: PointerOrTouchStartData<State>, + start_output: Output, + start_pos_within_output: Point<f64, Logical>, last_location: Point<f64, Logical>, window: Window, gesture: GestureState, + enable_view_offset: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum GestureState { Recognizing, Move, + ViewOffset, } impl MoveGrab { pub fn new( - start_data: PointerGrabStartData<State>, + state: &mut State, + start_data: PointerOrTouchStartData<State>, window: Window, - use_threshold: bool, - ) -> Self { - let gesture = if use_threshold { - GestureState::Recognizing - } else { - GestureState::Move - }; + enable_view_offset: bool, + ) -> Option<Self> { + let (output, pos_within_output) = state.niri.output_under(start_data.location())?; - Self { - last_location: start_data.location, + Some(Self { + last_location: start_data.location(), start_data, + start_output: output.clone(), + start_pos_within_output: pos_within_output, window, - gesture, - } + gesture: GestureState::Recognizing, + enable_view_offset, + }) } - fn on_ungrab(&mut self, state: &mut State) { - state.niri.layout.interactive_move_end(&self.window); + pub fn is_move(&self) -> bool { + self.gesture == GestureState::Move + } + + fn on_ungrab(&mut self, data: &mut State) { + let layout = &mut data.niri.layout; + match self.gesture { + GestureState::Recognizing => { + // Activate the window on release. This is most prominent in the overview where + // windows are not activated on click. In the overview, we also try to do a nice + // synchronized workspace animation. + if layout.is_overview_open() { + let res = layout.workspaces().find_map(|(mon, ws_idx, ws)| { + ws.windows() + .any(|w| w.window == self.window) + .then(|| (mon.map(|mon| mon.output().clone()), ws_idx)) + }); + if let Some((Some(output), ws_idx)) = res { + layout.focus_output(&output); + layout.toggle_overview_to_workspace(ws_idx); + } + } + + layout.activate_window(&self.window); + } + GestureState::Move => layout.interactive_move_end(&self.window), + GestureState::ViewOffset => { + layout.view_offset_gesture_end(Some(false)); + } + } + + if self.start_data.is_pointer() { + data.niri + .cursor_manager + .set_cursor_image(CursorImageStatus::default_named()); + } + // FIXME: only redraw the window output. - state.niri.queue_redraw_all(); - state - .niri - .cursor_manager - .set_cursor_image(CursorImageStatus::default_named()); + data.niri.queue_redraw_all(); } -} -impl PointerGrab<State> for MoveGrab { - fn motion( + fn begin_move(&mut self, data: &mut State) -> bool { + if !data.niri.layout.interactive_move_begin( + self.window.clone(), + &self.start_output, + self.start_pos_within_output, + ) { + // Can no longer start the move. + return false; + } + + self.gesture = GestureState::Move; + + if self.start_data.is_pointer() { + data.niri + .cursor_manager + .set_cursor_image(CursorImageStatus::Named(CursorIcon::Move)); + } + + true + } + + fn begin_view_offset(&mut self, data: &mut State) -> bool { + let layout = &mut data.niri.layout; + let Some((output, ws_idx)) = layout.workspaces().find_map(|(mon, ws_idx, ws)| { + let ws_idx = ws + .windows() + .any(|w| w.window == self.window) + .then_some(ws_idx)?; + let output = mon?.output().clone(); + Some((output, ws_idx)) + }) else { + // Can no longer start the gesture. + return false; + }; + + layout.view_offset_gesture_begin(&output, Some(ws_idx), false); + + self.gesture = GestureState::ViewOffset; + + if self.start_data.is_pointer() { + data.niri + .cursor_manager + .set_cursor_image(CursorImageStatus::Named(CursorIcon::AllScroll)); + } + + true + } + + fn on_motion( &mut self, data: &mut State, - handle: &mut PointerInnerHandle<'_, State>, - _focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>, - event: &MotionEvent, - ) { - // While the grab is active, no client has pointer focus. - handle.motion(data, None, event); + location: Point<f64, Logical>, + timestamp: Duration, + ) -> bool { + let mut delta = location - self.last_location; + self.last_location = location; - if self.window.alive() { - if let Some((output, pos_within_output)) = data.niri.output_under(event.location) { - let output = output.clone(); - let event_delta = event.location - self.last_location; - self.last_location = event.location; + // Try to recognize the gesture. + if self.gesture == GestureState::Recognizing { + // Check if the window has closed. + if !self.window.alive() { + return false; + } - if self.gesture == GestureState::Recognizing { - let c = event.location - self.start_data.location; + // Check if the gesture moved far enough to decide. + let c = location - self.start_data.location(); + if c.x * c.x + c.y * c.y >= 8. * 8. { + let is_floating = data + .niri + .layout + .workspaces() + .find_map(|(_, _, ws)| { + ws.windows() + .any(|w| w.window == self.window) + .then(|| ws.is_floating(&self.window)) + }) + .unwrap_or(false); - // Check if the gesture moved far enough to decide. - if c.x * c.x + c.y * c.y >= 8. * 8. { - self.gesture = GestureState::Move; + let is_view_offset = + self.enable_view_offset && !is_floating && c.x.abs() > c.y.abs(); - data.niri - .cursor_manager - .set_cursor_image(CursorImageStatus::Named(CursorIcon::Move)); - } + let started = if is_view_offset { + self.begin_view_offset(data) + } else { + self.begin_move(data) + }; + if !started { + return false; } - if self.gesture != GestureState::Move { - return; - } + // Apply the whole delta that accumulated during recognizing. + delta = c; + } + } + + match self.gesture { + GestureState::Recognizing => return true, + GestureState::Move => { + let Some((output, pos_within_output)) = data.niri.output_under(self.last_location) + else { + return true; + }; + let output = output.clone(); let ongoing = data.niri.layout.interactive_move_update( &self.window, - event_delta, + delta, output, pos_within_output, ); if ongoing { // FIXME: only redraw the previous and the new output. data.niri.queue_redraw_all(); - return; + return true; } - } else { - return; + } + GestureState::ViewOffset => { + let res = data + .niri + .layout + .view_offset_gesture_update(-delta.x, timestamp, false); + if let Some(output) = res { + if let Some(output) = output { + data.niri.queue_redraw(&output); + } + return true; + } + } + } + + false + } + + fn on_toggle_floating(&mut self, data: &mut State) -> bool { + if self.gesture == GestureState::ViewOffset { + return true; + } + + // Start move if still recognizing. + if self.gesture == GestureState::Recognizing { + let Some((output, pos_within_output)) = data.niri.output_under(self.last_location) + else { + return false; + }; + let output = output.clone(); + + if !self.begin_move(data) { + return false; + } + + // Apply the delta accumulated during recognizing. + let ongoing = data.niri.layout.interactive_move_update( + &self.window, + self.last_location - self.start_data.location(), + output, + pos_within_output, + ); + if !ongoing { + return false; } } - // The move is no longer ongoing. - handle.unset_grab(self, data, event.serial, event.time, true); + data.niri.layout.toggle_window_floating(Some(&self.window)); + data.niri.queue_redraw_all(); + + true + } +} + +impl PointerGrab<State> for MoveGrab { + fn motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>, + event: &MotionEvent, + ) { + // While the grab is active, no client has pointer focus. + handle.motion(data, None, event); + + let timestamp = Duration::from_millis(u64::from(event.time)); + if !self.on_motion(data, event.location, timestamp) { + // The gesture is no longer ongoing. + handle.unset_grab(self, data, event.serial, event.time, true); + } } fn relative_motion( @@ -129,18 +303,25 @@ impl PointerGrab<State> for MoveGrab { ) { handle.button(data, event); + let start_data = self.start_data.unwrap_pointer(); + + if !handle.current_pressed().contains(&start_data.button) { + // The button that initiated the grab was released. + handle.unset_grab(self, data, event.serial, event.time, true); + return; + } + // When moving with the left button, right toggles floating, and vice versa. - let toggle_floating_button = if self.start_data.button == 0x110 { + let toggle_floating_button = if start_data.button == 0x110 { 0x111 } else { 0x110 }; - if event.button == toggle_floating_button && event.state == ButtonState::Pressed { - data.niri.layout.toggle_window_floating(Some(&self.window)); + if event.state != ButtonState::Pressed || event.button != toggle_floating_button { + return; } - if !handle.current_pressed().contains(&self.start_data.button) { - // The button that initiated the grab was released. + if !self.on_toggle_floating(data) { handle.unset_grab(self, data, event.serial, event.time, true); } } @@ -231,7 +412,100 @@ impl PointerGrab<State> for MoveGrab { } fn start_data(&self) -> &PointerGrabStartData<State> { - &self.start_data + self.start_data.unwrap_pointer() + } + + fn unset(&mut self, data: &mut State) { + self.on_ungrab(data); + } +} + +impl TouchGrab<State> for MoveGrab { + fn down( + &mut self, + data: &mut State, + handle: &mut TouchInnerHandle<'_, State>, + _focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>, + event: &touch::DownEvent, + seq: Serial, + ) { + handle.down(data, None, event, seq); + + if event.slot == self.start_data.unwrap_touch().slot { + return; + } + + if !self.on_toggle_floating(data) { + handle.unset_grab(self, data); + } + } + + fn up( + &mut self, + data: &mut State, + handle: &mut TouchInnerHandle<'_, State>, + event: &touch::UpEvent, + seq: Serial, + ) { + handle.up(data, event, seq); + + if event.slot == self.start_data.unwrap_touch().slot { + handle.unset_grab(self, data); + } + } + + fn motion( + &mut self, + data: &mut State, + handle: &mut TouchInnerHandle<'_, State>, + _focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>, + event: &touch::MotionEvent, + seq: Serial, + ) { + handle.motion(data, None, event, seq); + + if event.slot != self.start_data.unwrap_touch().slot { + return; + } + + let timestamp = Duration::from_millis(u64::from(event.time)); + if !self.on_motion(data, event.location, timestamp) { + // The gesture is no longer ongoing. + handle.unset_grab(self, data); + } + } + + fn frame(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { + handle.frame(data, seq); + } + + fn cancel(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, seq: Serial) { + handle.cancel(data, seq); + handle.unset_grab(self, data); + } + + fn shape( + &mut self, + data: &mut State, + handle: &mut TouchInnerHandle<'_, State>, + event: &touch::ShapeEvent, + seq: Serial, + ) { + handle.shape(data, event, seq); + } + + fn orientation( + &mut self, + data: &mut State, + handle: &mut TouchInnerHandle<'_, State>, + event: &touch::OrientationEvent, + seq: Serial, + ) { + handle.orientation(data, event, seq); + } + + fn start_data(&self) -> &TouchGrabStartData<State> { + self.start_data.unwrap_touch() } fn unset(&mut self, data: &mut State) { |
