1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00

Drag-to-close panels (#8182)

* Closes https://github.com/emilk/egui/pull/7254

You can now drag-to-close a panel. Also drag-to-expand panels.

This is a breaking change: the animated panel functions now take a
`open: &mut bool` instead of `open: bool`.

This is only enabled for resizable panels
This commit is contained in:
Emil Ernerfeldt
2026-05-22 16:05:39 +02:00
committed by GitHub
parent dcefb2e3b8
commit 3cf844c542
10 changed files with 463 additions and 53 deletions

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:eba6e690937fbbd22c8edfce14078f50998e968324d8073d5db5829493d957e0
size 5292

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:84e6030760561e308a190d2eb9781f53b896fbea5d11a3548b73d68c49f4d525
size 65797

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:84e6030760561e308a190d2eb9781f53b896fbea5d11a3548b73d68c49f4d525
size 65797

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bcf9871c19199c94cfbc83818b4f70fe9fab5c396a3fb32cb79b713e9145bcae
size 3001

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cf09470a6628e62421bfa754ce238dd13820ba0bf8146ae835e539874d39a150
size 4882

View File

@@ -0,0 +1,157 @@
//! Snapshot tests for `Panel`'s drag-to-close and drag-to-expand gestures.
//!
//! Covers:
//! * [`Panel::show_animated_inside`] — drag-to-close on a `Left` panel.
//! * [`Panel::show_animated_between_inside`] — drag-to-close on the expanded panel
//! followed by drag-to-expand on the collapsed panel, both via the shared
//! resize handle.
use egui::{Panel, Pos2, Vec2};
use egui_kittest::{Harness, SnapshotResults};
/// Pure-data state for the kittest UI closure.
#[derive(Default)]
struct State {
is_expanded: bool,
}
#[test]
fn drag_to_close_animated_inside() {
let mut results = SnapshotResults::new();
let mut harness = Harness::builder()
.with_size(Vec2::new(400.0, 200.0))
.build_ui_state(
|ui, state: &mut State| {
Panel::left("test_left_panel")
.resizable(true)
.default_size(120.0)
.min_size(60.0)
.show_animated_inside(ui, &mut state.is_expanded, |ui| {
ui.label("Left panel content");
});
egui::CentralPanel::default().show_inside(ui, |ui| {
ui.label("Central");
});
},
State { is_expanded: true },
);
harness.run();
assert!(harness.state().is_expanded, "should start expanded");
results.add(harness.try_snapshot("panel_drag/inside_initial"));
// Query the actual resize edge from PanelState (avoids assumptions about
// Frame margins and the harness's ui padding).
let panel_state = egui::PanelState::load(&harness.ctx, egui::Id::new("test_left_panel"))
.expect("PanelState should be persisted after the first frame");
let resize_x = panel_state.outer_rect.right();
let resize_y = panel_state.outer_rect.center().y;
let drag_start = Pos2::new(resize_x, resize_y);
let drag_end = Pos2::new(resize_x - 200.0, resize_y);
harness.drag_at(drag_start);
harness.run();
harness.hover_at(drag_end);
harness.run();
harness.drop_at(drag_end);
harness.run();
assert!(
!harness.state().is_expanded,
"drag past min_size should have closed the panel"
);
results.add(harness.try_snapshot("panel_drag/inside_closed"));
}
#[test]
fn drag_to_close_and_reopen_animated_between() {
let mut results = SnapshotResults::new();
let panel_size = 400.0_f32;
let expanded_size = 120.0_f32;
let collapsed_size = 28.0_f32;
let mut harness = Harness::builder()
.with_size(Vec2::new(panel_size, 300.0))
.build_ui_state(
|ui, state: &mut State| {
let collapsed = Panel::bottom("between_collapsed")
.resizable(true)
.exact_size(collapsed_size);
let expanded = Panel::bottom("between_expanded")
.resizable(true)
.default_size(expanded_size);
Panel::show_animated_between_inside(
ui,
&mut state.is_expanded,
collapsed,
expanded,
|ui, expanded| {
if expanded {
ui.heading("Expanded panel");
ui.separator();
for i in 0..6 {
ui.label(format!(
"Row {i}: filler content so the \
expanded panel is clearly taller than the \
collapsed one in the snapshot."
));
}
} else {
ui.label("Collapsed");
}
},
);
egui::CentralPanel::default().show_inside(ui, |ui| {
ui.label("Central");
});
},
State { is_expanded: true },
);
harness.run();
assert!(harness.state().is_expanded, "should start expanded");
results.add(harness.try_snapshot("panel_drag/between_initial_expanded"));
// Drag-to-close: grab the top edge of the expanded bottom panel and drag
// it down past the panel's minimum height to collapse.
let expanded_state = egui::PanelState::load(&harness.ctx, egui::Id::new("between_expanded"))
.expect("expanded PanelState should be persisted");
let expanded_resize_y = expanded_state.outer_rect.top();
let drag_x = expanded_state.outer_rect.center().x;
let bottom_y = expanded_state.outer_rect.bottom();
harness.drag_at(Pos2::new(drag_x, expanded_resize_y));
harness.run();
harness.hover_at(Pos2::new(drag_x, bottom_y - 1.0));
harness.run();
harness.drop_at(Pos2::new(drag_x, bottom_y - 1.0));
harness.run();
assert!(
!harness.state().is_expanded,
"drag past min should have closed the expanded panel"
);
results.add(harness.try_snapshot("panel_drag/between_collapsed"));
// Drag-to-expand: grab the top edge of the (now visible) collapsed panel
// and drag it upward past the collapsed panel's exact_size cap.
let collapsed_state = egui::PanelState::load(&harness.ctx, egui::Id::new("between_collapsed"))
.expect("collapsed PanelState should be persisted");
let collapsed_resize_y = collapsed_state.outer_rect.top();
harness.drag_at(Pos2::new(drag_x, collapsed_resize_y));
harness.run();
harness.hover_at(Pos2::new(drag_x, collapsed_resize_y - 200.0));
harness.run();
harness.drop_at(Pos2::new(drag_x, collapsed_resize_y - 200.0));
harness.run();
assert!(
harness.state().is_expanded,
"drag past collapsed exact_size should have reopened the panel"
);
results.add(harness.try_snapshot("panel_drag/between_reopened"));
}