Dialogs: steady Select chip, and dialogs move by their title (#1417)
Checked locally on main merged with this: cargo check --workspace and the ui-egui tests (the only failures are the local-only TexturesDelta ones that main has too).
This commit is contained in:
1 parent
6b30326e6d
commit
b56648c055
2 files changed
+110
-4
No files matched your search
@@ -92,6 +92,63 @@ pub fn docked_on<'a>(title: impl Into<WidgetText>, id: egui::Id, anchor: Pos2, m
|
||||
.max_height(max_height.max(120.0))
|
||||
}
|
||||
|
||||
/// How far the open docked dialog titled `title` was dragged from its docked place (#1335);
|
||||
/// zero for a dialog that was not dragged, or once another dialog opens.
|
||||
pub fn dock_offset(ctx: &egui::Context, title: &str) -> egui::Vec2 {
|
||||
ctx.data(|d| d.get_temp::<(String, egui::Vec2)>(egui::Id::new("sc_dock_offset")))
|
||||
.filter(|(t, _)| t == title)
|
||||
.map(|(_, v)| v)
|
||||
.unwrap_or(egui::Vec2::ZERO)
|
||||
}
|
||||
|
||||
/// Forget a dragged dialog's place (it closed): the next one opens docked.
|
||||
pub fn reset_dock_offset(ctx: &egui::Context) {
|
||||
ctx.data_mut(|d| d.remove::<(String, egui::Vec2)>(egui::Id::new("sc_dock_offset")));
|
||||
}
|
||||
|
||||
/// A docked dialog's anchor moved by `offset`, kept so its docked corner stays in `vp` with
|
||||
/// room for its title band.
|
||||
pub fn offset_anchor(docked: Pos2, offset: egui::Vec2, vp: egui::Rect, side: Side) -> Pos2 {
|
||||
let p = docked + offset;
|
||||
let (lo, hi) = match side {
|
||||
Side::Right => (vp.left() + DIALOG_MIN_W, vp.right()),
|
||||
Side::Left => (vp.left(), vp.right() - DIALOG_MIN_W),
|
||||
};
|
||||
let x = if lo <= hi { p.x.clamp(lo, hi) } else { docked.x };
|
||||
let y = if vp.top() <= vp.bottom() - 40.0 { p.y.clamp(vp.top(), vp.bottom() - 40.0) } else { docked.y };
|
||||
egui::pos2(x, y)
|
||||
}
|
||||
|
||||
/// Let `band` (a docked dialog's title band) drag the dialog: the drag adds to its offset
|
||||
/// `moved` (as last frame left it; `applied` of it is where the dialog is now, after keeping
|
||||
/// it on screen). Called before the band's buttons are added, so they keep their clicks.
|
||||
pub fn drag_dock(ui: &Ui, band: egui::Rect, title: &str, moved: egui::Vec2, applied: egui::Vec2) {
|
||||
let response = ui.interact(band, ui.id().with("sc_dock_drag"), egui::Sense::drag());
|
||||
crate::scenario::publish_handle("dialog:title", band.center());
|
||||
// The drag runs from where the press started, so no movement is lost while egui decides
|
||||
// it is a drag, nor on the frame it ends.
|
||||
let base = egui::Id::new("sc_dock_drag_base");
|
||||
if response.drag_started()
|
||||
&& let Some(origin) = ui.input(|i| i.pointer.press_origin())
|
||||
{
|
||||
ui.ctx().data_mut(|d| d.insert_temp(base, (applied, origin)));
|
||||
}
|
||||
if response.dragged() || response.drag_stopped() {
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing);
|
||||
let start = ui.ctx().data(|d| d.get_temp::<(egui::Vec2, Pos2)>(base));
|
||||
let to = match (start, ui.input(|i| i.pointer.latest_pos())) {
|
||||
(Some((start, from)), Some(now)) => start + (now - from),
|
||||
_ => applied + response.drag_delta(),
|
||||
};
|
||||
ui.ctx().data_mut(|d| d.insert_temp(egui::Id::new("sc_dock_offset"), (title.to_string(), to)));
|
||||
} else if response.hovered() {
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::Grab);
|
||||
} else if moved != applied {
|
||||
// Kept on screen: remember where it is, not where it was dragged off to.
|
||||
ui.ctx().data_mut(|d| d.insert_temp(egui::Id::new("sc_dock_offset"), (title.to_string(), applied)));
|
||||
}
|
||||
}
|
||||
|
||||
/// A floating window, centred on the screen unless the caller places it, shrink-wrapped to its
|
||||
/// content up to its width limit.
|
||||
pub fn window<'a>(ctx: &egui::Context, title: impl Into<WidgetText>, width: Width) -> egui::Window<'a> {
|
||||
@@ -229,6 +286,43 @@ mod tests {
|
||||
assert!(h.app.dialog.is_none());
|
||||
}
|
||||
}
|
||||
/// #1335: a dialog keeps its size while its Select chip is hovered, and its title band
|
||||
/// drags it; it opens docked again after it closes.
|
||||
#[test]
|
||||
fn a_docked_dialog_keeps_its_size_under_the_pointer_and_moves_by_its_title() {
|
||||
use crate::scenario::Harness;
|
||||
use serde_json::json;
|
||||
let mut h = Harness::new();
|
||||
h.call("ui.resize", json!({"width":1400.0,"height":900.0}));
|
||||
h.step(&json!({"start":"sketch.create"})).unwrap();
|
||||
h.frames(6);
|
||||
let rest = h.app.viewport.dialog_rect.unwrap();
|
||||
let chip = crate::scenario::handle_at("dialog:Plane").unwrap();
|
||||
h.call("ui.move", json!({"x": chip.x, "y": chip.y}));
|
||||
h.frames(6);
|
||||
let hovered = h.app.viewport.dialog_rect.unwrap();
|
||||
assert!((hovered.size() - rest.size()).length() < 0.01, "hovering resized the dialog: {rest:?} -> {hovered:?}");
|
||||
// Dragged by its title text.
|
||||
let title = h.widget("CREATE SKETCH").map(|[x, y]| egui::pos2(x as f32, y as f32)).unwrap();
|
||||
h.call("ui.drag", json!({"x0": title.x, "y0": title.y, "x1": title.x - 300.0, "y1": title.y + 120.0, "steps": 8}));
|
||||
h.frames(4);
|
||||
let moved = h.app.viewport.dialog_rect.unwrap();
|
||||
assert!((moved.min - rest.min - egui::vec2(-300.0, 120.0)).length() < 2.0, "{rest:?} -> {moved:?}");
|
||||
assert!(h.app.dialog.is_some(), "dragging the title keeps the dialog open");
|
||||
// Dragged far off, it stays on screen.
|
||||
let title = crate::scenario::handle_at("dialog:title").unwrap();
|
||||
h.call("ui.drag", json!({"x0": title.x, "y0": title.y, "x1": title.x + 3000.0, "y1": title.y + 3000.0, "steps": 8}));
|
||||
h.frames(4);
|
||||
let viewport = h.app.viewport.rect.unwrap();
|
||||
assert!(viewport.contains(h.app.viewport.dialog_rect.unwrap().min));
|
||||
h.step(&json!({"key":"Escape"})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(h.app.dialog.is_none());
|
||||
h.step(&json!({"start":"sketch.create"})).unwrap();
|
||||
h.frames(6);
|
||||
assert!((h.app.viewport.dialog_rect.unwrap().min - rest.min).length() < 0.01, "a new dialog opens docked");
|
||||
}
|
||||
|
||||
/// Windows are made in this file only, so none can skip the width limits.
|
||||
#[test]
|
||||
fn windows_come_from_the_frame_helper() {
|
||||
|
||||
Reference in new issue
Block a user