Unify pointer & touch move grab, add view offset to it

This commit is contained in:
Ivan Molodetskikh
2025-10-30 08:35:19 +03:00
parent 9d522ed51e
commit 0cd8484bdc
5 changed files with 394 additions and 296 deletions
+9 -26
View File
@@ -40,7 +40,6 @@ use tracing::field::Empty;
use crate::input::move_grab::MoveGrab; use crate::input::move_grab::MoveGrab;
use crate::input::resize_grab::ResizeGrab; use crate::input::resize_grab::ResizeGrab;
use crate::input::touch_move_grab::TouchMoveGrab;
use crate::input::touch_resize_grab::TouchResizeGrab; use crate::input::touch_resize_grab::TouchResizeGrab;
use crate::input::{PointerOrTouchStartData, DOUBLE_CLICK_TIME}; use crate::input::{PointerOrTouchStartData, DOUBLE_CLICK_TIME};
use crate::layout::ActivateWindow; use crate::layout::ActivateWindow;
@@ -133,33 +132,17 @@ impl XdgShellHandler for State {
let window = mapped.window.clone(); let window = mapped.window.clone();
let output = output.clone(); let output = output.clone();
let output_pos = self match &start_data {
.niri PointerOrTouchStartData::Pointer(_) => {
.global_space if let Some(grab) = MoveGrab::new(self, start_data, window.clone(), true) {
.output_geometry(&output) pointer.set_grab(self, grab, serial, Focus::Clear);
.unwrap() }
.loc
.to_f64();
let pos_within_output = start_data.location() - output_pos;
if !self
.niri
.layout
.interactive_move_begin(window.clone(), &output, pos_within_output)
{
return;
}
match start_data {
PointerOrTouchStartData::Pointer(start_data) => {
let grab = MoveGrab::new(start_data, window, false);
pointer.set_grab(self, grab, serial, Focus::Clear);
} }
PointerOrTouchStartData::Touch(start_data) => { PointerOrTouchStartData::Touch(_) => {
let touch = self.niri.seat.get_touch().unwrap(); let touch = self.niri.seat.get_touch().unwrap();
let grab = TouchMoveGrab::new(start_data, window); if let Some(grab) = MoveGrab::new(self, start_data, window.clone(), true) {
touch.set_grab(self, grab, serial); touch.set_grab(self, grab, serial);
}
} }
} }
+44 -37
View File
@@ -36,7 +36,6 @@ use smithay::wayland::keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitor;
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint}; use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint};
use smithay::wayland::selection::data_device::DnDGrab; use smithay::wayland::selection::data_device::DnDGrab;
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait}; use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
use touch_move_grab::TouchMoveGrab;
use touch_overview_grab::TouchOverviewGrab; use touch_overview_grab::TouchOverviewGrab;
use self::move_grab::MoveGrab; use self::move_grab::MoveGrab;
@@ -59,7 +58,6 @@ pub mod scroll_swipe_gesture;
pub mod scroll_tracker; pub mod scroll_tracker;
pub mod spatial_movement_grab; pub mod spatial_movement_grab;
pub mod swipe_tracker; pub mod swipe_tracker;
pub mod touch_move_grab;
pub mod touch_overview_grab; pub mod touch_overview_grab;
pub mod touch_resize_grab; pub mod touch_resize_grab;
@@ -84,6 +82,28 @@ impl<D: SeatHandler> PointerOrTouchStartData<D> {
PointerOrTouchStartData::Touch(x) => x.location, PointerOrTouchStartData::Touch(x) => x.location,
} }
} }
pub fn unwrap_pointer(&self) -> &PointerGrabStartData<D> {
match self {
PointerOrTouchStartData::Pointer(x) => x,
PointerOrTouchStartData::Touch(_) => panic!("start_data is not Pointer"),
}
}
pub fn unwrap_touch(&self) -> &TouchGrabStartData<D> {
match self {
PointerOrTouchStartData::Pointer(_) => panic!("start_data is not Touch"),
PointerOrTouchStartData::Touch(x) => x,
}
}
pub fn is_pointer(&self) -> bool {
matches!(self, Self::Pointer(_))
}
pub fn is_touch(&self) -> bool {
matches!(self, Self::Touch(_))
}
} }
impl State { impl State {
@@ -2773,31 +2793,19 @@ impl State {
let mod_down = modifiers_from_state(mods).contains(mod_key.to_modifiers()); let mod_down = modifiers_from_state(mods).contains(mod_key.to_modifiers());
if is_overview_open || mod_down { if is_overview_open || mod_down {
let location = pointer.current_location(); let location = pointer.current_location();
let (output, pos_within_output) = self.niri.output_under(location).unwrap();
let output = output.clone();
if !is_overview_open { if !is_overview_open {
self.niri.layout.activate_window(&window); self.niri.layout.activate_window(&window);
} }
if self.niri.layout.interactive_move_begin( let start_data = PointerGrabStartData {
window.clone(), focus: None,
&output, button: button_code,
pos_within_output, location,
) { };
let start_data = PointerGrabStartData { let start_data = PointerOrTouchStartData::Pointer(start_data);
focus: None, if let Some(grab) = MoveGrab::new(self, start_data, window.clone(), false) {
button: button_code,
location,
};
let grab = MoveGrab::new(start_data, window.clone(), is_overview_open);
pointer.set_grab(self, grab, serial, Focus::Clear); pointer.set_grab(self, grab, serial, Focus::Clear);
if !is_overview_open {
self.niri
.cursor_manager
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Move));
}
} }
} }
} }
@@ -4037,22 +4045,15 @@ impl State {
} else if let Some((window, _)) = under.window { } else if let Some((window, _)) = under.window {
self.niri.layout.activate_window(&window); self.niri.layout.activate_window(&window);
// Check if we need to start an interactive move. // Check if we need to start a touch move grab.
if mod_down { if mod_down {
let (output, pos_within_output) = self.niri.output_under(pos).unwrap(); let start_data = TouchGrabStartData {
let output = output.clone(); focus: None,
slot,
if self.niri.layout.interactive_move_begin( location: pos,
window.clone(), };
&output, let start_data = PointerOrTouchStartData::Touch(start_data);
pos_within_output, if let Some(grab) = MoveGrab::new(self, start_data, window.clone(), true) {
) {
let start_data = TouchGrabStartData {
focus: None,
slot,
location: pos,
};
let grab = TouchMoveGrab::new(start_data, window.clone());
handle.set_grab(self, grab, serial); handle.set_grab(self, grab, serial);
} }
} }
@@ -4908,13 +4909,19 @@ fn grab_allows_hot_corner(grab: &(dyn PointerGrab<State> + 'static)) -> bool {
// //
// Some notable grabs not mentioned here: // Some notable grabs not mentioned here:
// - DnDGrab allows hot corner to DnD across workspaces. // - DnDGrab allows hot corner to DnD across workspaces.
// - MoveGrab allows hot corner to DnD across workspaces.
// - ClickGrab keeps pointer focus on the window, so the hot corner doesn't trigger. // - ClickGrab keeps pointer focus on the window, so the hot corner doesn't trigger.
// - Touch grabs: touch doesn't trigger the hot corner. // - Touch grabs: touch doesn't trigger the hot corner.
if grab.is::<ResizeGrab>() || grab.is::<SpatialMovementGrab>() { if grab.is::<ResizeGrab>() || grab.is::<SpatialMovementGrab>() {
return false; return false;
} }
if let Some(grab) = grab.downcast_ref::<MoveGrab>() {
// Window move allows hot corner to DnD across workspaces.
if !grab.is_move() {
return false;
}
}
true true
} }
+341 -67
View File
@@ -1,3 +1,5 @@
use std::time::Duration;
use smithay::backend::input::ButtonState; use smithay::backend::input::ButtonState;
use smithay::desktop::Window; use smithay::desktop::Window;
use smithay::input::pointer::{ use smithay::input::pointer::{
@@ -7,52 +9,260 @@ use smithay::input::pointer::{
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
RelativeMotionEvent, RelativeMotionEvent,
}; };
use smithay::input::touch::{
self, GrabStartData as TouchGrabStartData, TouchGrab, TouchInnerHandle,
};
use smithay::input::SeatHandler; 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; use crate::niri::State;
pub struct MoveGrab { 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>, last_location: Point<f64, Logical>,
window: Window, window: Window,
gesture: GestureState, gesture: GestureState,
enable_view_offset: bool,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum GestureState { enum GestureState {
Recognizing, Recognizing,
Move, Move,
ViewOffset,
} }
impl MoveGrab { impl MoveGrab {
pub fn new( pub fn new(
start_data: PointerGrabStartData<State>, state: &mut State,
start_data: PointerOrTouchStartData<State>,
window: Window, window: Window,
use_threshold: bool, enable_view_offset: bool,
) -> Self { ) -> Option<Self> {
let gesture = if use_threshold { let (output, pos_within_output) = state.niri.output_under(start_data.location())?;
GestureState::Recognizing
} else {
GestureState::Move
};
Self { Some(Self {
last_location: start_data.location, last_location: start_data.location(),
start_data, start_data,
start_output: output.clone(),
start_pos_within_output: pos_within_output,
window, window,
gesture, gesture: GestureState::Recognizing,
} enable_view_offset,
})
} }
fn on_ungrab(&mut self, state: &mut State) { pub fn is_move(&self) -> bool {
state.niri.layout.interactive_move_end(&self.window); 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. // FIXME: only redraw the window output.
state.niri.queue_redraw_all(); data.niri.queue_redraw_all();
state }
.niri
.cursor_manager fn begin_move(&mut self, data: &mut State) -> bool {
.set_cursor_image(CursorImageStatus::default_named()); 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,
location: Point<f64, Logical>,
timestamp: Duration,
) -> bool {
let mut delta = location - self.last_location;
self.last_location = location;
// Try to recognize the gesture.
if self.gesture == GestureState::Recognizing {
// Check if the window has closed.
if !self.window.alive() {
return false;
}
// 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);
let is_view_offset =
self.enable_view_offset && !is_floating && c.x.abs() > c.y.abs();
let started = if is_view_offset {
self.begin_view_offset(data)
} else {
self.begin_move(data)
};
if !started {
return false;
}
// 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,
delta,
output,
pos_within_output,
);
if ongoing {
// FIXME: only redraw the previous and the new output.
data.niri.queue_redraw_all();
return true;
}
}
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;
}
}
data.niri.layout.toggle_window_floating(Some(&self.window));
data.niri.queue_redraw_all();
true
} }
} }
@@ -67,47 +277,11 @@ impl PointerGrab<State> for MoveGrab {
// While the grab is active, no client has pointer focus. // While the grab is active, no client has pointer focus.
handle.motion(data, None, event); handle.motion(data, None, event);
if self.window.alive() { let timestamp = Duration::from_millis(u64::from(event.time));
if let Some((output, pos_within_output)) = data.niri.output_under(event.location) { if !self.on_motion(data, event.location, timestamp) {
let output = output.clone(); // The gesture is no longer ongoing.
let event_delta = event.location - self.last_location; handle.unset_grab(self, data, event.serial, event.time, true);
self.last_location = event.location;
if self.gesture == GestureState::Recognizing {
let c = event.location - self.start_data.location;
// Check if the gesture moved far enough to decide.
if c.x * c.x + c.y * c.y >= 8. * 8. {
self.gesture = GestureState::Move;
data.niri
.cursor_manager
.set_cursor_image(CursorImageStatus::Named(CursorIcon::Move));
}
}
if self.gesture != GestureState::Move {
return;
}
let ongoing = data.niri.layout.interactive_move_update(
&self.window,
event_delta,
output,
pos_within_output,
);
if ongoing {
// FIXME: only redraw the previous and the new output.
data.niri.queue_redraw_all();
return;
}
} else {
return;
}
} }
// The move is no longer ongoing.
handle.unset_grab(self, data, event.serial, event.time, true);
} }
fn relative_motion( fn relative_motion(
@@ -129,18 +303,25 @@ impl PointerGrab<State> for MoveGrab {
) { ) {
handle.button(data, event); 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. // 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 0x111
} else { } else {
0x110 0x110
}; };
if event.button == toggle_floating_button && event.state == ButtonState::Pressed { if event.state != ButtonState::Pressed || event.button != toggle_floating_button {
data.niri.layout.toggle_window_floating(Some(&self.window)); return;
} }
if !handle.current_pressed().contains(&self.start_data.button) { if !self.on_toggle_floating(data) {
// The button that initiated the grab was released.
handle.unset_grab(self, data, event.serial, event.time, true); 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> { 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) { fn unset(&mut self, data: &mut State) {
-136
View File
@@ -1,136 +0,0 @@
use smithay::desktop::Window;
use smithay::input::touch::{
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent, OrientationEvent, ShapeEvent,
TouchGrab, TouchInnerHandle, UpEvent,
};
use smithay::input::SeatHandler;
use smithay::utils::{IsAlive, Logical, Point, Serial};
use crate::niri::State;
pub struct TouchMoveGrab {
start_data: TouchGrabStartData<State>,
last_location: Point<f64, Logical>,
window: Window,
}
impl TouchMoveGrab {
pub fn new(start_data: TouchGrabStartData<State>, window: Window) -> Self {
Self {
last_location: start_data.location,
start_data,
window,
}
}
fn on_ungrab(&mut self, state: &mut State) {
state.niri.layout.interactive_move_end(&self.window);
// FIXME: only redraw the window output.
state.niri.queue_redraw_all();
}
}
impl TouchGrab<State> for TouchMoveGrab {
fn down(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
_focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
event: &DownEvent,
seq: Serial,
) {
handle.down(data, None, event, seq);
}
fn up(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &UpEvent,
seq: Serial,
) {
handle.up(data, event, seq);
if event.slot != self.start_data.slot {
return;
}
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: &MotionEvent,
seq: Serial,
) {
handle.motion(data, None, event, seq);
if event.slot != self.start_data.slot {
return;
}
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;
let ongoing = data.niri.layout.interactive_move_update(
&self.window,
event_delta,
output,
pos_within_output,
);
if ongoing {
// FIXME: only redraw the previous and the new output.
data.niri.queue_redraw_all();
return;
}
} else {
return;
}
}
// The move 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: &ShapeEvent,
seq: Serial,
) {
handle.shape(data, event, seq);
}
fn orientation(
&mut self,
data: &mut State,
handle: &mut TouchInnerHandle<'_, State>,
event: &OrientationEvent,
seq: Serial,
) {
handle.orientation(data, event, seq);
}
fn start_data(&self) -> &TouchGrabStartData<State> {
&self.start_data
}
fn unset(&mut self, data: &mut State) {
self.on_ungrab(data);
}
}
-30
View File
@@ -4040,16 +4040,12 @@ impl<W: LayoutElement> Layout<W> {
mon.dnd_scroll_gesture_end(); mon.dnd_scroll_gesture_end();
} }
let mut ws_id = None;
for ws in self.workspaces_mut() { for ws in self.workspaces_mut() {
let id = ws.id();
if let Some(tile) = ws.tiles_mut().find(|tile| *tile.window().id() == window_id) if let Some(tile) = ws.tiles_mut().find(|tile| *tile.window().id() == window_id)
{ {
let offset = tile.interactive_move_offset; let offset = tile.interactive_move_offset;
tile.interactive_move_offset = Point::from((0., 0.)); tile.interactive_move_offset = Point::from((0., 0.));
tile.animate_move_from(offset); tile.animate_move_from(offset);
ws_id = Some(id);
} }
// Unlock the view on the workspaces, but if the moved window was active, // Unlock the view on the workspaces, but if the moved window was active,
@@ -4064,32 +4060,6 @@ impl<W: LayoutElement> Layout<W> {
} }
} }
// In the overview, we want to click on a window to focus it, and also to
// click-and-drag to move the window. The way we handle this is by always starting
// the interactive move (to get frozen view), then, when in the overview, *not*
// calling interactive_move_update() until the cursor moves far enough. This means
// that if we "just click" then we end up in this branch with state == Starting.
// Close the overview in this case.
if self.overview_open {
let ws_id = ws_id.unwrap();
if let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set {
for mon in monitors {
if let Some(ws_idx) =
mon.workspaces.iter().position(|ws| ws.id() == ws_id)
{
mon.activate_workspace_with_anim_config(
ws_idx,
Some(self.options.animations.overview_open_close.0),
);
break;
}
}
}
self.activate_window(&window_id);
self.close_overview();
}
return; return;
} }
InteractiveMoveState::Moving(move_) => move_, InteractiveMoveState::Moving(move_) => move_,