better float positioning
This commit is contained in:
@@ -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 @@
|
||||
/nix/store/cq7bwr3w31mdn9rp3sa0isv5i4c6awqg-slerm-0.1.0
|
||||
/nix/store/f3ssbg9n2ykvy4n6wslfgl25gr8pzkbl-slerm-0.1.0
|
||||
+56
-12
@@ -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();
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user