Merge pull request #2953 from nrq6993-a11y/stream/vc-feat3-1753-workspace-dock-width

#1753: saved workspaces restore dock panel width
This commit is contained in:
BFlat
2026-10-19 00:37:00 +07:00
committed by GitHub
4 changed files with 93 additions and 4 deletions
+54 -3
View File
@@ -16,6 +16,55 @@ const ICON_COL: f32 = 38.0;
const HEADER: f32 = 14.0;
/// The tabbed group's width until the dock has been laid out (its default size).
const DOCK_WIDTH: f32 = 300.0;
pub(crate) const LEGACY_DOCK_PANEL: &str = "dock";
pub(crate) const SHARED_DOCK_PANEL: &str = "shared-panel-dock";
const LEGACY_DOCK_MIN: f32 = 230.0;
const LEGACY_DOCK_MAX: f32 = 520.0;
const SHARED_DOCK_MIN: f32 = 220.0;
const SHARED_DOCK_MAX: f32 = 720.0;
pub(crate) fn clamp_legacy_dock_width(width: f32) -> f32 {
width.clamp(LEGACY_DOCK_MIN, LEGACY_DOCK_MAX)
}
pub(crate) fn clamp_shared_dock_width(width: f32) -> f32 {
width.clamp(SHARED_DOCK_MIN, SHARED_DOCK_MAX)
}
fn seed_dock_panel_width(ctx: &egui::Context, panel: &str, width: f32) {
let screen = ctx.content_rect();
let w = width.max(1.0);
let outer_rect = egui::Rect::from_min_size(egui::pos2(screen.right() - w, screen.top()), egui::vec2(w, screen.height()));
let state = egui::containers::panel::PanelState { outer_rect };
ctx.data_mut(|data| data.insert_persisted(egui::Id::new(panel), state));
}
pub(crate) fn apply_pending_dock_width(app: &mut VectorcraftApp, ctx: &egui::Context, panel: &str, clamp: fn(f32) -> f32) {
if !app.ui.dock_width_pending {
return;
}
if let Some(width) = app.ui.dock_width.filter(|w| w.is_finite() && *w > 0.0) {
seed_dock_panel_width(ctx, panel, clamp(width));
}
app.ui.dock_width_pending = false;
}
pub(crate) fn note_dock_width(app: &mut VectorcraftApp, ctx: &egui::Context, panel: &str) {
if let Some(state) = egui::containers::panel::PanelState::load(ctx, egui::Id::new(panel)) {
let width = state.size().x;
if width.is_finite() && width > 0.0 {
app.ui.dock_width = Some(width);
}
}
}
pub(crate) fn legacy_dock_default_width(ui: &crate::state::UiState) -> f32 {
ui.dock_width.filter(|w| w.is_finite()).map(clamp_legacy_dock_width).unwrap_or(DOCK_WIDTH)
}
pub(crate) fn shared_dock_default_width(ui: &crate::state::UiState) -> f32 {
ui.dock_width.filter(|w| w.is_finite()).map(clamp_shared_dock_width).unwrap_or(310.0)
}
/// Top of a popped-out panel.
const FLYOUT_TOP: f32 = 110.0;
@@ -81,14 +130,15 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
if crate::panel_docking::show(app, ui) {
return;
}
apply_pending_dock_width(app, ui.ctx(), LEGACY_DOCK_PANEL, clamp_legacy_dock_width);
let t = Tokens::get(ui.ctx());
let shown = floating::shown_tab(&app.ui);
// Main tabbed group, unless it is collapsed to icons or all its panels float.
let group = shown.filter(|_| !app.ui.dock_collapsed).map(|active| {
egui::Panel::right("dock")
egui::Panel::right(LEGACY_DOCK_PANEL)
.resizable(true)
.default_size(DOCK_WIDTH)
.size_range(230.0..=520.0)
.default_size(legacy_dock_default_width(&app.ui))
.size_range(LEGACY_DOCK_MIN..=LEGACY_DOCK_MAX)
.frame(egui::Frame::NONE)
.show(ui, |ui| {
crate::theme::card(ui, &t, egui::Margin { left: 0, right: 6, top: 6, bottom: 6 }, |ui| {
@@ -114,6 +164,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
.response
.rect
});
note_dock_width(app, ui.ctx(), LEGACY_DOCK_PANEL);
// Collapsed icon-panel strip, left of the expanded panel group (like Illustrator's dock). A
// collapsed group's panels head it, under the « that expands them again. Floating panels leave
// it.
+8 -1
View File
@@ -651,6 +651,8 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
Some((layer, layout_rect, fill))
})
.collect();
crate::dock::apply_pending_dock_width(app, ui.ctx(), crate::dock::SHARED_DOCK_PANEL, crate::dock::clamp_shared_dock_width);
let shared_dock_default = crate::dock::shared_dock_default_width(&app.ui);
let mut draw = |ui: &mut egui::Ui| {
if layout.root.is_some() && crate::dock::collapse_header(ui, collapsed) {
let _ = app.run("window.collapseDock", json!({"collapsed":!collapsed}));
@@ -717,10 +719,15 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
output
};
let output = if layout.root.is_some() {
let panel = egui::Panel::right("shared-panel-dock").default_size(310.0).size_range(220.0..=720.0).resizable(true).frame(egui::Frame::NONE);
let panel = egui::Panel::right(crate::dock::SHARED_DOCK_PANEL)
.default_size(shared_dock_default)
.size_range(220.0..=720.0)
.resizable(true)
.frame(egui::Frame::NONE);
let panel = if collapsed { panel.exact_size(38.0).resizable(false) } else { panel };
let shown = panel.show(ui, |ui| crate::theme::card(ui, &t, egui::Margin { left: 0, right: 6, top: 6, bottom: 6 }, &mut draw));
ui.ctx().data_mut(|data| data.insert_temp(crate::floating::dock_rect_id(), shown.response.rect));
crate::dock::note_dock_width(app, ui.ctx(), crate::dock::SHARED_DOCK_PANEL);
shown.inner
} else {
draw(ui)
+8
View File
@@ -354,6 +354,12 @@ pub struct UiState {
/// Invalidates in-flight gestures when a workspace is replaced.
#[serde(skip)]
pub docking_generation: u64,
/// The main dock column's outer width in screen points (legacy or shared docking).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dock_width: Option<f32>,
/// Next frame should seed egui [`PanelState`] from [`Self::dock_width`] (workspace switch).
#[serde(skip)]
pub dock_width_pending: bool,
/// The Tools panel floats with its top-left corner here (dragged out by its title bar); `None`:
/// docked at the window's left edge.
#[serde(default)]
@@ -772,6 +778,8 @@ impl Default for UiState {
docking_hidden: Default::default(),
docking_icons: Default::default(),
docking_generation: 0,
dock_width: None,
dock_width_pending: false,
toolbar_pos: None,
status_bar: true,
dock: true,
+23
View File
@@ -41,6 +41,9 @@ pub struct Workspace {
pub docking_icons: std::collections::BTreeSet<String>,
/// Where the Tools panel floats (built-in workspaces: docked).
pub toolbar_pos: Option<[f32; 2]>,
/// Main dock column width in screen points (#1753).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dock_width: Option<f32>,
}
impl Default for Workspace {
@@ -140,6 +143,7 @@ pub fn capture(ui: &UiState, name: &str) -> Workspace {
docking_hidden: ui.docking_hidden.clone(),
docking_icons: ui.docking_icons.clone(),
toolbar_pos: ui.toolbar_pos,
dock_width: ui.dock_width,
}
}
@@ -165,6 +169,8 @@ pub fn apply(ui: &mut UiState, w: &Workspace) {
ui.docking_icons =
w.docking_icons.iter().filter(|id| crate::state::ICON_PANELS.iter().any(|entry| entry.0 == id.as_str())).take(64).cloned().collect();
ui.toolbar_pos = w.toolbar_pos;
ui.dock_width = w.dock_width;
ui.dock_width_pending = true;
FloatingPanels::sanitize(&mut ui.floating_panels, &mut ui.toolbar_pos);
ui.flyout = None;
ui.workspace = w.name.clone();
@@ -491,4 +497,21 @@ mod tests {
assert!(find(&ui, n).is_some(), "{n}");
}
}
#[test]
fn saved_workspaces_restore_dock_width() {
let mut ui = UiState { dock_width: Some(420.0), ..Default::default() };
save_as(&mut ui, "Wide").unwrap();
ui.dock_width = Some(260.0);
save_as(&mut ui, "Narrow").unwrap();
let wide = find(&ui, "Wide").unwrap();
apply(&mut ui, &wide);
assert_eq!(ui.dock_width, Some(420.0));
assert!(ui.dock_width_pending);
let narrow = find(&ui, "Narrow").unwrap();
apply(&mut ui, &narrow);
assert_eq!(ui.dock_width, Some(260.0));
let back: UiState = serde_json::from_value(serde_json::to_value(&ui).unwrap()).unwrap();
assert_eq!(back.custom_workspaces[1].dock_width, Some(260.0));
}
}