mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 03:37:00 -06:00
Merge pull request #3000 from nrq6993-a11y/stream/vc-issue-2995-panel-tab-reorder
Fix #2995: reorder grouped panel tabs by dragging within the tab strip
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
//! group to icons at the top of the icon column (`window.collapseDock`); the arrow above the column
|
||||
//! then expands it again.
|
||||
|
||||
use egui::{CornerRadius, Rect, Sense, Stroke, Ui, vec2};
|
||||
use egui::{CornerRadius, Pos2, Rect, Sense, Stroke, Ui, vec2};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::state::{DockTab, ICON_PANEL_GROUPS, ICON_PANELS};
|
||||
@@ -242,13 +242,16 @@ fn tab_strip(app: &mut VectorcraftApp, ui: &mut Ui, active: DockTab) -> bool {
|
||||
let menu = Rect::from_center_size(strip.right_center() - vec2(14.0, 0.0), vec2(16.0, 16.0));
|
||||
let area = Rect::from_min_max(strip.min, egui::pos2((menu.left() - 4.0).max(strip.left()), strip.bottom() - 1.0));
|
||||
let out = panel_tabs(ui, "docktab", area, &names, selected, theme::semibold(12.5), true);
|
||||
for (i, resp) in out.responses {
|
||||
let Some(&tab) = tabs.get(i) else { continue };
|
||||
let pointer = ui.input(|i| i.pointer.interact_pos());
|
||||
for (i, resp) in &out.responses {
|
||||
let Some(&tab) = tabs.get(*i) else { continue };
|
||||
let id = tab.info().0;
|
||||
if resp.clicked() {
|
||||
if let Some((from, to)) = tab_reorder_on_release(strip, pointer, resp, &out.responses) {
|
||||
apply_dock_tab_reorder(app, &tabs, from, to);
|
||||
} else if resp.clicked() {
|
||||
app.ui.dock_tab = tab;
|
||||
}
|
||||
crate::panel_docking::legacy_tab(app, ui, id, &resp);
|
||||
crate::panel_docking::legacy_tab_in_strip(app, ui, id, resp, Some(strip));
|
||||
}
|
||||
if let Some(tab) = out.picked.and_then(|i| tabs.get(i)).copied() {
|
||||
app.ui.dock_tab = tab;
|
||||
@@ -271,6 +274,38 @@ fn tab_strip(app: &mut VectorcraftApp, ui: &mut Ui, active: DockTab) -> bool {
|
||||
|
||||
/// App styling and overflow menus around the shared tab fitting/interaction components. Docking
|
||||
/// and floating groups consume the responses themselves, preserving their different drag rules.
|
||||
/// Drag a tab onto another tab in the same strip to reorder (pointer still on the strip at release),
|
||||
/// as in Illustrator's workspace basics
|
||||
/// (<https://helpx.adobe.com/illustrator/using/workspace-basics.html>).
|
||||
pub(crate) fn tab_reorder_on_release(
|
||||
strip: Rect,
|
||||
pointer: Option<Pos2>,
|
||||
source: &egui::Response,
|
||||
tabs: &[(usize, egui::Response)],
|
||||
) -> Option<(usize, usize)> {
|
||||
if !source.drag_stopped() {
|
||||
return None;
|
||||
}
|
||||
let pointer = pointer.filter(|p| p.is_finite())?;
|
||||
if !strip.expand(4.0).contains(pointer) {
|
||||
return None;
|
||||
}
|
||||
if source.drag_delta().length() < 2.0 {
|
||||
return None;
|
||||
}
|
||||
let from = tabs.iter().find(|(_, r)| r.id == source.id).map(|(i, _)| *i)?;
|
||||
let to = tabs.iter().find(|(_, r)| r.rect.contains(pointer)).map(|(i, _)| *i)?;
|
||||
(from != to).then_some((from, to))
|
||||
}
|
||||
|
||||
fn apply_dock_tab_reorder(app: &mut VectorcraftApp, visible: &[DockTab], from: usize, to: usize) {
|
||||
let Some(from_tab) = visible.get(from) else { return };
|
||||
let Some(to_tab) = visible.get(to) else { return };
|
||||
if let Err(error) = crate::panel_docking::strip_tab_reorder(app, from_tab.info().0, to_tab.info().0) {
|
||||
app.ui.status = error;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PanelTabs {
|
||||
pub responses: Vec<(usize, egui::Response)>,
|
||||
pub picked: Option<usize>,
|
||||
@@ -690,6 +725,7 @@ mod tests {
|
||||
fn arrow(&self) -> egui::Pos2 {
|
||||
egui::pos2(SCREEN.x - 10.0, HEADER / 2.0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn icon_panel_count() -> usize {
|
||||
@@ -801,6 +837,35 @@ mod tests {
|
||||
assert_eq!(h.app.ui.open_panel, None, "a click in the docked group puts {open:?} away");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_panel_move_reorders_legacy_dock_tabs_and_persists_order() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 300, "height": 300})).unwrap();
|
||||
assert!(app.ui.docking.is_none());
|
||||
app.run(
|
||||
"window.panel.move",
|
||||
json!({ "panel": "libraries", "anchor": "properties", "before": "properties" }),
|
||||
)
|
||||
.unwrap();
|
||||
let order: Vec<_> = floating::docked_tabs(&app.ui).map(|t| t.info().0).collect();
|
||||
assert_eq!(order, ["libraries", "properties", "layers"]);
|
||||
let saved: crate::state::UiState = serde_json::from_slice(&serde_json::to_vec(&app.ui).unwrap()).unwrap();
|
||||
assert_eq!(saved.dock_tab_order, app.ui.dock_tab_order);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_dock_tab_order_appends_tabs_missing_from_preferences() {
|
||||
let ui = crate::state::UiState {
|
||||
dock_tab: DockTab::Layers,
|
||||
dock_tab_order: vec![DockTab::Layers, DockTab::Properties],
|
||||
..Default::default()
|
||||
};
|
||||
let order = floating::resolved_dock_tab_order(&ui);
|
||||
assert_eq!(order.len(), DockTab::ALL.len());
|
||||
assert_eq!(order[..2], [DockTab::Layers, DockTab::Properties]);
|
||||
assert_eq!(order[2], DockTab::Libraries);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_collapse_command_sets_or_toggles_the_dock() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
|
||||
@@ -138,6 +138,16 @@ pub fn group_of(ui: &UiState, id: &str) -> Option<usize> {
|
||||
ui.floating_panels.iter().position(|g| g.panels.iter().any(|p| p == id))
|
||||
}
|
||||
|
||||
/// Reorder tabs in a floating group by index, keeping the active tab on the same panel.
|
||||
pub(crate) fn reorder_group_tab_indices(g: &mut FloatingPanels, from: usize, to: usize) {
|
||||
if from != to && from < g.panels.len() && to < g.panels.len() {
|
||||
let active = g.panels.get(g.active).cloned();
|
||||
let moved = g.panels.remove(from);
|
||||
g.panels.insert(to, moved);
|
||||
g.active = active.and_then(|id| g.panels.iter().position(|p| *p == id)).unwrap_or(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// The floating groups drawn with group `gi`, top to bottom: the groups of its set, or it alone.
|
||||
pub fn set_of(ui: &UiState, gi: usize) -> Vec<usize> {
|
||||
match ui.floating_panels.get(gi).and_then(|g| g.column) {
|
||||
@@ -209,9 +219,30 @@ fn ids_of(g: &FloatingPanels) -> Vec<&'static str> {
|
||||
g.panels.iter().filter_map(|p| panel(p)).map(|(id, _)| id).collect()
|
||||
}
|
||||
|
||||
/// Saved dock tab order, appending any tabs missing from older preferences.
|
||||
pub(crate) fn resolved_dock_tab_order(ui: &UiState) -> Vec<DockTab> {
|
||||
if ui.dock_tab_order.is_empty() {
|
||||
DockTab::ALL.to_vec()
|
||||
} else {
|
||||
let mut order = ui.dock_tab_order.clone();
|
||||
for tab in DockTab::ALL {
|
||||
if !order.contains(&tab) {
|
||||
order.push(tab);
|
||||
}
|
||||
}
|
||||
order
|
||||
}
|
||||
}
|
||||
|
||||
/// Persist dock tab order when it differs from the default visible strip.
|
||||
pub(crate) fn set_dock_tab_order(ui: &mut UiState, visible: Vec<DockTab>) {
|
||||
let default = DockTab::ALL.iter().copied().filter(|t| group_of(ui, t.info().0).is_none()).collect::<Vec<_>>();
|
||||
ui.dock_tab_order = if visible == default { Vec::new() } else { visible };
|
||||
}
|
||||
|
||||
/// The dock tabs left in the dock, in tab order.
|
||||
pub fn docked_tabs(ui: &UiState) -> impl Iterator<Item = DockTab> + '_ {
|
||||
DockTab::ALL.into_iter().filter(|t| group_of(ui, t.info().0).is_none())
|
||||
resolved_dock_tab_order(ui).into_iter().filter(|t| group_of(ui, t.info().0).is_none())
|
||||
}
|
||||
|
||||
/// The dock tab the tabbed group shows: the chosen one, or the first left when it floats.
|
||||
@@ -566,6 +597,8 @@ enum Action {
|
||||
Detach(usize, f32),
|
||||
/// Float this panel out of its group; its tab began this far right.
|
||||
Tear(&'static str, f32),
|
||||
/// Reorder tabs in floating group `gi` from index `from` to `to`.
|
||||
Reorder(usize, usize, usize),
|
||||
/// Float the docked column of this group, its top-left corner this far from the pointer.
|
||||
Undock(usize, Vec2),
|
||||
}
|
||||
@@ -624,6 +657,7 @@ fn apply(app: &mut VectorcraftApp, ctx: &egui::Context, action: Option<Action>)
|
||||
return;
|
||||
}
|
||||
let before = app.ui.clone();
|
||||
let skip_reconcile = matches!(action, Some(Action::Reorder(_, _, _)));
|
||||
match action {
|
||||
Some(Action::Activate(gi, k)) => {
|
||||
if let Some(g) = app.ui.floating_panels.get_mut(gi)
|
||||
@@ -654,6 +688,19 @@ fn apply(app: &mut VectorcraftApp, ctx: &egui::Context, action: Option<Action>)
|
||||
}
|
||||
Some(Action::Detach(gi, left)) => detach(app, ctx, gi, left),
|
||||
Some(Action::Tear(id, left)) => tear(app, ctx, &[id], id, strip_grab(ctx, left)),
|
||||
Some(Action::Reorder(gi, from, to)) => {
|
||||
let ids = app.ui.floating_panels.get(gi).and_then(|g| {
|
||||
let panel = g.panels.get(from)?;
|
||||
let before = g.panels.get(to)?;
|
||||
Some((panel.clone(), before.clone()))
|
||||
});
|
||||
if let Some((panel, before)) = ids
|
||||
&& let Err(error) = crate::panel_docking::strip_tab_reorder(app, &panel, &before)
|
||||
{
|
||||
app.ui.status = error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
Some(Action::Undock(gi, grab)) => {
|
||||
let Some(at) = ctx.input(|i| i.pointer.interact_pos()) else { return };
|
||||
let Some(id) = set_of(&app.ui, gi).first().and_then(|&i| shown(&app.ui, i)) else { return };
|
||||
@@ -669,7 +716,7 @@ fn apply(app: &mut VectorcraftApp, ctx: &egui::Context, action: Option<Action>)
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
if let Err(error) = crate::panel_docking::reconcile_native(app, before.clone()) {
|
||||
if !skip_reconcile && let Err(error) = crate::panel_docking::reconcile_native(app, before.clone()) {
|
||||
app.ui.status = error;
|
||||
}
|
||||
ctx.data_mut(|data| {
|
||||
@@ -889,12 +936,14 @@ fn draw_groups(
|
||||
let names: Vec<_> = g.ids.iter().zip(&labels).map(|(&id, label)| (id, label.as_str())).collect();
|
||||
let selected = g.ids.iter().position(|&id| id == g.active).unwrap_or(0);
|
||||
let out = dock::panel_tabs(ui, &format!("native-set-tabs-{}", g.gi), tab_area, &names, selected, theme::semibold(12.0), false);
|
||||
for (j, resp) in out.responses {
|
||||
let Some(&id) = g.ids.get(j) else { continue };
|
||||
if resp.double_clicked() {
|
||||
for (j, resp) in &out.responses {
|
||||
let Some(&id) = g.ids.get(*j) else { continue };
|
||||
if let Some((from, to)) = dock::tab_reorder_on_release(strip, pointer, resp, &out.responses) {
|
||||
*action = Some(Action::Reorder(g.gi, from, to));
|
||||
} else if resp.double_clicked() {
|
||||
*action = Some(Action::Collapse(g.gi));
|
||||
} else if resp.clicked() {
|
||||
*action = Some(Action::Activate(g.gi, j));
|
||||
*action = Some(Action::Activate(g.gi, *j));
|
||||
} else if lone && resp.drag_started() {
|
||||
*action = Some(match alone {
|
||||
Some(pos) => Action::Move(id, pos),
|
||||
@@ -1240,6 +1289,18 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floating_group_tabs_reorder_horizontally_within_the_strip() {
|
||||
let mut h = Harness::new();
|
||||
h.drag(h.icons()[2].center(), pos2(500.0, 250.0));
|
||||
h.drag(h.icons()[0].center(), h.shared_tab("swatches").center());
|
||||
assert_eq!(h.groups(), [["swatches", "color"]]);
|
||||
h.app
|
||||
.run("window.panel.move", json!({ "panel": "color", "anchor": "swatches", "before": "swatches" }))
|
||||
.unwrap();
|
||||
assert_eq!(h.groups(), [["color", "swatches"]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_dock_tab_dragged_out_floats_and_dropped_on_the_dock_docks_again() {
|
||||
let mut h = Harness::new();
|
||||
|
||||
@@ -142,6 +142,79 @@ fn number(params: &Value, key: &str, default: f32) -> Result<f32, String> {
|
||||
}
|
||||
}
|
||||
|
||||
const LEGACY_REORDER_BAD_DEST: &str = "move requires an existing destination panel";
|
||||
|
||||
enum LegacyStripReorder {
|
||||
DockStrip,
|
||||
FloatingGroup(usize),
|
||||
}
|
||||
|
||||
fn legacy_strip_reorder_target(
|
||||
app: &VectorcraftApp,
|
||||
before: &str,
|
||||
dock_strip: bool,
|
||||
panel_group: Option<usize>,
|
||||
) -> Result<LegacyStripReorder, String> {
|
||||
if dock_strip {
|
||||
return Ok(LegacyStripReorder::DockStrip);
|
||||
}
|
||||
let gi = panel_group.ok_or(LEGACY_REORDER_BAD_DEST)?;
|
||||
if crate::floating::group_of(&app.ui, before) != Some(gi) {
|
||||
return Err(LEGACY_REORDER_BAD_DEST.into());
|
||||
}
|
||||
Ok(LegacyStripReorder::FloatingGroup(gi))
|
||||
}
|
||||
|
||||
/// Legacy workspaces (no saved `docking` layout): reorder tabs in the dock strip or a floating
|
||||
/// group without promoting to a shared layout.
|
||||
fn legacy_same_strip_reorder(
|
||||
app: &mut VectorcraftApp,
|
||||
panel: &str,
|
||||
before: &str,
|
||||
target: LegacyStripReorder,
|
||||
) -> Result<Value, String> {
|
||||
use crate::state::DockTab;
|
||||
match target {
|
||||
LegacyStripReorder::DockStrip => {
|
||||
let mut order: Vec<DockTab> = crate::floating::docked_tabs(&app.ui).collect();
|
||||
let from = order.iter().position(|t| t.info().0 == panel).ok_or(LEGACY_REORDER_BAD_DEST)?;
|
||||
let to = order.iter().position(|t| t.info().0 == before).ok_or(LEGACY_REORDER_BAD_DEST)?;
|
||||
if from != to {
|
||||
let item = order.remove(from);
|
||||
order.insert(to, item);
|
||||
crate::floating::set_dock_tab_order(&mut app.ui, order);
|
||||
}
|
||||
}
|
||||
LegacyStripReorder::FloatingGroup(gi) => {
|
||||
let g = app.ui.floating_panels.get_mut(gi).ok_or("invalid restored panel group")?;
|
||||
let from = g.panels.iter().position(|id| id == panel).ok_or(LEGACY_REORDER_BAD_DEST)?;
|
||||
let to = g.panels.iter().position(|id| id == before).ok_or(LEGACY_REORDER_BAD_DEST)?;
|
||||
crate::floating::reorder_group_tab_indices(g, from, to);
|
||||
if let Some(layout) = app.ui.docking.as_mut() {
|
||||
let panels = g.panels.clone();
|
||||
let active = g.active.min(panels.len().saturating_sub(1));
|
||||
if let Some(group) = layout.floating.iter_mut().find(|group| group.panels.iter().any(|id| id == panel)) {
|
||||
group.panels = panels;
|
||||
group.active = active;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(json!({ "panel": panel, "before": before }))
|
||||
}
|
||||
|
||||
/// Same-strip tab reorder for `window.panel.move` and in-frame tab drags (`app.run` during egui deadlocks).
|
||||
pub(crate) fn strip_tab_reorder(app: &mut VectorcraftApp, panel: &str, before: &str) -> Result<Value, String> {
|
||||
use crate::state::DockTab;
|
||||
let panel_group = crate::floating::group_of(&app.ui, panel);
|
||||
let dock_strip_reorder = DockTab::from_id(panel).is_some()
|
||||
&& DockTab::from_id(before).is_some()
|
||||
&& panel_group.is_none()
|
||||
&& crate::floating::group_of(&app.ui, before).is_none();
|
||||
let target = legacy_strip_reorder_target(app, before, dock_strip_reorder, panel_group)?;
|
||||
legacy_same_strip_reorder(app, panel, before, target)
|
||||
}
|
||||
|
||||
fn placement(params: &Value) -> Result<Placement<String>, String> {
|
||||
if let Some(before) = params.get("before") {
|
||||
let id = before.as_str().and_then(normalize).ok_or("before must name a panel")?;
|
||||
@@ -294,6 +367,18 @@ fn apply_action(app: &mut VectorcraftApp, action: Action<String>) -> Result<Valu
|
||||
|
||||
fn change(app: &mut VectorcraftApp, operation: &str, params: &Value) -> Result<Value, String> {
|
||||
let panel = params.get("panel").and_then(Value::as_str).and_then(normalize).ok_or("panel must name a registered panel")?;
|
||||
if operation == "move" && params.get("before").is_some() {
|
||||
let before = params.get("before").and_then(Value::as_str).and_then(normalize).ok_or("before must name a panel")?;
|
||||
use crate::state::DockTab;
|
||||
let panel_group = crate::floating::group_of(&app.ui, panel);
|
||||
let dock_strip_reorder = DockTab::from_id(panel).is_some()
|
||||
&& DockTab::from_id(before).is_some()
|
||||
&& panel_group.is_none()
|
||||
&& crate::floating::group_of(&app.ui, before).is_none();
|
||||
if app.ui.docking.is_none() || panel_group.is_some() || dock_strip_reorder {
|
||||
return strip_tab_reorder(app, panel, before);
|
||||
}
|
||||
}
|
||||
if app.ui.docking.as_ref().is_some_and(|layout| !valid(layout)) {
|
||||
return Err("saved panel layout is invalid; reset the workspace first".into());
|
||||
}
|
||||
@@ -766,7 +851,9 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
|
||||
{
|
||||
return app.ui.docking.is_some();
|
||||
}
|
||||
ui.ctx().data_mut(|data| data.remove::<LegacyDrag>(egui::Id::new("vectorcraft-panel-docking").with("legacy-origin")));
|
||||
craft_ui::docking::cancel_drag::<String>(ui.ctx(), egui::Id::new("vectorcraft-panel-docking"));
|
||||
ui.ctx()
|
||||
.data_mut(|data| data.remove::<LegacyDrag>(egui::Id::new("vectorcraft-panel-docking").with("legacy-origin")));
|
||||
}
|
||||
for action in output.actions {
|
||||
if tab_drop
|
||||
@@ -774,7 +861,7 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Err(error) = app.run("window.panel.layout", json!({"action": action})) {
|
||||
if let Err(error) = apply_action(app, action) {
|
||||
app.ui.status = error;
|
||||
}
|
||||
}
|
||||
@@ -857,6 +944,16 @@ fn icon_rail(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
|
||||
pub(crate) fn legacy_tab(app: &mut VectorcraftApp, ui: &mut egui::Ui, panel: &str, response: &egui::Response) {
|
||||
legacy_tab_in_strip(app, ui, panel, response, None);
|
||||
}
|
||||
|
||||
pub(crate) fn legacy_tab_in_strip(
|
||||
app: &mut VectorcraftApp,
|
||||
ui: &mut egui::Ui,
|
||||
panel: &str,
|
||||
response: &egui::Response,
|
||||
strip: Option<egui::Rect>,
|
||||
) {
|
||||
let Some(panel) = normalize(panel) else { return };
|
||||
response.context_menu(|ui| {
|
||||
if ui.button(tl!("Float Panel")).clicked() {
|
||||
@@ -880,7 +977,17 @@ pub(crate) fn legacy_tab(app: &mut VectorcraftApp, ui: &mut egui::Ui, panel: &st
|
||||
}
|
||||
});
|
||||
});
|
||||
if response.drag_started()
|
||||
let inside_strip = strip.is_some_and(|strip| {
|
||||
ui.input(|i| i.pointer.interact_pos()).is_some_and(|p| strip.expand(4.0).contains(p))
|
||||
});
|
||||
let area = egui::Id::new("vectorcraft-panel-docking");
|
||||
let begun = ui.ctx().data(|d| d.get_temp::<LegacyDrag>(area.with("legacy-origin")).is_some());
|
||||
let begin = if strip.is_some() {
|
||||
!begun && (response.drag_started() || (!inside_strip && response.dragged()))
|
||||
} else {
|
||||
response.drag_started()
|
||||
};
|
||||
if begin
|
||||
&& let Some(id) = normalize(panel)
|
||||
{
|
||||
let mut layout = app.ui.docking.clone().unwrap_or_else(|| legacy(app));
|
||||
@@ -1035,9 +1142,10 @@ mod tests;
|
||||
|
||||
/// Commit a pointer drop after shared and native panel windows have finished rendering.
|
||||
pub(crate) fn finish_native_drop(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
if let Some((command, params)) =
|
||||
if let Some((id, params)) =
|
||||
ctx.data_mut(|data| data.remove_temp::<(&'static str, serde_json::Value)>(egui::Id::new("vectorcraft-native-panel-drop")))
|
||||
&& let Err(error) = app.run(command, params)
|
||||
&& let Some(result) = command(app, id, ¶ms)
|
||||
&& let Err(error) = result
|
||||
{
|
||||
app.ui.status = error;
|
||||
}
|
||||
|
||||
@@ -309,6 +309,9 @@ pub struct UiState {
|
||||
pub legacy_language: Option<String>,
|
||||
pub brightness: Brightness,
|
||||
pub dock_tab: DockTab,
|
||||
/// Tab order in the dock's tabbed group when it differs from [`DockTab::ALL`].
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub dock_tab_order: Vec<DockTab>,
|
||||
/// The dock's tabbed group (Properties | Layers | Libraries) is collapsed to icons at the top of
|
||||
/// the icon column (the dock's double arrow, `window.collapseDock`).
|
||||
#[serde(default)]
|
||||
@@ -778,6 +781,7 @@ impl Default for UiState {
|
||||
legacy_language: None,
|
||||
brightness: Brightness::MediumDark,
|
||||
dock_tab: DockTab::Properties,
|
||||
dock_tab_order: Vec::new(),
|
||||
dock_collapsed: false,
|
||||
open_panel: None,
|
||||
control_bar: false,
|
||||
|
||||
Reference in New Issue
Block a user