mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
floating: Implement smarter clamping for window location
A small part of the window always remains on-screen regardless of the working area changes. Interactive move lets the user position the window anywhere; automatic actions like toggle-window-floating and dialog opening try to put the window fully on-screen. The size-fraction canonical floating window position remains unclamped, and clamping happens when recomputing the logical position.
This commit is contained in:
+45
-10
@@ -16,7 +16,7 @@ use crate::niri_render_elements;
|
|||||||
use crate::render_helpers::renderer::NiriRenderer;
|
use crate::render_helpers::renderer::NiriRenderer;
|
||||||
use crate::render_helpers::RenderTarget;
|
use crate::render_helpers::RenderTarget;
|
||||||
use crate::utils::transaction::TransactionBlocker;
|
use crate::utils::transaction::TransactionBlocker;
|
||||||
use crate::utils::{ensure_min_max_size, ResizeEdge};
|
use crate::utils::{clamp_preferring_top_left_in_area, ensure_min_max_size, ResizeEdge};
|
||||||
use crate::window::ResolvedWindowRules;
|
use crate::window::ResolvedWindowRules;
|
||||||
|
|
||||||
/// Space for floating windows.
|
/// Space for floating windows.
|
||||||
@@ -105,6 +105,24 @@ impl Data {
|
|||||||
logical_pos.x *= self.working_area.size.w;
|
logical_pos.x *= self.working_area.size.w;
|
||||||
logical_pos.y *= self.working_area.size.h;
|
logical_pos.y *= self.working_area.size.h;
|
||||||
logical_pos += self.working_area.loc;
|
logical_pos += self.working_area.loc;
|
||||||
|
|
||||||
|
// Make sure the window doesn't go too much off-screen. Numbers taken from Mutter.
|
||||||
|
let min_on_screen_hor = f64::clamp(self.size.w / 4., 10., 75.);
|
||||||
|
let min_on_screen_ver = f64::clamp(self.size.h / 4., 10., 75.);
|
||||||
|
let max_off_screen_hor = f64::max(0., self.size.w - min_on_screen_hor);
|
||||||
|
let max_off_screen_ver = f64::max(0., self.size.h - min_on_screen_ver);
|
||||||
|
|
||||||
|
logical_pos.x = f64::max(logical_pos.x, -max_off_screen_hor);
|
||||||
|
logical_pos.y = f64::max(logical_pos.y, -max_off_screen_ver);
|
||||||
|
logical_pos.x = f64::min(
|
||||||
|
logical_pos.x,
|
||||||
|
self.working_area.size.w - self.size.w + max_off_screen_hor,
|
||||||
|
);
|
||||||
|
logical_pos.y = f64::min(
|
||||||
|
logical_pos.y,
|
||||||
|
self.working_area.size.h - self.size.h + max_off_screen_ver,
|
||||||
|
);
|
||||||
|
|
||||||
self.logical_pos = logical_pos;
|
self.logical_pos = logical_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +136,13 @@ impl Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update<W: LayoutElement>(&mut self, tile: &Tile<W>) {
|
pub fn update<W: LayoutElement>(&mut self, tile: &Tile<W>) {
|
||||||
self.size = tile.tile_size();
|
let size = tile.tile_size();
|
||||||
|
if self.size == size {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.size = size;
|
||||||
|
self.recompute_logical_pos();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_logical_pos(&mut self, logical_pos: Point<f64, Logical>) {
|
pub fn set_logical_pos(&mut self, logical_pos: Point<f64, Logical>) {
|
||||||
@@ -129,7 +153,7 @@ impl Data {
|
|||||||
|
|
||||||
self.pos = pos;
|
self.pos = pos;
|
||||||
|
|
||||||
// This should get close to the same result as what we started with.
|
// This will clamp the logical position to the current working area.
|
||||||
self.recompute_logical_pos();
|
self.recompute_logical_pos();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,16 +385,15 @@ impl<W: LayoutElement> FloatingSpace<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pos = pos.unwrap_or_else(|| {
|
let pos = pos.unwrap_or_else(|| {
|
||||||
let area_size = self.working_area.size.to_point();
|
let area_size = self.working_area.size.to_point();
|
||||||
let tile_size = tile.tile_size().to_point();
|
let tile_size = tile.tile_size().to_point();
|
||||||
let offset = (area_size - tile_size).downscale(2.);
|
let mut offset = (area_size - tile_size).downscale(2.);
|
||||||
|
// Prefer top-left corner if larger than the working area.
|
||||||
|
offset.x = f64::max(offset.x, 0.);
|
||||||
|
offset.y = f64::max(offset.y, 0.);
|
||||||
self.working_area.loc + offset
|
self.working_area.loc + offset
|
||||||
});
|
});
|
||||||
// TODO: also nudge pos + size inside the area.
|
|
||||||
// TODO: smart padding (if doesn't fit, try without padding).
|
|
||||||
pos.x = f64::max(pos.x, self.working_area.loc.x + 8.);
|
|
||||||
pos.y = f64::max(pos.y, self.working_area.loc.y + 8.);
|
|
||||||
|
|
||||||
let data = Data::new(self.working_area, &tile, pos);
|
let data = Data::new(self.working_area, &tile, pos);
|
||||||
self.data.insert(idx, data);
|
self.data.insert(idx, data);
|
||||||
@@ -387,7 +410,9 @@ impl<W: LayoutElement> FloatingSpace<W> {
|
|||||||
|
|
||||||
let above_pos = self.data[idx].logical_pos;
|
let above_pos = self.data[idx].logical_pos;
|
||||||
let above_size = self.data[idx].size;
|
let above_size = self.data[idx].size;
|
||||||
let pos = above_pos + (above_size.to_point() - tile.tile_size().to_point()).downscale(2.);
|
let tile_size = tile.tile_size();
|
||||||
|
let pos = above_pos + (above_size.to_point() - tile_size.to_point()).downscale(2.);
|
||||||
|
let pos = self.clamp_within_working_area(pos, tile_size);
|
||||||
|
|
||||||
self.add_tile_at(idx, tile, Some(pos), activate);
|
self.add_tile_at(idx, tile, Some(pos), activate);
|
||||||
}
|
}
|
||||||
@@ -796,6 +821,16 @@ impl<W: LayoutElement> FloatingSpace<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clamp_within_working_area(
|
||||||
|
&self,
|
||||||
|
pos: Point<f64, Logical>,
|
||||||
|
size: Size<f64, Logical>,
|
||||||
|
) -> Point<f64, Logical> {
|
||||||
|
let mut rect = Rectangle::from_loc_and_size(pos, size);
|
||||||
|
clamp_preferring_top_left_in_area(self.working_area, &mut rect);
|
||||||
|
rect.loc
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn working_area(&self) -> Rectangle<f64, Logical> {
|
pub fn working_area(&self) -> Rectangle<f64, Logical> {
|
||||||
self.working_area
|
self.working_area
|
||||||
|
|||||||
@@ -572,11 +572,14 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
.tiles_with_render_positions()
|
.tiles_with_render_positions()
|
||||||
.find(|(tile, _)| tile.window().id() == right_of)
|
.find(|(tile, _)| tile.window().id() == right_of)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Position the new tile in the center above the right_of tile. Think a dialog
|
// Position the new tile in the center above the right_of tile. Think a dialog
|
||||||
// opening on top of a window.
|
// opening on top of a window.
|
||||||
|
let tile_size = tile.tile_size();
|
||||||
let pos = render_pos
|
let pos = render_pos
|
||||||
+ (right_of_tile.tile_size().to_point() - tile.tile_size().to_point())
|
+ (right_of_tile.tile_size().to_point() - tile_size.to_point()).downscale(2.);
|
||||||
.downscale(2.);
|
let pos = self.floating.clamp_within_working_area(pos, tile_size);
|
||||||
|
|
||||||
self.floating.add_tile(tile, Some(pos), activate);
|
self.floating.add_tile(tile, Some(pos), activate);
|
||||||
if activate {
|
if activate {
|
||||||
self.floating_is_active = true;
|
self.floating_is_active = true;
|
||||||
@@ -929,7 +932,10 @@ impl<W: LayoutElement> Workspace<W> {
|
|||||||
} else {
|
} else {
|
||||||
let mut removed = self.scrolling.remove_tile(&id, Transaction::new());
|
let mut removed = self.scrolling.remove_tile(&id, Transaction::new());
|
||||||
removed.tile.stop_move_animations();
|
removed.tile.stop_move_animations();
|
||||||
let pos = render_pos + Point::from((50., 50.));
|
let pos = self.floating.clamp_within_working_area(
|
||||||
|
render_pos + Point::from((50., 50.)),
|
||||||
|
removed.tile.tile_size(),
|
||||||
|
);
|
||||||
self.floating
|
self.floating
|
||||||
.add_tile(removed.tile, Some(pos), target_is_active);
|
.add_tile(removed.tile, Some(pos), target_is_active);
|
||||||
if target_is_active {
|
if target_is_active {
|
||||||
|
|||||||
@@ -272,6 +272,18 @@ pub fn ensure_min_max_size(mut x: i32, min_size: i32, max_size: i32) -> i32 {
|
|||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clamp_preferring_top_left_in_area(
|
||||||
|
area: Rectangle<f64, Logical>,
|
||||||
|
rect: &mut Rectangle<f64, Logical>,
|
||||||
|
) {
|
||||||
|
rect.loc.x = f64::min(rect.loc.x, area.loc.x + area.size.w - rect.size.w);
|
||||||
|
rect.loc.y = f64::min(rect.loc.y, area.loc.y + area.size.h - rect.size.h);
|
||||||
|
|
||||||
|
// Clamp by top and left last so it takes precedence.
|
||||||
|
rect.loc.x = f64::max(rect.loc.x, area.loc.x);
|
||||||
|
rect.loc.y = f64::max(rect.loc.y, area.loc.y);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dbus")]
|
#[cfg(feature = "dbus")]
|
||||||
pub fn show_screenshot_notification(image_path: Option<PathBuf>) {
|
pub fn show_screenshot_notification(image_path: Option<PathBuf>) {
|
||||||
let mut notification = notify_rust::Notification::new();
|
let mut notification = notify_rust::Notification::new();
|
||||||
@@ -309,3 +321,40 @@ pub fn cause_panic() {
|
|||||||
let b = Duration::from_secs(2);
|
let b = Duration::from_secs(2);
|
||||||
let _ = a - b;
|
let _ = a - b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clamp_preferring_top_left() {
|
||||||
|
fn check(
|
||||||
|
(ax, ay, aw, ah): (i32, i32, i32, i32),
|
||||||
|
(rx, ry, rw, rh): (i32, i32, i32, i32),
|
||||||
|
(ex, ey): (i32, i32),
|
||||||
|
) {
|
||||||
|
let area = Rectangle::from_loc_and_size((ax, ay), (aw, ah)).to_f64();
|
||||||
|
let mut rect = Rectangle::from_loc_and_size((rx, ry), (rw, rh)).to_f64();
|
||||||
|
clamp_preferring_top_left_in_area(area, &mut rect);
|
||||||
|
assert_eq!(rect.loc, Point::from((ex, ey)).to_f64());
|
||||||
|
}
|
||||||
|
|
||||||
|
check((0, 0, 10, 20), (2, 3, 4, 5), (2, 3));
|
||||||
|
check((0, 0, 10, 20), (-2, 3, 4, 5), (0, 3));
|
||||||
|
check((0, 0, 10, 20), (2, -3, 4, 5), (2, 0));
|
||||||
|
check((0, 0, 10, 20), (-2, -3, 4, 5), (0, 0));
|
||||||
|
|
||||||
|
check((1, 1, 10, 20), (2, 3, 4, 5), (2, 3));
|
||||||
|
check((1, 1, 10, 20), (-2, 3, 4, 5), (1, 3));
|
||||||
|
check((1, 1, 10, 20), (2, -3, 4, 5), (2, 1));
|
||||||
|
check((1, 1, 10, 20), (-2, -3, 4, 5), (1, 1));
|
||||||
|
|
||||||
|
check((0, 0, 10, 20), (20, 3, 4, 5), (6, 3));
|
||||||
|
check((0, 0, 10, 20), (2, 30, 4, 5), (2, 15));
|
||||||
|
check((0, 0, 10, 20), (20, 30, 4, 5), (6, 15));
|
||||||
|
|
||||||
|
check((0, 0, 10, 20), (20, 30, 40, 5), (0, 15));
|
||||||
|
check((0, 0, 10, 20), (20, 30, 4, 50), (6, 0));
|
||||||
|
check((0, 0, 10, 20), (20, 30, 40, 50), (0, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user