diff options
| author | Rasmus Eneman <rasmus@eneman.eu> | 2024-07-15 15:51:48 +0200 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-10-27 23:07:39 -0700 |
| commit | e887ee93a30390b641bf647d694a1424f7ce4592 (patch) | |
| tree | 94a76c90c2433ad3a0d92015d7ca6ba569ab2979 /src | |
| parent | d640e8515899e552b845cf8f901ebeb126bb12a5 (diff) | |
| download | niri-e887ee93a30390b641bf647d694a1424f7ce4592.tar.gz niri-e887ee93a30390b641bf647d694a1424f7ce4592.tar.bz2 niri-e887ee93a30390b641bf647d694a1424f7ce4592.zip | |
Implement interactive window move
Diffstat (limited to 'src')
| -rw-r--r-- | src/handlers/xdg_shell.rs | 53 | ||||
| -rw-r--r-- | src/input/mod.rs | 37 | ||||
| -rw-r--r-- | src/input/move_grab.rs | 223 | ||||
| -rw-r--r-- | src/ipc/server.rs | 6 | ||||
| -rw-r--r-- | src/layout/mod.rs | 977 | ||||
| -rw-r--r-- | src/layout/monitor.rs | 53 | ||||
| -rw-r--r-- | src/layout/tile.rs | 13 | ||||
| -rw-r--r-- | src/layout/workspace.rs | 302 | ||||
| -rw-r--r-- | src/niri.rs | 14 |
9 files changed, 1622 insertions, 56 deletions
diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index be7dc813..2397f22a 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -6,7 +6,7 @@ use smithay::desktop::{ PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, PopupUngrabStrategy, Window, WindowSurfaceType, }; -use smithay::input::pointer::Focus; +use smithay::input::pointer::{Focus, PointerGrab}; use smithay::output::Output; use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1; use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_positioner::ConstraintAdjustment; @@ -36,6 +36,7 @@ use smithay::{ }; use tracing::field::Empty; +use crate::input::move_grab::MoveGrab; use crate::input::resize_grab::ResizeGrab; use crate::input::DOUBLE_CLICK_TIME; use crate::layout::workspace::ColumnWidth; @@ -65,8 +66,54 @@ impl XdgShellHandler for State { } } - fn move_request(&mut self, _surface: ToplevelSurface, _seat: WlSeat, _serial: Serial) { - // FIXME + fn move_request(&mut self, surface: ToplevelSurface, _seat: WlSeat, serial: Serial) { + let pointer = self.niri.seat.get_pointer().unwrap(); + if !pointer.has_grab(serial) { + return; + } + + let Some(start_data) = pointer.grab_start_data() else { + return; + }; + + let Some((focus, _)) = &start_data.focus else { + return; + }; + + let wl_surface = surface.wl_surface(); + if !focus.id().same_client_as(&wl_surface.id()) { + return; + } + + let Some((mapped, output)) = self.niri.layout.find_window_and_output(wl_surface) else { + return; + }; + + let window = mapped.window.clone(); + let output = output.clone(); + + let output_pos = self + .niri + .global_space + .output_geometry(&output) + .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; + } + + let grab = MoveGrab::new(start_data, window.clone()); + + pointer.set_grab(self, grab, serial, Focus::Clear); + self.niri.pointer_grab_ongoing = true; } fn resize_request( diff --git a/src/input/mod.rs b/src/input/mod.rs index 3bccb714..b3e38892 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -29,6 +29,7 @@ use smithay::utils::{Logical, Point, Rectangle, Transform, SERIAL_COUNTER}; use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint}; use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait}; +use self::move_grab::MoveGrab; use self::resize_grab::ResizeGrab; use self::spatial_movement_grab::SpatialMovementGrab; use crate::niri::State; @@ -36,6 +37,7 @@ use crate::ui::screenshot_ui::ScreenshotUi; use crate::utils::spawning::spawn; use crate::utils::{center, get_monotonic_time, ResizeEdge}; +pub mod move_grab; pub mod resize_grab; pub mod scroll_tracker; pub mod spatial_movement_grab; @@ -1535,8 +1537,41 @@ impl State { if let Some(mapped) = self.niri.window_under_cursor() { let window = mapped.window.clone(); + // Check if we need to start an interactive move. + if event.button() == Some(MouseButton::Left) && !pointer.is_grabbed() { + let mods = self.niri.seat.get_keyboard().unwrap().modifier_state(); + let mod_down = match self.backend.mod_key() { + CompositorMod::Super => mods.logo, + CompositorMod::Alt => mods.alt, + }; + if mod_down { + let location = pointer.current_location(); + let (output, pos_within_output) = self.niri.output_under(location).unwrap(); + let output = output.clone(); + + self.niri.layout.activate_window(&window); + + if self.niri.layout.interactive_move_begin( + window.clone(), + &output, + pos_within_output, + ) { + let start_data = PointerGrabStartData { + focus: None, + button: event.button_code(), + location, + }; + let grab = MoveGrab::new(start_data, window.clone()); + pointer.set_grab(self, grab, serial, Focus::Clear); + self.niri.pointer_grab_ongoing = true; + self.niri + .cursor_manager + .set_cursor_image(CursorImageStatus::Named(CursorIcon::Move)); + } + } + } // Check if we need to start an interactive resize. - if event.button() == Some(MouseButton::Right) && !pointer.is_grabbed() { + else if event.button() == Some(MouseButton::Right) && !pointer.is_grabbed() { let mods = self.niri.seat.get_keyboard().unwrap().modifier_state(); let mod_down = match self.backend.mod_key() { CompositorMod::Super => mods.logo, diff --git a/src/input/move_grab.rs b/src/input/move_grab.rs new file mode 100644 index 00000000..71d115d7 --- /dev/null +++ b/src/input/move_grab.rs @@ -0,0 +1,223 @@ +use std::time::Duration; + +use smithay::backend::input::ButtonState; +use smithay::desktop::Window; +use smithay::input::pointer::{ + AxisFrame, ButtonEvent, CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent, + GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, + GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, + MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent, +}; +use smithay::input::SeatHandler; +use smithay::utils::{IsAlive, Logical, Point}; + +use crate::niri::State; + +pub struct MoveGrab { + start_data: PointerGrabStartData<State>, + last_location: Point<f64, Logical>, + window: Window, + is_moving: bool, +} + +impl MoveGrab { + pub fn new(start_data: PointerGrabStartData<State>, window: Window) -> Self { + Self { + last_location: start_data.location, + start_data, + window, + is_moving: false, + } + } + + 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(); + state.niri.pointer_grab_ongoing = false; + state + .niri + .cursor_manager + .set_cursor_image(CursorImageStatus::default_named()); + } +} + +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); + + 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 { + let timestamp = Duration::from_millis(u64::from(event.time)); + if self.is_moving { + data.niri.layout.view_offset_gesture_update( + -event_delta.x, + timestamp, + false, + ); + } + return; + } + } else { + return; + } + } + + // The move is no longer ongoing. + handle.unset_grab(self, data, event.serial, event.time, true); + } + + fn relative_motion( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + _focus: Option<(<State as SeatHandler>::PointerFocus, Point<f64, Logical>)>, + event: &RelativeMotionEvent, + ) { + // While the grab is active, no client has pointer focus. + handle.relative_motion(data, None, event); + } + + fn button( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &ButtonEvent, + ) { + handle.button(data, event); + + // MouseButton::Middle + if event.button == 0x112 { + if event.state == ButtonState::Pressed { + let output = data + .niri + .output_under(handle.current_location()) + .map(|(output, _)| output) + .cloned(); + // TODO: workspace switch gesture. + if let Some(output) = output { + self.is_moving = true; + data.niri.layout.view_offset_gesture_begin(&output, false); + } + } else if event.state == ButtonState::Released { + self.is_moving = false; + data.niri.layout.view_offset_gesture_end(false, None); + } + } + + if handle.current_pressed().is_empty() { + // No more buttons are pressed, release the grab. + handle.unset_grab(self, data, event.serial, event.time, true); + } + } + + fn axis( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + details: AxisFrame, + ) { + handle.axis(data, details); + } + + fn frame(&mut self, data: &mut State, handle: &mut PointerInnerHandle<'_, State>) { + handle.frame(data); + } + + fn gesture_swipe_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeBeginEvent, + ) { + handle.gesture_swipe_begin(data, event); + } + + fn gesture_swipe_update( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeUpdateEvent, + ) { + handle.gesture_swipe_update(data, event); + } + + fn gesture_swipe_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureSwipeEndEvent, + ) { + handle.gesture_swipe_end(data, event); + } + + fn gesture_pinch_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchBeginEvent, + ) { + handle.gesture_pinch_begin(data, event); + } + + fn gesture_pinch_update( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchUpdateEvent, + ) { + handle.gesture_pinch_update(data, event); + } + + fn gesture_pinch_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GesturePinchEndEvent, + ) { + handle.gesture_pinch_end(data, event); + } + + fn gesture_hold_begin( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureHoldBeginEvent, + ) { + handle.gesture_hold_begin(data, event); + } + + fn gesture_hold_end( + &mut self, + data: &mut State, + handle: &mut PointerInnerHandle<'_, State>, + event: &GestureHoldEndEvent, + ) { + handle.gesture_hold_end(data, event); + } + + fn start_data(&self) -> &PointerGrabStartData<State> { + &self.start_data + } + + fn unset(&mut self, data: &mut State) { + self.on_ungrab(data); + } +} diff --git a/src/ipc/server.rs b/src/ipc/server.rs index 79af18d5..608bb629 100644 --- a/src/ipc/server.rs +++ b/src/ipc/server.rs @@ -551,12 +551,12 @@ impl State { } let Some(ipc_win) = state.windows.get(&id) else { - let window = make_ipc_window(mapped, Some(ws_id)); + let window = make_ipc_window(mapped, ws_id); events.push(Event::WindowOpenedOrChanged { window }); return; }; - let workspace_id = Some(ws_id.get()); + let workspace_id = ws_id.map(|id| id.get()); let mut changed = ipc_win.workspace_id != workspace_id; let wl_surface = mapped.toplevel().wl_surface(); @@ -572,7 +572,7 @@ impl State { }); if changed { - let window = make_ipc_window(mapped, Some(ws_id)); + let window = make_ipc_window(mapped, ws_id); events.push(Event::WindowOpenedOrChanged { window }); return; } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 3e6affb1..b404da16 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -43,19 +43,21 @@ use smithay::backend::renderer::element::Id; use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture}; use smithay::output::{self, Output}; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; -use smithay::utils::{Logical, Point, Scale, Serial, Size, Transform}; -use tile::Tile; +use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size, Transform}; +use tile::{Tile, TileRenderElement}; use workspace::WorkspaceId; pub use self::monitor::MonitorRenderElement; use self::monitor::{Monitor, WorkspaceSwitch}; -use self::workspace::{compute_working_area, Column, ColumnWidth, OutputId, Workspace}; +use self::workspace::{compute_working_area, Column, ColumnWidth, InsertHint, OutputId, Workspace}; +use crate::layout::workspace::InsertPosition; use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; use crate::render_helpers::snapshot::RenderSnapshot; use crate::render_helpers::solid_color::{SolidColorBuffer, SolidColorRenderElement}; use crate::render_helpers::texture::TextureBuffer; use crate::render_helpers::{BakedBuffer, RenderTarget, SplitElements}; +use crate::rubber_band::RubberBand; use crate::utils::transaction::{Transaction, TransactionBlocker}; use crate::utils::{output_matches_name, output_size, round_logical_in_physical_max1, ResizeEdge}; use crate::window::ResolvedWindowRules; @@ -70,6 +72,9 @@ pub mod workspace; /// Size changes up to this many pixels don't animate. pub const RESIZE_ANIMATION_THRESHOLD: f64 = 10.; +/// Pointer needs to move this far to pull a window from the layout. +const INTERACTIVE_MOVE_START_THRESHOLD: f64 = 256. * 256.; + niri_render_elements! { LayoutElementRenderElement<R> => { Wayland = WaylandSurfaceRenderElement<R>, @@ -80,6 +85,41 @@ niri_render_elements! { pub type LayoutElementRenderSnapshot = RenderSnapshot<BakedBuffer<TextureBuffer<GlesTexture>>, BakedBuffer<SolidColorBuffer>>; +#[derive(Debug)] +enum InteractiveMoveState<W: LayoutElement> { + /// Initial rubberbanding; the window remains in the layout. + Starting { + /// The window we're moving. + window_id: W::Id, + /// Current pointer delta from the starting location. + pointer_delta: Point<f64, Logical>, + /// Pointer location within the visual window geometry as ratio from geometry size. + /// + /// This helps the pointer remain inside the window as it resizes. + pointer_ratio_within_window: (f64, f64), + }, + /// Moving; the window is no longer in the layout. + Moving(InteractiveMoveData<W>), +} + +#[derive(Debug)] +struct InteractiveMoveData<W: LayoutElement> { + /// The window being moved. + pub(self) tile: Tile<W>, + /// Output where the window is currently located/rendered. + pub(self) output: Output, + /// Current pointer position within output. + pub(self) pointer_pos_within_output: Point<f64, Logical>, + /// Window column width. + pub(self) width: ColumnWidth, + /// Whether the window column was full-width. + pub(self) is_full_width: bool, + /// Pointer location within the visual window geometry as ratio from geometry size. + /// + /// This helps the pointer remain inside the window as it resizes. + pub(self) pointer_ratio_within_window: (f64, f64), +} + #[derive(Debug, Clone, Copy)] pub struct InteractiveResizeData { pub(self) edges: ResizeEdge, @@ -216,6 +256,8 @@ pub struct Layout<W: LayoutElement> { /// This normally indicates that the layout has keyboard focus, but not always. E.g. when the /// screenshot UI is open, it keeps the layout drawing as active. is_active: bool, + /// Ongoing interactive move. + interactive_move: Option<InteractiveMoveState<W>>, /// Configurable properties of the layout. options: Rc<Options>, } @@ -246,6 +288,7 @@ pub struct Options { pub struts: Struts, pub focus_ring: niri_config::FocusRing, pub border: niri_config::Border, + pub insert_hint: niri_config::InsertHint, pub center_focused_column: CenterFocusedColumn, pub always_center_single_column: bool, /// Column widths that `toggle_width()` switches between. @@ -267,6 +310,7 @@ impl Default for Options { struts: Default::default(), focus_ring: Default::default(), border: Default::default(), + insert_hint: Default::default(), center_focused_column: Default::default(), always_center_single_column: false, preset_column_widths: vec![ @@ -296,6 +340,31 @@ pub struct RemovedTile<W: LayoutElement> { is_full_width: bool, } +impl<W: LayoutElement> InteractiveMoveState<W> { + fn moving(&self) -> Option<&InteractiveMoveData<W>> { + match self { + InteractiveMoveState::Moving(move_) => Some(move_), + _ => None, + } + } +} + +impl<W: LayoutElement> InteractiveMoveData<W> { + fn tile_render_location(&self) -> Point<f64, Logical> { + let scale = Scale::from(self.output.current_scale().fractional_scale()); + let window_size = self.tile.window_size(); + let pointer_offset_within_window = Point::from(( + window_size.w * self.pointer_ratio_within_window.0, + window_size.h * self.pointer_ratio_within_window.1, + )); + let pos = + self.pointer_pos_within_output - pointer_offset_within_window - self.tile.window_loc() + + self.tile.render_offset(); + // Round to physical pixels. + pos.to_physical_precise_round(scale).to_logical(scale) + } +} + impl Options { fn from_config(config: &Config) -> Self { let layout = &config.layout; @@ -329,6 +398,7 @@ impl Options { struts: layout.struts, focus_ring: layout.focus_ring, border: layout.border, + insert_hint: layout.insert_hint, center_focused_column: layout.center_focused_column, always_center_single_column: layout.always_center_single_column, preset_column_widths, @@ -360,6 +430,7 @@ impl<W: LayoutElement> Layout<W> { Self { monitor_set: MonitorSet::NoOutputs { workspaces: vec![] }, is_active: true, + interactive_move: None, options: Rc::new(options), } } @@ -376,6 +447,7 @@ impl<W: LayoutElement> Layout<W> { Self { monitor_set: MonitorSet::NoOutputs { workspaces }, is_active: true, + interactive_move: None, options: opts, } } @@ -693,6 +765,18 @@ impl<W: LayoutElement> Layout<W> { width: Option<ColumnWidth>, is_full_width: bool, ) -> Option<&Output> { + if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { + if right_of == move_.tile.window().id() { + let output = move_.output.clone(); + if self.monitor_for_output(&output).is_some() { + self.add_window_on_output(&output, window, width, is_full_width); + return Some(&self.monitor_for_output(&output).unwrap().output); + } else { + return self.add_window(window, width, is_full_width); + } + } + } + let width = self.resolve_default_width(&window, width); match &mut self.monitor_set { @@ -765,6 +849,30 @@ impl<W: LayoutElement> Layout<W> { window: &W::Id, transaction: Transaction, ) -> Option<RemovedTile<W>> { + if let Some(state) = &self.interactive_move { + match state { + InteractiveMoveState::Starting { window_id, .. } => { + if window_id == window { + self.interactive_move_end(window); + } + } + InteractiveMoveState::Moving(move_) => { + if move_.tile.window().id() == window { + let Some(InteractiveMoveState::Moving(move_)) = + self.interactive_move.take() + else { + unreachable!() + }; + return Some(RemovedTile { + tile: move_.tile, + width: move_.width, + is_full_width: move_.is_full_width, + }); + } + } + } + } + match &mut self.monitor_set { MonitorSet::Normal { monitors, .. } => { for mon in monitors { @@ -811,6 +919,13 @@ impl<W: LayoutElement> Layout<W> { } pub fn update_window(&mut self, window: &W::Id, serial: Option<Serial>) { + if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { + if move_.tile.window().id() == window { + move_.tile.update_window(); + return; + } + } + match &mut self.monitor_set { MonitorSet::Normal { monitors, .. } => { for mon in monitors { @@ -834,6 +949,12 @@ impl<W: LayoutElement> Layout<W> { } pub fn find_window_and_output(&self, wl_surface: &WlSurface) -> Option<(&W, &Output)> { + if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { + if move_.tile.window().is_wl_surface(wl_surface) { + return Some((move_.tile.window(), &move_.output)); + } + } + if let MonitorSet::Normal { monitors, .. } = &self.monitor_set { for mon in monitors { for ws in &mon.workspaces { @@ -939,6 +1060,12 @@ impl<W: LayoutElement> Layout<W> { &mut self, wl_surface: &WlSurface, ) -> Option<(&mut W, Option<&Output>)> { + if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { + if move_.tile.window().is_wl_surface(wl_surface) { + return Some((move_.tile.window_mut(), Some(&move_.output))); + } + } + match &mut self.monitor_set { MonitorSet::Normal { monitors, .. } => { for mon in monitors { @@ -962,6 +1089,12 @@ impl<W: LayoutElement> Layout<W> { } pub fn window_loc(&self, window: &W::Id) -> Option<Point<f64, Logical>> { + if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { + if move_.tile.window().id() == window { + return Some(move_.tile.window_loc()); + } + } + match &self.monitor_set { MonitorSet::Normal { monitors, .. } => { for mon in monitors { @@ -1012,6 +1145,12 @@ impl<W: LayoutElement> Layout<W> { } pub fn scroll_amount_to_activate(&self, window: &W::Id) -> f64 { + if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { + if move_.tile.window().id() == window { + return 0.; + } + } + let MonitorSet::Normal { monitors, .. } = &self.monitor_set else { return 0.; }; @@ -1034,6 +1173,12 @@ impl<W: LayoutElement> Layout<W> { // // This function allows focus-follows-mouse to trigger only on the animation target // workspace. + if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { + if move_.tile.window().id() == window { + return true; + } + } + let MonitorSet::Normal { monitors, .. } = &self.monitor_set else { return true; }; @@ -1057,6 +1202,12 @@ impl<W: LayoutElement> Layout<W> { } pub fn activate_window(&mut self, window: &W::Id) { + if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { + if move_.tile.window().id() == window { + return; + } + } + let MonitorSet::Normal { monitors, active_monitor_idx, @@ -1146,6 +1297,10 @@ impl<W: LayoutElement> Layout<W> { } pub fn active_window(&self) -> Option<(&W, &Output)> { + if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { + return Some((move_.tile.window(), &move_.output)); + } + let MonitorSet::Normal { monitors, active_monitor_idx, @@ -1171,17 +1326,31 @@ impl<W: LayoutElement> Layout<W> { panic!() }; + let moving_window = self + .interactive_move + .as_ref() + .and_then(|x| x.moving()) + .filter(|move_| move_.output == *output) + .map(|move_| move_.tile.window()) + .into_iter(); + let mon = monitors.iter().find(|mon| &mon.output == output).unwrap(); - mon.workspaces.iter().flat_map(|ws| ws.windows()) + let mon_windows = mon.workspaces.iter().flat_map(|ws| ws.windows()); + + moving_window.chain(mon_windows) } - pub fn with_windows(&self, mut f: impl FnMut(&W, Option<&Output>, WorkspaceId)) { + pub fn with_windows(&self, mut f: impl FnMut(&W, Option<&Output>, Option<WorkspaceId>)) { + if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { + f(move_.tile.window(), Some(&move_.output), None); + } + match &self.monitor_set { MonitorSet::Normal { monitors, .. } => { for mon in monitors { for ws in &mon.workspaces { for win in ws.windows() { - f(win, Some(&mon.output), ws.id()); + f(win, Some(&mon.output), Some(ws.id())); } } } @@ -1189,7 +1358,7 @@ impl<W: LayoutElement> Layout<W> { MonitorSet::NoOutputs { workspaces } => { for ws in workspaces { for win in ws.windows() { - f(win, None, ws.id()); + f(win, None, Some(ws.id())); } } } @@ -1197,6 +1366,10 @@ impl<W: LayoutElement> Layout<W> { } pub fn with_windows_mut(&mut self, mut f: impl FnMut(&mut W, Option<&Output>)) { + if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { + f(move_.tile.window_mut(), Some(&move_.output)); + } + match &mut self.monitor_set { MonitorSet::Normal { monitors, .. } => { for mon in monitors { @@ -1248,7 +1421,15 @@ impl<W: LayoutElement> Layout<W> { return None; }; - monitors.iter().find(|monitor| &monitor.output == output) + monitors.iter().find(|mon| &mon.output == output) + } + + pub fn monitor_for_output_mut(&mut self, output: &Output) -> Option<&mut Monitor<W>> { + let MonitorSet::Normal { monitors, .. } = &mut self.monitor_set else { + return None; + }; + + monitors.iter_mut().find(|mon| &mon.output == output) } pub fn monitor_for_workspace(&self, workspace_name: &str) -> Option<&Monitor<W>> { @@ -1362,6 +1543,12 @@ impl<W: LayoutElement> Layout<W> { } pub fn consume_or_expel_window_left(&mut self, window: Option<&W::Id>) { + if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { + if window == Some(move_.tile.window().id()) { + return; + } + } + let workspace = if let Some(window) = window { Some( self.workspaces_mut() @@ -1379,6 +1566,12 @@ impl<W: LayoutElement> Layout<W> { } pub fn consume_or_expel_window_right(&mut self, window: Option<&W::Id>) { + if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { + if window == Some(move_.tile.window().id()) { + return; + } + } + let workspace = if let Some(window) = window { Some( self.workspaces_mut() @@ -1576,6 +1769,12 @@ impl<W: LayoutElement> Layout<W> { } pub fn move_to_workspace(&mut self, window: Option<&W::Id>, idx: usize) { + if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { + if window == Some(move_.tile.window().id()) { + return; + } + } + let Some(monitor) = self.active_monitor() else { return; }; @@ -1666,6 +1865,10 @@ impl<W: LayoutElement> Layout<W> { } pub fn focus(&self) -> Option<&W> { + if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { + return Some(move_.tile.window()); + } + let MonitorSet::Normal { monitors, active_monitor_idx, @@ -1694,6 +1897,20 @@ impl<W: LayoutElement> Layout<W> { return None; }; + if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { + let tile_pos = move_.tile_render_location(); + let pos_within_tile = pos_within_output - tile_pos; + + if move_.tile.is_in_input_region(pos_within_tile) { + let pos_within_surface = tile_pos + move_.tile.buf_loc(); + return Some((move_.tile.window(), Some(pos_within_surface))); + } else if move_.tile.is_in_activation_region(pos_within_tile) { + return Some((move_.tile.window(), None)); + } + + return None; + }; + let mon = monitors.iter().find(|mon| &mon.output == output)?; mon.window_under(pos_within_output) } @@ -1715,8 +1932,43 @@ impl<W: LayoutElement> Layout<W> { fn verify_invariants(&self) { use std::collections::HashSet; + use approx::assert_abs_diff_eq; + use crate::layout::monitor::WorkspaceSwitch; + let mut move_win_id = None; + if let Some(state) = &self.interactive_move { + match state { + InteractiveMoveState::Starting { + window_id, + pointer_delta: _, + pointer_ratio_within_window: _, + } => { + assert!( + self.has_window(window_id), + "interactive move must be on an existing window" + ); + move_win_id = Some(window_id.clone()); + } + InteractiveMoveState::Moving(move_) => { + let scale = move_.output.current_scale().fractional_scale(); + let options = Options::clone(&self.options).adjusted_for_scale(scale); + assert_eq!( + &*move_.tile.options, &options, + "interactive moved tile options must be \ + base options adjusted for output scale" + ); + + let tile_pos = move_.tile_render_location(); + let rounded_pos = tile_pos.to_physical_precise_round(scale).to_logical(scale); + + // Tile position must be rounded to physical pixels. + assert_abs_diff_eq!(tile_pos.x, rounded_pos.x, epsilon = 1e-5); + assert_abs_diff_eq!(tile_pos.y, rounded_pos.y, epsilon = 1e-5); + } + } + } + let mut seen_workspace_id = HashSet::new(); let mut seen_workspace_name = Vec::<String>::new(); @@ -1760,7 +2012,7 |
