diff --git a/docs/wiki/Configuration:-Window-Rules.md b/docs/wiki/Configuration:-Window-Rules.md index 5c7dd28e65..0d27223726 100644 --- a/docs/wiki/Configuration:-Window-Rules.md +++ b/docs/wiki/Configuration:-Window-Rules.md @@ -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. diff --git a/niri-config/src/window_rule.rs b/niri-config/src/window_rule.rs index 0465d28fad..fb9834fefb 100644 --- a/niri-config/src/window_rule.rs +++ b/niri-config/src/window_rule.rs @@ -117,4 +117,13 @@ pub enum RelativeTo { Bottom, Left, Right, + ActiveWindowCenter, + ActiveWindowTopLeft, + ActiveWindowTopRight, + ActiveWindowBottomLeft, + ActiveWindowBottomRight, + ActiveWindowTop, + ActiveWindowBottom, + ActiveWindowLeft, + ActiveWindowRight, } diff --git a/src/layout/floating.rs b/src/layout/floating.rs index 3fac30f22b..8fa72b68a6 100644 --- a/src/layout/floating.rs +++ b/src/layout/floating.rs @@ -402,11 +402,22 @@ impl FloatingSpace { self.tiles.is_empty() } - pub fn add_tile(&mut self, tile: Tile, activate: bool) { - self.add_tile_at(0, tile, activate); + pub fn add_tile( + &mut self, + tile: Tile, + activate: bool, + active_window_area: Option>, + ) { + self.add_tile_at(0, tile, activate, active_window_area); } - fn add_tile_at(&mut self, mut idx: usize, mut tile: Tile, activate: bool) { + fn add_tile_at( + &mut self, + mut idx: usize, + mut tile: Tile, + activate: bool, + active_window_area: Option>, + ) { tile.update_config(self.view_size, self.scale, self.options.clone()); // Restore the previous floating window size, and in case the tile is fullscreen, @@ -431,10 +442,7 @@ impl FloatingSpace { 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) { @@ -444,14 +452,20 @@ impl FloatingSpace { } } - 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); } @@ -465,7 +479,7 @@ impl FloatingSpace { 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) { @@ -1269,35 +1283,66 @@ impl FloatingSpace { Size::from((width, height)) } - pub fn stored_or_default_tile_pos(&self, tile: &Tile) -> Option> { + pub fn stored_or_default_tile_pos( + &self, + tile: &Tile, + active_window_area: Option>, + ) -> Option> { 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 }) }) } diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 0df4662096..61e6ceeac1 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -475,6 +475,14 @@ impl Workspace { } } + fn active_window_rectangle(&self) -> Option> { + 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() @@ -617,7 +625,8 @@ impl Workspace { // 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; @@ -666,7 +675,7 @@ impl Workspace { 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() { @@ -1415,7 +1424,9 @@ impl Workspace { 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 { @@ -1430,7 +1441,9 @@ impl Workspace { 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; } @@ -1507,7 +1520,7 @@ impl Workspace { 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.