mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 02:19:56 -06:00
Merge pull request #5395 from dotCooCoo/fix/toolbar-left-panel-docking
Workspace: add an option to keep Tools beside the canvas with left-side panels
This commit is contained in:
+3
-1
@@ -16,7 +16,7 @@
|
||||
|
||||
**Stage: alpha** · next: beta, ~13 points (ready for real work ~62% → ~75%, and reliable `.ai` exchange) and ~170–270 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Selection QoL: marquee modes and visible feedback, #5313; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Tools stays beside the canvas with left-side panels; new gradients follow document color mode; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
VectorCraft is a clean-room, open-source, pure-Rust reimplementation of the Adobe Illustrator workflow. It runs
|
||||
on macOS, Windows, Linux, FreeBSD and the web (WASM), and agents can drive all of it over MCP, the CLI and a JSON
|
||||
@@ -36,6 +36,7 @@ blocking it ([Alpha gate](docs/roadmap.md#alpha-gate)).
|
||||
|
||||
| | Value | Kind |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | minor | Docked Tools stays between left-side panels and the canvas; redocking accepts canvas and window edges. Requested layout choice; scores and estimates unchanged. |
|
||||
| **Feature breadth** (Illustrator's menu items, tools, effects, formats exist) | **~88%** | measured in part: menu items 348/375 wired (92.8%), tools 89/92, Illustrator effects 46/50, Photoshop effects 57/57, 3D 4/4 (initial types, no materials) |
|
||||
| **Feature depth** (weighted by use, scored by behaviour) | **~75%** | estimated |
|
||||
| **To beta** | **~170–270 h** one agent · ~50–80 h with 4–6 agents | estimated |
|
||||
@@ -136,6 +137,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-11-05: Window › Toolbars › Keep Tools Beside the Canvas, off by default, docks the Tools panel between left-side panels and the canvas instead of at the window's left edge, and the canvas's left edge then also docks a floating Tools panel (`window.toolsBesideCanvas`, #5395). Workspaces keep the setting; workspaces saved without it keep Tools at the window's edge. An optional VectorCraft layout choice; headline numbers unchanged.
|
||||
- 2026-11-05: Release Type on a Path joins the Type Tools add-on (Object › Plug-ins › Type Tools): each selected type on a path splits into point type with the same text and attributes, on one straight line starting where its first character sat, and the plain path it was set on, with its exact shape and place in the stacking order and no paint; or keeps only the path or only the text, optionally leaving the original. A dialog with Preview, one undo step (`addon.type.releasePath`, #5532). A VectorCraft addition: headline numbers unchanged.
|
||||
- 2026-11-04: New Document: the units chosen in Preset Details now belong to the chosen document, as the pane's options are listed for the selected preset; the chosen card shows them, every other Recent and Saved card keeps the units its document or preset was made in, and the built-in tabs still follow the chosen units, so switching between inches and millimetres no longer restyles every recent template. Scores and estimates unchanged.
|
||||
- 2026-11-02: Mouse wheel over a closed font-family field in Character, Properties or Control changes selected text through the family list using saved font filters (excluding search text), one undo step per notch, stopping at the ends (#5253). Scores and estimates unchanged.
|
||||
|
||||
@@ -454,7 +454,8 @@ impl VectorcraftApp {
|
||||
/// in a floating window, the tab strip's document draws as a pane.
|
||||
pub(crate) fn canvas_area(&mut self, ui: &mut egui::Ui) {
|
||||
let t = theme::Tokens::get(ui.ctx());
|
||||
ui.painter().rect_filled(ui.max_rect(), 0.0, t.pasteboard);
|
||||
// Tools may already occupy the left side of this panel.
|
||||
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, t.pasteboard);
|
||||
self.canvas.windows.strip = None;
|
||||
if self.ui.screen_mode < 3 {
|
||||
chrome::doc_tabs(self, ui);
|
||||
@@ -542,8 +543,16 @@ impl VectorcraftApp {
|
||||
widgets::set_menu_floor(&ctx, ctx.content_rect().bottom());
|
||||
}
|
||||
if self.ui.toolbar && self.ui.screen_mode < 3 {
|
||||
tools_float::set_window_dock_zone(&ctx, ui.available_rect_before_wrap());
|
||||
}
|
||||
if !self.ui.tools_beside_canvas {
|
||||
// A canvas-edge target left from before the setting was turned off docks nothing.
|
||||
tools_float::clear_canvas_dock_zone(&ctx);
|
||||
}
|
||||
// Beside the canvas, docked Tools draws inside the canvas panel (`toolbar::show_beside_canvas`).
|
||||
if self.ui.toolbar && self.ui.screen_mode < 3 && (!self.ui.tools_beside_canvas || self.ui.toolbar_pos.is_some()) {
|
||||
toolbar::show(self, ui);
|
||||
} else if self.ui.screen_mode < 3 && self.ui.tab_hid.is_none() {
|
||||
} else if !self.ui.toolbar && self.ui.screen_mode < 3 && self.ui.tab_hid.is_none() {
|
||||
// Window › Tools hides the Tools panel alone; Tab hides the toolbars made with New
|
||||
// Toolbar too.
|
||||
toolbar::show_custom(self, ui.ctx());
|
||||
|
||||
@@ -523,12 +523,17 @@ pub fn sync(ui: &UiState) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Window › Toolbars: Advanced and Double Column, the toolbars made with New Toolbar (each checked
|
||||
/// while it shows), New Toolbar… and Manage Toolbars….
|
||||
/// Window › Toolbars: Advanced, Double Column and Keep Tools Beside the Canvas, the toolbars made
|
||||
/// with New Toolbar (each checked while it shows), New Toolbar… and Manage Toolbars….
|
||||
pub fn menu_items() -> Vec<crate::menus::Item> {
|
||||
use crate::menus::Item;
|
||||
let c = |label: &'static str, id: &'static str| Item::Cmd(label, id, Value::Null);
|
||||
let mut v = vec![c("Advanced", "window.toolbarAdvanced"), c("Double Column", "window.toolbarColumns"), Item::Sep];
|
||||
let mut v = vec![
|
||||
c("Advanced", "window.toolbarAdvanced"),
|
||||
c("Double Column", "window.toolbarColumns"),
|
||||
c("Keep Tools Beside the Canvas", "window.toolsBesideCanvas"),
|
||||
Item::Sep,
|
||||
];
|
||||
let names = names_store().read().map(|v| v.clone()).unwrap_or_default();
|
||||
if !names.is_empty() {
|
||||
v.extend(names.into_iter().map(|n| Item::Cmd(n, "window.toolbar.custom", json!({ "name": n }))));
|
||||
|
||||
@@ -3120,6 +3120,7 @@ digits Malayalam Малаялам
|
||||
Simulate Colored Paper Симулиране на цветна хартия
|
||||
Simulate Paper Симулиране на хартия
|
||||
Double Column Две колони
|
||||
Keep Tools Beside the Canvas Инструментите до платното
|
||||
Single Left Quotation Mark Лява единична кавичка
|
||||
Single Quotes: Единични кавички:
|
||||
Single Right Quotation Mark Дясна единична кавичка
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -670,6 +670,7 @@
|
||||
Toolbars Trake s alatima
|
||||
Advanced Napredno
|
||||
Double Column Dvostruka kolona
|
||||
Keep Tools Beside the Canvas Alati pored platna
|
||||
Actions Akcije
|
||||
Asset Export Izvoz resursa
|
||||
Attributes Atributi
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1206,6 +1206,7 @@
|
||||
Double Bar Barra doble
|
||||
Double Click To Isolate Doble clic per aïllar
|
||||
Double Column Columna doble
|
||||
Keep Tools Beside the Canvas Eines al costat del llenç
|
||||
Double Left Quotation Marks Cometes dobles esquerres
|
||||
Double Quotes: Cometes dobles:
|
||||
Double Right Quotation Marks Cometes dobles dretes
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -678,6 +678,7 @@
|
||||
Toolbars Panely nástrojů
|
||||
Advanced Rozšířené
|
||||
Double Column Dva sloupce
|
||||
Keep Tools Beside the Canvas Nástroje vedle plátna
|
||||
Actions Akce
|
||||
Asset Export Export prvků
|
||||
Attributes Atributy
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -627,6 +627,7 @@
|
||||
Toolbars Værktøjslinjer
|
||||
Advanced Avanceret
|
||||
Double Column Dobbelt kolonne
|
||||
Keep Tools Beside the Canvas Værktøjer ved siden af canvaset
|
||||
Actions Handlinger
|
||||
Asset Export Aktiveksport
|
||||
Attributes Attributter
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3164,6 +3164,7 @@ digits Malayalam Malayalam
|
||||
Simulate Colored Paper Farbiges Papier simulieren
|
||||
Simulate Paper Papier simulieren
|
||||
Double Column Zweispaltig
|
||||
Keep Tools Beside the Canvas Werkzeuge neben der Arbeitsfläche
|
||||
Single Left Quotation Mark Einfaches öffnendes Anführungszeichen
|
||||
Single Quotes: Einfache Anführungszeichen:
|
||||
Single Right Quotation Mark Einfaches schließendes Anführungszeichen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3618,6 +3618,7 @@ digits Malayalam Μαλαγιάλαμ
|
||||
Simulate Colored Paper Προσομοίωση χρωματιστού χαρτιού
|
||||
Simulate Paper Προσομοίωση χαρτιού
|
||||
Double Column Διπλή στήλη
|
||||
Keep Tools Beside the Canvas Εργαλεία δίπλα στον καμβά
|
||||
Single Left Quotation Mark Μονό αριστερό εισαγωγικό
|
||||
Single Quotes: Μονά εισαγωγικά:
|
||||
Single Right Quotation Mark Μονό δεξί εισαγωγικό
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 168 and column 5.
|
@@ -3204,6 +3204,7 @@ digits Malayalam Malayálam
|
||||
Simulate Colored Paper Simular papel de color
|
||||
Simulate Paper Simular papel
|
||||
Double Column Columna doble
|
||||
Keep Tools Beside the Canvas Herramientas junto al lienzo
|
||||
Single Left Quotation Mark Comilla simple de apertura
|
||||
Single Quotes: Comillas simples:
|
||||
Single Right Quotation Mark Comilla simple de cierre
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -669,6 +669,7 @@
|
||||
Toolbars Työkalurivit
|
||||
Advanced Lisäasetukset
|
||||
Double Column Kaksi saraketta
|
||||
Keep Tools Beside the Canvas Työkalut kankaan vieressä
|
||||
Actions Toiminnot
|
||||
Asset Export Resurssien vienti
|
||||
Attributes Määritteet
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3196,6 +3196,7 @@ digits Malayalam Malayalam
|
||||
Simulate Colored Paper Simuler un papier de couleur
|
||||
Simulate Paper Simuler le papier
|
||||
Double Column Deux colonnes
|
||||
Keep Tools Beside the Canvas Outils à côté de la zone de travail
|
||||
Single Left Quotation Mark Guillemet simple ouvrant
|
||||
Single Quotes: Guillemets simples :
|
||||
Single Right Quotation Mark Guillemet simple fermant
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1111,6 +1111,7 @@ font-technology Variable Változó
|
||||
Double Bar Kettős vonal
|
||||
Double Click To Isolate Elkülönítés dupla kattintással
|
||||
Double Column Két oszlop
|
||||
Keep Tools Beside the Canvas Eszközök a vászon mellett
|
||||
Double Left Quotation Marks Bal oldali dupla idézőjel
|
||||
Double Quotes: Dupla idézőjelek:
|
||||
Double Right Quotation Marks Jobb oldali dupla idézőjel
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -627,6 +627,7 @@
|
||||
Toolbars Bilah alat
|
||||
Advanced Lanjutan
|
||||
Double Column Kolom ganda
|
||||
Keep Tools Beside the Canvas Alat di samping kanvas
|
||||
Actions Tindakan
|
||||
Asset Export Ekspor aset
|
||||
Attributes Atribut
|
||||
|
||||
|
@@ -3240,6 +3240,7 @@ digits Malayalam Malayalam
|
||||
Simulate Colored Paper Simula carta colorata
|
||||
Simulate Paper Simula carta
|
||||
Double Column Colonna doppia
|
||||
Keep Tools Beside the Canvas Strumenti accanto all'area di disegno
|
||||
Single Left Quotation Mark Virgoletta singola di apertura
|
||||
Single Quotes: Virgolette singole:
|
||||
Single Right Quotation Mark Virgoletta singola di chiusura
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -684,6 +684,7 @@ digits Malayalam マラヤーラム数字
|
||||
Toolbars ツールバー
|
||||
Advanced 詳細
|
||||
Double Column 2 列
|
||||
Keep Tools Beside the Canvas ツールをキャンバスの横に配置
|
||||
Actions アクション
|
||||
Asset Export アセットの書き出し
|
||||
Attributes 属性
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -580,6 +580,7 @@
|
||||
Toolbars ხელსაწყოთა ზოლები
|
||||
Advanced დამატებითი
|
||||
Double Column ორი სვეტი
|
||||
Keep Tools Beside the Canvas ხელსაწყოები ტილოს გვერდით
|
||||
Actions მოქმედებები
|
||||
Asset Export რესურსების ექსპორტირება
|
||||
Attributes ატრიბუტები
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -677,6 +677,7 @@ digits Malayalam 말라얄람어
|
||||
Toolbars 도구 모음
|
||||
Advanced 고급
|
||||
Double Column 2열
|
||||
Keep Tools Beside the Canvas 캔버스 옆에 도구 배치
|
||||
Actions 액션
|
||||
Asset Export 에셋 내보내기
|
||||
Attributes 속성
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -641,6 +641,7 @@
|
||||
Toolbars Werkbalken
|
||||
Advanced Geavanceerd
|
||||
Double Column Dubbele kolom
|
||||
Keep Tools Beside the Canvas Gereedschappen naast het canvas
|
||||
Actions Acties
|
||||
Asset Export Assetexport
|
||||
Attributes Kenmerken
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -628,6 +628,7 @@
|
||||
Toolbars Paski narzędzi
|
||||
Advanced Zaawansowany
|
||||
Double Column Dwie kolumny
|
||||
Keep Tools Beside the Canvas Narzędzia obok płótna
|
||||
Actions Operacje
|
||||
Asset Export Eksport zasobów
|
||||
Attributes Atrybuty
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2687,6 +2687,7 @@ digits Malayalam Malaiala
|
||||
Simulate Colored Paper Simular Papel Colorido
|
||||
Simulate Paper Simular Papel
|
||||
Double Column Coluna Dupla
|
||||
Keep Tools Beside the Canvas Ferramentas ao Lado da Tela
|
||||
Single Left Quotation Mark Aspa Esquerda Única
|
||||
Single Quotes: Aspas Simples:
|
||||
Single Right Quotation Mark Aspa Direita Única
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -3235,6 +3235,7 @@ digits Malayalam Малаялам
|
||||
Simulate Colored Paper Имитировать цветную бумагу
|
||||
Simulate Paper Имитировать бумагу
|
||||
Double Column Двойной столбец
|
||||
Keep Tools Beside the Canvas Инструменты рядом с холстом
|
||||
Single Left Quotation Mark Одинарная открывающая кавычка
|
||||
Single Quotes: Одинарные кавычки:
|
||||
Single Right Quotation Mark Одинарная закрывающая кавычка
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3126,6 +3126,7 @@ digits Malayalam Malayalam
|
||||
Simulate Colored Paper Simulera färgat papper
|
||||
Simulate Paper Simulera papper
|
||||
Double Column Dubbel kolumn
|
||||
Keep Tools Beside the Canvas Verktyg bredvid arbetsytan
|
||||
Single Left Quotation Mark Enkelt vänster citattecken
|
||||
Single Quotes: Enkla citattecken:
|
||||
Single Right Quotation Mark Enkelt höger citattecken
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -3029,6 +3029,7 @@ digits Malayalam Malayalam
|
||||
Simulate Colored Paper Renkli Kağıdı Simüle Et
|
||||
Simulate Paper Kağıdı Simüle Et
|
||||
Double Column Çift Sütun
|
||||
Keep Tools Beside the Canvas Araçları Tuvalin Yanında Tut
|
||||
Single Left Quotation Mark Tek Sol Tırnak İşareti
|
||||
Single Quotes: Tek Alıntılar:
|
||||
Single Right Quotation Mark Tek Sağ Tırnak İşareti
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -3232,6 +3232,7 @@ digits Malayalam Малаялам
|
||||
Simulate Colored Paper Імітувати кольоровий папір
|
||||
Simulate Paper Імітувати папір
|
||||
Double Column Подвійний стовпець
|
||||
Keep Tools Beside the Canvas Інструменти поруч із полотном
|
||||
Single Left Quotation Mark Одинарна лапка, що відкриває
|
||||
Single Quotes: Одинарні лапки:
|
||||
Single Right Quotation Mark Одинарна лапка, що закриває
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 182 and column 5.
|
@@ -997,6 +997,7 @@ font-technology Variable Biến thiên
|
||||
Dots Chấm
|
||||
Double Click To Isolate Bấm đúp để cô lập
|
||||
Double Column Hai cột
|
||||
Keep Tools Beside the Canvas Công cụ cạnh vùng vẽ
|
||||
Double Left Quotation Marks Dấu ngoặc kép trái đôi
|
||||
Double Quotes: Dấu ngoặc kép:
|
||||
Double Right Quotation Marks Dấu ngoặc kép phải đôi
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 204 and column 5.
|
@@ -3073,6 +3073,7 @@ digits Malayalam 马拉雅拉姆数字
|
||||
Simulate Colored Paper 模拟有色纸张
|
||||
Simulate Paper 模拟纸张
|
||||
Double Column 双列
|
||||
Keep Tools Beside the Canvas 工具置于画布旁
|
||||
Single Left Quotation Mark 左单引号
|
||||
Single Quotes: 单引号:
|
||||
Single Right Quotation Mark 右单引号
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4106,6 +4106,7 @@ digits Malayalam 馬來亞拉姆數字
|
||||
Simulate Colored Paper 模擬有色紙張
|
||||
Simulate Paper 模擬紙張
|
||||
Double Column 雙欄
|
||||
Keep Tools Beside the Canvas 工具置於畫布旁
|
||||
Single Left Quotation Mark 左單引號
|
||||
Single Quotes: 單引號:
|
||||
Single Right Quotation Mark 右單引號
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -477,6 +477,11 @@ fn run_ui_part5(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<
|
||||
})
|
||||
}
|
||||
"window.toolbarAdvanced" => flag(&mut app.ui.toolbar_advanced),
|
||||
"window.toolsBesideCanvas" => opt_bool(p, "on").map(|on| {
|
||||
let on = on.unwrap_or(!app.ui.tools_beside_canvas);
|
||||
app.ui.tools_beside_canvas = on;
|
||||
json!(on)
|
||||
}),
|
||||
"imageTrace.preview" => opt_bool(p, "on").map(|on| {
|
||||
let on = on.unwrap_or(!app.ui.image_trace_preview);
|
||||
app.ui.image_trace_preview = on;
|
||||
|
||||
@@ -64,6 +64,7 @@ pub fn checked(app: &VectorcraftApp, id: &str, p: &Value) -> Option<bool> {
|
||||
}
|
||||
"window.toolbar" => app.ui.toolbar,
|
||||
"window.toolbarAdvanced" => app.ui.toolbar_advanced,
|
||||
"window.toolsBesideCanvas" => app.ui.tools_beside_canvas,
|
||||
"window.toolbarColumns" => match p.get("toolbar").and_then(Value::as_str) {
|
||||
None => app.ui.toolbar_double,
|
||||
Some(name) => crate::custom_toolbars::find(&app.ui, name).is_some_and(|b| b.double),
|
||||
|
||||
@@ -461,6 +461,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"{double?: bool, toolbar?: name} show the toolbar's tools (with `toolbar`, that toolbar made with New Toolbar's) in two columns (true), one (false) or toggle (omitted), as the double arrow at the top of the toolbar does; returns the new state",
|
||||
),
|
||||
("window.toolbarAdvanced", "Toolbar: Advanced / Basic", "", "{}"),
|
||||
(
|
||||
"window.toolsBesideCanvas",
|
||||
"Keep Tools Beside the Canvas",
|
||||
"",
|
||||
"{on?: bool} dock the Tools panel between the left-side panels and the canvas (true) or at the window's left edge (false, the default), or toggle (omitted or null; any other value is an error); a floating Tools panel stays floating and docks at the chosen place when it is docked again. Kept with the workspace; a workspace saved without it docks Tools at the window's edge. Returns the new state",
|
||||
),
|
||||
(
|
||||
"window.floatTools",
|
||||
"Float Tool Group",
|
||||
@@ -596,7 +602,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"window.panel.dock",
|
||||
"Dock Panel",
|
||||
"",
|
||||
"{panel: id or \"tools\", anchor?: id, zone?: tab|center|left|right|top|bottom|above|below, before?: id, group?: bool, column?: bool or n, side?: Start|End, onto?: id, above?: id, below?: id, icon?: bool, collapsed?: bool} dock a panel (with `group`, its whole group): with no destination a floating panel goes back where it floated from (else joins the first dock group) and a hidden one shows where it was; with `anchor` (or `onto`) in the group holding that panel, `zone` as for window.panel.move; with `side`, as a new innermost column of that dock (`column: true` then moves its whole dock column or floating window); `column: n` with no other destination docks its group, or the whole floating window it floats in, as a new dock column at that place (1: next to the icon column, counting inwards; `true`: innermost); `icon: true` puts it in a row of its own at the end of the icon column; `above` / `below` as a group of its own above or below the group holding that panel. `collapsed` then minimizes or restores its group. \"tools\" docks the Tools panel at the left edge of the window. Returns {layout, closed, floating}",
|
||||
"{panel: id or \"tools\", anchor?: id, zone?: tab|center|left|right|top|bottom|above|below, before?: id, group?: bool, column?: bool or n, side?: Start|End, onto?: id, above?: id, below?: id, icon?: bool, collapsed?: bool} dock a panel (with `group`, its whole group): with no destination a floating panel goes back where it floated from (else joins the first dock group) and a hidden one shows where it was; with `anchor` (or `onto`) in the group holding that panel, `zone` as for window.panel.move; with `side`, as a new innermost column of that dock (`column: true` then moves its whole dock column or floating window); `column: n` with no other destination docks its group, or the whole floating window it floats in, as a new dock column at that place (1: next to the icon column, counting inwards; `true`: innermost); `icon: true` puts it in a row of its own at the end of the icon column; `above` / `below` as a group of its own above or below the group holding that panel. `collapsed` then minimizes or restores its group. \"tools\" docks the Tools panel at the left edge of the window, or beside the canvas inside any left-side dock while window.toolsBesideCanvas is on. Returns {layout, closed, floating}",
|
||||
),
|
||||
(
|
||||
"window.collapseDock",
|
||||
|
||||
@@ -1095,7 +1095,7 @@ fn known(action: &Action<String>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the canvas and the panels in the space left by the bars and the Tools panel, and apply
|
||||
/// Draw the canvas, its docked Tools panel and the other panels below the bars, and apply
|
||||
/// what the user did to them.
|
||||
pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
|
||||
let area = area_id();
|
||||
@@ -1346,6 +1346,7 @@ struct PanelContent<'a> {
|
||||
impl craft_ui::docking::DockContent<String> for PanelContent<'_> {
|
||||
fn body(&mut self, ui: &mut egui::Ui, id: &String) {
|
||||
if id == CANVAS {
|
||||
crate::toolbar::show_beside_canvas(self.app, ui);
|
||||
self.app.canvas_area(ui);
|
||||
return;
|
||||
}
|
||||
@@ -1389,7 +1390,8 @@ impl craft_ui::docking::DockContent<String> for PanelContent<'_> {
|
||||
}
|
||||
|
||||
fn limits(&self, id: &String) -> craft_ui::docking::PanelLimits {
|
||||
let min = if id == CANVAS { egui::vec2(200.0, 120.0) } else { egui::vec2(PANEL_MIN_WIDTH, 80.0) };
|
||||
let min =
|
||||
if id == CANVAS { egui::vec2(200.0 + crate::toolbar::docked_width(&self.app.ui), 120.0) } else { egui::vec2(PANEL_MIN_WIDTH, 80.0) };
|
||||
craft_ui::docking::PanelLimits { min, ..Default::default() }
|
||||
}
|
||||
|
||||
|
||||
@@ -274,7 +274,7 @@ fn switching_workspace_during_a_tab_drag_keeps_the_new_workspace_layout() {
|
||||
#[test]
|
||||
fn the_tools_panel_floats_by_its_title_bar_and_docks_on_the_left_edge() {
|
||||
let mut h = Harness::new();
|
||||
let zone = h.ctx.data(|d| d.get_temp::<Rect>(crate::tools_float::tools_zone_id())).expect("the Tools panel's docking strip");
|
||||
let zone = docked_tools(&h);
|
||||
let title = zone.min + vec2(20.0, 12.0);
|
||||
h.drag(title, pos2(600.0, 400.0));
|
||||
let [x, y] = h.app.ui.toolbar_pos.expect("the Tools panel floats");
|
||||
@@ -284,6 +284,244 @@ fn the_tools_panel_floats_by_its_title_bar_and_docks_on_the_left_edge() {
|
||||
assert_eq!(h.app.ui.toolbar_pos, None, "dropped on the left edge, it docks");
|
||||
}
|
||||
|
||||
/// The docked Tools panel as last drawn.
|
||||
fn docked_tools(h: &Harness) -> Rect {
|
||||
h.ctx.data(|d| d.get_temp::<Rect>(crate::toolbar::docked_rect_id())).expect("the docked Tools panel")
|
||||
}
|
||||
|
||||
fn settle(h: &mut Harness) {
|
||||
for _ in 0..3 {
|
||||
h.frame(vec![]);
|
||||
}
|
||||
}
|
||||
|
||||
fn keep_tools_beside_canvas(h: &mut Harness, on: bool) {
|
||||
assert_eq!(h.app.run("window.toolsBesideCanvas", json!({"on": on})).unwrap(), json!(on));
|
||||
settle(h);
|
||||
}
|
||||
|
||||
/// Where docked Tools sits with a left-side dock: `Some(true)` between the dock and the canvas,
|
||||
/// `Some(false)` at the window's edge outside the dock.
|
||||
fn tools_place(h: &Harness) -> Option<bool> {
|
||||
let tools = docked_tools(h);
|
||||
let layers = h.tab("layers")?;
|
||||
let canvas = h.app.canvas_rect?;
|
||||
if layers.right() <= tools.left() && tools.right() <= canvas.left() && canvas.left() - tools.right() < 40.0 {
|
||||
Some(true)
|
||||
} else if tools.left() < 10.0 && tools.right() <= layers.left() {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Off by default: docked Tools stays at the window's edge, outside a left-side dock, and the
|
||||
/// setting moves it between the dock and the canvas and back.
|
||||
#[test]
|
||||
fn docked_tools_keep_the_window_edge_unless_kept_beside_the_canvas() {
|
||||
let mut h = Harness::new();
|
||||
assert!(!h.app.ui.tools_beside_canvas);
|
||||
assert_eq!(crate::menus::checked(&h.app, "window.toolsBesideCanvas", &json!({})), Some(false));
|
||||
h.app.run("window.panel.dock", json!({"panel": "layers", "side": "Start"})).unwrap();
|
||||
settle(&mut h);
|
||||
assert_eq!(tools_place(&h), Some(false), "Tools, then the left dock, then the canvas");
|
||||
assert_eq!(crate::toolbar::docked_width(&h.app.ui), 0.0, "the canvas reserves no room for Tools");
|
||||
keep_tools_beside_canvas(&mut h, true);
|
||||
assert_eq!(crate::menus::checked(&h.app, "window.toolsBesideCanvas", &json!({})), Some(true));
|
||||
assert_eq!(tools_place(&h), Some(true), "the left dock, then Tools, then the canvas");
|
||||
keep_tools_beside_canvas(&mut h, false);
|
||||
assert_eq!(tools_place(&h), Some(false), "back at the window's edge");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keep_tools_beside_the_canvas_toggles_and_rejects_a_value_that_is_not_true_or_false() {
|
||||
let mut h = Harness::new();
|
||||
assert_eq!(h.app.run("window.toolsBesideCanvas", json!({})).unwrap(), json!(true));
|
||||
assert_eq!(h.app.run("window.toolsBesideCanvas", json!({})).unwrap(), json!(false));
|
||||
assert!(h.app.run("window.toolsBesideCanvas", json!({"on": "yes"})).is_err());
|
||||
assert!(!h.app.ui.tools_beside_canvas, "a bad value changes nothing");
|
||||
}
|
||||
|
||||
/// Off, only the window's edge docks a floating Tools panel; on, the canvas's edge does too, and
|
||||
/// both dock it beside the canvas.
|
||||
#[test]
|
||||
fn tools_redock_at_the_targets_of_the_chosen_placement() {
|
||||
for beside in [false, true] {
|
||||
let mut h = Harness::new();
|
||||
keep_tools_beside_canvas(&mut h, beside);
|
||||
h.app.run("window.panel.dock", json!({"panel": "layers", "side": "Start"})).unwrap();
|
||||
for at_window_edge in [false, true] {
|
||||
h.app.run("window.panel.float", json!({"panel": "tools", "x": 600, "y": 300})).unwrap();
|
||||
settle(&mut h);
|
||||
let canvas = h.app.canvas_rect.unwrap();
|
||||
let canvas_edge = h.ctx.data(|d| d.get_temp::<Rect>(crate::tools_float::tools_zone_id()));
|
||||
assert_eq!(canvas_edge.is_some(), beside, "a canvas-edge target only while kept beside the canvas");
|
||||
let canvas_edge = canvas_edge.map_or(pos2(canvas.left() + 12.0, canvas.center().y), |r| r.center());
|
||||
let target = if at_window_edge { pos2(12.0, canvas.center().y) } else { canvas_edge };
|
||||
h.drag(pos2(620.0, 307.0), target);
|
||||
if !beside && !at_window_edge {
|
||||
assert!(h.app.ui.toolbar_pos.is_some(), "the canvas's edge does not dock Tools while the setting is off");
|
||||
continue;
|
||||
}
|
||||
assert_eq!(h.app.ui.toolbar_pos, None, "beside: {beside}, at the window edge: {at_window_edge}");
|
||||
settle(&mut h);
|
||||
assert_eq!(tools_place(&h), Some(beside), "beside: {beside}, at the window edge: {at_window_edge}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Turning the setting off while Tools floats forgets the canvas-edge target, and turning it on
|
||||
/// leaves a floating Tools panel floating.
|
||||
#[test]
|
||||
fn toggling_while_tools_floats_keeps_it_floating_and_drops_the_canvas_target() {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("window.panel.dock", json!({"panel": "layers", "side": "Start"})).unwrap();
|
||||
keep_tools_beside_canvas(&mut h, true);
|
||||
h.app.run("window.panel.float", json!({"panel": "tools", "x": 600, "y": 300})).unwrap();
|
||||
settle(&mut h);
|
||||
let zone = h.ctx.data(|d| d.get_temp::<Rect>(crate::tools_float::tools_zone_id())).expect("the canvas-edge target");
|
||||
keep_tools_beside_canvas(&mut h, false);
|
||||
assert!(h.app.ui.toolbar_pos.is_some(), "turning it off leaves Tools floating");
|
||||
assert!(h.ctx.data(|d| d.get_temp::<Rect>(crate::tools_float::tools_zone_id())).is_none(), "the canvas-edge target is gone");
|
||||
h.drag(pos2(620.0, 307.0), zone.center());
|
||||
assert!(h.app.ui.toolbar_pos.is_some(), "the old canvas-edge strip docks nothing");
|
||||
let [x, y] = h.app.ui.toolbar_pos.unwrap();
|
||||
keep_tools_beside_canvas(&mut h, true);
|
||||
assert_eq!(h.app.ui.toolbar_pos, Some([x, y]), "turning it on leaves Tools floating where it was");
|
||||
keep_tools_beside_canvas(&mut h, false);
|
||||
h.drag(pos2(x + 20.0, y + 7.0), pos2(12.0, zone.center().y));
|
||||
assert_eq!(h.app.ui.toolbar_pos, None);
|
||||
settle(&mut h);
|
||||
assert_eq!(tools_place(&h), Some(false));
|
||||
}
|
||||
|
||||
/// The setting is kept with a workspace; state and workspaces saved without it keep Tools at the
|
||||
/// window's edge.
|
||||
#[test]
|
||||
fn keep_tools_beside_the_canvas_is_saved_and_off_in_state_saved_without_it() {
|
||||
let mut saved = serde_json::to_value(crate::state::UiState { tools_beside_canvas: true, ..Default::default() }).unwrap();
|
||||
assert_eq!(saved["tools_beside_canvas"], json!(true));
|
||||
let reloaded: crate::state::UiState = serde_json::from_value(saved.clone()).unwrap();
|
||||
assert!(reloaded.tools_beside_canvas);
|
||||
saved.as_object_mut().unwrap().remove("tools_beside_canvas");
|
||||
let old: crate::state::UiState = serde_json::from_value(saved).unwrap();
|
||||
assert!(!old.tools_beside_canvas && old.toolbar_pos.is_none(), "older state docks Tools at the window's edge");
|
||||
|
||||
let mut h = Harness::new();
|
||||
h.app.run("window.panel.dock", json!({"panel": "layers", "side": "Start"})).unwrap();
|
||||
keep_tools_beside_canvas(&mut h, true);
|
||||
let mut workspace = serde_json::to_value(crate::workspaces::capture(&h.app.ui, "Mine")).unwrap();
|
||||
assert_eq!(workspace["toolsBesideCanvas"], json!(true));
|
||||
workspace.as_object_mut().unwrap().remove("toolsBesideCanvas");
|
||||
let old: crate::workspaces::Workspace = serde_json::from_value(workspace).unwrap();
|
||||
assert!(!old.tools_beside_canvas, "a workspace saved without it docks Tools at the window's edge");
|
||||
|
||||
h.app.run("window.workspace.new", json!({"name": "Mine"})).unwrap();
|
||||
h.app.run("window.workspace", json!({"name": "Essentials"})).unwrap();
|
||||
settle(&mut h);
|
||||
assert!(!h.app.ui.tools_beside_canvas, "built-in workspaces keep Tools at the window's edge");
|
||||
h.app.run("window.workspace", json!({"name": "Mine"})).unwrap();
|
||||
settle(&mut h);
|
||||
assert!(h.app.ui.tools_beside_canvas);
|
||||
assert_eq!(tools_place(&h), Some(true), "the saved workspace brings Tools back beside the canvas");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tools_follow_left_panels_when_resized_hidden_and_restored() {
|
||||
for (beside, double) in [(false, false), (false, true), (true, false), (true, true)] {
|
||||
let mut h = Harness::new();
|
||||
keep_tools_beside_canvas(&mut h, beside);
|
||||
h.app.run("window.panel.dock", json!({"panel": "layers", "side": "Start"})).unwrap();
|
||||
gesture(&mut h.app, Action::ResizeColumn { panel: "layers".into(), width: 430.0 }).unwrap();
|
||||
h.app.ui.toolbar_double = double;
|
||||
let width = if double { 80.0 } else { 48.0 };
|
||||
for hidden in [false, true, false] {
|
||||
if hidden == h.app.ui.dock {
|
||||
h.app.run("window.panels", json!({})).unwrap();
|
||||
}
|
||||
settle(&mut h);
|
||||
let tools = docked_tools(&h);
|
||||
assert!((tools.width() - (width + 6.0)).abs() < 1.0, "{} columns: {tools:?}", if double { 2 } else { 1 });
|
||||
if hidden {
|
||||
assert!(h.tab("layers").is_none());
|
||||
assert!(tools.left() < 10.0);
|
||||
} else {
|
||||
assert_eq!(tools_place(&h), Some(beside), "beside: {beside}, double: {double}");
|
||||
assert!(h.tab("layers").unwrap().width() > 0.0);
|
||||
}
|
||||
let canvas = h.app.canvas_rect.unwrap();
|
||||
assert!(canvas.left() - tools.left() >= width, "Tools has its own room: {tools:?}, {canvas:?}");
|
||||
assert!(canvas.width() > 200.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A layout with panels above or below the canvas: the full-height strip along the window's left
|
||||
/// edge docks Tools in both placements; off, Tools runs the window's height beside those panels,
|
||||
/// and on, it stays in the canvas's row.
|
||||
#[test]
|
||||
fn tools_redock_at_window_edge_above_and_below_canvas() {
|
||||
for (beside, above) in [(false, true), (false, false), (true, true), (true, false)] {
|
||||
let mut h = Harness::new();
|
||||
keep_tools_beside_canvas(&mut h, beside);
|
||||
let tabs = |panel: &str| Box::new(Node::Tabs { panels: vec![panel.to_string()], active: 0 });
|
||||
h.app.ui.docking = Some(Layout {
|
||||
root: Some(Node::Split {
|
||||
axis: craft_ui::layout::SplitAxis::Vertical,
|
||||
size: craft_ui::layout::SplitSize::FixedFirst(180.0),
|
||||
first: tabs("layers"),
|
||||
second: Box::new(Node::Split {
|
||||
axis: craft_ui::layout::SplitAxis::Vertical,
|
||||
size: craft_ui::layout::SplitSize::FixedSecond(180.0),
|
||||
first: tabs(CANVAS),
|
||||
second: tabs("properties"),
|
||||
}),
|
||||
}),
|
||||
floating: vec![],
|
||||
});
|
||||
assert!(valid(&h.layout()));
|
||||
h.app.run("window.panel.float", json!({"panel": "tools", "x": 600, "y": 300})).unwrap();
|
||||
for _ in 0..3 {
|
||||
h.frame(vec![]);
|
||||
}
|
||||
let panel = h.tab(if above { "layers" } else { "properties" }).unwrap();
|
||||
let canvas = h.app.canvas_rect.unwrap();
|
||||
assert!(panel.center().y < canvas.top() || panel.center().y > canvas.bottom());
|
||||
h.drag(pos2(620.0, 307.0), pos2(12.0, panel.center().y));
|
||||
assert_eq!(h.app.ui.toolbar_pos, None, "window-edge drop, beside: {beside}, above the canvas: {above}");
|
||||
settle(&mut h);
|
||||
let tools = docked_tools(&h);
|
||||
let panel = h.tab(if above { "layers" } else { "properties" }).unwrap();
|
||||
assert!(tools.left() < 10.0 && tools.right() <= panel.left() + 60.0, "{tools:?}");
|
||||
match (beside, above) {
|
||||
(false, true) => assert!(tools.top() <= panel.top(), "Tools runs past the panel above: {tools:?}, {panel:?}"),
|
||||
(false, false) => assert!(tools.bottom() >= panel.bottom(), "Tools runs past the panel below: {tools:?}, {panel:?}"),
|
||||
(true, true) => assert!(tools.top() >= panel.bottom(), "Tools stays below the panel above: {tools:?}, {panel:?}"),
|
||||
(true, false) => assert!(tools.bottom() <= panel.top(), "Tools stays above the panel below: {tools:?}, {panel:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "manual visual check; set LEFT_TOOLS_SCREENSHOTS to an existing harness directory"]
|
||||
fn capture_tools_beside_left_panels() {
|
||||
let dir = std::path::PathBuf::from(std::env::var_os("LEFT_TOOLS_SCREENSHOTS").unwrap());
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
|
||||
app.run("window.toolsBesideCanvas", json!({"on": true})).unwrap();
|
||||
app.run("window.panel.dock", json!({"panel": "layers", "side": "Start"})).unwrap();
|
||||
let mut h = egui_kittest::Harness::builder()
|
||||
.with_size(vec2(1200.0, 800.0))
|
||||
.with_accessibility_check(false)
|
||||
.wgpu()
|
||||
.build_ui_state(|ui, app: &mut VectorcraftApp| app.ui(ui), app);
|
||||
h.run_steps(6);
|
||||
h.render().unwrap().save(dir.join("tools-inside-left-dock.png")).unwrap();
|
||||
h.state_mut().ui.toolbar_double = true;
|
||||
h.run_steps(3);
|
||||
h.render().unwrap().save(dir.join("tools-inside-left-dock-double.png")).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_library_panel_opens_renames_and_closes() {
|
||||
let mut h = Harness::new();
|
||||
|
||||
@@ -460,9 +460,14 @@ pub struct UiState {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub dock_width: Option<f32>,
|
||||
/// The Tools panel floats with its top-left corner here (dragged out by its title bar); `None`:
|
||||
/// docked at the window's left edge.
|
||||
/// docked at the window's left edge, or beside the canvas with [`Self::tools_beside_canvas`].
|
||||
#[serde(default)]
|
||||
pub toolbar_pos: Option<[f32; 2]>,
|
||||
/// The docked Tools panel sits between the left-side panels and the canvas instead of at the
|
||||
/// window's left edge (Window › Toolbars › Keep Tools Beside the Canvas,
|
||||
/// `window.toolsBesideCanvas`). Off by default, and off in state saved without it.
|
||||
#[serde(default)]
|
||||
pub tools_beside_canvas: bool,
|
||||
pub status_bar: bool,
|
||||
pub dock: bool,
|
||||
/// The bars that Tab (`window.dock`) hid, which the next Tab shows again; none when Tab has not
|
||||
@@ -992,6 +997,7 @@ impl Default for UiState {
|
||||
docking_generation: 0,
|
||||
dock_width: None,
|
||||
toolbar_pos: None,
|
||||
tools_beside_canvas: false,
|
||||
status_bar: true,
|
||||
dock: true,
|
||||
tab_hid: None,
|
||||
|
||||
@@ -95,24 +95,50 @@ pub fn remember(app: &mut VectorcraftApp, id: &str) {
|
||||
crate::custom_toolbars::remember(&mut app.ui, id);
|
||||
}
|
||||
|
||||
/// The docked Tools panel's rect as last laid out; removed while it floats.
|
||||
pub(crate) fn docked_rect_id() -> egui::Id {
|
||||
egui::Id::unique("toolbar-docked-rect")
|
||||
}
|
||||
|
||||
fn panel_width(ui: &crate::state::UiState) -> f32 {
|
||||
if ui.toolbar_double { 80.0 } else { WIDTH }
|
||||
}
|
||||
|
||||
/// Space reserved inside the protected canvas panel for a visible Tools panel docked beside the
|
||||
/// canvas (`UiState::tools_beside_canvas`); none while Tools docks at the window's edge.
|
||||
pub(crate) fn docked_width(ui: &crate::state::UiState) -> f32 {
|
||||
if ui.tools_beside_canvas && ui.toolbar && ui.screen_mode < 3 && ui.toolbar_pos.is_none() { panel_width(ui) + 6.0 } else { 0.0 }
|
||||
}
|
||||
|
||||
/// With `UiState::tools_beside_canvas` on, the canvas-edge redocking target, registered even
|
||||
/// while Tools floats, and the docked panel, which follows the canvas's left edge as dock columns
|
||||
/// resize, collapse or hide. With it off, `app.rs` draws Tools before the docking layout.
|
||||
pub(crate) fn show_beside_canvas(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
if app.ui.tools_beside_canvas && app.ui.toolbar && app.ui.screen_mode < 3 {
|
||||
crate::tools_float::set_dock_zones(ui.ctx(), ui.available_rect_before_wrap(), WIDTH);
|
||||
if app.ui.toolbar_pos.is_none() {
|
||||
show(app, ui);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
// The user's choice alone: a window too short for the tools scrolls them.
|
||||
let cols = if app.ui.toolbar_double { 2 } else { 1 };
|
||||
// Two 36 pt buttons with 2 pt either side inside the border and separator line (at 76 they
|
||||
// overran the right border).
|
||||
let w = if cols == 2 { 80.0 } else { WIDTH };
|
||||
// Where the panel docks: along the window's left edge, under the bars.
|
||||
let edge = ui.available_rect_before_wrap();
|
||||
ui.ctx().data_mut(|d| d.insert_temp(crate::tools_float::tools_zone_id(), egui::Rect::from_min_size(edge.min, vec2(WIDTH, edge.height()))));
|
||||
let w = panel_width(&app.ui);
|
||||
match app.ui.toolbar_pos {
|
||||
None => {
|
||||
let margin = egui::Margin { left: 6, right: 0, top: 6, bottom: 6 };
|
||||
egui::Panel::left("toolbar").resizable(false).exact_size(w + 6.0).frame(egui::Frame::NONE).show(ui, |ui| {
|
||||
let docked = egui::Panel::left("toolbar").resizable(false).exact_size(w + 6.0).frame(egui::Frame::NONE).show(ui, |ui| {
|
||||
crate::theme::card(ui, &t, margin, |ui| body(app, ui, cols, None));
|
||||
});
|
||||
ui.ctx().data_mut(|d| d.insert_temp(docked_rect_id(), docked.response.rect));
|
||||
}
|
||||
Some(pos) => {
|
||||
ui.ctx().data_mut(|d| d.remove::<egui::Rect>(docked_rect_id()));
|
||||
// Floating, kept inside the window, its tools scrolling when they don't fit under it.
|
||||
let ctx = ui.ctx().clone();
|
||||
let id = egui::Id::unique("toolbar-floating");
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
//! The Tools panel floats by its title bar and docks again on the window's left edge
|
||||
//! ([`crate::toolbar`]). It stays outside the docking layout: dragging its title bar out of the
|
||||
//! docked panel floats it under the pointer, a floating panel's title bar moves it, and a release
|
||||
//! over the strip along the window's left edge (lit up) docks it there. Escape or losing the
|
||||
//! window's focus puts it back where the drag began.
|
||||
//! over the strip along the window's left edge (lit up) docks it there. With Keep Tools Beside the
|
||||
//! Canvas on (`UiState::tools_beside_canvas`), a release over the canvas's left edge docks it too,
|
||||
//! and either release docks it beside the canvas. Escape or losing the window's focus puts it back
|
||||
//! where the drag began.
|
||||
//!
|
||||
//! `window.panel.float {panel: "tools"}` / `window.panel.dock {panel: "tools"}` do the same for
|
||||
//! agents. Its position is kept with the preferences and in user workspaces, and a position saved
|
||||
@@ -41,11 +43,35 @@ pub(crate) fn escape_cancelled_move(ctx: &egui::Context) -> bool {
|
||||
ctx.data(|d| d.get_temp::<u64>(escape_cancel_id())) == Some(ctx.cumulative_frame_nr_for(ctx.viewport_id()))
|
||||
}
|
||||
|
||||
/// The strip along the window's left edge where the Tools panel docks, as last laid out.
|
||||
/// The strip along the canvas's left edge where the Tools panel docks while Keep Tools Beside the
|
||||
/// Canvas is on, as last laid out.
|
||||
pub(crate) fn tools_zone_id() -> Id {
|
||||
Id::unique("tools-float-zone")
|
||||
}
|
||||
|
||||
fn window_zone_id() -> Id {
|
||||
Id::unique("tools-float-window-zone")
|
||||
}
|
||||
|
||||
/// Accept a drop next to panels docked left, independently of the window-edge gesture.
|
||||
pub(crate) fn set_dock_zones(ctx: &egui::Context, canvas: Rect, width: f32) {
|
||||
ctx.data_mut(|d| {
|
||||
d.insert_temp(tools_zone_id(), Rect::from_min_size(canvas.min, vec2(width, canvas.height())));
|
||||
});
|
||||
}
|
||||
|
||||
/// Forget the canvas-edge target, so a strip laid out while Keep Tools Beside the Canvas was on
|
||||
/// does not dock Tools after it is turned off.
|
||||
pub(crate) fn clear_canvas_dock_zone(ctx: &egui::Context) {
|
||||
ctx.data_mut(|d| d.remove::<Rect>(tools_zone_id()));
|
||||
}
|
||||
|
||||
/// The legacy 48-point window-edge target spans the full layout below the bars, including
|
||||
/// panels above and below the canvas. Its width does not follow the toolbar's column count.
|
||||
pub(crate) fn set_window_dock_zone(ctx: &egui::Context, window: Rect) {
|
||||
ctx.data_mut(|d| d.insert_temp(window_zone_id(), Rect::from_min_size(window.min, vec2(48.0, window.height()))));
|
||||
}
|
||||
|
||||
/// `pos` moved so a panel of `size` stays inside `screen` (a position saved on a bigger window).
|
||||
pub fn clamp(pos: [f32; 2], size: Vec2, screen: Rect) -> Pos2 {
|
||||
let [x, y] = pos;
|
||||
@@ -90,9 +116,13 @@ pub(crate) fn drag_tools(app: &mut VectorcraftApp, ctx: &egui::Context, title: &
|
||||
}
|
||||
}
|
||||
|
||||
/// The docking strip under `at`, if any.
|
||||
fn drop_zone(ctx: &egui::Context, at: Pos2) -> Option<Rect> {
|
||||
ctx.data(|d| d.get_temp::<Rect>(tools_zone_id())).filter(|r| r.contains(at))
|
||||
/// The docking strip under `at`, if any: the window's left edge, and with `beside_canvas` the
|
||||
/// canvas's left edge as well.
|
||||
fn drop_zone(ctx: &egui::Context, at: Pos2, beside_canvas: bool) -> Option<Rect> {
|
||||
ctx.data(|d| {
|
||||
let canvas = d.get_temp::<Rect>(tools_zone_id()).filter(|_| beside_canvas);
|
||||
[canvas, d.get_temp::<Rect>(window_zone_id())].into_iter().flatten().find(|r| r.contains(at))
|
||||
})
|
||||
}
|
||||
|
||||
fn highlight(ctx: &egui::Context, zone: Rect) {
|
||||
@@ -116,7 +146,7 @@ pub fn track(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
return;
|
||||
}
|
||||
let at = ctx.input(|i| i.pointer.interact_pos());
|
||||
let zone = at.filter(|at| at.distance(m.from) > TRAVEL).and_then(|at| drop_zone(ctx, at));
|
||||
let zone = at.filter(|at| at.distance(m.from) > TRAVEL).and_then(|at| drop_zone(ctx, at, app.ui.tools_beside_canvas));
|
||||
if ctx.input(|i| i.pointer.primary_down()) {
|
||||
if let Some(p) = at.map(|at| at - m.grab)
|
||||
&& p.x.is_finite()
|
||||
|
||||
@@ -57,6 +57,9 @@ pub struct Workspace {
|
||||
pub docking_collapsed: std::collections::BTreeSet<String>,
|
||||
/// Where the Tools panel floats (built-in workspaces: docked).
|
||||
pub toolbar_pos: Option<[f32; 2]>,
|
||||
/// The docked Tools panel sits beside the canvas, inside any left-side panels (built-in
|
||||
/// workspaces and workspaces saved without it: at the window's left edge).
|
||||
pub tools_beside_canvas: bool,
|
||||
/// Main dock column width in screen points while `docking` is none (#1753).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub dock_width: Option<f32>,
|
||||
@@ -197,6 +200,7 @@ pub fn capture(ui: &UiState, name: &str) -> Workspace {
|
||||
docking_icons: Default::default(),
|
||||
docking_collapsed: Default::default(),
|
||||
toolbar_pos: ui.toolbar_pos,
|
||||
tools_beside_canvas: ui.tools_beside_canvas,
|
||||
dock_width: ui.dock_width,
|
||||
toolbars: ui.custom_toolbars.clone(),
|
||||
}
|
||||
@@ -223,6 +227,7 @@ pub fn apply(ui: &mut UiState, w: &Workspace) {
|
||||
ui.docking_hidden = w.docking_hidden.clone();
|
||||
ui.docking_hidden_groups = w.docking_hidden_groups.clone();
|
||||
ui.toolbar_pos = w.toolbar_pos;
|
||||
ui.tools_beside_canvas = w.tools_beside_canvas;
|
||||
ui.dock_width = w.dock_width;
|
||||
crate::state::sanitize_toolbar_pos(&mut ui.toolbar_pos);
|
||||
ui.custom_toolbars = w.toolbars.clone();
|
||||
|
||||
@@ -429,7 +429,9 @@ below). Dragging a tab, a group's drag bar or an icon out of the dock and droppi
|
||||
floats it; dropping it on a group's tabs, on a group's upper or lower edge or beside a column docks it there. On
|
||||
the desktop each floating window is an OS window of its own; on the web and on Wayland it floats inside the app
|
||||
window. `ui.toolbar_pos` in `ui.inspect` is the Tools panel's corner (null: docked); `window.panel.float` and
|
||||
`window.panel.dock` with `panel: "tools"` float it at `x`, `y` or dock it on the window's left edge.
|
||||
`window.panel.dock` with `panel: "tools"` float it at `x`, `y` or dock it on the window's left edge. With
|
||||
`window.toolsBesideCanvas {on?}` (Window › Toolbars › Keep Tools Beside the Canvas, off by default; `ui.tools_beside_canvas`)
|
||||
the docked Tools panel sits between left-side panels and the canvas, and `window.panel.dock` docks it there.
|
||||
|
||||
Flatten Transparency: `ui.flattenTransparencyDialog` opens the `flattenTransparency` dialog for the selection
|
||||
(fields `preset`: a preset name, setting it loads that preset's options; the option keys of
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# Gaps
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-03 · **Change:** minor (Tools stays beside the canvas with left-side panels; new gradients follow document color mode; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
Docked Tools stays beside the canvas, inside any left-side panels. Both the canvas edge and the window edge accept floating Tools. Existing saved workspace fields are reused. This is a requested layout choice; [toolbar documentation](https://helpx.adobe.com/illustrator/using/tools.html) and [workspace documentation](https://helpx.adobe.com/illustrator/using/customizing-workspace.html) describe movability and docking, without establishing this exact relative placement. Sidebar resizing (#5348) remains separate. Scores and estimates are unchanged.
|
||||
> **QoL update (2026-11-02):** Selection-tool marquees offer Touching (default), Fully Enclosed and Drag Direction in Preferences › Selection & Anchor Display. Drag Direction uses screen left/right, including rotated views, retaining its last rule within three horizontal screen pixels of the press. E reverses the rule for the current drag. A solid enclosed outline or dashed touching outline and a translated label show the active rule. The selection region remains aligned to the document axes. This is a VectorCraft addition; scores and estimates are unchanged.
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-02 · **Change:** minor (Selection QoL: marquee modes and visible feedback, #5313; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
@@ -41,6 +44,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | minor | Docked Tools stays between left-side panels and the canvas; redocking accepts canvas and window edges. Requested layout choice; scores and estimates unchanged. |
|
||||
| 2026-11-02 | minor | Selection QoL: saved marquee modes, screen drag direction with a 3 px dead zone, and translated mode/E feedback (#5313); VectorCraft addition, scores and estimates unchanged. |
|
||||
| 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. |
|
||||
| 2026-11-05 | minor | G15: long open strokes with an undashed width profile are outlined near the view while keeping profile positions on the full path; a 200,000-segment outline took 59.95 ms before and 8.44 ms after in local median runs. Closed and dashed profile strokes remain whole; estimate unchanged. |
|
||||
|
||||
+15
@@ -1693,6 +1693,21 @@ changes neither option. Behavior reference: [View artwork](https://helpx.adobe.c
|
||||
{"name":"invoke_menu","arguments":{"command":"ui.navigatorPanelOptions","params":{"color":"Light Blue","solidDashes":true}}}
|
||||
```
|
||||
|
||||
## Tools panel placement
|
||||
|
||||
By default the docked Tools panel sits at the window's left edge, outside any panels docked on the left.
|
||||
`window.toolsBesideCanvas {on?}` (Window › Toolbars › Keep Tools Beside the Canvas, remote mode, `invoke_menu`)
|
||||
docks it between those panels and the canvas instead (`on: true`), back at the window's edge (`on: false`), or
|
||||
toggles it (`on` omitted or null), and answers the new state. Any other value of `on` is an error and changes
|
||||
nothing. With the setting on, a floating Tools panel docks when it is dropped on the canvas's left edge as well as
|
||||
on the window's left edge, and `window.panel.dock {panel: "tools"}` docks it beside the canvas. A floating Tools
|
||||
panel stays floating when the setting changes. The setting is VectorCraft's own layout option, off by default; it is
|
||||
kept with the workspace, and built-in workspaces and workspaces saved without it keep Tools at the window's edge.
|
||||
|
||||
```json
|
||||
{"name":"invoke_menu","arguments":{"command":"window.toolsBesideCanvas","params":{"on":true}}}
|
||||
```
|
||||
|
||||
## Ruler guides
|
||||
|
||||
Ruler guides are numbered in the order they were made. `guide.add {vertical, pos, artboard?}` makes one (the x of a
|
||||
|
||||
@@ -32,6 +32,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | minor | Docked Tools stays between left-side panels and the canvas; redocking accepts canvas and window edges. Requested layout choice; scores and estimates unchanged. |
|
||||
| 2026-11-02 | minor | Selection QoL: saved marquee modes, screen drag direction with a 3 px dead zone, and translated mode/E feedback (#5313); VectorCraft addition, scores and estimates unchanged. |
|
||||
| 2026-11-07 | minor | The canvas keeps selection outlines, anchors and handles between frames, so an idle frame with a large selection redraws them as one mesh; scores and estimates unchanged |
|
||||
| 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. |
|
||||
@@ -366,7 +367,7 @@ what keeps it from beta, not from alpha.
|
||||
| Dimension | % | One agent (h) | Kind | Doc |
|
||||
|---|---:|---:|---|---|
|
||||
| Features (depth, weighted) | 75% | 220–360 | estimated | this file |
|
||||
| UI/UX fidelity (look ~80%, interaction ~45%) | 55% | 70–110 | estimated | [ui-parity.md](ui-parity.md) |
|
||||
| UI/UX fidelity (look ~80%, interaction ~45%) | 55% | 70–110 | estimated | [ui-parity.md](ui-parity.md); Tools sits inside left-side panels beside the canvas (layout choice; no rescoring) |
|
||||
| File formats | 75% | 15–30 | estimated | [file-format-parity.md](file-format-parity.md) |
|
||||
| Hardware (GPU, pen, touch, displays) | 35% | 25–45 | estimated | [hardware-parity.md](hardware-parity.md) |
|
||||
| Localization (the 12 key languages, equally weighted) | 74% | 50–85 | measured from catalogs | [localization-parity.md](localization-parity.md) |
|
||||
@@ -694,6 +695,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- 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.
|
||||
- Window › Toolbars › New Toolbar… and New Toolbar… in the All Tools drawer's menu ask for a name and make a blank toolbar that floats on its own, moved by its title bar and listed under Window › Toolbars, whose item shows or hides it. The title bar's × hides it, and its double arrow shows its tools in one column or two. Its ••• button opens the All Tools drawer beside it, and tools dragged from the drawer fill it. The toolbars are kept with the workspace they were made in: switching workspaces shows that workspace's toolbars, Reset Workspace keeps them, and New Workspace starts the new workspace with a copy of them (<https://helpx.adobe.com/illustrator/using/tools.html>; `window.toolbar.new`, `window.toolbar.custom`, the `toolbar` param of `window.editToolbar` and `toolbar.addTool`/`removeTool`/`reset`; `tools_added_to_a_new_toolbar_survive_a_workspace_reload`).
|
||||
- Window › Toolbars › Manage Toolbars… and Manage Toolbars… in the All Tools drawer's menu open a dialog that lists the toolbars made with New Toolbar: a new name typed for the selected toolbar renames it, + copies it with its tools (with none selected, + makes a blank toolbar), and the trash can deletes it. Each change shows under Window › Toolbars and stays in the workspace (<https://helpx.adobe.com/illustrator/using/tools.html>; `window.toolbar.manage`, `window.toolbar.rename`, `window.toolbar.duplicate`, `window.toolbar.delete`; `manage_toolbars_renames_duplicates_and_deletes`).
|
||||
- Window › Toolbars › Keep Tools Beside the Canvas, a VectorCraft layout option that is off by default, docks the Tools panel between left-side panels and the canvas instead of at the window's left edge; the canvas's left edge then also docks a floating Tools panel, and workspaces keep the setting. Scores are unchanged (`window.toolsBesideCanvas`; `docked_tools_keep_the_window_edge_unless_kept_beside_the_canvas`, `tools_redock_at_the_targets_of_the_chosen_placement`).
|
||||
- Hindi (2026-11-06): Hindi (`hi`): 7,877-row machine-assisted catalog, zero/one singular plurals, desktop font fallback and optional Devanagari craft-fonts fallback; all 55 translation tests pass; native review and a web font remain; scores and estimates unchanged. [Terminology and limitations](localization-hi.md).
|
||||
- Czech render-site labels: desktop/web file filters, diagnostics, suggested preset names and contributor tooltips/plurals now use catalog lookups; gradients have Czech adjective agreement and printer marks have a separate context (PDF/Print and Summary). Search, Home, toolbar/panel tooltips and Preferences already look up their labels. Every complete catalog has rows for these labels and for the Roman and Japanese printer mark styles. Evidence: `i18n::tests::czech_render_site_follow_up`, `i18n::tests::complete_catalogs_translate_render_site_labels`, `dialogs::tests_modal::printer_mark_labels_are_distinct_from_language_names`; see [localization-parity.md](localization-parity.md).
|
||||
- The History panel, Edit › Undo and Edit › Redo translate the names of the steps that apply or redefine a character or paragraph style and of the step that redefines a graphic style: each such step keeps its template (`Apply {name}`, `Redefine {name}`, `Redefine Graphic Style “{name}”`) and the style's name, every complete catalog has a row for each template, and the panel and the menu show the style's name as the document holds it, except the Normal styles' names, which they show in the interface language (`steps_named_after_a_style_list_the_template_around_the_name`, `undo_and_redo_name_a_style_step_around_the_style_name`).
|
||||
|
||||
Reference in New Issue
Block a user