Merge pull request #1020 from redrun6767/feat/panel-sets-dock-columns

UI: floating panel sets and a multi-column dock
This commit is contained in:
BFlat 2026-10-11 06:03:43 +07:00 committed by GitHub
commit d7339804e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1040 additions and 135 deletions

View File

@ -123,7 +123,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap
| Date | What landed |
|---|---|
| 2026-10-11 | Effect › Brush Strokes: Accented Edges, Angled Strokes, Crosshatch, Dark Strokes, Ink Outlines, Spatter, Sprayed Strokes, Sumi-e (G2). Command parameters from agents and files checked before anything changes (paint, place, cuts, dashes, crop, asset ids, anchors, paths, distort; #984–#999); command dialogs keep whole numbers whole, so Create Object Mosaic takes the tiles typed (#1000); stepper arrows take Shift (×10) and Ctrl/Cmd (÷10) like the arrow keys (#991). Exports and Rasterize supersample Art Optimized edges, so shapes meeting edge to edge leave no seam (#983). Character and paragraph styles take leading, kerning, mojikumi and the other attributes left out at their defaults (#1001). Drawing, typing and pasting with no layer shown and unlocked is refused instead of adding art to a hidden or locked layer (#1002). MCP `add_text` gives type on or in a path the colour asked for (#1003). MCP `screenshot` of a connected app renders the artboard asked for (#1004). `vectorcraft-cli run` ends quietly when its reader closes early (#1005). Select › All on Active Artboard selects on the artboard made active, not always the first (#1006). Reverse Gradient keeps off-centre midpoints, mirrored (#1007). A polar grid with no radial dividers draws none (#1008). Use Precise Cursors also covers the Pen's join pointer (#1009). SVGs with one absolute and one unitless root side import at their size, unstretched (#1010). DXF US survey inches, yards and miles open at their size and name (#1011). A file placed into a document with no other links follows its first change even while the app sits idle (#1031). PDF import keeps the style a font name says when the file doesn't embed the font (#1023). Shift selects ruler guides and art together, and Delete removes both (#1022). Open filled paths are hit by their fill on every side, the rotate cursor bows around the corner it is at, the flip icons match their flips, the Color Picker previews on the selection, and Properties › Transform reaches Scale Corners and Scale Strokes & Effects (#1036). Offset Path and the other booleans finish on outlines where the sweep used to loop until memory ran out (linesweeper 0.5, #1028). |
| 2026-10-11 | Effect › Brush Strokes: Accented Edges, Angled Strokes, Crosshatch, Dark Strokes, Ink Outlines, Spatter, Sprayed Strokes, Sumi-e (G2). Command parameters from agents and files checked before anything changes (paint, place, cuts, dashes, crop, asset ids, anchors, paths, distort; #984–#999); command dialogs keep whole numbers whole, so Create Object Mosaic takes the tiles typed (#1000); stepper arrows take Shift (×10) and Ctrl/Cmd (÷10) like the arrow keys (#991). Exports and Rasterize supersample Art Optimized edges, so shapes meeting edge to edge leave no seam (#983). Character and paragraph styles take leading, kerning, mojikumi and the other attributes left out at their defaults (#1001). Drawing, typing and pasting with no layer shown and unlocked is refused instead of adding art to a hidden or locked layer (#1002). MCP `add_text` gives type on or in a path the colour asked for (#1003). MCP `screenshot` of a connected app renders the artboard asked for (#1004). `vectorcraft-cli run` ends quietly when its reader closes early (#1005). Select › All on Active Artboard selects on the artboard made active, not always the first (#1006). Reverse Gradient keeps off-centre midpoints, mirrored (#1007). A polar grid with no radial dividers draws none (#1008). Use Precise Cursors also covers the Pen's join pointer (#1009). SVGs with one absolute and one unitless root side import at their size, unstretched (#1010). DXF US survey inches, yards and miles open at their size and name (#1011). A file placed into a document with no other links follows its first change even while the app sits idle (#1031). PDF import keeps the style a font name says when the file doesn't embed the font (#1023). Shift selects ruler guides and art together, and Delete removes both (#1022). Open filled paths are hit by their fill on every side, the rotate cursor bows around the corner it is at, the flip icons match their flips, the Color Picker previews on the selection, and Properties › Transform reaches Scale Corners and Scale Strokes & Effects (#1036). Offset Path and the other booleans finish on outlines where the sweep used to loop until memory ran out (linesweeper 0.5, #1028). Floating panel groups stack into sets that move together, collapse to their tabs and dock as extra dock columns that scroll (#1020). |
| 2026-10-10 | Initial live Revolve (Effect › 3D and Materials, #846, #605), in its own `three-d` crate. Progress docs re-measured and restructured to the craftrules standard (this page, `docs/target-app-parity.md`, `gaps.md`, `ui-parity.md`, the format, hardware, localization, effects and type checklists). Same day: v0.8.0; Effect › Distort, Pixelate and Texture filters (13); German interface; Variables (data merge); Graph Type value axes and tick marks; restart when the first frame never reaches the screen (#964) |
| 2026-10-09 | Radial Blur, Smart Blur, Unsharp Mask (gap 2 begins); 340 commits, the busiest day |
| 2026-10-08 | v0.5.0–v0.7.0; community contributions: Pen and shape-tool modifiers, Layers, Artboards, Japanese composition, Hebrew/Arabic type, interface languages, saved selections |

View File

@ -1,5 +1,6 @@
//! The right dock: Properties | Layers | Libraries tabs, plus the collapsed icon-panel column
//! whose panels pop out to the left. The double arrow at the top of the dock collapses the tabbed
//! whose panels pop out to the left, and left of it the floating sets docked as columns
//! ([`floating::docked_columns`]). The double arrow at the top of the dock collapses the tabbed
//! group to icons at the top of the icon column (`window.collapseDock`); the arrow above the column
//! then expands it again.
@ -23,6 +24,12 @@ fn column_left_id() -> egui::Id {
egui::Id::new("dock-icon-column-left")
}
/// Where the dock's left edge was drawn last: the icon column's, or the leftmost dock column's
/// (egui temp memory, one frame old). Popped-out panels open left of it.
fn dock_left_id() -> egui::Id {
egui::Id::new("dock-left")
}
/// Collapse the tabbed group to icons or expand it again (`window.collapseDock`). Expanding while
/// one of its panels is popped out shows that panel's tab instead.
pub fn set_collapsed(app: &mut VectorcraftApp, collapsed: bool) {
@ -148,7 +155,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
});
let column = column.response.rect;
let dock = group.map_or(column, |g| g.union(column));
// Floating sets docked as columns, left of the icon column.
let left = floating::docked_columns(app, ui).iter().map(|r| r.left()).fold(column.left(), f32::min);
ui.ctx().data_mut(|d| {
d.insert_temp(dock_left_id(), left);
d.insert_temp(column_left_id(), column.left());
d.insert_temp(floating::dock_rect_id(), dock);
d.insert_temp(floating::icons_rect_id(), column);
@ -226,7 +236,9 @@ pub fn floating_panel(app: &mut VectorcraftApp, ctx: &egui::Context) {
} else {
screen.right()
};
let right = column - 6.0;
let left = ctx.data(|d| d.get_temp::<f32>(dock_left_id())).filter(|x| x.is_finite() && *x > screen.left() && *x <= column).unwrap_or(column);
let right = if app.ui.dock && app.ui.screen_mode < 3 { left } else { column };
let right = right - 6.0;
// The tabbed group's panels fill their height (Layers) or scroll (Properties): give them one
// that keeps the flyout on screen.
let width = panel_width(id);

File diff suppressed because it is too large Load Diff

View File

@ -628,6 +628,7 @@
% of all commits % všech commitů
Lines +/− Řádky +/−
Drag to move Přetažením přesunete
Drag to move out of the set Přetažením přesunete mimo sadu
Drag to float as a toolbar Přetažením vytvoříte plovoucí panel nástrojů
Put back in the toolbar Vrátit do panelu nástrojů
Float Panel Plovoucí panel

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3213,6 +3213,7 @@ axis Vertical Vertikal
axis Both Beide
theme Appearance Erscheinungsbild
Drag to move Zum Verschieben ziehen
Drag to move out of the set Ziehen, um aus dem Satz zu lösen
Drag to float as a toolbar Ziehen, um als eigene Werkzeugleiste abzulösen
Put back in the toolbar Zurück in die Werkzeugleiste setzen
Float Panel Bedienfeld ablösen
@ -3769,6 +3770,9 @@ theme Appearance Erscheinungsbild
@msg tool (a tool id) is required tool (eine Werkzeug-ID) ist erforderlich
@msg {tool} isn't in a toolbar group of several tools {tool} gehört zu keiner Werkzeuggruppe mit mehreren Werkzeugen
@msg onto must name a panel floating in another group onto muss ein Bedienfeld nennen, das in einer anderen Gruppe schwebt
@msg below and above must name a panel floating in another group below und above müssen ein Bedienfeld nennen, das in einer anderen Gruppe schwebt
@msg give only one of onto, below and above nur eines von onto, below und above angeben
@msg column must be true or a number from 1 column muss true oder eine Zahl ab 1 sein
@msg x and y must both be numbers x und y müssen beide Zahlen sein
@msg "{0}" is not a colour: write it as #rrggbb "{0}" ist keine Farbe: Schreiben Sie sie als #rrggbb
@msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead dies ist kein Logo aus flächigen Farben ({_1} % seiner einfarbigen Bereiche entsprechen wenigen flächigen Farben), daher würde der Modus „Flaches Logo“ seine Verläufe in Flecken zerlegen: Verwenden Sie stattdessen den Modus „Farbe“

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3317,6 +3317,7 @@ axis Horizontal Horizontal
axis Vertical Vertical
axis Both Ambos
Drag to move Arrastrar para mover
Drag to move out of the set Arrastrar para sacar del conjunto
Drag to float as a toolbar Arrastrar para flotar como barra de herramientas
Put back in the toolbar Devolver a la barra de herramientas
Float Panel Hacer flotar el panel
@ -3873,6 +3874,9 @@ axis Both Ambos
@msg tool (a tool id) is required se requiere tool (un id de herramienta)
@msg {tool} isn't in a toolbar group of several tools {tool} no está en un grupo de la barra de herramientas con varias herramientas
@msg onto must name a panel floating in another group onto debe nombrar un panel que flote en otro grupo
@msg below and above must name a panel floating in another group below y above deben nombrar un panel que flote en otro grupo
@msg give only one of onto, below and above indica solo uno de onto, below y above
@msg column must be true or a number from 1 column debe ser true o un número desde 1
@msg x and y must both be numbers x e y deben ser números
@msg "{0}" is not a colour: write it as #rrggbb "{0}" no es un color: escríbelo como #rrggbb
@msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead esto no es un logotipo de colores planos ({_1}% de sus zonas sólidas coinciden con unos pocos colores planos), así que el modo Logotipo plano convertiría sus degradados en manchas: usa el modo Color

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3314,6 +3314,7 @@ axis Horizontal Horizontal
axis Vertical Vertical
axis Both Les deux
Drag to move Faire glisser pour déplacer
Drag to move out of the set Faire glisser pour sortir de l’ensemble
Drag to float as a toolbar Faire glisser pour détacher en barre d’outils flottante
Put back in the toolbar Remettre dans la barre d’outils
Float Panel Rendre le panneau flottant
@ -3870,6 +3871,9 @@ axis Both Les deux
@msg tool (a tool id) is required tool (un identifiant d’outil) est obligatoire
@msg {tool} isn't in a toolbar group of several tools {tool} ne fait pas partie d’un groupe de plusieurs outils dans la barre d’outils
@msg onto must name a panel floating in another group onto doit désigner un panneau flottant dans un autre groupe
@msg below and above must name a panel floating in another group below et above doivent désigner un panneau flottant dans un autre groupe
@msg give only one of onto, below and above indiquez un seul de onto, below et above
@msg column must be true or a number from 1 column doit être true ou un nombre à partir de 1
@msg x and y must both be numbers x et y doivent tous deux être des nombres
@msg "{0}" is not a colour: write it as #rrggbb « {0} » n’est pas une couleur : écrivez-la sous la forme #rrggbb
@msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead ce n’est pas un logo en aplats ({_1} % de ses zones pleines correspondent à quelques couleurs unies), donc le mode Logo en aplats transformerait ses dégradés en taches : utilisez le mode Couleur

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3317,6 +3317,7 @@ axis Horizontal Orizzontale
axis Vertical Verticale
axis Both Entrambi
Drag to move Trascina per spostare
Drag to move out of the set Trascina per spostare fuori dall’insieme
Drag to float as a toolbar Trascina per rendere mobile come barra degli strumenti
Put back in the toolbar Riporta nella barra degli strumenti
Float Panel Rendi mobile pannello
@ -3873,6 +3874,9 @@ axis Both Entrambi
@msg tool (a tool id) is required tool (un id di strumento) è obbligatorio
@msg {tool} isn't in a toolbar group of several tools {tool} non fa parte di un gruppo di più strumenti nella barra degli strumenti
@msg onto must name a panel floating in another group onto deve indicare un pannello mobile in un altro gruppo
@msg below and above must name a panel floating in another group below e above devono indicare un pannello mobile in un altro gruppo
@msg give only one of onto, below and above indica solo uno tra onto, below e above
@msg column must be true or a number from 1 column deve essere true o un numero da 1
@msg x and y must both be numbers x e y devono essere entrambi numeri
@msg "{0}" is not a colour: write it as #rrggbb "{0}" non è un colore: scrivilo come #rrggbb
@msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead questo non è un logo a colori piatti ({_1}% delle sue aree piene corrisponde a pochi colori piatti), quindi la modalità Logo piatto trasformerebbe i suoi sfumati in macchie: usa la modalità Colore

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3265,6 +3265,7 @@ axis Horizontal 水平方向
axis Vertical 垂直方向
axis Both 両方
Drag to move 移動するにはドラッグ
Drag to move out of the set ドラッグしてセットから出す
Drag to float as a toolbar ドラッグしてツールバーとして浮かせる
Put back in the toolbar ツールバーに戻す
Float Panel パネルをフローティング
@ -3796,6 +3797,9 @@ theme Appearance 外観
@msg give the path `id` and the width point `index` パスの `id` と幅ポイントの `index` を指定してください
@msg no such width point 指定された幅ポイントが見つかりません
@msg onto must name a panel floating in another group onto には、別のグループでフローティングしているパネルを指定してください
@msg below and above must name a panel floating in another group below と above には別のグループでフローティングしているパネルを指定してください
@msg give only one of onto, below and above onto、below、above のうち 1 つだけを指定してください
@msg column must be true or a number from 1 column には true または 1 以上の数値を指定してください
@msg unknown panel `{raw}` `{raw}` は不明なパネルです
@msg x and y must both be numbers x と y はどちらも数値で指定してください
@msg Downloaded {what} {what} をダウンロードしました

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -2697,6 +2697,7 @@
% of all commits % de todos os commits
Lines +/− Linhas +/−
Drag to move Arraste para mover
Drag to move out of the set Arraste para tirar do conjunto
Drag to float as a toolbar Arraste para flutuar como barra de ferramentas
Put back in the toolbar Devolver à barra de ferramentas
Float Panel Flutuar Painel

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -3313,6 +3313,7 @@ axis Horizontal Горизонтально
axis Vertical Вертикально
axis Both Оба
Drag to move Перетащите для перемещения
Drag to move out of the set Перетащите, чтобы вынести из набора
Drag to float as a toolbar Перетащите, чтобы сделать плавающей панелью инструментов
Put back in the toolbar Вернуть на панель инструментов
Float Panel Сделать панель плавающей
@ -3869,6 +3870,9 @@ axis Both Оба
@msg tool (a tool id) is required требуется tool (идентификатор инструмента)
@msg {tool} isn't in a toolbar group of several tools {tool} не входит в группу панели инструментов из нескольких инструментов
@msg onto must name a panel floating in another group onto должен называть панель, плавающую в другой группе
@msg below and above must name a panel floating in another group below и above должны называть панель, плавающую в другой группе
@msg give only one of onto, below and above укажите только один из onto, below и above
@msg column must be true or a number from 1 column должен быть true или числом от 1
@msg x and y must both be numbers x и y должны быть числами
@msg "{0}" is not a colour: write it as #rrggbb «{0}» не является цветом: запишите его как #rrggbb
@msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead это не логотип из плоских цветов ({_1}% его сплошных областей совпадает с несколькими плоскими цветами), поэтому режим «Плоский логотип» превратил бы градиенты в пятна: используйте режим «Цвет»

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3305,6 +3305,7 @@ axis Horizontal Горизонтальна
axis Vertical Вертикальна
axis Both Обидві
Drag to move Перетягніть для переміщення
Drag to move out of the set Перетягніть, щоб винести з набору
Drag to float as a toolbar Перетягніть, щоб від’єднати панель інструментів
Put back in the toolbar Повернути на панель інструментів
Float Panel Від’єднати панель
@ -3856,6 +3857,9 @@ axis Both Обидві
@msg tool (a tool id) is required потрібно tool (ідентифікатор інструменту)
@msg {tool} isn't in a toolbar group of several tools {tool} не входить до групи панелі інструментів з кількох інструментів
@msg onto must name a panel floating in another group onto має називати панель, що плаває в іншій групі
@msg below and above must name a panel floating in another group below і above мають називати панель, що плаває в іншій групі
@msg give only one of onto, below and above вкажіть лише один із onto, below і above
@msg column must be true or a number from 1 column має бути true або числом від 1
@msg x and y must both be numbers x і y мають бути числами
@msg "{0}" is not a colour: write it as #rrggbb "{0}" не є кольором: запишіть його як #rrggbb
@msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead це не логотип із плоских кольорів ({_1}% його суцільних областей збігається з кількома плоскими кольорами), тому режим «Плоский логотип» перетворив би градієнти на плями: використовуйте режим «Колір»

Can't render this file because it has a wrong number of fields in line 13.

View File

@ -3517,6 +3517,7 @@ axis Horizontal 水平
axis Vertical 垂直
axis Both 两者
Drag to move 拖动以移动
Drag to move out of the set 拖动以移出该组合
Drag to float as a toolbar 拖动使其浮动为工具栏
Put back in the toolbar 放回工具栏
Float Panel 浮动面板

Can't render this file because it contains an unexpected character in line 2 and column 73.

View File

@ -3295,6 +3295,7 @@ axis Horizontal 水平
axis Vertical 垂直
axis Both 兩者
Drag to move 拖曳以移動
Drag to move out of the set 拖曳以移出該組合
Drag to float as a toolbar 拖曳使其浮動為工具列
Put back in the toolbar 放回工具列
Float Panel 浮動面板

Can't render this file because it contains an unexpected character in line 6 and column 47.

View File

@ -260,13 +260,13 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"window.panel.float",
"Float Panel",
"",
"{panel: id or \"tools\", x?, y?, onto?: id, group?: bool} float a panel out of the dock (or out of its floating group) as its own floating group with its top-left corner at x, y in window points (default: cascaded), as dragging its tab out of the dock does; `onto` stacks it with the floating group holding that panel instead; `group` takes its whole group (its floating group, or the Properties | Layers | Libraries tabs left in the dock); a panel alone in its group is moved. \"tools\" floats the Tools panel. Returns {panel, floating, group, pos}",
"{panel: id or \"tools\", x?, y?, onto?: id, below?: id, above?: id, group?: bool, collapsed?: bool} float a panel out of the dock (or out of its floating group) as its own floating group with its top-left corner at x, y in window points (default: cascaded), as dragging its tab out of the dock does; `onto` stacks it as a tab with the floating group holding that panel instead; `below` / `above` puts its group in a set with that group, right below or above it, as dropping it on that group's lower or upper half does (the set's groups stack top to bottom and move together); `group` takes its whole group (its floating group, or the Properties | Layers | Libraries tabs left in the dock); a panel alone in its group is moved (out of its set, given x, y); `collapsed` shows its group's tabs only, as a double-click on a tab does; a docked group floats its dock column. \"tools\" floats the Tools panel. Returns {panel, floating, group, set, pos, collapsed, column}",
),
(
"window.panel.dock",
"Dock Panel",
"",
"{panel: id or \"tools\", group?: bool} put a floating panel (with `group`, its whole group) back in the dock, as dropping it on the dock or its group's × does: Properties, Layers and Libraries in the tabbed group, the other panels in the icon column; \"tools\" docks the Tools panel at the window's left edge",
"{panel: id or \"tools\", group?: bool, column?: true or n} put a floating panel (with `group`, its whole group) back in the dock, as dropping it on the dock or its group's × does: Properties, Layers and Libraries in the tabbed group, the other panels in the icon column; with `column`, dock its group and the rest of its set as a dock column instead, as dropping it on the left edge of the icon column or of a dock column does: `true` at the dock's left, n at that place (1: next to the icon column, counting left); \"tools\" docks the Tools panel at the window's left edge. Returns {panel, floating, column}",
),
(
"window.collapseDock",
@ -1126,6 +1126,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
&& let Some(g) = crate::floating::group_of(&app.ui, p).and_then(|i| app.ui.floating_panels.get_mut(i))
{
g.active = g.panels.iter().position(|q| q == p).unwrap_or(g.active);
g.collapsed = false;
let out = json!({ "floating": g.panels });
app.ui.dock = true;
return Some(Ok(out));

View File

@ -434,7 +434,12 @@ pub struct TaskBarPlace {
}
/// A group of panels dragged out of the dock: it floats as a stack of tabs, one panel shown.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
///
/// Groups sharing a `column` float as one set, stacked top to bottom in list order, like the panel
/// groups of a dock in Illustrator's workspace
/// (<https://helpx.adobe.com/illustrator/using/workspace-basics.html>): the set moves by the top
/// group's title bar, and a group dragged by its tab strip leaves it.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct FloatingPanels {
/// Panel ids (`window.panel`), in tab order.
pub panels: Vec<String>,
@ -446,6 +451,17 @@ pub struct FloatingPanels {
/// A width the group was given (the Tabs panel sized over its text), else its panels' own.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub width: Option<f32>,
/// The set this group is stacked in with others (the same number), top to bottom in list
/// order; none when it floats on its own.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub column: Option<u32>,
/// Collapsed to its tabs (a double-click on a tab), its panel hidden.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub collapsed: bool,
/// Docked as a column of the dock (with the rest of its set) instead of floating: the columns
/// stand left of the icon column, 1 next to it, counting left.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub docked: Option<u32>,
}
impl FloatingPanels {
@ -458,10 +474,45 @@ impl FloatingPanels {
g.active = g.active.min(g.panels.len().saturating_sub(1));
!g.panels.is_empty()
});
Self::tidy(groups);
if toolbar_pos.is_some_and(|p| !p.iter().all(|v| v.is_finite())) {
*toolbar_pos = None;
}
}
/// Keep each set's groups together in the list, after its first one and in the order they
/// come, at its first one's position and in its dock column; a set left with one group floats
/// (or stays docked) on its own; the dock columns are numbered 1, 2… without gaps.
pub fn tidy(groups: &mut Vec<FloatingPanels>) {
let mut out: Vec<FloatingPanels> = Vec::with_capacity(groups.len());
for mut g in groups.drain(..) {
match g.column.and_then(|c| out.iter().rposition(|o| o.column == Some(c))) {
Some(i) => {
if let Some(first) = out.iter().find(|o| o.column == g.column) {
g.pos = first.pos;
g.docked = first.docked;
}
out.insert(i + 1, g);
}
None => out.push(g),
}
}
let alone: Vec<u32> = out.iter().filter_map(|g| g.column).filter(|c| out.iter().filter(|g| g.column == Some(*c)).count() < 2).collect();
for g in &mut out {
if g.column.is_some_and(|c| alone.contains(&c)) {
g.column = None;
}
}
let mut numbers: Vec<u32> = out.iter().filter_map(|g| g.docked).collect();
numbers.sort_unstable();
numbers.dedup();
for g in &mut out {
if let Some(d) = g.docked {
g.docked = numbers.iter().position(|n| *n == d).and_then(|k| u32::try_from(k + 1).ok());
}
}
*groups = out;
}
}
impl UiState {

View File

@ -260,13 +260,26 @@ Floating panels: dragging a dock tab, a panel icon or a popped-out panel's title
inside the window, and the strip right of the dock's tabs floats the whole Properties | Layers | Libraries group. A
floating group moves by its title bar (or the strip right of its tabs); a tab dragged out of it floats on its own;
dropped on another group's title bar or tabs it stacks with that group, and dropped on the dock (lit up while the
pointer is over it) or closed with its × its panels go back to the tabbed group or the icon column.
`window.panel.float {panel, x?, y?, onto?, group?}` floats a panel (with `group`, its whole group) with its top-left
corner at `x`, `y` (window points; default cascaded), or stacks it with the floating group holding `onto`;
`window.panel.dock {panel, group?}` puts it back. `panel` takes the `window.panel` ids and labels, and `"tools"` for
pointer is over it) or closed with its × its panels go back to the tabbed group or the icon column. Dropped on the upper or
lower half of another group, floating or docked (a line shows where), a group joins it in a set right above or below
it: the groups stack top to bottom, each
showing one of its panels, and move together by the top group's title bar, whose × docks them all; a group dragged by
its tab strip (a lone panel's tab or the strip right of the tabs) leaves the set. A double-click on a tab collapses a
group to its tabs, and another expands it. Dropped on the left edge of the icon column or of a dock column, a set
docks as a new dock column there: its groups stack in it as in a floating set, take groups dropped on their edges and
tabs, and leave it dragged by their tab strip; the strip along the column's top floats the whole column again, and
its × puts its panels back where they live.
`window.panel.float {panel, x?, y?, onto?, below?, above?, group?, collapsed?}` floats a panel (with `group`, its whole
group) with its top-left corner at `x`, `y` (window points; default cascaded), or stacks it as a tab with the floating
group holding `onto`, or puts its group in a set right `below` or `above` the group holding that panel; `collapsed`
collapses or expands its group;
`window.panel.dock {panel, group?, column?}` puts it back, or with `column` (`true`, or the place: 1 next to the icon
column, counting left) docks its group and set as a dock column; `window.panel.float` on a docked panel floats its
column. `panel` takes the `window.panel` ids and labels, and `"tools"` for
the Tools panel, which floats by its title bar and docks on the window's left edge. `window.panel` on a floating
panel shows its tab. `ui.floating_panels` in `ui.inspect` lists the groups (panel ids, the index of the tab shown,
the top-left corner) and `ui.toolbar_pos` the Tools panel's corner (null: docked); both are saved with the
panel shows its tab (expanding its group). `ui.floating_panels` in `ui.inspect` lists the groups (panel ids, the
index of the tab shown, the top-left corner, `column`: the set it is stacked in, top to bottom in list order,
`collapsed`, and `docked`: the dock column it stands in) and `ui.toolbar_pos` the Tools panel's corner (null: docked); both are saved with the
preferences and in user workspaces (the built-in workspaces dock everything), and a corner saved on a bigger window
is clamped into this one when drawn. Floating panels stay inside the app window (no separate OS windows).

View File

@ -375,7 +375,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Hint bar, contextual task bar, 19 dock panels with ≡ menus.
- The contextual task bar's handle drags it anywhere on the canvas (kept whole on it as the window resizes); unpinned it keeps that offset as it follows the selection, and its More Options menu hides it, pins its position across selection changes (`window.taskBar.pin`), resets it under the selection (`window.taskBar.reset`) and shows the Properties panel. As in Illustrator the position isn't kept across launches. Not done: the bar's tool-specific variants (only the selection tools show it).
- Tool flyouts tear off: dragging or clicking the tear-off bar on a flyout's right side (or releasing the long press over it) floats its group as a strip of tool buttons, moved by the same bar and put back in the toolbar by the × at its top (`window.floatTools`). While a group floats, the presses that would open its flyout raise the strip instead. Floating strips are kept with the preferences and in user workspaces. Not done: turning a strip vertical, docking it.
- Panels undock (#495): a dock tab, a panel icon or a popped-out panel's title dragged out of the dock floats that panel inside the window, and the strip right of the dock's tabs floats the whole tabbed group. A floating group moves by its title bar, a tab dragged out of it floats on its own, and dropped on another group's title bar or tabs it stacks with that group; dropped on the dock, which lights up under the pointer, its panels go back where they live (the tabbed group or the icon column), as its × and the panel menus' Float Panel / Dock Panel do (`window.panel.float`, `window.panel.dock`). The Tools panel floats by its title bar and docks again on the window's left edge. Floating panels and the Tools panel's position are kept with the preferences and in user workspaces, and clamped into a smaller window. Not done: panels in separate OS windows, docking a panel into a dock group it doesn't belong to, reordering tabs by dragging, collapsing a floating group to icons, resizing floating panels.
- Panels undock (#495): a dock tab, a panel icon or a popped-out panel's title dragged out of the dock floats that panel inside the window, and the strip right of the dock's tabs floats the whole tabbed group. A floating group moves by its title bar, a tab dragged out of it floats on its own, and dropped on another group's title bar or tabs it stacks with that group; dropped on the dock, which lights up under the pointer, its panels go back where they live (the tabbed group or the icon column), as its × and the panel menus' Float Panel / Dock Panel do (`window.panel.float`, `window.panel.dock`). The Tools panel floats by its title bar and docks again on the window's left edge. Floating groups stack into sets: dropped on the upper or lower half of another group (a line shows where), a group joins it right above or below it, and the set's groups stack top to bottom, each showing one panel, moving together by the top group's title bar, whose × docks them all; a group dragged by its tab strip leaves the set (`window.panel.float {below, above}`). A double-click on a tab collapses a floating group to its tabs (`collapsed`). Sets dropped on the left edge of the icon column or of another dock column dock as dock columns (a multi-column dock, `window.panel.dock {column}`), whose groups stack, tab and tear out as a floating set's do, and whose top strip floats the column again. Floating panels, their sets and the Tools panel's position are kept with the preferences and in user workspaces, and clamped into a smaller window. Not done: panels in separate OS windows, docking a panel into the default tabbed group it doesn't belong to, reordering tabs by dragging, collapsing a set or dock column to icons, dock columns right of the default dock, resizing floating panels and the groups of a set.
- The dock's double arrow collapses Properties, Layers and Libraries to icons at the top of the icon column, popping out like the other icon panels, and expands them again (`window.collapseDock`, kept with the preferences and in saved workspaces). The icon column's own arrow (expanding the icon panels into a full column) is not done.
- Localised UI: menus (in-window and native), panels, dialogs, toolbar and Preferences read from per-language catalogs (`crates/ui-egui/src/i18n`, see `docs/development.md`); Traditional Chinese (Taiwan), Simplified Chinese (mainland), Japanese, German, Spanish, French, Italian, Russian and Ukrainian ship complete, and Czech and Brazilian Portuguese cover every menu label (and more), with system-locale detection on macOS, Windows, Linux and the web (the browser's languages, or `?lang=`), a Language preference and, on macOS, a VectorCraft ▸ Language menu. Names are never translated (fonts and their styles, layers, artboards, saved presets, user libraries, custom workspaces, recent files, plug-ins).
- Native macOS menu bar, vector tool cursors, a ⌘K command palette, middle-button panning with any tool. Native menus explicitly end tracking on command selection, outside window presses and Escape; menu updates wait until tracking ends, and a dismissing press never starts a canvas gesture (M14.8).

View File

@ -143,6 +143,7 @@ Layers panel.
|---|---|---|
| ~40 panels with ≡ menus; dock, icon column, collapse, tabs | done | 51 panel modules incl. tests |
| Panels float inside the window, stack, dock back | done | #495 |
| Floating groups stack into sets; sets dock as extra dock columns (a multi-column dock); groups drop anywhere in a set or column, or onto its tabs; a column taller than the window scrolls | done | `window.panel.float {below, above}`, `window.panel.dock {column}` |
| Panels outside the app window (another monitor) | missing | single OS window |
| Properties panel per context (every selection type's Quick Actions) | partial | |
| Contextual task bar | done | |