Docking: a dragged divider beside Library or Tools keeps that panel's width

craft-ui stores a moved divider as a ratio. After each frame the split
beside a side panel takes the width the panel was drawn at, so the panel
keeps its width when the window resizes and left_width / right_width follow
the drag.
This commit is contained in:
aiongg
2026-10-24 19:44:32 +00:00
parent 5ce171da23
commit c964b7f654
+45 -5
View File
@@ -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::<Panel>(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<f32>); 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: 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<Panel>| match node {
Node::Tabs { panels, .. } => panels.contains(&panel),
Node::Stack { entries } => entries.iter().any(|entry| entry.panel == panel),
_ => false,
};
let contains = |node: &Node<Panel>| holds(node, panel);
if *axis == SplitAxis::Horizontal {
if contains(first) {
change = Some(Action::ResizeSplit { path: path.clone(), size: SplitSize::FixedFirst(width) });