diff --git a/crates/ui-egui/src/docking.rs b/crates/ui-egui/src/docking.rs index 4128b1628..52914dd91 100644 --- a/crates/ui-egui/src/docking.rs +++ b/crates/ui-egui/src/docking.rs @@ -314,6 +314,7 @@ pub fn show(app: &mut crate::LightcraftApp, ui: &mut egui::Ui) { style.font = t.font(12.0); // Tool and preset panels publish hover previews before the photo consumes them. let mut canvas = None; + let (mut library_width, mut tools_width) = (None, None); let output = DockArea::new(egui::Id::new("lightcraft-docking")).locked(workspace.locked).show_with_limits( ui, &workspace.layout, @@ -326,10 +327,12 @@ pub fn show(app: &mut crate::LightcraftApp, ui: &mut egui::Ui) { canvas = Some(ui.new_child(egui::UiBuilder::new().id_salt("photo-view").max_rect(ui.max_rect()))); } Panel::Library => { + library_width = Some(ui.max_rect().width()); crate::widgets::register(ui.ctx(), "panel:left_panel", ui.max_rect()); crate::panels::left::show(app, ui); } Panel::Tools => { + tools_width = Some(ui.max_rect().width()); crate::widgets::register(ui.ctx(), "panel:right_panel", ui.max_rect()); crate::panels::right::show(app, ui); } @@ -364,6 +367,8 @@ pub fn show(app: &mut crate::LightcraftApp, ui: &mut egui::Ui) { let stale = workspace.layout != rendered.layout || workspace.hidden != rendered.hidden; if stale { craft_ui::docking::cancel_drag::(ui.ctx(), egui::Id::new("lightcraft-docking")); + } else { + keep_side_widths(&mut workspace, [(Panel::Library, library_width), (Panel::Tools, tools_width)]); } publish_visibility(app, &workspace); app.ui.docking = Some(workspace); @@ -380,6 +385,45 @@ pub fn show(app: &mut crate::LightcraftApp, ui: &mut egui::Ui) { } } +/// A dragged divider stores a ratio. The side panels keep the width they were drawn at instead, +/// so a window resize leaves them as they are and `left_width` / `right_width` follow them. +fn keep_side_widths(workspace: &mut Workspace, drawn: [(Panel, Option); 2]) { + let mut nodes = workspace.layout.root.as_ref().map(|node| vec![(node, Vec::new())]).unwrap_or_default(); + let mut changes = Vec::new(); + while let Some((node, path)) = nodes.pop() { + let Node::Split { axis, size, first, second } = node else { continue }; + if *axis == SplitAxis::Horizontal && matches!(size, SplitSize::Ratio(_)) { + let fixed = drawn.iter().find_map(|(panel, width)| { + // A width drawn from a ratio carries float noise; in hundredths a panel at its + // limit stores the limit itself. + let width = width.filter(|width| width.is_finite() && *width > 0.0).map(|width| (width * 100.0).round() / 100.0)?; + if holds(first, *panel) { Some(SplitSize::FixedFirst(width)) } else { holds(second, *panel).then_some(SplitSize::FixedSecond(width)) } + }); + if let Some(size) = fixed { + changes.push(Action::ResizeSplit { path: path.clone(), size }); + } + } + for (child, side) in [(second, true), (first, false)] { + let mut path = path.clone(); + path.push(side); + nodes.push((child, path)); + } + } + for action in changes { + // A size the layout refuses leaves the ratio, which still draws the same widths. + let _ = apply(workspace, action); + } +} + +/// Whether `node` is the group (tabs or accordion) holding `panel`. +fn holds(node: &Node, panel: Panel) -> bool { + match node { + Node::Tabs { panels, .. } => panels.contains(&panel), + Node::Stack { entries } => entries.iter().any(|entry| entry.panel == panel), + _ => false, + } +} + /// Keep the established ui.set width controls connected to the panel's current parent split. pub(crate) fn set_widths(state: &mut crate::UiState, params: &Value) -> Result<(), String> { state.left_width = crate::state::LEFT_WIDTH.clamp(state.left_width); @@ -413,11 +457,7 @@ pub(crate) fn set_widths(state: &mut crate::UiState, params: &Value) -> Result<( let mut change = None; while let Some((node, path)) = nodes.pop() { if let Node::Split { axis, first, second, .. } = node { - let contains = |node: &Node| match node { - Node::Tabs { panels, .. } => panels.contains(&panel), - Node::Stack { entries } => entries.iter().any(|entry| entry.panel == panel), - _ => false, - }; + let contains = |node: &Node| holds(node, panel); if *axis == SplitAxis::Horizontal { if contains(first) { change = Some(Action::ResizeSplit { path: path.clone(), size: SplitSize::FixedFirst(width) });