better float positioning

This commit is contained in:
2026-09-08 01:16:07 +02:00
parent 292375aed1
commit 689dcfbed9
4 changed files with 75 additions and 13 deletions
+3
View File
@@ -21,6 +21,9 @@ allow_shared_memory = true
max_panes = 32
floating_width = 0.65
floating_height = 0.65
# Fractions of the workspace, capped at these many points on large displays.
floating_max_width = 960
floating_max_height = 640
# Providing [keys] replaces the entire default map.
[keys]
+1 -1
View File
@@ -1 +1 @@
/nix/store/cq7bwr3w31mdn9rp3sa0isv5i4c6awqg-slerm-0.1.0
/nix/store/f3ssbg9n2ykvy4n6wslfgl25gr8pzkbl-slerm-0.1.0
+56 -12
View File
@@ -432,25 +432,17 @@ impl eframe::App for App {
}
});
if tab.floats_visible && !tab.zoomed {
for id in tab.floats.clone() {
for (index, id) in tab.floats.clone().into_iter().enumerate() {
let pane = self.panes.get_mut(&id).unwrap();
let window_id = egui::Id::new(("float", tab.id, id));
let active = tab.active == id;
let (pos, size) = float_placement(workspace, index, &self.config.multiplexer);
let result = egui::Window::new(pane_title(id, pane))
.id(window_id)
.title_bar(false)
.constrain_to(workspace)
.default_pos(
workspace.min
+ vec2(40.0 + (id % 5) as f32 * 22.0, 40.0 + (id % 5) as f32 * 22.0),
)
.default_size(
workspace.size()
* vec2(
self.config.multiplexer.floating_width,
self.config.multiplexer.floating_height,
),
)
.default_pos(pos)
.default_size(size)
.min_size(vec2(160.0, 100.0))
.frame(egui::Frame::NONE.fill(bg))
.show(ctx, |ui| {
@@ -569,6 +561,23 @@ fn show_pane(
focused
}
/// Where a floating pane opens, from its place in the tab's float list rather than
/// its pane id: the first one is centred and every later one steps down and right
/// from it, so the same slot always lands in the same spot. The cascade wraps
/// before it can walk a float off the far edge.
fn float_placement(
workspace: Rect,
index: usize,
config: &config::Multiplexer,
) -> (egui::Pos2, egui::Vec2) {
// The fractions are of the workspace, so on a large display they hand a scratch
// terminal far more room than it needs; the maximums keep it in hand.
let size = (workspace.size() * vec2(config.floating_width, config.floating_height))
.min(vec2(config.floating_max_width, config.floating_max_height));
let step = (index % 6) as f32 * 28.0;
(workspace.center() - size / 2.0 + vec2(step, step), size)
}
fn pane_title(id: PaneId, pane: &Pane) -> String {
let title = pane.terminal.title().unwrap_or("");
format!(
@@ -640,6 +649,41 @@ mod tests {
);
}
#[test]
fn floats_open_centered_then_cascade_and_stay_within_the_maximums() {
let config = config::Multiplexer::default();
let workspace = Rect::from_min_size(egui::pos2(0.0, 30.0), vec2(3000.0, 1800.0));
let (first, size) = float_placement(workspace, 0, &config);
assert_eq!(
size,
vec2(config.floating_max_width, config.floating_max_height),
"a large workspace must not hand out a proportionally large float"
);
assert_eq!(
first + size / 2.0,
workspace.center(),
"the first is centered"
);
let (second, _) = float_placement(workspace, 1, &config);
assert!(
second.x > first.x && second.y > first.y,
"later floats step off it"
);
// Six slots later the cascade is back where it started, so it cannot walk away.
assert_eq!(float_placement(workspace, 6, &config).0, first);
assert_eq!(float_placement(workspace, 7, &config).0, second);
// A small workspace keeps the fractions instead of the maximums.
let small = Rect::from_min_size(egui::Pos2::ZERO, vec2(800.0, 600.0));
let (pos, size) = float_placement(small, 0, &config);
assert_eq!(
size,
small.size() * vec2(config.floating_width, config.floating_height)
);
assert_eq!(pos + size / 2.0, small.center());
}
#[test]
fn toggle_creates_first_float_then_reuses_it_and_close_removes_focus() {
let ctx = egui::Context::default();
+15
View File
@@ -40,6 +40,8 @@ pub struct Multiplexer {
pub max_panes: usize,
pub floating_width: f32,
pub floating_height: f32,
pub floating_max_width: f32,
pub floating_max_height: f32,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
@@ -83,6 +85,10 @@ impl Default for Multiplexer {
max_panes: 32,
floating_width: 0.65,
floating_height: 0.65,
// The fractions alone make a float fill half of a large display, which is
// far more than a scratch terminal needs, so cap them in points too.
floating_max_width: 960.0,
floating_max_height: 640.0,
}
}
}
@@ -181,6 +187,15 @@ impl Config {
"floating dimensions must be fractions in 0.2..0.95"
);
}
for points in [
config.multiplexer.floating_max_width,
config.multiplexer.floating_max_height,
] {
ensure!(
(160.0..=16384.0).contains(&points),
"floating maximums must be 160..16384 points"
);
}
for color in [&config.background, &config.foreground, &config.accent] {
rgb(color)?;
}