Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/wiki/Configuration:-Window-Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,9 @@ Afterward, the window will remember its last floating position.
By default, new floating windows open at the center of the screen, and windows from the tiling layout open close to their visual screen position.

The position uses logical coordinates relative to the working area.
By default, they are relative to the top-left corner of the working area, but you can change this by setting `relative-to` to one of these values: `top-left`, `top-right`, `bottom-left`, `bottom-right`, `top`, `bottom`, `left`, or `right`.
By default, they are relative to the top-left corner of the working area, but you can change this by setting `relative-to` to one of these values: `top-left`, `top-right`, `bottom-left`, `bottom-right`, `top`, `bottom`, `left`, `right`, `active-window-center`, `active-window-top-left`, `active-window-top-right`, `active-window-bottom-left`, `active-window-bottom-right`, `active-window-top`, `active-window-bottom`, `active-window-left`, or `active-window-right`.

The `active-window-*` values align the matching point of the floating window with that point on the active window.

For example, if you have a bar at the top, then `x=0 y=0` will put the top-left corner of the window directly below the bar.
If instead you write `x=0 y=0 relative-to="top-right"`, then the top-right corner of the window will align with the top-right corner of the workspace, also directly below the bar.
Expand Down
9 changes: 9 additions & 0 deletions niri-config/src/window_rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,13 @@ pub enum RelativeTo {
Bottom,
Left,
Right,
ActiveWindowCenter,
ActiveWindowTopLeft,
ActiveWindowTopRight,
ActiveWindowBottomLeft,
ActiveWindowBottomRight,
ActiveWindowTop,
ActiveWindowBottom,
ActiveWindowLeft,
ActiveWindowRight,
}
93 changes: 69 additions & 24 deletions src/layout/floating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,11 +402,22 @@ impl<W: LayoutElement> FloatingSpace<W> {
self.tiles.is_empty()
}

pub fn add_tile(&mut self, tile: Tile<W>, activate: bool) {
self.add_tile_at(0, tile, activate);
pub fn add_tile(
&mut self,
tile: Tile<W>,
activate: bool,
active_window_area: Option<Rectangle<f64, Logical>>,
) {
self.add_tile_at(0, tile, activate, active_window_area);
}

fn add_tile_at(&mut self, mut idx: usize, mut tile: Tile<W>, activate: bool) {
fn add_tile_at(
&mut self,
mut idx: usize,
mut tile: Tile<W>,
activate: bool,
active_window_area: Option<Rectangle<f64, Logical>>,
) {
tile.update_config(self.view_size, self.scale, self.options.clone());

// Restore the previous floating window size, and in case the tile is fullscreen,
Expand All @@ -431,10 +442,7 @@ impl<W: LayoutElement> FloatingSpace<W> {
size.h = ensure_min_max_size_maybe_zero(size.h, min_size.h, max_size.h);

win.request_size_once(size, true);

if activate || self.tiles.is_empty() {
self.active_window_id = Some(win.id().clone());
}
let window_id = win.id().clone();

// Make sure the tile isn't inserted below its parent.
for (i, tile_above) in self.tiles.iter().enumerate().take(idx) {
Expand All @@ -444,14 +452,20 @@ impl<W: LayoutElement> FloatingSpace<W> {
}
}

let pos = self.stored_or_default_tile_pos(&tile).unwrap_or_else(|| {
center_preferring_top_left_in_area(self.working_area, tile.tile_size())
});
let pos = self
.stored_or_default_tile_pos(&tile, active_window_area)
.unwrap_or_else(|| {
center_preferring_top_left_in_area(self.working_area, tile.tile_size())
});

let data = Data::new(self.working_area, &tile, pos);
self.data.insert(idx, data);
self.tiles.insert(idx, tile);

if activate || self.tiles.len() == 1 {
self.active_window_id = Some(window_id);
}

self.bring_up_descendants_of(idx);
}

Expand All @@ -465,7 +479,7 @@ impl<W: LayoutElement> FloatingSpace<W> {
let pos = self.clamp_within_working_area(pos, tile_size);
tile.floating_pos = Some(self.logical_to_size_frac(pos));

self.add_tile_at(idx, tile, activate);
self.add_tile_at(idx, tile, activate, None);
}

fn bring_up_descendants_of(&mut self, idx: usize) {
Expand Down Expand Up @@ -1269,35 +1283,66 @@ impl<W: LayoutElement> FloatingSpace<W> {
Size::from((width, height))
}

pub fn stored_or_default_tile_pos(&self, tile: &Tile<W>) -> Option<Point<f64, Logical>> {
pub fn stored_or_default_tile_pos(
&self,
tile: &Tile<W>,
active_window_area: Option<Rectangle<f64, Logical>>,
) -> Option<Point<f64, Logical>> {
let pos = tile.floating_pos.map(|pos| self.scale_by_working_area(pos));
pos.or_else(|| {
tile.window().rules().default_floating_position.map(|pos| {
let relative_to = pos.relative_to;
let size = tile.tile_size();
let area = self.working_area;
let (relative_to_active_window, right_align, bottom_align, center_x, center_y) =
match relative_to {
RelativeTo::TopLeft => (false, false, false, false, false),
RelativeTo::TopRight => (false, true, false, false, false),
RelativeTo::BottomLeft => (false, false, true, false, false),
RelativeTo::BottomRight => (false, true, true, false, false),
RelativeTo::Top => (false, false, false, true, false),
RelativeTo::Bottom => (false, false, true, true, false),
RelativeTo::Left => (false, false, false, false, true),
RelativeTo::Right => (false, true, false, false, true),
RelativeTo::ActiveWindowCenter => (true, false, false, true, true),
RelativeTo::ActiveWindowTopLeft => (true, false, false, false, false),
RelativeTo::ActiveWindowTopRight => (true, true, false, false, false),
RelativeTo::ActiveWindowBottomLeft => (true, false, true, false, false),
RelativeTo::ActiveWindowBottomRight => (true, true, true, false, false),
RelativeTo::ActiveWindowTop => (true, false, false, true, false),
RelativeTo::ActiveWindowBottom => (true, false, true, true, false),
RelativeTo::ActiveWindowLeft => (true, false, false, false, true),
RelativeTo::ActiveWindowRight => (true, true, false, false, true),
};

let area = if relative_to_active_window {
active_window_area
.or_else(|| {
self.active_window_id.as_ref().and_then(|id| {
self.idx_of(id).map(|idx| {
Rectangle::new(self.data[idx].logical_pos, self.data[idx].size)
})
})
})
.unwrap_or(self.working_area)
} else {
self.working_area
};

let mut pos = Point::from((pos.x.0, pos.y.0));
if relative_to == RelativeTo::TopRight
|| relative_to == RelativeTo::BottomRight
|| relative_to == RelativeTo::Right
{
if right_align {
pos.x = area.size.w - size.w - pos.x;
}
if relative_to == RelativeTo::BottomLeft
|| relative_to == RelativeTo::BottomRight
|| relative_to == RelativeTo::Bottom
{
if bottom_align {
pos.y = area.size.h - size.h - pos.y;
}
if relative_to == RelativeTo::Top || relative_to == RelativeTo::Bottom {
if center_x {
pos.x += area.size.w / 2.0 - size.w / 2.0
}
if relative_to == RelativeTo::Left || relative_to == RelativeTo::Right {
if center_y {
pos.y += area.size.h / 2.0 - size.h / 2.0
}

pos + self.working_area.loc
pos + area.loc
})
})
}
Expand Down
23 changes: 18 additions & 5 deletions src/layout/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,14 @@ impl<W: LayoutElement> Workspace<W> {
}
}

fn active_window_rectangle(&self) -> Option<Rectangle<f64, Logical>> {
let active_id = self.active_window().map(|win| win.id().clone())?;

self.tiles_with_render_positions()
.find(|(tile, _, _)| tile.window().id() == &active_id)
.map(|(tile, pos, _)| Rectangle::new(pos, tile.tile_size()))
}

pub fn active_window_mut(&mut self) -> Option<&mut W> {
if self.floating_is_active.get() {
self.floating.active_window_mut()
Expand Down Expand Up @@ -617,7 +625,8 @@ impl<W: LayoutElement> Workspace<W> {
// If the tile is pending maximized or fullscreen, open it in the scrolling layout
// where it can do that.
if is_floating && tile.window().pending_sizing_mode().is_normal() {
self.floating.add_tile(tile, activate);
let active_window_area = self.active_window_rectangle();
self.floating.add_tile(tile, activate, active_window_area);

if activate || self.scrolling.is_empty() {
self.floating_is_active = FloatingActive::Yes;
Expand Down Expand Up @@ -666,7 +675,7 @@ impl<W: LayoutElement> Workspace<W> {
let pos = self.floating.logical_to_size_frac(pos);
tile.floating_pos = Some(pos);

self.floating.add_tile(tile, activate);
self.floating.add_tile(tile, activate, None);
}

if activate || self.scrolling.is_empty() {
Expand Down Expand Up @@ -1415,7 +1424,9 @@ impl<W: LayoutElement> Workspace<W> {
removed.tile.stop_move_animations();

// Come up with a default floating position close to the tile position.
let stored_or_default = self.floating.stored_or_default_tile_pos(&removed.tile);
let stored_or_default = self
.floating
.stored_or_default_tile_pos(&removed.tile, self.active_window_rectangle());
if stored_or_default.is_none() {
let offset =
if self.options.layout.center_focused_column == CenterFocusedColumn::Always {
Expand All @@ -1430,7 +1441,9 @@ impl<W: LayoutElement> Workspace<W> {
removed.tile.floating_pos = Some(pos);
}

self.floating.add_tile(removed.tile, target_is_active);
let active_window_area = self.active_window_rectangle();
self.floating
.add_tile(removed.tile, target_is_active, active_window_area);
if target_is_active {
self.floating_is_active = FloatingActive::Yes;
}
Expand Down Expand Up @@ -1507,7 +1520,7 @@ impl<W: LayoutElement> Workspace<W> {
return;
};

let pos = self.floating.stored_or_default_tile_pos(tile);
let pos = self.floating.stored_or_default_tile_pos(tile, None);

// If there's no stored floating position, we can only set both components at once, not
// adjust.
Expand Down