Implement vertical middle mouse gesture

This commit is contained in:
Ivan Molodetskikh
2024-06-19 21:54:46 +03:00
parent 226273f660
commit db89d4d3dd
4 changed files with 128 additions and 40 deletions
+8 -8
View File
@@ -29,7 +29,7 @@ use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerCons
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
use self::resize_grab::ResizeGrab;
use self::view_offset_grab::ViewOffsetGrab;
use self::spatial_movement_grab::SpatialMovementGrab;
use crate::niri::State;
use crate::ui::screenshot_ui::ScreenshotUi;
use crate::utils::spawning::spawn;
@@ -37,8 +37,8 @@ use crate::utils::{center, get_monotonic_time, ResizeEdge};
pub mod resize_grab;
pub mod scroll_tracker;
pub mod spatial_movement_grab;
pub mod swipe_tracker;
pub mod view_offset_grab;
pub const DOUBLE_CLICK_TIME: Duration = Duration::from_millis(400);
@@ -1252,15 +1252,13 @@ impl State {
};
if mod_down {
if let Some(output) = self.niri.output_under_cursor() {
self.niri.layout.view_offset_gesture_begin(&output, false);
let location = pointer.current_location();
let start_data = PointerGrabStartData {
focus: None,
button: event.button_code(),
location,
};
let grab = ViewOffsetGrab::new(start_data);
let grab = SpatialMovementGrab::new(start_data, output);
pointer.set_grab(self, grab, serial, Focus::Clear);
self.niri.pointer_grab_ongoing = true;
self.niri
@@ -1699,7 +1697,9 @@ impl State {
if cx.abs() > cy.abs() {
self.niri.layout.view_offset_gesture_begin(&output, true);
} else {
self.niri.layout.workspace_switch_gesture_begin(&output);
self.niri
.layout
.workspace_switch_gesture_begin(&output, true);
}
}
}
@@ -1711,7 +1711,7 @@ impl State {
let res = self
.niri
.layout
.workspace_switch_gesture_update(delta_y, timestamp);
.workspace_switch_gesture_update(delta_y, timestamp, true);
if let Some(output) = res {
if let Some(output) = output {
self.niri.queue_redraw(&output);
@@ -1757,7 +1757,7 @@ impl State {
let res = self
.niri
.layout
.workspace_switch_gesture_end(event.cancelled());
.workspace_switch_gesture_end(event.cancelled(), Some(true));
if let Some(output) = res {
self.niri.queue_redraw(&output);
handled = true;
@@ -7,28 +7,45 @@ use smithay::input::pointer::{
MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent,
};
use smithay::input::SeatHandler;
use smithay::output::Output;
use smithay::utils::{Logical, Point};
use crate::niri::State;
pub struct ViewOffsetGrab {
pub struct SpatialMovementGrab {
start_data: PointerGrabStartData<State>,
last_location: Point<f64, Logical>,
output: Output,
gesture: GestureState,
}
impl ViewOffsetGrab {
pub fn new(start_data: PointerGrabStartData<State>) -> Self {
#[derive(Debug, Clone, Copy)]
enum GestureState {
Recognizing,
ViewOffset,
WorkspaceSwitch,
}
impl SpatialMovementGrab {
pub fn new(start_data: PointerGrabStartData<State>, output: Output) -> Self {
Self {
last_location: start_data.location,
start_data,
output,
gesture: GestureState::Recognizing,
}
}
fn on_ungrab(&mut self, state: &mut State) {
let res = state
.niri
.layout
.view_offset_gesture_end(false, Some(false));
let layout = &mut state.niri.layout;
let res = match self.gesture {
GestureState::Recognizing => None,
GestureState::ViewOffset => layout.view_offset_gesture_end(false, Some(false)),
GestureState::WorkspaceSwitch => {
layout.workspace_switch_gesture_end(false, Some(false))
}
};
if let Some(output) = res {
state.niri.queue_redraw(&output);
}
@@ -41,7 +58,7 @@ impl ViewOffsetGrab {
}
}
impl PointerGrab<State> for ViewOffsetGrab {
impl PointerGrab<State> for SpatialMovementGrab {
fn motion(
&mut self,
data: &mut State,
@@ -56,10 +73,34 @@ impl PointerGrab<State> for ViewOffsetGrab {
let delta = event.location - self.last_location;
self.last_location = event.location;
let res = data
.niri
.layout
.view_offset_gesture_update(-delta.x, timestamp, false);
let layout = &mut data.niri.layout;
let res = match self.gesture {
GestureState::Recognizing => {
let c = event.location - self.start_data.location;
// Check if the gesture moved far enough to decide. Threshold copied from GTK 4.
if c.x * c.x + c.y * c.y >= 8. * 8. {
if c.x.abs() > c.y.abs() {
self.gesture = GestureState::ViewOffset;
layout.view_offset_gesture_begin(&self.output, false);
layout.view_offset_gesture_update(-c.x, timestamp, false)
} else {
self.gesture = GestureState::WorkspaceSwitch;
layout.workspace_switch_gesture_begin(&self.output, false);
layout.workspace_switch_gesture_update(-c.y, timestamp, false)
}
} else {
Some(None)
}
}
GestureState::ViewOffset => {
layout.view_offset_gesture_update(-delta.x, timestamp, false)
}
GestureState::WorkspaceSwitch => {
layout.workspace_switch_gesture_update(-delta.y, timestamp, false)
}
};
if let Some(output) = res {
if let Some(output) = output {
data.niri.queue_redraw(&output);
+32 -12
View File
@@ -2002,7 +2002,7 @@ impl<W: LayoutElement> Layout<W> {
}
}
pub fn workspace_switch_gesture_begin(&mut self, output: &Output) {
pub fn workspace_switch_gesture_begin(&mut self, output: &Output, is_touchpad: bool) {
let monitors = match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => monitors,
MonitorSet::NoOutputs { .. } => unreachable!(),
@@ -2011,11 +2011,11 @@ impl<W: LayoutElement> Layout<W> {
for monitor in monitors {
// Cancel the gesture on other outputs.
if &monitor.output != output {
monitor.workspace_switch_gesture_end(true);
monitor.workspace_switch_gesture_end(true, None);
continue;
}
monitor.workspace_switch_gesture_begin();
monitor.workspace_switch_gesture_begin(is_touchpad);
}
}
@@ -2023,6 +2023,7 @@ impl<W: LayoutElement> Layout<W> {
&mut self,
delta_y: f64,
timestamp: Duration,
is_touchpad: bool,
) -> Option<Option<Output>> {
let monitors = match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => monitors,
@@ -2030,7 +2031,9 @@ impl<W: LayoutElement> Layout<W> {
};
for monitor in monitors {
if let Some(refresh) = monitor.workspace_switch_gesture_update(delta_y, timestamp) {
if let Some(refresh) =
monitor.workspace_switch_gesture_update(delta_y, timestamp, is_touchpad)
{
if refresh {
return Some(Some(monitor.output.clone()));
} else {
@@ -2042,14 +2045,18 @@ impl<W: LayoutElement> Layout<W> {
None
}
pub fn workspace_switch_gesture_end(&mut self, cancelled: bool) -> Option<Output> {
pub fn workspace_switch_gesture_end(
&mut self,
cancelled: bool,
is_touchpad: Option<bool>,
) -> Option<Output> {
let monitors = match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => monitors,
MonitorSet::NoOutputs { .. } => return None,
};
for monitor in monitors {
if monitor.workspace_switch_gesture_end(cancelled) {
if monitor.workspace_switch_gesture_end(cancelled, is_touchpad) {
return Some(monitor.output.clone());
}
}
@@ -2750,14 +2757,17 @@ mod tests {
WorkspaceSwitchGestureBegin {
#[proptest(strategy = "1..=5usize")]
output_idx: usize,
is_touchpad: bool,
},
WorkspaceSwitchGestureUpdate {
#[proptest(strategy = "-400f64..400f64")]
delta: f64,
timestamp: Duration,
is_touchpad: bool,
},
WorkspaceSwitchGestureEnd {
cancelled: bool,
is_touchpad: Option<bool>,
},
InteractiveResizeBegin {
#[proptest(strategy = "1..=5usize")]
@@ -3135,19 +3145,29 @@ mod tests {
// We don't handle cancels in this gesture.
layout.view_offset_gesture_end(false, is_touchpad);
}
Op::WorkspaceSwitchGestureBegin { output_idx: id } => {
Op::WorkspaceSwitchGestureBegin {
output_idx: id,
is_touchpad,
} => {
let name = format!("output{id}");
let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else {
return;
};
layout.workspace_switch_gesture_begin(&output);
layout.workspace_switch_gesture_begin(&output, is_touchpad);
}
Op::WorkspaceSwitchGestureUpdate { delta, timestamp } => {
layout.workspace_switch_gesture_update(delta, timestamp);
Op::WorkspaceSwitchGestureUpdate {
delta,
timestamp,
is_touchpad,
} => {
layout.workspace_switch_gesture_update(delta, timestamp, is_touchpad);
}
Op::WorkspaceSwitchGestureEnd { cancelled } => {
layout.workspace_switch_gesture_end(cancelled);
Op::WorkspaceSwitchGestureEnd {
cancelled,
is_touchpad,
} => {
layout.workspace_switch_gesture_end(cancelled, is_touchpad);
}
Op::InteractiveResizeBegin { window, edges } => {
layout.interactive_resize_begin(window, edges);
+35 -8
View File
@@ -54,10 +54,12 @@ pub enum WorkspaceSwitch {
#[derive(Debug)]
pub struct WorkspaceSwitchGesture {
/// Index of the workspace where the gesture was started.
pub center_idx: usize,
center_idx: usize,
/// Current, fractional workspace index.
pub current_idx: f64,
pub tracker: SwipeTracker,
tracker: SwipeTracker,
/// Whether the gesture is controlled by the touchpad.
is_touchpad: bool,
}
pub type MonitorRenderElement<R> =
@@ -973,7 +975,7 @@ impl<W: LayoutElement> Monitor<W> {
}
}
pub fn workspace_switch_gesture_begin(&mut self) {
pub fn workspace_switch_gesture_begin(&mut self, is_touchpad: bool) {
let center_idx = self.active_workspace_idx;
let current_idx = self
.workspace_switch
@@ -985,6 +987,7 @@ impl<W: LayoutElement> Monitor<W> {
center_idx,
current_idx,
tracker: SwipeTracker::new(),
is_touchpad,
};
self.workspace_switch = Some(WorkspaceSwitch::Gesture(gesture));
}
@@ -993,14 +996,24 @@ impl<W: LayoutElement> Monitor<W> {
&mut self,
delta_y: f64,
timestamp: Duration,
is_touchpad: bool,
) -> Option<bool> {
let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
return None;
};
if gesture.is_touchpad != is_touchpad {
return None;
}
gesture.tracker.push(delta_y, timestamp);
let pos = gesture.tracker.pos() / WORKSPACE_GESTURE_MOVEMENT;
let total_height = if gesture.is_touchpad {
WORKSPACE_GESTURE_MOVEMENT
} else {
self.workspaces[0].view_size().h
};
let pos = gesture.tracker.pos() / total_height;
let min = gesture.center_idx.saturating_sub(1) as f64;
let max = (gesture.center_idx + 1).min(self.workspaces.len() - 1) as f64;
@@ -1015,20 +1028,34 @@ impl<W: LayoutElement> Monitor<W> {
Some(true)
}
pub fn workspace_switch_gesture_end(&mut self, cancelled: bool) -> bool {
pub fn workspace_switch_gesture_end(
&mut self,
cancelled: bool,
is_touchpad: Option<bool>,
) -> bool {
let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
return false;
};
if is_touchpad.map_or(false, |x| gesture.is_touchpad != x) {
return false;
}
if cancelled {
self.workspace_switch = None;
self.clean_up_workspaces();
return true;
}
let mut velocity = gesture.tracker.velocity() / WORKSPACE_GESTURE_MOVEMENT;
let current_pos = gesture.tracker.pos() / WORKSPACE_GESTURE_MOVEMENT;
let pos = gesture.tracker.projected_end_pos() / WORKSPACE_GESTURE_MOVEMENT;
let total_height = if gesture.is_touchpad {
WORKSPACE_GESTURE_MOVEMENT
} else {
self.workspaces[0].view_size().h
};
let mut velocity = gesture.tracker.velocity() / total_height;
let current_pos = gesture.tracker.pos() / total_height;
let pos = gesture.tracker.projected_end_pos() / total_height;
let min = gesture.center_idx.saturating_sub(1) as f64;
let max = (gesture.center_idx + 1).min(self.workspaces.len() - 1) as f64;