From 56f6612f78b8c471df3c09b93d6eb3a919392828 Mon Sep 17 00:00:00 2001 From: Marco Arnulfo Date: Tue, 13 Oct 2026 02:02:46 +0200 Subject: [PATCH] Dialogs: drag a modal dialog by its title to move it off the viewer (fixes #1155) --- crates/ui-egui/src/panels/dialogs.rs | 17 +++++++++++++++-- crates/ui-egui/tests/ui_tail.rs | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/panels/dialogs.rs b/crates/ui-egui/src/panels/dialogs.rs index e95a667e..4560b565 100644 --- a/crates/ui-egui/src/panels/dialogs.rs +++ b/crates/ui-egui/src/panels/dialogs.rs @@ -178,14 +178,27 @@ fn modal_with_scroll(ctx: &egui::Context, title: &str, size: egui::Vec2, t: &Tok /// window. pub(crate) fn modal_fit(ctx: &egui::Context, title: &str, size: egui::Vec2, t: &Tokens, body: impl FnOnce(&mut egui::Ui, f32)) { let size = size.min(modal_space(ctx)); - egui::Modal::new(egui::Id::new(("modal", title))) + let id = egui::Id::new(("modal", title)); + // Dragging the title row moves the dialog off what it edits. The offset is kept within the + // window, so dragging back never has to undo a part the dialog could not follow. + let offset_id = id.with("offset"); + let room = ((ctx.content_rect().size() - size) * 0.5).max(vec2(0.0, 0.0)); + let offset = ctx.data(|d| d.get_temp::(offset_id)).unwrap_or_default().clamp(-room, room); + egui::Modal::new(id) + .area(egui::Modal::default_area(id).anchor(Align2::CENTER_CENTER, offset)) .backdrop_color(Color32::from_black_alpha(120)) .frame(egui::Frame::window(&ctx.style_of(ctx.theme())).fill(t.panel_bg).inner_margin(egui::Margin::same(18))) .show(ctx, |ui| { ui.set_width(size.x - 36.0); ui.set_min_height(size.y - 36.0); // The id stays keyed on the untranslated source, so switching language cannot move it. - ui.label(egui::RichText::new(tr(title)).font(Tokens::semibold(15.0)).color(t.tab_text_active)); + let head = ui.label(egui::RichText::new(tr(title)).font(Tokens::semibold(15.0)).color(t.tab_text_active)); + let row = Rect::from_x_y_ranges(ui.max_rect().x_range(), head.rect.y_range()); + let grip = ui.interact(row, id.with("grip"), Sense::drag()).on_hover_cursor(egui::CursorIcon::Grab); + if grip.dragged() { + let moved = (offset + grip.drag_delta()).clamp(-room, room); + ui.ctx().data_mut(|d| d.insert_temp(offset_id, moved)); + } ui.add_space(10.0); let title_h = ui.cursor().top() - ui.min_rect().top(); body(ui, (size.y - 36.0 - title_h).max(40.0)); diff --git a/crates/ui-egui/tests/ui_tail.rs b/crates/ui-egui/tests/ui_tail.rs index aa4128cb..f7e49ae0 100644 --- a/crates/ui-egui/tests/ui_tail.rs +++ b/crates/ui-egui/tests/ui_tail.rs @@ -103,6 +103,24 @@ fn layer_style_dialog_adds_previews_and_cancels() { assert_eq!(styles(&mut h), ["stroke"]); } +#[test] +fn layer_style_dialog_moves_by_its_title() { + let mut h = harness(1400.0); + let lid = h.state().session.active_comp().unwrap().layers[0].id.0; + h.state_mut().session.execute("layer.select", json!({"layers": [lid]})).unwrap(); + let ctx = h.ctx.clone(); + effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, "layer.style.dropShadow", json!({})).unwrap(); + h.run_steps(3); + let list0 = rect(&h, "dialog.layerStyle.list.blendingOptions"); + // The title row sits just above the style list. + let grab = pos2(list0.center().x, list0.min.y - 20.0); + drag(&mut h, grab, grab + vec2(-150.0, 60.0)); + h.run_steps(2); + let list1 = rect(&h, "dialog.layerStyle.list.blendingOptions"); + assert!((list1.min - list0.min - vec2(-150.0, 60.0)).length() < 2.0, "the dialog followed the drag: {:?} → {:?}", list0.min, list1.min); + assert_eq!(h.state().dialog, Some(effectcraft_ui_egui::Dialog::LayerStyles), "dragging does not close it"); +} + #[test] fn project_panel_columns_scroll_horizontally() { let mut h = harness(1400.0);