better float positioning
This commit is contained in:
@@ -21,6 +21,9 @@ allow_shared_memory = true
|
|||||||
max_panes = 32
|
max_panes = 32
|
||||||
floating_width = 0.65
|
floating_width = 0.65
|
||||||
floating_height = 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.
|
# Providing [keys] replaces the entire default map.
|
||||||
[keys]
|
[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 {
|
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 pane = self.panes.get_mut(&id).unwrap();
|
||||||
let window_id = egui::Id::new(("float", tab.id, id));
|
let window_id = egui::Id::new(("float", tab.id, id));
|
||||||
let active = tab.active == 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))
|
let result = egui::Window::new(pane_title(id, pane))
|
||||||
.id(window_id)
|
.id(window_id)
|
||||||
.title_bar(false)
|
.title_bar(false)
|
||||||
.constrain_to(workspace)
|
.constrain_to(workspace)
|
||||||
.default_pos(
|
.default_pos(pos)
|
||||||
workspace.min
|
.default_size(size)
|
||||||
+ 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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.min_size(vec2(160.0, 100.0))
|
.min_size(vec2(160.0, 100.0))
|
||||||
.frame(egui::Frame::NONE.fill(bg))
|
.frame(egui::Frame::NONE.fill(bg))
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
@@ -569,6 +561,23 @@ fn show_pane(
|
|||||||
focused
|
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 {
|
fn pane_title(id: PaneId, pane: &Pane) -> String {
|
||||||
let title = pane.terminal.title().unwrap_or("");
|
let title = pane.terminal.title().unwrap_or("");
|
||||||
format!(
|
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]
|
#[test]
|
||||||
fn toggle_creates_first_float_then_reuses_it_and_close_removes_focus() {
|
fn toggle_creates_first_float_then_reuses_it_and_close_removes_focus() {
|
||||||
let ctx = egui::Context::default();
|
let ctx = egui::Context::default();
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ pub struct Multiplexer {
|
|||||||
pub max_panes: usize,
|
pub max_panes: usize,
|
||||||
pub floating_width: f32,
|
pub floating_width: f32,
|
||||||
pub floating_height: f32,
|
pub floating_height: f32,
|
||||||
|
pub floating_max_width: f32,
|
||||||
|
pub floating_max_height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
||||||
@@ -83,6 +85,10 @@ impl Default for Multiplexer {
|
|||||||
max_panes: 32,
|
max_panes: 32,
|
||||||
floating_width: 0.65,
|
floating_width: 0.65,
|
||||||
floating_height: 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"
|
"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] {
|
for color in [&config.background, &config.foreground, &config.accent] {
|
||||||
rgb(color)?;
|
rgb(color)?;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user