mirror of
https://github.com/storytold/craft-libs.git
synced 2026-10-31 07:39:23 +00:00
docking: a minimized native window shrinks to its bar (#320)
* docking: a minimized native window shrinks to its bar A floating window shown in its own OS window kept its full size when minimized, with an empty body under the tab strip. Minimizing now sizes the OS window to its bar and the tab strip of its only group, at the same width and top-left corner, and fixes its height with the window's min and max inner size; expanding restores the saved size. The layout's native and in-window rects keep the expanded height: the collapsed height the window reports is not recorded, its position and width are. * docking: a minimized native window's limits change before its size, and follow its bar height
This commit is contained in:
1 parent
46d8cd1930
commit
7010c5e93e
3 files changed
+88
-7
No files matched your search
@@ -328,6 +328,62 @@ fn a_restored_window_opens_on_a_connected_monitor() {
|
||||
assert_eq!(window(&d, 5).monitor.as_ref().and_then(|m| m.name.as_deref()), Some("left"));
|
||||
}
|
||||
|
||||
/// The last `InnerSize`, `MinInnerSize` and `MaxInnerSize` sent to `viewport`.
|
||||
fn size_commands(d: &DockDriver<u32, Content>, viewport: egui::ViewportId) -> [Option<egui::Vec2>; 3] {
|
||||
let mut sizes = [None; 3];
|
||||
for (_, c) in d.host().commands.iter().filter(|(v, _)| *v == viewport) {
|
||||
match c {
|
||||
ViewportCommand::InnerSize(s) => sizes[0] = Some(*s),
|
||||
ViewportCommand::MinInnerSize(s) => sizes[1] = Some(*s),
|
||||
ViewportCommand::MaxInnerSize(s) => sizes[2] = Some(*s),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
sizes
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_minimized_native_window_shrinks_to_its_bar_and_keeps_its_expanded_size() {
|
||||
let mut d = driver(layout(true));
|
||||
let viewport = d.viewport(window(&d, 5).id);
|
||||
let open = os_window(&d, 5).unwrap().0;
|
||||
let (native, rect) = (window(&d, 5).native, window(&d, 5).rect);
|
||||
assert_eq!(native, Some([open.left(), open.top(), 240.0, 300.0]));
|
||||
|
||||
// Its bar and its group's tab strip, at the same width and top-left corner; only the width
|
||||
// resizes.
|
||||
let collapsed = BAR + super::DockStyle::default().tab_height;
|
||||
d.layout.apply(Action::SetMinimized { panel: 5, minimized: true }).unwrap();
|
||||
d.settle();
|
||||
assert_eq!(os_window(&d, 5).unwrap().0, Rect::from_min_size(open.min, vec2(240.0, collapsed)));
|
||||
let [_, min, max] = size_commands(&d, viewport);
|
||||
assert_eq!((min.map(|s| s.y), max.map(|s| s.y)), (Some(collapsed), Some(collapsed)));
|
||||
|
||||
// The layout keeps the expanded size after the window reports the collapsed one, past the
|
||||
// wait for commanded moves, and takes a new width.
|
||||
for _ in 0..40 {
|
||||
d.step(vec![]);
|
||||
}
|
||||
assert_eq!((window(&d, 5).native, window(&d, 5).rect), (native, rect));
|
||||
d.host().windows.get_mut(&viewport).unwrap().rect = Rect::from_min_size(open.min, vec2(300.0, collapsed));
|
||||
for _ in 0..40 {
|
||||
d.step(vec![]);
|
||||
}
|
||||
assert_eq!(window(&d, 5).native, Some([open.left(), open.top(), 300.0, 300.0]));
|
||||
assert_eq!(window(&d, 5).rect[2..], [300.0, 300.0]);
|
||||
|
||||
// Expanded: the saved height comes back, and the height resizes again.
|
||||
d.layout.apply(Action::SetMinimized { panel: 5, minimized: false }).unwrap();
|
||||
d.settle();
|
||||
assert_eq!(os_window(&d, 5).unwrap().0, Rect::from_min_size(open.min, vec2(300.0, 300.0)));
|
||||
assert_eq!(size_commands(&d, viewport), [Some(vec2(300.0, 300.0)), Some(vec2(160.0, 100.0)), Some(egui::Vec2::INFINITY)]);
|
||||
// The limits change before the size: the OS clamps a new size to the limits it has.
|
||||
let commands: Vec<_> = d.host().commands.iter().filter(|(v, _)| *v == viewport).map(|(_, c)| c.clone()).collect();
|
||||
let last = |f: fn(&ViewportCommand) -> bool| commands.iter().rposition(f);
|
||||
assert!(last(|c| matches!(c, ViewportCommand::MaxInnerSize(_))) < last(|c| matches!(c, ViewportCommand::InnerSize(_))), "{commands:?}");
|
||||
assert_eq!(window(&d, 5).native, Some([open.left(), open.top(), 300.0, 300.0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_locked_area_moves_no_native_window_and_records_where_the_os_put_it() {
|
||||
let mut d = driver(layout(true));
|
||||
|
||||
@@ -39,15 +39,19 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
pub(super) fn window_limits(&self, window: &Floating<P>) -> PanelLimits {
|
||||
let chrome = self.window_chrome(window);
|
||||
let tree = node_limits(&window.root, self.style, &*self.content);
|
||||
let body = match (&window.root, window.minimized) {
|
||||
(Node::Tabs { .. }, true) => (self.style.tab_height, self.style.tab_height),
|
||||
(_, true) => (0.0, 0.0),
|
||||
(_, false) => (tree.min.y, tree.max.y),
|
||||
let body = match window.minimized {
|
||||
true => (self.minimized_body(window), self.minimized_body(window)),
|
||||
false => (tree.min.y, tree.max.y),
|
||||
};
|
||||
let min = vec2(tree.min.x, chrome + body.0).min(vec2(1_000_000.0, 1_000_000.0));
|
||||
PanelLimits { min, max: vec2(tree.max.x, chrome + body.1).max(min).min(vec2(1_000_000.0, 1_000_000.0)) }
|
||||
}
|
||||
|
||||
/// The height of a minimized window's body: the tab strip of its only group, or nothing.
|
||||
pub(super) fn minimized_body(&self, window: &Floating<P>) -> f32 {
|
||||
if matches!(window.root, Node::Tabs { .. }) { self.style.tab_height } else { 0.0 }
|
||||
}
|
||||
|
||||
/// The title bar and, for a movable (resizable) window, the resize band above it.
|
||||
fn window_chrome(&self, window: &Floating<P>) -> f32 {
|
||||
let band = if window.panels().all(|panel| self.permissions(panel).movable) { RESIZE_BAND } else { 0.0 };
|
||||
|
||||
@@ -164,6 +164,9 @@ struct Place {
|
||||
corrected: bool,
|
||||
/// Shown, as it opened or was last told.
|
||||
shown: bool,
|
||||
/// The height of its bar while minimized, as it was last told. `known` keeps the expanded
|
||||
/// size meanwhile.
|
||||
collapsed: Option<f32>,
|
||||
}
|
||||
|
||||
/// Whether two rects differ by more than rounding.
|
||||
@@ -448,6 +451,9 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
let moving = |ctx: &Context| ctx.data(|d| d.get_temp::<WindowDrag>(drag_key)).is_some_and(|d| d.window == window.id);
|
||||
let info = ctx.input(|i| i.raw.viewports.get(&viewport).cloned());
|
||||
let frame = info.as_ref().and_then(content_of).map_or(Vec2::ZERO, |(_, frame)| frame);
|
||||
// A minimized window shows its bar and what `floating_body` draws below it.
|
||||
let collapsed = window.minimized.then(|| BAR_HEIGHT + self.minimized_body(window));
|
||||
let shown = |[x, y, w, h]: [f32; 4]| [x, y, w, collapsed.unwrap_or(h)];
|
||||
let mut place = match ctx.data(|d| d.get_temp::<Place>(key)) {
|
||||
Some(mut place) => {
|
||||
// The application changed the window's place (undo, reset, a reloaded layout):
|
||||
@@ -456,7 +462,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
&& let Some(native) = window.native.filter(|native| differs(*native, place.known))
|
||||
{
|
||||
let target = kept_reachable(native, &self.monitors);
|
||||
place_window(ctx, viewport, target, frame);
|
||||
place_window(ctx, viewport, shown(target), frame);
|
||||
(place.known, place.pending) = (target, Some(now + MOVE_WAIT));
|
||||
}
|
||||
place
|
||||
@@ -474,9 +480,22 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
};
|
||||
seen.insert(window.id);
|
||||
ctx.data_mut(|d| d.insert_temp(seen_key, seen));
|
||||
Place { spawn, known: spawn, pending: Some(now + MOVE_WAIT), corrected: false, shown: true }
|
||||
Place { spawn, known: spawn, pending: Some(now + MOVE_WAIT), corrected: false, shown: true, collapsed: None }
|
||||
}
|
||||
};
|
||||
// Minimized or expanded: the window keeps its top-left corner and width, and its
|
||||
// height is fixed while it shows only its bar.
|
||||
// The limits go first: the OS clamps the new size to the limits it has.
|
||||
if place.collapsed != collapsed {
|
||||
let (min, max) = match collapsed {
|
||||
Some(height) => (vec2(MIN_SIZE[0], height), vec2(1_000_000.0, height)),
|
||||
None => (Vec2::from(MIN_SIZE), Vec2::INFINITY),
|
||||
};
|
||||
ctx.send_viewport_cmd_to(viewport, ViewportCommand::MinInnerSize(min));
|
||||
ctx.send_viewport_cmd_to(viewport, ViewportCommand::MaxInnerSize(max));
|
||||
place_window(ctx, viewport, shown(place.known), frame);
|
||||
(place.collapsed, place.pending) = (collapsed, Some(now + MOVE_WAIT));
|
||||
}
|
||||
let spawn = place.spawn;
|
||||
let title = crate::docking::dock_model::first_active(&window.root).map(|p| self.label_of(p)).unwrap_or_default();
|
||||
let builder = ViewportBuilder::default()
|
||||
@@ -599,7 +618,9 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
|
||||
let opacity = if this { drag_opacity(drag.as_ref(), float_only, self.style.title_bar_docks) } else { 1.0 };
|
||||
set_window_opacity(&title, main.as_deref(), opacity);
|
||||
}
|
||||
if let Some(placed) = report.placed.filter(|_| !moving) {
|
||||
// The collapsed height a minimized window reports is not the size to keep.
|
||||
let reported = report.placed.map(|[x, y, w, h]| [x, y, w, if place.collapsed.is_some() { place.known[3] } else { h }]);
|
||||
if let Some(placed) = reported.filter(|_| !moving) {
|
||||
let waiting = place.pending.is_some_and(|until| now < until);
|
||||
if !differs(placed, place.known) {
|
||||
place.pending = None;
|
||||
|
||||
Reference in new issue
Block a user