diff --git a/Cargo.lock b/Cargo.lock index ac705b0f6..836d2b953 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -849,7 +849,7 @@ dependencies = [ [[package]] name = "craft-ui" version = "0.1.0" -source = "git+https://github.com/aiongg/craft-libs?rev=bfda13d2afd92947f2fe0f671385d350b4d5932a#bfda13d2afd92947f2fe0f671385d350b4d5932a" +source = "git+https://github.com/aiongg/craft-libs?rev=13be52fbaa2c4936c8d7b3d2b5abe6ab72892123#13be52fbaa2c4936c8d7b3d2b5abe6ab72892123" dependencies = [ "egui", "serde", diff --git a/Cargo.toml b/Cargo.toml index 7b148b927..7adf940b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,4 +126,4 @@ hayro-jbig2 = { git = "https://github.com/storytold/pdfcraft", rev = "b5467d93a0 # Docking trial: craft-ui from the craft-libs `docking` branch. [patch."https://github.com/storytold/craft-libs"] -craft-ui = { git = "https://github.com/aiongg/craft-libs", rev = "bfda13d2afd92947f2fe0f671385d350b4d5932a" } +craft-ui = { git = "https://github.com/aiongg/craft-libs", rev = "13be52fbaa2c4936c8d7b3d2b5abe6ab72892123" } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 5fd50284e..ba0c871de 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -322,7 +322,8 @@ pub struct UiState { pub docking: Option>, #[serde(default, skip_serializing_if = "HiddenPanels::is_empty")] pub docking_hidden: HiddenPanels, - /// The panel (or the canvas) last clicked or selected in the docking layout. + /// The panel (or the canvas) last clicked or selected in the docking layout, while the panel + /// stays where the user left it. #[serde(skip)] pub docking_focused: Option, /// Invalidates in-flight gestures when a workspace is replaced. diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index abed8cea7..716d3b11c 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -1871,9 +1871,7 @@ fn run_ui_inner(app: &mut DesignApp, id: &str, p: &Value) -> Option { app.ui.docking_generation = app.ui.docking_generation.wrapping_add(1); - app.ui.docking_raise = None; - app.ui.docking_opened = None; - app.ui.docking_flyout = None; + crate::panel_docking::forget_panels(app); let name = p.get("name").and_then(Value::as_str).unwrap_or("Essentials"); if let Some(w) = app.ui.custom_workspaces.iter().find(|w| w.name == name).cloned() { if let Some(hidden) = w.hidden_menu_items { diff --git a/crates/ui-egui/src/panel_docking.rs b/crates/ui-egui/src/panel_docking.rs index f55f50187..f2b98860f 100644 --- a/crates/ui-egui/src/panel_docking.rs +++ b/crates/ui-egui/src/panel_docking.rs @@ -298,6 +298,52 @@ pub(crate) fn tab_group(layout: &Layout, panel: &str) -> Vec { vec![panel.to_string()] } +/// Where a panel is: its floating window and group, or the path to its tab group, accordion or +/// dock in the tree with the dock column and group. A different place means the panel moved. +#[derive(Debug, PartialEq)] +enum Place { + Floating { window: usize, group: usize }, + Tree { path: Vec, column: usize, group: usize }, +} + +fn place(layout: &Layout, panel: &str) -> Option { + let has = |group: &Group| group.panels.iter().any(|p| p == panel); + for (window, floating) in layout.floating.iter().enumerate() { + if let Some(group) = floating.groups.iter().position(has) { + return Some(Place::Floating { window, group }); + } + } + let mut visit: Vec<(&Node, Vec)> = layout.root.iter().map(|node| (node, Vec::new())).collect(); + let mut seen = 0usize; + while let Some((node, path)) = visit.pop() { + seen = seen.saturating_add(1); + if seen > 2 * craft_ui::docking::MAX_PANELS || path.len() > craft_ui::docking::MAX_DEPTH { + return None; + } + match node { + Node::Split { first, second, .. } => { + let mut second_path = path.clone(); + second_path.push(true); + let mut first_path = path; + first_path.push(false); + visit.push((second, second_path)); + visit.push((first, first_path)); + } + Node::Tabs { panels, .. } if panels.iter().any(|p| p == panel) => return Some(Place::Tree { path, column: 0, group: 0 }), + Node::Stack { entries } if entries.iter().any(|e| e.panel == panel) => return Some(Place::Tree { path, column: 0, group: 0 }), + Node::Dock { columns, .. } => { + for (column, c) in columns.iter().enumerate() { + if let Some(group) = c.groups.iter().position(has) { + return Some(Place::Tree { path, column, group }); + } + } + } + _ => {} + } + } + None +} + /// Is `panel` an open entry of an accordion (which Minimize closes)? fn open_in_stack(layout: &Layout, panel: &str) -> bool { let mut visit: Vec<&Node> = layout.root.iter().collect(); @@ -567,6 +613,12 @@ impl Transaction { } let reveal = reveal.filter(|panel| layout.contains(panel)); let flyout = reveal.as_ref().filter(|panel| in_iconic_column(&layout, panel)).cloned(); + // Focus stays on a panel only while it stays where the user left it, as craft-ui's tab + // focus does: a command that moves it gives the focus up. + let before = layout_of(app); + if app.ui.docking_focused.as_deref().is_some_and(|panel| panel != CANVAS && place(&before, panel) != place(&layout, panel)) { + app.ui.docking_focused = None; + } app.ui.docking = Some(layout); app.ui.docking_hidden = hidden; release_closed(app, &closed); @@ -578,6 +630,7 @@ impl Transaction { } else if app.ui.docking_opened.as_ref().is_some_and(|panel| !app.ui.docking.as_ref().is_some_and(|layout| layout.contains(panel))) { app.ui.docking_opened = None; } + release_flyout(app); app.ui.open_panel = None; json!({"layout": app.ui.docking, "closed": closed, "floating": floating_panels(app)}) } @@ -701,6 +754,22 @@ fn release_closed(app: &mut DesignApp, closed: &[String]) { } } +/// Forget the panel focused, opened, raised or showing as a flyout: the workspace was replaced. +pub(crate) fn forget_panels(app: &mut DesignApp) { + app.ui.docking_focused = None; + app.ui.docking_opened = None; + app.ui.docking_raise = None; + app.ui.docking_flyout = None; +} + +/// A flyout opens only from an iconic column; forget it once its panel is no longer in one. +fn release_flyout(app: &mut DesignApp) { + let layout = layout_of(app); + if app.ui.docking_flyout.as_deref().is_some_and(|panel| !in_iconic_column(&layout, panel)) { + app.ui.docking_flyout = None; + } +} + /// Window › Collapse to Icons collapses every dock column; `iconic: false` (the control /// channel's `dockExpanded`) expands the outer column. pub(crate) fn set_iconic(app: &mut DesignApp, iconic: bool) -> Result { @@ -721,6 +790,7 @@ pub(crate) fn set_iconic(app: &mut DesignApp, iconic: bool) -> Result) -> Vec<(craft_ui::docking::Hit, egui::Rect)> { craft_ui::docking::DockArea::::hit_rects(&h.ctx, egui::Id::new("designcraft-panel-docking")) }