aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/input/mod.rs16
-rw-r--r--src/layout/floating.rs45
-rw-r--r--src/layout/mod.rs47
-rw-r--r--src/layout/scrolling.rs9
-rw-r--r--src/layout/workspace.rs51
5 files changed, 152 insertions, 16 deletions
diff --git a/src/input/mod.rs b/src/input/mod.rs
index d874ee34..83336fb3 100644
--- a/src/input/mod.rs
+++ b/src/input/mod.rs
@@ -1361,6 +1361,22 @@ impl State {
// FIXME: granular
self.niri.queue_redraw_all();
}
+ Action::MoveFloatingWindowById { id, x, y } => {
+ let window = if let Some(id) = id {
+ let window = self.niri.layout.windows().find(|(_, m)| m.id().get() == id);
+ let window = window.map(|(_, m)| m.window.clone());
+ if window.is_none() {
+ return;
+ }
+ window
+ } else {
+ None
+ };
+
+ self.niri.layout.move_floating_window(window.as_ref(), x, y);
+ // FIXME: granular
+ self.niri.queue_redraw_all();
+ }
}
}
diff --git a/src/layout/floating.rs b/src/layout/floating.rs
index 0552b01d..d59a33e7 100644
--- a/src/layout/floating.rs
+++ b/src/layout/floating.rs
@@ -3,7 +3,7 @@ use std::iter::zip;
use std::rc::Rc;
use niri_config::PresetSize;
-use niri_ipc::SizeChange;
+use niri_ipc::{PositionChange, SizeChange};
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Point, Rectangle, Scale, Serial, Size};
@@ -839,16 +839,19 @@ impl<W: LayoutElement> FloatingSpace<W> {
}
}
+ fn move_to(&mut self, idx: usize, new_pos: Point<f64, Logical>) {
+ self.move_and_animate(idx, new_pos);
+ self.interactive_resize_end(None);
+ }
+
fn move_by(&mut self, amount: Point<f64, Logical>) {
let Some(active_id) = &self.active_window_id else {
return;
};
- let active_idx = self.idx_of(active_id).unwrap();
-
- let new_pos = self.data[active_idx].logical_pos + amount;
- self.move_and_animate(active_idx, new_pos);
+ let idx = self.idx_of(active_id).unwrap();
- self.interactive_resize_end(None);
+ let new_pos = self.data[idx].logical_pos + amount;
+ self.move_to(idx, new_pos)
}
pub fn move_left(&mut self) {
@@ -867,17 +870,32 @@ impl<W: LayoutElement> FloatingSpace<W> {
self.move_by(Point::from((0., DIRECTIONAL_MOVE_PX)));
}
+ pub fn move_window(&mut self, id: Option<&W::Id>, x: PositionChange, y: PositionChange) {
+ let Some(id) = id.or(self.active_window_id.as_ref()) else {
+ return;
+ };
+ let idx = self.idx_of(id).unwrap();
+
+ let mut new_pos = self.data[idx].logical_pos;
+ match x {
+ PositionChange::SetFixed(x) => new_pos.x = x + self.working_area.loc.x,
+ PositionChange::AdjustFixed(x) => new_pos.x += x,
+ }
+ match y {
+ PositionChange::SetFixed(y) => new_pos.y = y + self.working_area.loc.y,
+ PositionChange::AdjustFixed(y) => new_pos.y += y,
+ }
+ self.move_to(idx, new_pos);
+ }
+
pub fn center_window(&mut self) {
let Some(active_id) = &self.active_window_id else {
return;
};
- let active_idx = self.idx_of(active_id).unwrap();
-
- let new_pos =
- center_preferring_top_left_in_area(self.working_area, self.data[active_idx].size);
- self.move_and_animate(active_idx, new_pos);
+ let idx = self.idx_of(active_id).unwrap();
- self.interactive_resize_end(None);
+ let new_pos = center_preferring_top_left_in_area(self.working_area, self.data[idx].size);
+ self.move_to(idx, new_pos);
}
pub fn descendants_added(&mut self, id: &W::Id) -> bool {
@@ -1082,7 +1100,7 @@ impl<W: LayoutElement> FloatingSpace<W> {
rect.loc
}
- fn scale_by_working_area(&self, pos: Point<f64, SizeFrac>) -> Point<f64, Logical> {
+ pub fn scale_by_working_area(&self, pos: Point<f64, SizeFrac>) -> Point<f64, Logical> {
Data::scale_by_working_area(self.working_area, pos)
}
@@ -1163,7 +1181,6 @@ impl<W: LayoutElement> FloatingSpace<W> {
self.view_size
}
- #[cfg(test)]
pub fn working_area(&self) -> Rectangle<f64, Logical> {
self.working_area
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index c11eb6ab..ed92fad3 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -40,7 +40,7 @@ use niri_config::{
CenterFocusedColumn, Config, CornerRadius, FloatOrInt, PresetSize, Struts,
Workspace as WorkspaceConfig,
};
-use niri_ipc::SizeChange;
+use niri_ipc::{PositionChange, SizeChange};
use scrolling::{Column, ColumnWidth, InsertHint, InsertPosition};
use smithay::backend::renderer::element::surface::WaylandSurfaceRenderElement;
use smithay::backend::renderer::element::Id;
@@ -2750,6 +2750,30 @@ impl<W: LayoutElement> Layout<W> {
workspace.switch_focus_floating_tiling();
}
+ pub fn move_floating_window(
+ &mut self,
+ id: Option<&W::Id>,
+ x: PositionChange,
+ y: PositionChange,
+ ) {
+ if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move {
+ if id.is_none() || id == Some(move_.tile.window().id()) {
+ return;
+ }
+ }
+
+ let workspace = if let Some(id) = id {
+ Some(self.workspaces_mut().find(|ws| ws.has_window(id)).unwrap())
+ } else {
+ self.active_workspace_mut()
+ };
+
+ let Some(workspace) = workspace else {
+ return;
+ };
+ workspace.move_floating_window(id, x, y);
+ }
+
pub fn focus_output(&mut self, output: &Output) {
if let MonitorSet::Normal {
monitors,
@@ -4202,6 +4226,15 @@ mod tests {
]
}
+ fn arbitrary_position_change() -> impl Strategy<Value = PositionChange> {
+ prop_oneof![
+ (-1000f64..1000f64).prop_map(PositionChange::SetFixed),
+ (-1000f64..1000f64).prop_map(PositionChange::AdjustFixed),
+ any::<f64>().prop_map(PositionChange::SetFixed),
+ any::<f64>().prop_map(PositionChange::AdjustFixed),
+ ]
+ }
+
fn arbitrary_min_max() -> impl Strategy<Value = (i32, i32)> {
prop_oneof![
Just((0, 0)),
@@ -4410,6 +4443,14 @@ mod tests {
FocusFloating,
FocusTiling,
SwitchFocusFloatingTiling,
+ MoveFloatingWindow {
+ #[proptest(strategy = "proptest::option::of(1..=5usize)")]
+ id: Option<usize>,
+ #[proptest(strategy = "arbitrary_position_change()")]
+ x: PositionChange,
+ #[proptest(strategy = "arbitrary_position_change()")]
+ y: PositionChange,
+ },
SetParent {
#[proptest(strategy = "1..=5usize")]
id: usize,
@@ -4934,6 +4975,10 @@ mod tests {
Op::SwitchFocusFloatingTiling => {
layout.switch_focus_floating_tiling();
}
+ Op::MoveFloatingWindow { id, x, y } => {
+ let id = id.filter(|id| layout.has_window(id));
+ layout.move_floating_window(id.as_ref(), x, y);
+ }
Op::SetParent {
id,
mut new_parent_id,
diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs
index c0de770a..1a8c8f5f 100644
--- a/src/layout/scrolling.rs
+++ b/src/layout/scrolling.rs
@@ -373,6 +373,15 @@ impl<W: LayoutElement> ScrollingSpace<W> {
Some(col.tiles[col.active_tile_idx].window())
}
+ pub fn active_tile_mut(&mut self) -> Option<&mut Tile<W>> {
+ if self.columns.is_empty() {
+ return None;
+ }
+
+ let col = &mut self.columns[self.active_column_idx];
+ Some(&mut col.tiles[col.active_tile_idx])
+ }
+
pub fn is_active_fullscreen(&self) -> bool {
if self.columns.is_empty() {
return false;
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index 06900afc..de190275 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -3,7 +3,7 @@ use std::rc::Rc;
use std::time::Duration;
use niri_config::{OutputName, PresetSize, Workspace as WorkspaceConfig};
-use niri_ipc::SizeChange;
+use niri_ipc::{PositionChange, SizeChange};
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::desktop::{layer_map_for_output, Window};
use smithay::output::Output;
@@ -1185,6 +1185,55 @@ impl<W: LayoutElement> Workspace<W> {
};
}
+ pub fn move_floating_window(
+ &mut self,
+ id: Option<&W::Id>,
+ x: PositionChange,
+ y: PositionChange,
+ ) {
+ if id.map_or(self.floating_is_active.get(), |id| {
+ self.floating.has_window(id)
+ }) {
+ self.floating.move_window(id, x, y);
+ } else {
+ // If the target tile isn't floating, set its stored floating position.
+ let tile = if let Some(id) = id {
+ self.scrolling
+ .tiles_mut()
+ .find(|tile| tile.window().id() == id)
+ .unwrap()
+ } else if let Some(tile) = self.scrolling.active_tile_mut() {
+ tile
+ } else {
+ return;
+ };
+
+ let working_area_loc = self.floating.working_area().loc;
+ // If there's no stored floating position, we can only set both components at once, not
+ // adjust.
+ let Some(pos) = tile.floating_pos.or_else(|| {
+ (matches!(x, PositionChange::SetFixed(_))
+ && matches!(y, PositionChange::SetFixed(_)))
+ .then_some(Point::default())
+ }) else {
+ return;
+ };
+
+ let mut pos = self.floating.scale_by_working_area(pos);
+ match x {
+ PositionChange::SetFixed(x) => pos.x = x + working_area_loc.x,
+ PositionChange::AdjustFixed(x) => pos.x += x,
+ }
+ match y {
+ PositionChange::SetFixed(y) => pos.y = y + working_area_loc.y,
+ PositionChange::AdjustFixed(y) => pos.y += y,
+ }
+
+ let pos = self.floating.logical_to_size_frac(pos);
+ tile.floating_pos = Some(pos);
+ }
+ }
+
pub fn has_windows(&self) -> bool {
self.windows().next().is_some()
}