mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
Implement vertical middle mouse gesture
This commit is contained in:
+8
-8
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user