aboutsummaryrefslogtreecommitdiff
path: root/src/input/move_grab.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/input/move_grab.rs')
-rw-r--r--src/input/move_grab.rs392
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) {