diff --git a/crates/ui-egui/src/dock_ui.rs b/crates/ui-egui/src/dock_ui.rs index 26b30601a..98951de89 100644 --- a/crates/ui-egui/src/dock_ui.rs +++ b/crates/ui-egui/src/dock_ui.rs @@ -97,6 +97,11 @@ impl FilmcraftApp { Action::Activate { panel } => Some(*panel), _ => None, }; + // a panel closed while floating remembers where; closed while docked, it forgets (#1985) + let closing = match &action { + Action::Close { panel } => Some((*panel, self.ui.floating.iter().find(|g| g.panels.contains(panel)).map(|g| g.rect))), + _ => None, + }; self.commit_dock(|shared| { crate::dock_shared::check_last_docked(shared, &action)?; shared.apply(action).map_err(|error| error.to_string()) @@ -104,6 +109,12 @@ impl FilmcraftApp { if let Some(panel) = reveal { self.pending_panel_raise = Some(panel); } + if let Some((panel, rect)) = closing { + self.ui.closed_floating.retain(|(p, _)| *p != panel); + if let Some(rect) = rect { + self.ui.closed_floating.push((panel, rect)); + } + } if relocating || self.ui.keys.maximized.is_some_and(|panel| !self.ui.dock.contains(panel) && !self.ui.floating.iter().any(|group| group.panels.contains(&panel))) { diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index e3a644ff8..b529f7dba 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -872,6 +872,16 @@ impl FilmcraftApp { _ => PanelKind::Project, }; self.ui.dock.open_near(p, near); + // closed while floating: it floats again where it was (#1985) + if let Some(i) = self.ui.closed_floating.iter().position(|(k, _)| *k == p) { + let (_, rect) = self.ui.closed_floating.remove(i); + if rect.iter().all(|v| v.is_finite()) { + let rect = [rect[0], rect[1], rect[2].max(160.0), rect[3].max(120.0)]; + if let Err(e) = self.apply_dock(craft_ui::docking::Action::Float { panel: p, rect }) { + log::warn!("could not float {} again: {e}", p.id()); + } + } + } } let _ = self.apply_dock(craft_ui::docking::Action::Activate { panel: p }); self.ui.focused = p; diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index f24e5fa30..e0710236c 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -436,6 +436,10 @@ pub struct UiState { pub dock: DockNode, #[serde(default)] pub floating: Vec, + /// Where each panel closed while floating was ([x, y, width, height]): opening it again + /// brings it back there, floating, instead of docked (#1985). + #[serde(default)] + pub closed_floating: Vec<(PanelKind, [f32; 4])>, /// Focused panel (blue outline, receives shortcuts). pub focused: PanelKind, pub timeline: TimelineView, @@ -960,6 +964,7 @@ impl Default for UiState { mode: Mode::Edit, workspace: "Editing".into(), floating: vec![], + closed_floating: vec![], dock: crate::dock::workspace("Editing"), focused: PanelKind::Timeline, timeline: TimelineView::default(), diff --git a/crates/ui-egui/tests/shared_docking_ui.rs b/crates/ui-egui/tests/shared_docking_ui.rs index 3df87b903..903355ac5 100644 --- a/crates/ui-egui/tests/shared_docking_ui.rs +++ b/crates/ui-egui/tests/shared_docking_ui.rs @@ -559,3 +559,25 @@ fn narration_selection_reveals_covered_floating_tts_once_without_editing_the_pro assert_eq!(h.state().ui.tts, draft); assert!(std::sync::Arc::ptr_eq(&project, &h.state().session.project)); } + +/// #1985: a panel closed while floating came back docked when opened again from the Window menu; +/// it now floats again where it was. Closed while docked, it opens docked as before. +#[test] +fn a_panel_closed_while_floating_reopens_floating_where_it_was() { + let mut app = FilmcraftApp::new(Session::default()); + let ctx = egui::Context::default(); + menus::invoke(&mut app, &ctx, "window.floatPanel", json!({"panel": "Info", "rect": [45, 65, 420, 320]})).unwrap(); + let rect = app.ui.floating.iter().find(|g| g.panels.contains(&PanelKind::Info)).unwrap().rect; + menus::invoke(&mut app, &ctx, "window.closePanel", json!({"panel": "Info"})).unwrap(); + assert!(!app.ui.dock.contains(PanelKind::Info) && !app.ui.floating.iter().any(|g| g.panels.contains(&PanelKind::Info))); + menus::invoke(&mut app, &ctx, &menus::panel_command_id(PanelKind::Info), json!({})).unwrap(); + let back = app.ui.floating.iter().find(|g| g.panels.contains(&PanelKind::Info)).expect("Info floats again"); + assert_eq!(back.rect, rect, "where it was"); + assert!(!app.ui.dock.contains(PanelKind::Info)); + // docked, then closed: it opens docked + menus::invoke(&mut app, &ctx, "window.dockPanel", json!({"panel": "Info", "anchor": "Project"})).unwrap(); + menus::invoke(&mut app, &ctx, "window.closePanel", json!({"panel": "Info"})).unwrap(); + menus::invoke(&mut app, &ctx, &menus::panel_command_id(PanelKind::Info), json!({})).unwrap(); + assert!(app.ui.dock.contains(PanelKind::Info)); + assert!(!app.ui.floating.iter().any(|g| g.panels.contains(&PanelKind::Info))); +}