aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRasmus Eneman <rasmus@eneman.eu>2024-07-15 15:51:48 +0200
committerIvan Molodetskikh <yalterz@gmail.com>2024-10-27 23:07:39 -0700
commite887ee93a30390b641bf647d694a1424f7ce4592 (patch)
tree94a76c90c2433ad3a0d92015d7ca6ba569ab2979 /src
parentd640e8515899e552b845cf8f901ebeb126bb12a5 (diff)
downloadniri-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.rs53
-rw-r--r--src/input/mod.rs37
-rw-r--r--src/input/move_grab.rs223
-rw-r--r--src/ipc/server.rs6
-rw-r--r--src/layout/mod.rs977
-rw-r--r--src/layout/monitor.rs53
-rw-r--r--src/layout/tile.rs13
-rw-r--r--src/layout/workspace.rs302
-rw-r--r--src/niri.rs14
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