Merge pull request #3281 from gorkematlyn/upstream-ui8

UI.8: collapse dock groups to their tabs
This commit is contained in:
BFlat authored and GitHub committed 2026-10-22 04:13:54 +07:00
commit 0ac1f133b0
34 files changed
+572 -28

No files matched your search

+12 -3
View File
@@ -127,6 +127,7 @@ fn panel_icon(app: &mut VectorcraftApp, ui: &mut Ui, id: &'static str, label: &s
}
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
crate::panel_docking::prune_collapsed(&mut app.ui);
if crate::panel_docking::show(app, ui) {
return;
}
@@ -159,6 +160,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
// It floats from this frame on.
return;
}
// Collapsed to its tabs (a double-click on one).
if app.ui.docking_collapsed.contains(app.ui.dock_tab.info().0) {
return;
}
egui::Frame::NONE.inner_margin(egui::Margin { left: 12, right: 10, top: 10, bottom: 8 }).show(ui, |ui| match active {
DockTab::Properties => {
egui::ScrollArea::vertical()
@@ -230,9 +235,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
});
}
/// The tabbed group's tab strip, showing `active`: a click on a tab shows it, a tab dragged out of
/// the strip floats on its own, and the strip right of the tabs floats the whole group. True when
/// the shown panel floats now.
/// The tabbed group's tab strip, showing `active`: a click on a tab shows it, a double-click
/// collapses the group to its tabs or expands it, a tab dragged out of the strip floats on its own,
/// and the strip right of the tabs floats the whole group. True when the shown panel floats now.
fn tab_strip(app: &mut VectorcraftApp, ui: &mut Ui, active: DockTab) -> bool {
let t = Tokens::get(ui.ctx());
let (strip, _) = ui.allocate_exact_size(vec2(ui.available_width(), 33.0), Sense::hover());
@@ -258,6 +263,10 @@ fn tab_strip(app: &mut VectorcraftApp, ui: &mut Ui, active: DockTab) -> bool {
let id = tab.info().0;
if let Some((from, to)) = tab_reorder_on_release(strip, pointer, resp, &out.responses) {
apply_dock_tab_reorder(app, &tabs, from, to);
} else if resp.double_clicked() {
if let Err(error) = app.run("window.panel.collapse", json!({ "panel": id })) {
app.ui.status = error;
}
} else if resp.clicked() {
app.ui.dock_tab = tab;
}
+1
View File
@@ -583,6 +583,7 @@ pub fn track(app: &mut VectorcraftApp, ctx: &egui::Context) {
app.ui.docking = m.original.docking;
app.ui.docking_hidden = m.original.docking_hidden;
app.ui.docking_icons = m.original.docking_icons;
app.ui.docking_collapsed = m.original.docking_collapsed;
app.ui.floating_panels = m.original.floating_panels;
app.ui.open_panel = m.original.open_panel;
app.ui.dock_tab = m.original.dock_tab;
+4
View File
@@ -4474,6 +4474,7 @@ effect Soft Меко
Activate Panel Активиране на панела
Change Panel Layout Промяна на оформлението на панела
Close Panel Затваряне на панела
Collapse Panel Group Свиване на групата панели
Move Panel Преместване на панела
Group with Групиране с
Resize panels Преоразмеряване на панели
@@ -4484,6 +4485,9 @@ effect Soft Меко
@msg before must name a panel параметърът „преди“ трябва да указва панел
@msg docking action contains an unknown panel or an excessive split path действието по закачане съдържа непознат панел или прекомерен път на разделяне
@msg group must be true or false групата трябва да е истина или лъжа
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} панелът е плаващ; свийте групата му с window.panel.float {panel, collapsed}
@msg panel is not in a dock group панелът не е в група на дока
@msg only a dock group above or below others collapses, not one beside another свива се само група на дока над или под други, не и такава до друга
@msg icon must be true or false icon трябва да е истина или лъжа
@msg icon only docks a panel in the icon column, with no anchor icon закачва панел само в колоната с икони, без anchor
@msg column docks a set as a dock column, with no anchor or icon column закачва набор като колона на дока, без anchor или icon
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -829,6 +829,7 @@
Activate Panel Aktivovat panel
Change Panel Layout Změnit rozložení panelů
Close Panel Zavřít panel
Collapse Panel Group Sbalit skupinu panelů
Move Panel Přesunout panel
Group with Seskupit s
Resize panels Změnit velikost panelů
@@ -4752,6 +4753,9 @@ imageTrace Outline Obrys
@msg before must name a panel before musí určovat panel
@msg docking action contains an unknown panel or an excessive split path akce ukotvení obsahuje neznámý panel nebo příliš dlouhou cestu rozdělení
@msg group must be true or false group musí být true nebo false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} panel je plovoucí; jeho skupinu sbalíte pomocí window.panel.float {panel, collapsed}
@msg panel is not in a dock group panel není ve skupině doku
@msg only a dock group above or below others collapses, not one beside another sbalit lze jen skupinu doku nad nebo pod jinými, ne vedle jiné
@msg icon must be true or false icon musí být true nebo false
@msg icon only docks a panel in the icon column, with no anchor icon jen ukotví panel ve sloupci ikon, bez anchor
@msg column docks a set as a dock column, with no anchor or icon column ukotví sadu jako sloupec doku, bez anchor nebo icon
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -761,6 +761,7 @@
Activate Panel Aktivér panel
Change Panel Layout Skift panelets layout
Close Panel Luk panel
Collapse Panel Group Skjul panelgruppe
Move Panel Flyt panel
Group with Gruppér med
Resize panels Tilpas størrelse på paneler
@@ -4450,6 +4451,9 @@ imageTrace Outline Kontur
@msg before must name a panel before skal angive et panel
@msg docking action contains an unknown panel or an excessive split path dokningshandlingen indeholder et ukendt panel eller en overdreven opdelingssti
@msg group must be true or false group skal være true eller false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} panelet flyder; skjul dets gruppe med window.panel.float {panel, collapsed}
@msg panel is not in a dock group panelet er ikke i en dockgruppe
@msg only a dock group above or below others collapses, not one beside another kun en dockgruppe over eller under andre kan skjules, ikke en ved siden af en anden
@msg icon must be true or false icon skal være true eller false
@msg icon only docks a panel in the icon column, with no anchor icon docker kun et panel i ikonkolonnen, uden anchor
@msg column docks a set as a dock column, with no anchor or icon column docker et sæt som en dockkolonne, uden anchor eller icon
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4554,6 +4554,7 @@ theme Appearance Erscheinungsbild
Activate Panel Bedienfeld aktivieren
Change Panel Layout Bedienfeldanordnung ändern
Close Panel Bedienfeld schließen
Collapse Panel Group Bedienfeldgruppe minimieren
Move Panel Bedienfeld verschieben
Group with Gruppieren mit
Resize panel window Bedienfeldfenster skalieren
@@ -4564,6 +4565,9 @@ theme Appearance Erscheinungsbild
@msg before must name a panel before muss ein Bedienfeld benennen
@msg docking action contains an unknown panel or an excessive split path Die Docking-Aktion enthält ein unbekanntes Bedienfeld oder einen zu langen Teilungspfad
@msg group must be true or false group muss true oder false sein
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} Bedienfeld ist abgelöst; seine Gruppe mit window.panel.float {panel, collapsed} minimieren
@msg panel is not in a dock group Bedienfeld ist in keiner Dock-Gruppe
@msg only a dock group above or below others collapses, not one beside another nur eine Dock-Gruppe über oder unter anderen lässt sich minimieren, nicht eine daneben
@msg icon must be true or false icon muss true oder false sein
@msg icon only docks a panel in the icon column, with no anchor icon dockt ein Bedienfeld nur in der Symbolspalte an, ohne anchor
@msg column docks a set as a dock column, with no anchor or icon column dockt einen Satz als Dock-Spalte an, ohne anchor oder icon
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -2229,6 +2229,7 @@ effect Soft Απαλό
Activate Panel Ενεργοποίηση πάνελ
Change Panel Layout Αλλαγή διάταξης πάνελ
Close Panel Κλείσιμο πάνελ
Collapse Panel Group Σύμπτυξη ομάδας πάνελ
Move Panel Μετακίνηση πάνελ
Group with Ομαδοποίηση με
Resize panels Αλλαγή μεγέθους πάνελ
@@ -4469,6 +4470,9 @@ theme Appearance Εμφάνιση
@msg before must name a panel το before πρέπει να ορίζει πίνακα
@msg docking action contains an unknown panel or an excessive split path η ενέργεια σύνδεσης περιέχει άγνωστο πίνακα ή υπερβολικά βαθιά διαδρομή διαίρεσης
@msg group must be true or false το group πρέπει να είναι true ή false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} Το πάνελ αιωρείται· συμπτύξτε την ομάδα του με window.panel.float {panel, collapsed}
@msg panel is not in a dock group Το πάνελ δεν βρίσκεται σε ομάδα αγκύρωσης
@msg only a dock group above or below others collapses, not one beside another συμπτύσσεται μόνο ομάδα αγκύρωσης πάνω ή κάτω από άλλες, όχι δίπλα σε άλλη
@msg icon must be true or false το icon πρέπει να είναι true ή false
@msg icon only docks a panel in the icon column, with no anchor το icon αγκυρώνει πάνελ μόνο στη στήλη εικονιδίων, χωρίς anchor
@msg column docks a set as a dock column, with no anchor or icon το column αγκυρώνει ένα σύνολο ως στήλη αγκύρωσης, χωρίς anchor ή icon
Can't render this file because it contains an unexpected character in line 2039 and column 19.
+4
View File
@@ -4557,6 +4557,7 @@ axis Both Ambos
Activate Panel Activar panel
Change Panel Layout Cambiar distribución de paneles
Close Panel Cerrar panel
Collapse Panel Group Contraer grupo de paneles
Move Panel Mover panel
Group with Agrupar con
Resize panels Cambiar tamaño de los paneles
@@ -4567,6 +4568,9 @@ axis Both Ambos
@msg before must name a panel before debe indicar un panel
@msg docking action contains an unknown panel or an excessive split path La acción de anclaje contiene un panel desconocido o una ruta de divisiones demasiado larga
@msg group must be true or false group debe ser true o false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} el panel flota; contraiga su grupo con window.panel.float {panel, collapsed}
@msg panel is not in a dock group el panel no está en un grupo del acoplamiento
@msg only a dock group above or below others collapses, not one beside another solo se contrae un grupo del acoplamiento situado encima o debajo de otros, no al lado
@msg icon must be true or false icon debe ser true o false
@msg icon only docks a panel in the icon column, with no anchor icon solo acopla un panel en la columna de iconos, sin anchor
@msg column docks a set as a dock column, with no anchor or icon column acopla un conjunto como columna del acoplamiento, sin anchor ni icon
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -812,6 +812,7 @@
Activate Panel Aktivoi paneeli
Change Panel Layout Muuta paneelien asettelua
Close Panel Sulje paneeli
Collapse Panel Group Kutista paneeliryhmä
Move Panel Siirrä paneelia
Group with Ryhmitä kohteen kanssa
Resize panels Muuta paneelien kokoa
@@ -4661,6 +4662,9 @@ imageTrace Outline Ääriviiva
@msg before must name a panel before-arvon on nimettävä paneeli
@msg docking action contains an unknown panel or an excessive split path telakointitoiminto sisältää tuntemattoman paneelin tai liian pitkän jakopolun
@msg group must be true or false group-arvon on oltava true tai false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} paneeli kelluu; kutista sen ryhmä komennolla window.panel.float {panel, collapsed}
@msg panel is not in a dock group paneeli ei ole telakkaryhmässä
@msg only a dock group above or below others collapses, not one beside another vain muiden ylä- tai alapuolella oleva telakkaryhmä kutistuu, ei toisen vieressä oleva
@msg icon must be true or false icon-arvon on oltava true tai false
@msg icon only docks a panel in the icon column, with no anchor icon telakoi paneelin vain kuvakesarakkeeseen, ilman anchor-arvoa
@msg column docks a set as a dock column, with no anchor or icon column telakoi joukon telakkasarakkeeksi, ilman anchor- tai icon-arvoa
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4569,6 +4569,7 @@ effect Soft Doux
Activate Panel Activer le panneau
Change Panel Layout Modifier la disposition des panneaux
Close Panel Fermer le panneau
Collapse Panel Group Réduire le groupe de panneaux
Move Panel Déplacer le panneau
Group with Regrouper avec
Resize panels Redimensionner les panneaux
@@ -4579,6 +4580,9 @@ effect Soft Doux
@msg before must name a panel before doit désigner un panneau
@msg docking action contains an unknown panel or an excessive split path L’action d’ancrage contient un panneau inconnu ou un chemin de divisions trop long
@msg group must be true or false group doit être true ou false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} le panneau est flottant ; réduisez son groupe avec window.panel.float {panel, collapsed}
@msg panel is not in a dock group le panneau n’est dans aucun groupe du dock
@msg only a dock group above or below others collapses, not one beside another seul un groupe du dock situé au-dessus ou au-dessous d’autres se réduit, pas un groupe à côté
@msg icon must be true or false icon doit être true ou false
@msg icon only docks a panel in the icon column, with no anchor icon ne fait qu’ancrer un panneau dans la colonne d’icônes, sans anchor
@msg column docks a set as a dock column, with no anchor or icon column ancre un ensemble comme colonne du dock, sans anchor ni icon
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -641,6 +641,7 @@
Close All Összes bezárása
Close Library Könyvtár bezárása
Close Panel Panel bezárása
Collapse Panel Group Panelcsoport összecsukása
Close Path Útvonal lezárása
Close paths when ends are within: Útvonalak lezárása, ha a végek távolsága legfeljebb:
Clumped Csomós
@@ -4595,6 +4596,9 @@ imageTrace Outline Körvonal
@msg give the path `id` and the width point `index` add meg az útvonal `id` és a szélességi pont `index` értékét
@msg gradient “{name}” isn't defined in the file: a plain colour stands in for it a színátmenet nincs megadva a fájlban: „{name}”; helyette egyszínű kitöltés áll
@msg group must be true or false a group értéke true vagy false lehet
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} a panel lebeg; a csoportját a window.panel.float {panel, collapsed} paranccsal csukhatja össze
@msg panel is not in a dock group a panel nincs dokkolt csoportban
@msg only a dock group above or below others collapses, not one beside another csak a többi fölötti vagy alatti dokkolt csoport csukható össze, a mellettük lévő nem
@msg icon must be true or false az icon értéke true vagy false lehet
@msg icon only docks a panel in the icon column, with no anchor az icon csak az ikonoszlopba dokkol egy panelt, anchor nélkül
@msg column docks a set as a dock column, with no anchor or icon a column egy készletet dokkoszlopként dokkol, anchor és icon nélkül
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -761,6 +761,7 @@
Activate Panel Aktifkan panel
Change Panel Layout Ubah tata letak panel
Close Panel Tutup panel
Collapse Panel Group Ciutkan grup panel
Move Panel Pindahkan panel
Group with Grupkan dengan
Resize panels Ubah ukuran panel
@@ -4450,6 +4451,9 @@ imageTrace Outline Kontur
@msg before must name a panel before harus menyebutkan panel
@msg docking action contains an unknown panel or an excessive split path tindakan docking berisi panel tidak dikenal atau jalur pemisahan berlebihan
@msg group must be true or false group harus true atau false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} panel mengambang; ciutkan grupnya dengan window.panel.float {panel, collapsed}
@msg panel is not in a dock group panel tidak berada dalam grup dok
@msg only a dock group above or below others collapses, not one beside another hanya grup dok di atas atau di bawah grup lain yang bisa diciutkan, bukan yang berada di sampingnya
@msg icon must be true or false icon harus true atau false
@msg icon only docks a panel in the icon column, with no anchor icon hanya menambatkan panel di kolom ikon, tanpa anchor
@msg column docks a set as a dock column, with no anchor or icon column menambatkan satu set sebagai kolom dok, tanpa anchor atau icon
Can't render this file because it contains an unexpected character in line 3810 and column 19.
+4
View File
@@ -4599,6 +4599,7 @@ effect Regular Normale
Activate Panel Attiva pannello
Change Panel Layout Modifica disposizione dei pannelli
Close Panel Chiudi pannello
Collapse Panel Group Comprimi gruppo di pannelli
Move Panel Sposta pannello
Group with Raggruppa con
Resize panels Ridimensiona pannelli
@@ -4609,6 +4610,9 @@ effect Regular Normale
@msg before must name a panel before deve indicare un pannello
@msg docking action contains an unknown panel or an excessive split path L’azione di ancoraggio contiene un pannello sconosciuto o un percorso di divisioni troppo lungo
@msg group must be true or false group deve essere true o false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} il pannello è mobile; comprimi il suo gruppo con window.panel.float {panel, collapsed}
@msg panel is not in a dock group il pannello non è in un gruppo del dock
@msg only a dock group above or below others collapses, not one beside another si comprime solo un gruppo del dock sopra o sotto altri, non uno accanto
@msg icon must be true or false icon deve essere true o false
@msg icon only docks a panel in the icon column, with no anchor icon ancora un pannello solo nella colonna delle icone, senza anchor
@msg column docks a set as a dock column, with no anchor or icon column ancora un insieme come colonna del dock, senza anchor né icon
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4638,6 +4638,7 @@ effect Vertical 垂直
Activate Panel パネルをアクティブにする
Change Panel Layout パネルの配置を変更
Close Panel パネルを閉じる
Collapse Panel Group パネルグループを折りたたむ
Move Panel パネルを移動
Group with グループ化先
Resize panels パネルのサイズを変更
@@ -4649,6 +4650,9 @@ effect Vertical 垂直
@msg before must name a panel before にパネルを指定してください
@msg docking action contains an unknown panel or an excessive split path ドッキング操作に不明なパネルまたは長すぎる分割パスが含まれています
@msg group must be true or false group は true または false にしてください
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} パネルはフローティング中です。グループは window.panel.float {panel, collapsed} で折りたたんでください
@msg panel is not in a dock group パネルはドックのグループにありません
@msg only a dock group above or below others collapses, not one beside another 折りたためるのは他のグループの上下にあるドックのグループだけで、横に並んだグループは折りたためません
@msg icon must be true or false icon は true または false である必要があります
@msg icon only docks a panel in the icon column, with no anchor icon はパネルをアイコン列にのみドッキングします(anchor なし)
@msg column docks a set as a dock column, with no anchor or icon column はセットをドック列としてドッキングします(anchor・icon なし)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -703,6 +703,7 @@
Activate Panel პანელის გააქტიურება
Change Panel Layout პანელების განლაგების შეცვლა
Close Panel პანელის დახურვა
Collapse Panel Group პანელების ჯგუფის აკეცვა
Move Panel პანელის გადაადგილება
Group with ერთად დაჯგუფება
Resize panels პანელების ზომის შეცვლა
@@ -4201,6 +4202,9 @@ effect Regular ჩვეულებრივი
@msg before must name a panel before უნდა ასახელებდეს პანელს
@msg docking action contains an unknown panel or an excessive split path მიმაგრების მოქმედება შეიცავს უცნობ პანელს ან ზედმეტად გრძელ გაყოფის გზას
@msg group must be true or false group უნდა იყოს true ან false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} პანელი მცურავია; მისი ჯგუფი აკეცეთ window.panel.float {panel, collapsed}-ით
@msg panel is not in a dock group პანელი არ არის დოკის ჯგუფში
@msg only a dock group above or below others collapses, not one beside another იკეცება მხოლოდ სხვების ზემოთ ან ქვემოთ მდებარე დოკის ჯგუფი, არა სხვის გვერდით მდებარე
@msg icon must be true or false icon უნდა იყოს true ან false
@msg icon only docks a panel in the icon column, with no anchor icon პანელს მხოლოდ ხატულების სვეტში ამაგრებს, anchor-ის გარეშე
@msg column docks a set as a dock column, with no anchor or icon column ნაკრებს დოკის სვეტად ამაგრებს, anchor-ის ან icon-ის გარეშე
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4568,6 +4568,7 @@ effect Vertical 세로
Activate Panel 패널 활성화
Change Panel Layout 패널 레이아웃 변경
Close Panel 패널 닫기
Collapse Panel Group 패널 그룹 접기
Move Panel 패널 이동
Group with 함께 그룹화
Resize panels 패널 크기 조절
@@ -4579,6 +4580,9 @@ effect Vertical 세로
@msg before must name a panel before는 패널을 지정해야 합니다
@msg docking action contains an unknown panel or an excessive split path 도킹 동작에 알 수 없는 패널이나 지나치게 많은 분할 경로가 있습니다
@msg group must be true or false group은 true 또는 false여야 합니다
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} 패널이 분리되어 있습니다. 그룹은 window.panel.float {panel, collapsed}로 접으세요
@msg panel is not in a dock group 패널이 도크 그룹에 없습니다
@msg only a dock group above or below others collapses, not one beside another 다른 그룹의 위나 아래에 있는 도크 그룹만 접을 수 있으며, 옆에 있는 그룹은 접을 수 없습니다
@msg icon must be true or false icon은 true 또는 false여야 합니다
@msg icon only docks a panel in the icon column, with no anchor icon은 anchor 없이 패널을 아이콘 열에만 도킹합니다
@msg column docks a set as a dock column, with no anchor or icon column은 anchor나 icon 없이 세트를 도크 열로 도킹합니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -775,6 +775,7 @@
Activate Panel Paneel activeren
Change Panel Layout Paneelindeling wijzigen
Close Panel Paneel sluiten
Collapse Panel Group Paneelgroep samenvouwen
Move Panel Paneel verplaatsen
Group with Groeperen met
Resize panels Panelen van formaat wijzigen
@@ -4462,6 +4463,9 @@ imageTrace Outline Contour
@msg before must name a panel before moet een paneel benoemen
@msg docking action contains an unknown panel or an excessive split path dokactie bevat een onbekend paneel of een overmatig splitspad
@msg group must be true or false group moet true of false zijn
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} paneel zweeft; vouw de groep samen met window.panel.float {panel, collapsed}
@msg panel is not in a dock group paneel staat niet in een dockgroep
@msg only a dock group above or below others collapses, not one beside another alleen een dockgroep boven of onder andere vouwt samen, niet een ernaast
@msg icon must be true or false icon moet true of false zijn
@msg icon only docks a panel in the icon column, with no anchor icon dockt een paneel alleen in de pictogramkolom, zonder anchor
@msg column docks a set as a dock column, with no anchor or icon column dockt een set als dockkolom, zonder anchor of icon
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -770,6 +770,7 @@
Activate Panel Aktywuj panel
Change Panel Layout Zmień układ paneli
Close Panel Zamknij panel
Collapse Panel Group Zwiń grupę paneli
Move Panel Przesuń panel
Group with Grupuj z
Resize panels Zmień rozmiar paneli
@@ -4580,6 +4581,9 @@ imageTrace Outline Kontur
@msg icon must be true or false icon musi mieć wartość true lub false
@msg icon only docks a panel in the icon column, with no anchor icon tylko dokuje panel w kolumnie ikon, bez anchor
@msg column docks a set as a dock column, with no anchor or icon column dokuje zestaw jako kolumnę doku, bez anchor ani icon
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} panel jest pływający; zwiń jego grupę za pomocą window.panel.float {panel, collapsed}
@msg panel is not in a dock group panel nie należy do żadnej grupy doku
@msg only a dock group above or below others collapses, not one beside another zwinąć można tylko grupę doku nad lub pod innymi, a nie obok innej
@msg move requires a destination panel przeniesienie wymaga panelu docelowego
@msg move requires an existing destination panel przeniesienie wymaga istniejącego panelu docelowego
@msg onto must name a floating panel onto musi wskazywać panel pływający
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -3056,6 +3056,7 @@ imageTrace Outline Contorno
Activate Panel Ativar Painel
Change Panel Layout Alterar Layout dos Painéis
Close Panel Fechar Painel
Collapse Panel Group Recolher Grupo de Painéis
Move Panel Mover Painel
Group with Agrupar com
Resize panels Redimensionar painéis
@@ -4021,6 +4022,9 @@ axis Both Ambos
@msg before must name a panel before deve nomear um painel
@msg docking action contains an unknown panel or an excessive split path a ação de encaixe contém um painel desconhecido ou um caminho de divisão excessivo
@msg group must be true or false group deve ser true ou false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} o painel está flutuando; recolha o grupo com window.panel.float {panel, collapsed}
@msg panel is not in a dock group o painel não está em um grupo do encaixe
@msg only a dock group above or below others collapses, not one beside another só um grupo do encaixe acima ou abaixo de outros é recolhido, não um ao lado
@msg icon must be true or false icon deve ser true ou false
@msg icon only docks a panel in the icon column, with no anchor icon apenas encaixa um painel na coluna de ícones, sem anchor
@msg column docks a set as a dock column, with no anchor or icon column encaixa um conjunto como coluna do encaixe, sem anchor nem icon
Can't render this file because it contains an unexpected character in line 6 and column 47.
+4
View File
@@ -4595,6 +4595,7 @@ axis Both Оба
Activate Panel Активировать панель
Change Panel Layout Изменить расположение панелей
Close Panel Закрыть панель
Collapse Panel Group Свернуть группу панелей
Move Panel Переместить панель
Group with Сгруппировать с
Resize panels Изменить размер панелей
@@ -4605,6 +4606,9 @@ axis Both Оба
@msg before must name a panel before должен указывать панель
@msg docking action contains an unknown panel or an excessive split path Действие прикрепления содержит неизвестную панель или слишком длинный путь разделений
@msg group must be true or false group должен иметь значение true или false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} панель плавающая; сверните её группу через window.panel.float {panel, collapsed}
@msg panel is not in a dock group панель не находится в группе дока
@msg only a dock group above or below others collapses, not one beside another свернуть можно только группу дока над или под другими, а не рядом с ними
@msg icon must be true or false icon должен быть true или false
@msg icon only docks a panel in the icon column, with no anchor icon только закрепляет панель в столбце значков, без anchor
@msg column docks a set as a dock column, with no anchor or icon column закрепляет набор как столбец дока, без anchor и icon
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -4480,6 +4480,7 @@ effect Soft Mjuk
Activate Panel Aktivera panel
Change Panel Layout Ändra panellayout
Close Panel Stäng panel
Collapse Panel Group Komprimera panelgrupp
Move Panel Flytta panel
Group with Gruppera med
Resize panels Ändra storlek på paneler
@@ -4490,6 +4491,9 @@ effect Soft Mjuk
@msg before must name a panel before måste ange en panel
@msg docking action contains an unknown panel or an excessive split path dockningsåtgärden innehåller en okänd panel eller en alltför djup delningssökväg
@msg group must be true or false group måste vara true eller false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} panelen flyter; komprimera dess grupp med window.panel.float {panel, collapsed}
@msg panel is not in a dock group panelen ligger inte i en dockningsgrupp
@msg only a dock group above or below others collapses, not one beside another bara en dockningsgrupp ovanför eller under andra komprimeras, inte en bredvid en annan
@msg icon must be true or false icon måste vara true eller false
@msg icon only docks a panel in the icon column, with no anchor icon dockar bara en panel i ikonkolumnen, utan anchor
@msg column docks a set as a dock column, with no anchor or icon column dockar en uppsättning som en dockningskolumn, utan anchor eller icon
Can't render this file because it contains an unexpected character in line 5 and column 25.
+4
View File
@@ -4581,6 +4581,7 @@ axis Both Обидві
Activate Panel Активувати панель
Change Panel Layout Змінити розташування панелей
Close Panel Закрити панель
Collapse Panel Group Згорнути групу панелей
Move Panel Перемістити панель
Group with Згрупувати з
Resize panels Змінити розмір панелей
@@ -4591,6 +4592,9 @@ axis Both Обидві
@msg before must name a panel before має вказувати панель
@msg docking action contains an unknown panel or an excessive split path Дія прикріплення містить невідому панель або надто довгий шлях поділів
@msg group must be true or false group має мати значення true або false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} панель від’єднана; згорніть її групу через window.panel.float {panel, collapsed}
@msg panel is not in a dock group панель не входить до групи дока
@msg only a dock group above or below others collapses, not one beside another згорнути можна лише групу дока над або під іншими, а не поруч з ними
@msg icon must be true or false icon має бути true або false
@msg icon only docks a panel in the icon column, with no anchor icon лише закріплює панель у стовпці значків, без anchor
@msg column docks a set as a dock column, with no anchor or icon column закріплює набір як стовпець дока, без anchor та icon
Can't render this file because it contains an unexpected character in line 733 and column 40.
+4
View File
@@ -554,6 +554,7 @@
Close All Đóng tất cả
Close Library Đóng thư viện
Close Panel Đóng bảng
Collapse Panel Group Thu gọn nhóm bảng
Close Path Đóng đường dẫn
Close paths when ends are within: Đóng đường dẫn khi hai đầu cách nhau trong:
Clumped Dạng cụm
@@ -4244,6 +4245,9 @@ theme Appearance Giao diện
@msg icon must be true or false icon phải là true hoặc false
@msg icon only docks a panel in the icon column, with no anchor icon chỉ neo bảng vào cột biểu tượng, không có anchor
@msg column docks a set as a dock column, with no anchor or icon column neo một bộ thành cột neo, không có anchor hay icon
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} bảng đang nổi; hãy thu gọn nhóm của nó bằng window.panel.float {panel, collapsed}
@msg panel is not in a dock group bảng không thuộc nhóm nào của dock
@msg only a dock group above or below others collapses, not one beside another chỉ thu gọn được nhóm dock nằm trên hoặc dưới các nhóm khác, không phải nhóm nằm cạnh
@msg guides are locked đường căn bị khóa
@msg handle must be in or out handle phải là in hoặc out
@msg image '{key}' has no pixels and was left out ảnh '{key}' không có pixel và bị bỏ qua
Can't render this file because it contains an unexpected character in line 4022 and column 10.
+4
View File
@@ -4274,6 +4274,9 @@ imageTrace Outline 轮廓
@msg before must name a panel before 必须指定面板
@msg docking action contains an unknown panel or an excessive split path 停靠操作包含未知面板或过长的拆分路径
@msg group must be true or false group 必须是 true 或 false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} 面板处于浮动状态;请使用 window.panel.float {panel, collapsed} 折叠其面板组
@msg panel is not in a dock group 面板不在停放组中
@msg only a dock group above or below others collapses, not one beside another 只有位于其他组上方或下方的停放组可以折叠,并排的不行
@msg icon must be true or false icon 必须为 true 或 false
@msg icon only docks a panel in the icon column, with no anchor icon 只会把面板停放到图标栏中,不能带 anchor
@msg column docks a set as a dock column, with no anchor or icon column 将一组面板停放为停放栏,不能带 anchor 或 icon
@@ -4886,6 +4889,7 @@ effect Vertical 垂直
Activate Panel 激活面板
Change Panel Layout 更改面板布局
Close Panel 关闭面板
Collapse Panel Group 折叠面板组
Move Panel 移动面板
Group with 组合到
Resize panels 调整面板大小
Can't render this file because it contains an unexpected character in line 2 and column 73.
+4
View File
@@ -2258,6 +2258,9 @@ file-filter Templates 範本
@msg before must name a panel before 必須指定面板
@msg docking action contains an unknown panel or an excessive split path 停駐動作包含未知的面板或過長的分割路徑
@msg group must be true or false group 必須是 true 或 false
@msg panel floats; collapse its group with window.panel.float {panel, collapsed} 面板處於浮動狀態;請使用 window.panel.float {panel, collapsed} 摺疊其面板群組
@msg panel is not in a dock group 面板不在停駐群組中
@msg only a dock group above or below others collapses, not one beside another 只有位於其他群組上方或下方的停駐群組可以摺疊,並排的不行
@msg icon must be true or false icon 必須為 true 或 false
@msg icon only docks a panel in the icon column, with no anchor icon 只會將面板停駐到圖示欄中,不能帶 anchor
@msg column docks a set as a dock column, with no anchor or icon column 會將一組面板停駐為停駐欄,不能帶 anchor 或 icon
@@ -4692,6 +4695,7 @@ effect Soft 柔和
Activate Panel 啟用面板
Change Panel Layout 變更面板配置
Close Panel 關閉面板
Collapse Panel Group 摺疊面板群組
Move Panel 移動面板
Group with 群組至
Resize panels 調整面板大小
Can't render this file because it contains an unexpected character in line 6 and column 47.
+6
View File
@@ -459,6 +459,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
),
("window.panel.activate", "Activate Panel", "", "{panel} show and select a panel in its current group"),
("window.panel.close", "Close Panel", "", "{panel} hide a panel"),
(
"window.panel.collapse",
"Collapse Panel Group",
"",
"{panel, collapsed?: bool} show the dock group holding a panel as its tabs only (or its panel again), as a double-click on its tab does; the panel becomes the group's shown one. Without `collapsed` it toggles. Floating groups collapse through window.panel.float {panel, collapsed}. Returns {panel, collapsed}",
),
(
"window.panel.float",
"Float Panel",
+216 -17
View File
@@ -12,6 +12,7 @@ struct LegacyDrag {
open_panel: Option<String>,
floating: Vec<crate::state::FloatingPanels>,
icons: std::collections::BTreeSet<String>,
collapsed: std::collections::BTreeSet<String>,
}
#[derive(Clone)]
@@ -19,6 +20,7 @@ struct RootGroupDrag {
panel: String,
layout: Layout<String>,
hidden: std::collections::BTreeMap<String, craft_ui::docking::Location<String>>,
collapsed: std::collections::BTreeSet<String>,
open_panel: Option<String>,
generation: u64,
}
@@ -31,6 +33,7 @@ fn cancel_legacy_drag(app: &mut VectorcraftApp, ctx: &egui::Context) -> bool {
app.ui.floating_panels = previous.floating;
app.ui.docking = previous.layout;
app.ui.docking_icons = previous.icons;
app.ui.docking_collapsed = previous.collapsed;
craft_ui::docking::cancel_drag::<String>(ctx, egui::Id::new("vectorcraft-panel-docking"));
true
} else {
@@ -233,6 +236,9 @@ fn placement(params: &Value) -> Result<Placement<String>, String> {
}
pub(crate) fn command(app: &mut VectorcraftApp, id: &str, params: &Value) -> Option<Result<Value, String>> {
if id == "window.panel.collapse" {
return Some(collapse(app, params));
}
// Docking into the dock's tree or its icon column is the shared layout's, whatever set the
// panel floats in. A dock column is the native sets', so it doesn't combine with either.
let to_icons = params.get("icon").is_some_and(|icon| icon != &Value::Bool(false));
@@ -348,6 +354,16 @@ fn apply_action(app: &mut VectorcraftApp, action: Action<String>) -> Result<Valu
Action::Activate { panel } => Some(panel.clone()),
_ => None,
};
// A panel moved, floated or closed leaves its group: that group isn't collapsed any more, and
// the panel doesn't take the collapse along.
let moved = match &action {
Action::Move { panel, .. }
| Action::OpenAt { panel, .. }
| Action::Open { panel, .. }
| Action::Float { panel, .. }
| Action::Close { panel } => Some(panel.clone()),
_ => None,
};
layout.apply(action).map_err(|error| error.to_string())?;
restore_float_fallback(&mut layout, fallback)?;
if let Some((panel, location)) = returning {
@@ -358,6 +374,9 @@ fn apply_action(app: &mut VectorcraftApp, action: Action<String>) -> Result<Valu
}
app.ui.docking_icons = icons;
app.ui.docking = Some(layout);
if let Some(panel) = moved {
app.ui.docking_collapsed.remove(&panel);
}
if let Some(panel) = activated {
for group in &mut app.ui.floating_panels {
if group.panels.contains(&panel) {
@@ -370,6 +389,130 @@ fn apply_action(app: &mut VectorcraftApp, action: Action<String>) -> Result<Valu
Ok(json!({"layout": app.ui.docking}))
}
/// The panels shown by the dock's groups that can collapse to their tabs: each group of the shared
/// layout's tree that is the whole tree or above or below another (one beside another can't be
/// shorter than it), or the tabbed group of the legacy dock.
fn dock_groups_shown(ui: &crate::state::UiState) -> Vec<String> {
let Some(layout) = &ui.docking else {
return crate::floating::shown_tab(ui).map(|tab| tab.info().0.to_string()).into_iter().collect();
};
let mut out = Vec::new();
// Each node, and whether it may be shortened (the root, or in a top/bottom split).
let mut pending: Vec<(&Node<String>, bool)> = layout.root.iter().map(|root| (root, true)).collect();
while let Some((node, shortens)) = pending.pop() {
match node {
Node::Tabs { panels, active } if shortens => out.extend(panels.get(*active).cloned()),
Node::Split { axis, first, second, .. } => {
let vertical = *axis == craft_ui::layout::SplitAxis::Vertical;
pending.extend([(second.as_ref(), vertical), (first.as_ref(), vertical)]);
}
_ => {}
}
}
out
}
/// Whether the group of the shared layout's tree holding `panel` as a tab can be shortened to its
/// tabs (the whole tree, or above or below another group); `None` when `panel` isn't a tab of one
/// (it floats, or is in an accordion).
fn tab_group_shortens(layout: &Layout<String>, panel: &str) -> Option<bool> {
let mut pending: Vec<(&Node<String>, bool)> = layout.root.iter().map(|root| (root, true)).collect();
while let Some((node, shortens)) = pending.pop() {
match node {
Node::Tabs { panels, .. } if panels.iter().any(|id| id == panel) => return Some(shortens),
Node::Split { axis, first, second, .. } => {
let vertical = *axis == craft_ui::layout::SplitAxis::Vertical;
pending.extend([(second.as_ref(), vertical), (first.as_ref(), vertical)]);
}
_ => {}
}
}
None
}
/// Forget the collapsed groups that show another panel now (another tab clicked, or the panel
/// moved or closed): those are expanded.
pub(crate) fn prune_collapsed(ui: &mut crate::state::UiState) {
if ui.docking_collapsed.is_empty() {
return;
}
let shown = dock_groups_shown(ui);
ui.docking_collapsed.retain(|id| shown.contains(id));
}
/// `window.panel.collapse`: show the dock group holding `panel` as its tabs only, or expanded.
fn collapse(app: &mut VectorcraftApp, params: &Value) -> Result<Value, String> {
let panel = params.get("panel").and_then(Value::as_str).and_then(normalize).ok_or("panel must name a registered panel")?;
let collapsed = crate::menus::opt_bool(params, "collapsed")?;
if crate::floating::group_of(&app.ui, panel).is_some() {
return Err("panel floats; collapse its group with window.panel.float {panel, collapsed}".into());
}
match app.ui.docking.as_mut() {
Some(layout) => {
match tab_group_shortens(layout, panel) {
None => return Err("panel is not in a dock group".into()),
Some(false) => return Err("only a dock group above or below others collapses, not one beside another".into()),
Some(true) => {}
}
layout.apply(Action::Activate { panel: panel.into() }).map_err(|e| e.to_string())?;
sync_legacy(app);
}
None => {
let tab = crate::state::DockTab::from_id(panel)
.filter(|_| crate::floating::docked_tabs(&app.ui).any(|t| t.info().0 == panel))
.ok_or("panel is not in a dock group")?;
app.ui.dock_tab = tab;
}
}
prune_collapsed(&mut app.ui);
let now = collapsed.unwrap_or(!app.ui.docking_collapsed.contains(panel));
if now {
app.ui.docking_collapsed.insert(panel.into());
} else {
app.ui.docking_collapsed.remove(panel);
}
Ok(json!({"panel": panel, "collapsed": now}))
}
/// `node` as drawn with its collapsed groups (showing a panel in `collapsed`) as tall as their
/// tabs in a top/bottom split. The paths of the splits so sized go in `fixed`, the panels of
/// collapsed groups in `members`. Also returns the node's height when it is only collapsed groups.
fn collapse_node(
node: &Node<String>,
collapsed: &std::collections::BTreeSet<String>,
(tab, gap): (f32, f32),
path: &mut Vec<bool>,
fixed: &mut Vec<Vec<bool>>,
members: &mut std::collections::BTreeSet<String>,
) -> (Node<String>, Option<f32>) {
use craft_ui::layout::{SplitAxis, SplitSize};
match node {
Node::Tabs { panels, active } if panels.get(*active).is_some_and(|id| collapsed.contains(id)) => {
members.extend(panels.iter().cloned());
(node.clone(), Some(tab))
}
Node::Split { axis, size, first, second } => {
path.push(false);
let (a, ha) = collapse_node(first, collapsed, (tab, gap), path, fixed, members);
path.pop();
path.push(true);
let (b, hb) = collapse_node(second, collapsed, (tab, gap), path, fixed, members);
path.pop();
let (sized, height) = match (axis, ha, hb) {
(SplitAxis::Vertical, Some(ha), Some(hb)) => (Some(SplitSize::FixedFirst(ha)), Some(ha + hb + gap)),
(SplitAxis::Vertical, Some(ha), None) => (Some(SplitSize::FixedFirst(ha)), None),
(SplitAxis::Vertical, None, Some(hb)) => (Some(SplitSize::FixedSecond(hb)), None),
_ => (None, None),
};
if sized.is_some() {
fixed.push(path.clone());
}
(Node::Split { axis: *axis, size: sized.unwrap_or(*size), first: Box::new(a), second: Box::new(b) }, height)
}
_ => (node.clone(), None),
}
}
fn change(app: &mut VectorcraftApp, operation: &str, params: &Value) -> Result<Value, String> {
let panel = params.get("panel").and_then(Value::as_str).and_then(normalize).ok_or("panel must name a registered panel")?;
if operation == "move" && params.get("before").is_some() {
@@ -584,6 +727,12 @@ fn change(app: &mut VectorcraftApp, operation: &str, params: &Value) -> Result<V
app.ui.docking = Some(layout);
app.ui.docking_hidden = hidden;
app.ui.docking_icons = icons;
if operation != "activate" {
app.ui.docking_collapsed.remove(panel);
for member in &members {
app.ui.docking_collapsed.remove(member);
}
}
if operation == "activate" {
for group in &mut app.ui.floating_panels {
if group.panels.iter().any(|id| id == panel) {
@@ -603,10 +752,15 @@ fn change(app: &mut VectorcraftApp, operation: &str, params: &Value) -> Result<V
struct PanelContent<'a> {
app: &'a mut VectorcraftApp,
commands: Vec<(&'static str, Value)>,
/// The panels of groups collapsed to their tabs: no body.
shut: std::collections::BTreeSet<String>,
}
impl craft_ui::docking::DockContent<String> for PanelContent<'_> {
fn body(&mut self, ui: &mut egui::Ui, panel: &String) {
if self.shut.contains(panel) {
return;
}
let t = crate::theme::Tokens::get(ui.ctx());
let back = ui.max_rect();
if back.is_positive() {
@@ -654,6 +808,7 @@ impl craft_ui::docking::DockContent<String> for PanelContent<'_> {
panel: active.clone(),
layout: self.app.ui.docking.clone().unwrap_or_else(|| legacy(self.app)),
hidden: self.app.ui.docking_hidden.clone(),
collapsed: self.app.ui.docking_collapsed.clone(),
open_panel: self.app.ui.open_panel.clone(),
generation: self.app.ui.docking_generation,
},
@@ -742,6 +897,7 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
} else if ui.input(|input| !input.focused || input.key_pressed(egui::Key::Escape)) {
app.ui.docking = Some(drag.layout);
app.ui.docking_hidden = drag.hidden;
app.ui.docking_collapsed = drag.collapsed;
app.ui.open_panel = drag.open_panel;
sync_legacy(app);
ui.ctx().data_mut(|data| data.remove::<RootGroupDrag>(root_key));
@@ -820,6 +976,8 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
.collect();
crate::dock::apply_pending_dock_width(app, ui.ctx(), crate::dock::SHARED_DOCK_PANEL, crate::dock::clamp_shared_dock_width);
let shared_dock_default = crate::dock::shared_dock_default_width(&app.ui);
// Splits sized to a collapsed group's tabs: their dividers don't resize the saved layout.
let mut fixed: Vec<Vec<bool>> = Vec::new();
let mut draw = |ui: &mut egui::Ui| {
if layout.root.is_some() && crate::dock::collapse_header(ui, collapsed) {
let _ = app.run("window.collapseDock", json!({"collapsed":!collapsed}));
@@ -862,23 +1020,60 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
style.panels_label = tl!("Panels").to_string();
style.resize_label = tl!("Resize panels").to_string();
style.resize_window_label = tl!("Resize panel window").to_string();
let tree = ui.available_rect_before_wrap();
let mut content = PanelContent { app, commands: Vec::new() };
let output = craft_ui::docking::DockArea::new(area).show_customized(
ui,
&display,
&style,
|id| {
crate::state::all_panels()
.find(|p| p.0 == id)
.map(|p| tl!(p.1).to_string())
.or_else(|| library_label(id))
.unwrap_or_else(|| id.clone())
},
|_| craft_ui::docking::Permissions::default(),
|_| craft_ui::docking::PanelLimits { min: egui::vec2(220.0, 80.0), ..Default::default() },
&mut content,
);
// Panels set their own minimum height, so a collapsed group can be as short as its tabs.
style.min_pane = 0.0;
let mut tree = ui.available_rect_before_wrap();
let mut shut = std::collections::BTreeSet::new();
let mut drawn = display.clone();
// The height of the tree when it is only collapsed groups: drawn that tall, so no group
// takes the rest of the dock as a blank pane.
let mut only_tabs = None;
if let Some(root) = &display.root {
let (node, height) =
collapse_node(root, &app.ui.docking_collapsed, (style.tab_height, style.gap), &mut Vec::new(), &mut fixed, &mut shut);
drawn.root = Some(node);
only_tabs = height.filter(|h| h.is_finite() && *h > 0.0);
}
let short = shut.clone();
let mut content = PanelContent { app, commands: Vec::new(), shut };
let mut dock = |ui: &mut egui::Ui| {
craft_ui::docking::DockArea::new(area).show_customized(
ui,
&drawn,
&style,
|id| {
crate::state::all_panels()
.find(|p| p.0 == id)
.map(|p| tl!(p.1).to_string())
.or_else(|| library_label(id))
.unwrap_or_else(|| id.clone())
},
|_| craft_ui::docking::Permissions::default(),
|id| craft_ui::docking::PanelLimits { min: egui::vec2(220.0, if short.contains(id) { 0.0 } else { 80.0 }), ..Default::default() },
&mut content,
)
};
let output = match only_tabs {
Some(height) => {
let size = egui::vec2(ui.available_width(), height.min(ui.available_height()));
ui.allocate_ui(size, |ui| {
ui.set_max_height(size.y);
tree = ui.available_rect_before_wrap();
dock(ui)
})
.inner
}
None => dock(ui),
};
// A double-click on a tab of the dock's tree collapses its group to its tabs, or expands it.
if let Some(root) = &drawn.root {
let tabs = Layout { root: Some(root.clone()), floating: Vec::new() };
if let Some(panel) =
tabs.panels().into_iter().find(|p| ui.ctx().read_response(area.with(("tab", *p))).is_some_and(|r| r.double_clicked()))
{
content.commands.push(("window.panel.collapse", json!({"panel": panel})));
}
}
let groups = if collapsed { Vec::new() } else { root_groups(ui.ctx(), &display, tree, style.tab_height) };
ui.ctx().data_mut(|data| data.insert_temp(crate::floating::root_groups_id(), groups));
for (id, params) in content.commands {
@@ -950,6 +1145,9 @@ pub(crate) fn show(app: &mut VectorcraftApp, ui: &mut egui::Ui) -> bool {
ui.ctx().data_mut(|data| data.remove::<LegacyDrag>(egui::Id::new("vectorcraft-panel-docking").with("legacy-origin")));
}
for action in output.actions {
if matches!(&action, Action::ResizeSplit { path, .. } if fixed.contains(path)) {
continue;
}
if tab_drop
.is_some_and(|(panel, _)| matches!(&action, Action::Float { panel: moved, .. } | Action::Move { panel: moved, .. } if moved == panel))
{
@@ -1129,6 +1327,7 @@ pub(crate) fn legacy_tab_in_strip(
open_panel: app.ui.open_panel.clone(),
floating: app.ui.floating_panels.clone(),
icons: app.ui.docking_icons.clone(),
collapsed: app.ui.docking_collapsed.clone(),
},
)
});
+211
View File
@@ -919,3 +919,214 @@ fn a_panel_the_icon_column_has_no_place_for_still_shows_there_once_dropped_on_it
let column = h.ctx.data(|d| d.get_temp::<egui::Rect>(crate::floating::icons_rect_id())).unwrap();
assert!(h.query_all_by_label("Character Styles").any(|node| column.contains_rect(node.rect())), "its icon is in the column");
}
/// Double-clicks at `at`, then waits out the double-click time.
fn double_click(h: &mut Harness<'static, VectorcraftApp>, at: Pos2) {
h.event(Event::PointerMoved(at));
h.run_steps(1);
for _ in 0..2 {
h.event(Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE });
h.run_steps(1);
h.event(Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: false, modifiers: Modifiers::NONE });
h.run_steps(1);
}
// Past the double- and triple-click times, so the next press is a click of its own.
h.run_steps(45);
}
/// The Layers panel's body is drawn (its search field).
fn layers_drawn(h: &Harness<'static, VectorcraftApp>) -> bool {
h.ctx.read_response(egui::Id::new("layers-search").with("edit")).is_some()
}
/// Clicks at `at`.
fn click(h: &mut Harness<'static, VectorcraftApp>, at: Pos2) {
h.event(Event::PointerMoved(at));
h.run_steps(1);
h.event(Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE });
h.run_steps(1);
h.event(Event::PointerButton { pos: at, button: PointerButton::Primary, pressed: false, modifiers: Modifiers::NONE });
h.run_steps(3);
}
/// The middle of `panel`'s tab in the shared dock.
/// Two stacked groups both collapsed are both as short as their tabs: the lower one doesn't take
/// the rest of the dock as a blank pane.
#[test]
fn two_stacked_groups_both_collapsed_are_both_as_short_as_their_tabs() {
let mut app = app();
app.run("window.panel.move", json!({"panel":"swatches", "anchor":"layers", "zone":"top"})).unwrap();
app.run("window.panel.collapse", json!({"panel":"swatches", "collapsed": true})).unwrap();
app.run("window.panel.collapse", json!({"panel":"layers", "collapsed": true})).unwrap();
let h = harness(app, vec2(1280.0, 800.0));
let groups = root_groups(&h);
assert_eq!(groups.len(), 2, "{groups:?}");
for group in &groups {
assert!(group.rect.height() < 40.0, "{groups:?}");
}
}
fn tab_center(h: &Harness<'static, VectorcraftApp>, panel: &str) -> Pos2 {
h.ctx.read_response(egui::Id::new("vectorcraft-panel-docking").with(("tab", &panel.to_string()))).unwrap().rect.center()
}
/// A double-click on a tab of the shared dock collapses its group to its tabs; another tab expands it.
#[test]
fn a_double_click_on_a_dock_tab_collapses_its_group_to_its_tabs_and_another_expands_it() {
let mut app = app();
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
app.run("window.panel.move", json!({"panel":"swatches", "anchor":"layers", "zone":"top"})).unwrap();
let saved = app.ui.docking.clone();
let mut h = harness(app, vec2(1280.0, 800.0));
let groups = root_groups(&h);
assert_eq!(groups.len(), 2, "{groups:?}");
let (top, lower) = (groups[0].rect, groups[1].rect);
assert!(top.height() > 100.0, "{top:?}");
// The upper group shrinks to its tabs and the lower one takes the room; the saved layout keeps
// its sizes.
let at = tab_center(&h, "swatches");
double_click(&mut h, at);
assert!(h.state().ui.docking_collapsed.contains("swatches"));
let groups = root_groups(&h);
assert!(groups[0].rect.height() < 40.0, "{groups:?}");
assert!(groups[1].rect.top() < lower.top() - 50.0, "{groups:?}");
assert_eq!(h.state().ui.docking, saved);
// Again, and it is as tall as before.
let at = tab_center(&h, "swatches");
double_click(&mut h, at);
assert!(h.state().ui.docking_collapsed.is_empty());
let groups = root_groups(&h);
assert!((groups[0].rect.height() - top.height()).abs() < 2.0, "{groups:?} {top:?}");
// The lower group, tabbed: its body goes, and a click on another of its tabs brings it back
// showing that panel.
let at = tab_center(&h, "layers");
double_click(&mut h, at);
assert!(h.state().ui.docking_collapsed.contains("layers"));
assert!(!layers_drawn(&h), "no Layers body");
let at = tab_center(&h, "properties");
click(&mut h, at);
assert!(h.state().ui.docking_collapsed.is_empty());
assert_eq!(root_group_of(h.state(), "properties"), ["properties", "layers", "libraries"]);
assert!(valid(h.state().ui.docking.as_ref().unwrap()));
}
/// A double-click on a tab of the legacy dock collapses the tabbed group to its tabs.
#[test]
fn a_double_click_on_a_legacy_dock_tab_collapses_the_tabbed_group() {
let mut app = app();
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
let mut h = harness(app, vec2(1280.0, 800.0));
let tab = h.query_all_by_label("Layers").find(|node| node.rect().height() <= 45.0).unwrap().rect().center();
click(&mut h, tab);
h.run_steps(45);
assert!(layers_drawn(&h), "Layers shows");
double_click(&mut h, tab);
assert!(h.state().ui.docking.is_none(), "still the legacy dock");
assert!(h.state().ui.docking_collapsed.contains("layers"));
assert!(!layers_drawn(&h), "no Layers body");
double_click(&mut h, tab);
assert!(h.state().ui.docking_collapsed.is_empty());
assert!(layers_drawn(&h), "Layers shows again");
}
/// `window.panel.collapse` checks its parameters, and a collapsed group survives a reload.
#[test]
fn the_collapse_command_validates_and_survives_a_reload() {
let mut app = app();
assert_eq!(app.run("window.panel.collapse", json!({"panel":"layers"})).unwrap(), json!({"panel":"layers", "collapsed":true}));
assert_eq!(app.ui.dock_tab, crate::state::DockTab::Layers, "it shows the panel");
let saved = serde_json::to_value(&app.ui).unwrap();
let restored = serde_json::from_value::<crate::state::UiState>(saved).unwrap().sanitized();
assert!(restored.docking_collapsed.contains("layers"));
assert_eq!(app.run("window.panel.collapse", json!({"panel":"layers", "collapsed":false})).unwrap()["collapsed"], false);
for params in [json!({"panel":"nope"}), json!({"panel":"layers", "collapsed":"yes"}), json!({"panel":"swatches"})] {
assert!(app.run("window.panel.collapse", params.clone()).is_err(), "{params}");
}
app.run("window.panel.float", json!({"panel":"layers", "x":300, "y":300})).unwrap();
assert!(app.run("window.panel.collapse", json!({"panel":"layers"})).is_err(), "floating groups collapse with float");
assert!(app.ui.docking_collapsed.is_empty());
}
/// A panel moved out of a collapsed group doesn't take the collapse along.
#[test]
fn a_collapsed_groups_panel_moved_elsewhere_takes_no_collapse_along() {
let mut app = app();
app.run("window.panel.move", json!({"panel":"swatches", "anchor":"layers", "zone":"top"})).unwrap();
app.run("window.panel.collapse", json!({"panel":"layers"})).unwrap();
// Layers, shown by the collapsed group, goes to the Swatches group and is shown there.
app.run("window.panel.move", json!({"panel":"layers", "anchor":"swatches"})).unwrap();
app.run("window.panel.activate", json!({"panel":"layers"})).unwrap();
crate::panel_docking::prune_collapsed(&mut app.ui);
assert!(app.ui.docking_collapsed.is_empty(), "neither group is collapsed");
// The same through a serialized action (a drag).
app.run("window.panel.collapse", json!({"panel":"layers"})).unwrap();
app.run("window.panel.layout", json!({"action": {"Move": {"panel":"layers", "anchor":"properties", "placement": {"Tab": {"before": null}}}}}))
.unwrap();
assert!(app.ui.docking_collapsed.is_empty());
}
/// `window.panel.collapse` refuses a panel that is in an accordion, not a tab group.
#[test]
fn the_collapse_command_rejects_an_accordion_panel() {
let mut app = app();
app.ui.docking = Some(Layout {
root: Some(Node::Stack {
entries: vec![
craft_ui::docking::StackEntry { panel: "layers".into(), open: true, height: None },
craft_ui::docking::StackEntry { panel: "swatches".into(), open: true, height: None },
],
}),
floating: Vec::new(),
});
assert!(valid(app.ui.docking.as_ref().unwrap()));
assert!(app.run("window.panel.collapse", json!({"panel":"layers"})).is_err());
assert!(app.ui.docking_collapsed.is_empty());
}
/// Escape on a collapsed group's drag puts it back collapsed.
#[test]
fn escape_on_a_collapsed_groups_drag_leaves_it_collapsed() {
let mut app = app();
app.run("window.panel.move", json!({"panel":"swatches", "anchor":"layers", "zone":"top"})).unwrap();
app.run("window.panel.collapse", json!({"panel":"layers"})).unwrap();
let mut h = harness(app, vec2(1280.0, 800.0));
// An icon panel's flyout is open too.
h.state_mut().ui.open_panel = Some("color".into());
h.run_steps(2);
let groups = root_groups(&h);
let lower = groups.iter().find(|g| g.panels.contains(&"layers")).unwrap().strip;
// The grip right of the lower group's tabs, dragged out of the dock, then Escape.
let from = lower.right_center() - vec2(50.0, 0.0);
h.event(Event::PointerMoved(from));
h.run_steps(1);
h.event(Event::PointerButton { pos: from, button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE });
h.run_steps(1);
for step in 1..=6 {
h.event(Event::PointerMoved(from - vec2(80.0 * step as f32, 0.0)));
h.run_steps(1);
}
assert!(h.state().ui.docking.as_ref().unwrap().floating.iter().any(|g| g.panels.contains(&"layers".into())), "the group floats mid-drag");
h.event(Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE });
h.run_steps(3);
assert!(h.state().ui.docking.as_ref().unwrap().floating.is_empty(), "back in the dock");
assert!(h.state().ui.docking_collapsed.contains("layers"), "and still collapsed");
assert_eq!(h.state().ui.open_panel.as_deref(), Some("color"), "the flyout is back");
}
/// A group beside another (a left/right split) is refused: it can't be shorter than its neighbour.
#[test]
fn a_group_beside_another_does_not_collapse() {
let mut app = app();
app.run("window.panel.move", json!({"panel":"swatches", "anchor":"layers", "zone":"left"})).unwrap();
let before = app.ui.docking.clone();
assert!(app.run("window.panel.collapse", json!({"panel":"swatches"})).is_err());
assert!(app.run("window.panel.collapse", json!({"panel":"properties"})).is_err());
assert_eq!(app.ui.docking, before, "refused without a change");
assert!(app.ui.docking_collapsed.is_empty());
// One left collapsed by an earlier layout (a hand-edited file) is expanded.
app.ui.docking_collapsed.insert("swatches".into());
crate::panel_docking::prune_collapsed(&mut app.ui);
assert!(app.ui.docking_collapsed.is_empty());
}
+10
View File
@@ -369,6 +369,10 @@ pub struct UiState {
/// Panels whose saved return destination is the iconic rail.
#[serde(default, skip_serializing_if = "std::collections::BTreeSet::is_empty")]
pub docking_icons: std::collections::BTreeSet<String>,
/// The panels shown by dock groups collapsed to their tabs (a double-click on a tab); a group
/// showing another of its panels is expanded.
#[serde(default, skip_serializing_if = "std::collections::BTreeSet::is_empty")]
pub docking_collapsed: std::collections::BTreeSet<String>,
/// Invalidates in-flight gestures when a workspace is replaced.
#[serde(skip)]
pub docking_generation: u64,
@@ -782,6 +786,11 @@ impl UiState {
self.docking = self.docking.filter(crate::panel_docking::valid);
// The tabbed group's panels live in the icon column too once dropped there.
self.docking_icons.retain(|id| rail_panel(id).is_some());
self.docking_collapsed = std::mem::take(&mut self.docking_collapsed)
.into_iter()
.filter(|id| crate::menus::normalize_panel(id) == Some(id.as_str()))
.take(64)
.collect();
// Changes kept for workspaces that still exist, under their saved spelling and one per
// workspace whatever the case (a hand-edited file).
let mut seen = std::collections::BTreeSet::new();
@@ -825,6 +834,7 @@ impl Default for UiState {
docking: None,
docking_hidden: Default::default(),
docking_icons: Default::default(),
docking_collapsed: Default::default(),
docking_generation: 0,
dock_width: None,
dock_width_pending: false,
+10 -3
View File
@@ -44,6 +44,9 @@ pub struct Workspace {
/// Panels whose saved return destination is the iconic rail.
#[serde(default, skip_serializing_if = "std::collections::BTreeSet::is_empty")]
pub docking_icons: std::collections::BTreeSet<String>,
/// The panels shown by dock groups collapsed to their tabs.
#[serde(default, skip_serializing_if = "std::collections::BTreeSet::is_empty")]
pub docking_collapsed: std::collections::BTreeSet<String>,
/// Where the Tools panel floats (built-in workspaces: docked).
pub toolbar_pos: Option<[f32; 2]>,
/// Main dock column width in screen points (#1753).
@@ -180,6 +183,7 @@ pub fn capture(ui: &UiState, name: &str) -> Workspace {
docking: ui.docking.clone(),
docking_hidden: ui.docking_hidden.clone(),
docking_icons: ui.docking_icons.clone(),
docking_collapsed: ui.docking_collapsed.clone(),
toolbar_pos: ui.toolbar_pos,
dock_width: ui.dock_width,
}
@@ -205,6 +209,7 @@ pub fn apply(ui: &mut UiState, w: &Workspace) {
ui.docking = w.docking.clone().filter(crate::panel_docking::valid);
ui.docking_hidden = w.docking_hidden.clone();
ui.docking_icons = w.docking_icons.iter().filter(|id| crate::state::rail_panel(id).is_some()).take(64).cloned().collect();
ui.docking_collapsed = w.docking_collapsed.iter().filter(|id| crate::menus::normalize_panel(id) == Some(id.as_str())).take(64).cloned().collect();
ui.toolbar_pos = w.toolbar_pos;
ui.dock_width = w.dock_width;
ui.dock_width_pending = true;
@@ -557,12 +562,14 @@ mod tests {
// Changes made in it after saving (a panel moved, the control bar shown)…
ui.control_bar = true;
ui.dock_tab = DockTab::Layers;
ui.docking_icons.insert("color".into());
ui.docking_icons.insert("layers".into());
ui.docking_collapsed.insert("layers".into());
// …are still there after switching away and back.
switch(&mut ui, ESSENTIALS).unwrap();
assert!(!ui.control_bar && ui.docking_icons.is_empty(), "Essentials comes as saved");
assert!(!ui.control_bar && ui.docking_icons.is_empty() && ui.docking_collapsed.is_empty(), "Essentials comes as saved");
switch(&mut ui, "Görkem").unwrap();
assert!(ui.control_bar && ui.dock_tab == DockTab::Layers && ui.docking_icons.contains("color"));
assert!(ui.control_bar && ui.dock_tab == DockTab::Layers && ui.docking_icons.contains("layers"));
assert!(ui.docking_collapsed.contains("layers"), "its collapsed group too");
// Built-in workspaces remember theirs too.
switch(&mut ui, ESSENTIALS).unwrap();
ui.status_bar = false;
+5
View File
@@ -858,6 +858,11 @@ positions of hidden panels. Resetting a built-in workspace restores its default
dock placement when possible, or to the supplied destination.
- `window.panel.activate {panel}` reveals and selects a panel, restoring its saved
position when possible. `window.panel.close {panel}` hides it.
- `window.panel.collapse {panel, collapsed?}` shows the dock group holding a panel as its tabs
only, as a double-click on a tab does (without `collapsed` it toggles), and makes the panel the
group's shown one; showing another of its panels expands the group. Only a group that is the
whole dock or sits above or below another collapses (one beside another is refused). Floating
groups collapse with `window.panel.float {panel, collapsed}`. Returns `{panel, collapsed}`.
Panel IDs are stable across themes. Invalid IDs, non-finite or invalid geometry, and
invalid destinations return an error without changing the arrangement.
+3 -2
View File
@@ -1,6 +1,6 @@
# Roadmap: milestones and what's next
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (M14: panels dock into any dock group or the icon column by dragging (UI.6), workspaces keep their layout changes (UI.7); estimates unchanged; Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog; Recolor model selection is available through the engine and MCP only (#2520); the dialog retains its existing controls; 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; M12 duplicate milestone rows consolidated without losing distinct details; scores and estimates unchanged; live-trace bounds note placed under Image Trace rather than Graphs; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; M3.57: CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; DXF circular cubic paths export as native CIRCLE/ARC with R12 weighted strokes kept as POLYLINE and closing-chord safeguards (#1299); cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 graph pruning and junction merging measured; M12 exact crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; correction: M12's Image Trace quick presets leave the panel as it was, the Image Trace button opens it; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); Warp Options controls verified by focused checks and original-image review; estimates unchanged; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: per-corner radius, #1682; Width Point Edit from the Width tool, #1814, #1630; guides, #1782, #1836; anchor X/Y/W/H, #1824; toolbar and All Tools, #1751; macOS save sheets, #1855; rendering caches, #1667–#1792; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; Dim Images, #1683; estimates unchanged; M12 row: Image Trace's enhanced presets; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Expand on repeats, #1756; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #Line truncated
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (M14: panels dock into any dock group or the icon column by dragging (UI.6), workspaces keep their layout changes (UI.7), dock groups collapse to their tabs by a double-click (UI.8); estimates unchanged; Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog; Recolor model selection is available through the engine and MCP only (#2520); the dialog retains its existing controls; 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; M12 duplicate milestone rows consolidated without losing distinct details; scores and estimates unchanged; live-trace bounds note placed under Image Trace rather than Graphs; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; M3.57: CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; DXF circular cubic paths export as native CIRCLE/ARC with R12 weighted strokes kept as POLYLINE and closing-chord safeguards (#1299); cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 graph pruning and junction merging measured; M12 exact crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; correction: M12's Image Trace quick presets leave the panel as it was, the Image Trace button opens it; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); Warp Options controls verified by focused checks and original-image review; estimates unchanged; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: per-corner radius, #1682; Width Point Edit from the Width tool, #1814, #1630; guides, #1782, #1836; anchor X/Y/W/H, #1824; toolbar and All Tools, #1751; macOS save sheets, #1855; rendering caches, #1667–#1792; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; Dim Images, #1683; estimates unchanged; M12 row: Image Trace's enhanced presets; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Expand on repeats, #1756; file and input hardening, #Line truncated
Forward-looking plan. Where VectorCraft stands is in [`ROADMAP.md`](../ROADMAP.md) and
[target-app-parity.md](target-app-parity.md); the ranked work list is [gaps.md](gaps.md). Hours are Opus 5.5
@@ -66,7 +66,7 @@ Checked 2026-10-10 from the code, tests and open issues; no workflow fails, so V
| M12 | Advanced color & art (CMYK/ICC, separations, Gradient Mesh, Live Paint, Image Trace, Graphs) | 🟡 Gradient Mesh, Live Paint, Image Trace (junction-returning pieces retain their positioned boundaries; a simpler contour walk with identical output; an edge's sub-pixel shift is the same from either side; notches read only their own piece; collapsed loops keep few segments; large images trace with a warning; Auto Color keeps rare distinct colours; quick presets trace at once and the Image Trace button opens the adjustment controls; Properties reopens them; tiny fitted contours retain islands and holes; Create Strokes: exact crop EDT, Guo–Hall thinning and maximal-disc graph simplification; 15 presets, the 14 documented ones each with an enhanced version; Ignore Color, Shapes, Auto Grouping and Gradients options; measured in `image-trace-quality.md`; closed outlines retain their interior after straightening; the Flat Logo mode places edges to a fraction of a pixel, restores sharp corners, snaps lines and circles, and refuses gradients; live trace result bounds and selection highlights exclude the retained bitmap except in source-image views), Recolor Artwork, colour management (ICC, soft proofing, separations preview), Graphs (all 9 tools, Graph Data/Type, group-selected series colours persist through regenerate/save/open) done; Graph Design and Marker designs, Transpose and Switch x/y, `\|` label line breaks, the pie options, drop shadows, the legend across the top, overlapping widths, First Row/Column in Front, filled lines, kept label formatting and the Graph Data worksheet done; graph Column designs pending | 6–10 |
| M13 | Automation (Actions ✅ record/playback, persisted; variables, scripting, batch) | 🟡 MCP `add_text` exposes existing vertical point, area and path type; broader scripting and batch work remains | 18–27 |
| M13 | Automation (Actions ✅ record/playback, persisted; variables, scripting, batch) | 🟡 | 18–27 |
| M14 | 1.0 polish (preferences, shortcut editor, workspaces, accessibility, packaging for all OSes) (status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; sample widths cached by language/unit/DPI; regression-tested and contributor-confirmed 2026-10-19; estimates unchanged) | 🟡 Numeric-field pointer selection and focus regression-tested (#1326, in part; short movements still select all); RPM runtime capabilities and glibc floor checked (#1275; real-distro install/start-up pending), Eyedropper cursor with precise-setting checks (#1269), Preferences, shortcut editor (chords with Shift and a punctuation key fire as typed; other apps' text shortcut sets imported), workspaces (each keeps its layout changes until Reset <name>, UI.7), panels docked by dragging into any dock group (as tabs or a top/bottom split) or into the icon column, and closed from a dock tab's context menu (UI.6), a custom title bar on Windows/Linux with a Home button, content-sized dialogs, menus that scroll when longer than the window and macOS native menu dismissal done (M14.8), and native macOS save sheets with a Format menu (#866); an experimental, unsupported Windows 7 x64 portable OpenGL build (not verified by maintainers); accessibility and broader Windows/Linux packaging/QA pending | 18–28 |
| M14 | 1.0 polish (preferences, shortcut editor, workspaces, accessibility, packaging for all OSes) (status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; sample widths cached by language/unit/DPI; regression-tested and contributor-confirmed 2026-10-19; estimates unchanged) | 🟡 Dock groups collapse to their tabs by a double-click on a tab (UI.8); Numeric-field pointer selection and focus regression-tested (#1326, in part; short movements still select all); RPM runtime capabilities and glibc floor checked (#1275; real-distro install/start-up pending), Eyedropper cursor with precise-setting checks (#1269), Preferences, shortcut editor (chords with Shift and a punctuation key fire as typed; other apps' text shortcut sets imported), workspaces (each keeps its layout changes until Reset <name>, UI.7), panels docked by dragging into any dock group (as tabs or a top/bottom split) or into the icon column, and closed from a dock tab's context menu (UI.6), a custom title bar on Windows/Linux with a Home button, content-sized dialogs, menus that scroll when longer than the window and macOS native menu dismissal done (M14.8), and native macOS save sheets with a Format menu (#866); an experimental, unsupported Windows 7 x64 portable OpenGL build (not verified by maintainers); accessibility and broader Windows/Linux packaging/QA pending | 18–28 |
| — | Interaction fidelity pass (every tool's modifiers, Properties panel per context, isolation, nuance; width-point creation and adjoining adjustment regression-tested; dialog layout reviewed) | ⬜ (new ruler-guide Alt orientation implemented; existing-guide Escape cancellation regression-tested; broader pass remains) | 60–90 |
| — | Hardening at scale (big-file corpus, fuzzing, cross-platform + browser QA) | 🟡 PngSuite integrity and bounded fetching verified (#1729); real-world corpus/manual QA remain | 50–80 |
| M14 follow-up | Color Picker layout (#2008) | ✅ Compact controls, centered header, stacked actions, aligned fields and screen sampling; narrow windows scroll vertically | Included above |
@@ -97,6 +97,7 @@ sampling coexistence. Scores and estimates unchanged. See [development.md](devel
|---|---|---|
| 2026-10-21 | minor | M14: workspaces keep the layout changes made in them across switches and restarts until Reset <name> (UI.7); estimates unchanged |
| 2026-10-21 | minor | M14: panels dock by dragging into any dock group (tabs or a top/bottom split) or the icon column; dock tabs close from their context menu (UI.6); estimates unchanged |
| 2026-10-21 | minor | M14: a double-click on a dock tab collapses its group to its tabs and expands it again (UI.8); estimates unchanged |
| 2026-10-20 | minor | M14: workspaces keep the layout changes made in them across switches and restarts until Reset <name> (UI.7); estimates unchanged |
| 2026-10-21 | minor | M3: highlighted-text Eyedropper sampling preserves ranges and rejects unsupported source/target transparency atomically; regression-tested; scores and estimates unchanged. |
| 2026-10-21 | minor | Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog. Scores and estimates unchanged. |
+3 -2
View File
@@ -1,6 +1,6 @@
# Parity with Adobe Illustrator
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (UI chrome: panels dock into any dock group or the icon column by dragging (UI.6), workspaces keep their layout changes (UI.7); scores and hours unchanged; Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog; Recolor model selection is available through the engine and MCP only (#2520); the dialog retains its existing controls; filter plug-ins read and set effect records and stroke joins, caps and miter limits, G17; the Print dialog's Media list has the Japanese papers: JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 beside the ISO B4/B5, which keep their ISO 216 sizes, and はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号 and 四六判; a paper is named by its JSON id or its dialog label, any case and spacing; imported PDF type that is letter-spaced keeps its word spaces and no others; Fractional Widths: Character panel menu, per type object, on by default; System Layout remains a panel stub; scores unchanged; G4: manual Tate-chu-yoko; G4: No Break; scores unchanged; the selection tools take a clipping path on its outline, G1; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; live-effect plug-ins given their object's fills and strokes and the item they sit on, G17; plug-in parameters: labels, units, groups, help and conditions in their dialogs, text and number-list parameters, up to 256 of them, G17; exporter plug-ins: plug-in file formats in Export As and document.export, G17; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; Libraries panel: the list scrolls, items and groups reorder by dragging, items drag into and out of groups (`library.moveItem` before/after, `library.moveGroup`); scores unchanged; PDF invisible text opens as type with no fill or stroke and is written back invisible, #2675; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 maximal-disc graph pruning and junction merging measured; M12 crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; installed plug-ins stay installed after a restart, #2541; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); G5: Symbol Options ExLine truncated
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-22 · **Change:** minor (UI chrome: panels dock into any dock group or the icon column by dragging (UI.6), workspaces keep their layout changes (UI.7), dock groups collapse to their tabs by a double-click (UI.8); scores and hours unchanged; Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog; Recolor model selection is available through the engine and MCP only (#2520); the dialog retains its existing controls; filter plug-ins read and set effect records and stroke joins, caps and miter limits, G17; the Print dialog's Media list has the Japanese papers: JIS B4 257 × 364 mm, JIS B5 182 × 257 mm and JIS B6 beside the ISO B4/B5, which keep their ISO 216 sizes, and はがき, 往復はがき, 封筒 長形3号/長形4号/角形2号/角形3号 and 四六判; a paper is named by its JSON id or its dialog label, any case and spacing; imported PDF type that is letter-spaced keeps its word spaces and no others; Fractional Widths: Character panel menu, per type object, on by default; System Layout remains a panel stub; scores unchanged; G4: manual Tate-chu-yoko; G4: No Break; scores unchanged; the selection tools take a clipping path on its outline, G1; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; CSS swatch libraries retain fractional gradient stop positions; scores and hours unchanged; live-effect plug-ins given their object's fills and strokes and the item they sit on, G17; plug-in parameters: labels, units, groups, help and conditions in their dialogs, text and number-list parameters, up to 256 of them, G17; exporter plug-ins: plug-in file formats in Export As and document.export, G17; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; Libraries panel: the list scrolls, items and groups reorder by dragging, items drag into and out of groups (`library.moveItem` before/after, `library.moveGroup`); scores unchanged; PDF invisible text opens as type with no fill or stroke and is written back invisible, #2675; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 maximal-disc graph pruning and junction merging measured; M12 crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; installed plug-ins stay installed after a restart, #2541; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop Line truncated
The authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it; [`gaps.md`](gaps.md) is
the work list made from it. Deep areas have their own checklists: [UI and interaction](ui-parity.md),
@@ -486,7 +486,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Screen readers get a name for the Tools panel's tool buttons, flyout rows, floating strips and tear-off bars, and for the icon buttons, text buttons, toggles, checkboxes, radio buttons, tabs, chips, sliders, angle dials and combo boxes that panels and dialogs draw with `crates/ui-egui/src/widgets.rs`: the text, label or tool tip each one shows, or a name of its own where it shows none (the angle dial, the spinner, the preset arrow, the color wheel and the Reference Point's squares). The tool buttons, toggles, checkboxes, radio buttons, tabs and the Reference Point's nine squares also report whether they are on, an icon button that is lit, such as the chosen Align To button, reports that it is pressed, and the angle dial reports its angle. The status bar's First, Previous, Next and Last Artboard buttons have tool tips.
- These dialog sliders, each beside a number field, respond to the keyboard: those of the effect, tool option, brush and transform dialogs, the Perspective Grid, the 3D dialogs, and the flattener options, which the Flattener Preview panel also shows. A click, or Tab, gives a slider the keyboard focus, and an outline shows it. Then → and ↑ step the field's number up to the next whole number and ← and ↓ step it down to the next whole number, Shift with an arrow and Page Up and Page Down step it by ten, and Home and End set the slider's first and last value; no step goes past either end. Screen readers' increment and decrement actions step the slider as → and ← do. The Color panel's, Swatch Options', the Navigator's and the Character panel's sliders, and the Color Picker's channel slider, respond only to the pointer. The keys follow the WAI-ARIA slider pattern (<https://www.w3.org/WAI/ARIA/apg/patterns/slider/>); the reference app's help does not describe keys on a dialog's slider, so the step sizes are VectorCraft's own.
- Screen readers get the value of the color and channel sliders that panels and dialogs draw (the Color panel, the Color Picker, Swatch Options, the Navigator's Zoom, the Character panel's variable font axes, the Perspective Grid and flattener options, and the sliders of the effect, tool option and transform dialogs): the number and text of the field beside each one, such as 128 for G or 50% for an opacity, with the slider's range, which extends to include a number past the slider's end. The harmony wheel's Brightness slider, which has no field, reports its brightness in percent.
- 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 a dock group's tabs, which light up under the pointer, its panels join that group, and on the group's upper or lower half they make a new group above or below it (`window.panel.dock {anchor, zone}`); dropped on the icon column they become icons there, the tabbed group's panels too (`window.panel.dock {icon: true}`); dropped anywhere else on the dock, which lights up, they 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, 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.
- 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 a dock group's tabs, which light up under the pointer, its panels join that group, and on the group's upper or lower half they make a new group above or below it (`window.panel.dock {anchor, zone}`); dropped on the icon column they become icons there, the tabbed group's panels too (`window.panel.dock {icon: true}`); dropped anywhere else on the dock, which lights up, they 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`), and a dock group too, a group below it moving up into the room; a second double-click, or a click on another of its tabs, expands it (`window.panel.collapse`, kept with the preferences and in saved workspaces). 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, 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.
- 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`, `every_undo_label_is_translated`).
@@ -933,6 +933,7 @@ sampling coexistence. Scores and estimates unchanged. See [development.md](devel
|---|---|---|
| 2026-10-21 | minor | UI chrome: workspaces keep the layout changes made in them when switching away and back, and across restarts; Reset <name> returns to the saved layout (UI.7); score and hours unchanged (estimated) |
| 2026-10-21 | minor | UI chrome: a panel group dragged onto a dock group's tabs joins it, onto its upper or lower half makes a new group above or below it, onto the icon column becomes icons (the tabbed group's panels too); dock tabs close from their context menu (UI.6); score and hours unchanged (estimated) |
| 2026-10-21 | minor | UI chrome: a double-click on a dock tab collapses its group to its tabs and expands it again, as floating groups already did (UI.8); scores and hours unchanged (estimated) |
| 2026-10-20 | minor | UI chrome: workspaces keep the layout changes made in them when switching away and back, and across restarts; Reset <name> returns to the saved layout (UI.7); score and hours unchanged (estimated) |
| 2026-10-21 | minor | M3: highlighted-text Eyedropper sampling preserves ranges and rejects unsupported source/target transparency atomically; regression-tested; scores and estimates unchanged. |
| 2026-10-21 | minor | Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog. Scores and estimates unchanged. |
+3 -1
View File
@@ -1,6 +1,6 @@
# UI and interaction parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (Panels dock into any dock group or the icon column by dragging (UI.6), workspaces keep their layout changes (UI.7); scores unchanged; Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog; 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; one selected clip group's Properties panel has Edit Clipping Path and Edit Contents at its top, G1; Distribute Objects and Distribute Spacing space from a key guide (#3165) with the Distribute Spacing value; Distribute spaces selected ruler guides with the objects, #1597 in part; the selection tools take a clipping path on its outline, over the art it clips; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; a ruler guide can be the key object Align aligns to, and selected guides align to a key object, #2143; Pencil: Alt key toggles to Smooth Tool, #2001; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; on-canvas Mirror Repeat axis, #2877; Background Export progress dialog with Cancel, #2564; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; scores unchanged; library panels: one per library, dockable and tabbable, with Persistent, #2603; the Selection tool threads text from the out port, #2572 in part; scores unchanged; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; scores unchanged; transparency grid visibility saved in native documents, #1968; Live Corners radius up to the neighbouring cuts, #2332; Image Trace quick presets: wording corrected to the shipped behaviour, the Image Trace button opens the controls; batch exporting, #1935; document window scroll bars, #2322; Anchor Point tool: a click on a handle end retracts that handle only, #2267; scores unchanged; Smart Guides: the dragged end of a straight segment keeps to its extension at any angle, #2120; Warp Options controls verified by focused checks and original-image review; estimates unchanged; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; later 2026-10-14 merges: macOS save sheet Format menu back to missing, #1855, #866 reopened; anchor X/Y/W/H in the Transform panel, Properties and Control bar, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar labels, columns and the All Tools drawer, #1751; Actions panel selection, #1629 in part; scores unchanged; 2026-10-14 merges: anchor Align and Distribute, #1636; Delete on closed paths and whole objects, #1674; selection inside isolation, #1666; Escape in a Direct Selection drag and while typing, #1743, #1686; Pen clicks under 3 px, #1663; [ and ] on more tools, #1661 in part; fields commit on click away, #1787; command search keys, #1757; a guide's position in the Transform panel is a convenience with no parity claim, #1659; scores unchanged; intersect Smart Guides for drawn points and dragged anchors, #1602 in part; a ruler guide's position in the Transform panel, #1597 in part; Symbol Options before New Symbol, #1560; tool flyout widths, #1616; Pathfinder Trim, Merge and Crop strokes, #1570; scores unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; per-corner radius fields with a link toggle, split from #1594; Join merges anchors lying on one another, #1533; corLine truncated
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-21 · **Change:** minor (Panels dock into any dock group or the icon column by dragging (UI.6), workspaces keep their layout changes (UI.7), dock groups collapse to their tabs by a double-click (UI.8); scores unchanged; Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog; 3D: original Standard, Diffuse, Top Left and Right lighting presets; command access, effective-setting recognition and preview/history; scores and estimates unchanged; one selected clip group's Properties panel has Edit Clipping Path and Edit Contents at its top, G1; Distribute Objects and Distribute Spacing space from a key guide (#3165) with the Distribute Spacing value; Distribute spaces selected ruler guides with the objects, #1597 in part; the selection tools take a clipping path on its outline, over the art it clips; Polygon and Star exact-size placement remembers the chosen count for later drags and dialog openings; Cancel preserves the previous count; Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views; a ruler guide can be the key object Align aligns to, and selected guides align to a key object, #2143; Pencil: Alt key toggles to Smooth Tool, #2001; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; on-canvas Mirror Repeat axis, #2877; Background Export progress dialog with Cancel, #2564; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; scores unchanged; library panels: one per library, dockable and tabbable, with Persistent, #2603; the Selection tool threads text from the out port, #2572 in part; scores unchanged; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; scores unchanged; transparency grid visibility saved in native documents, #1968; Live Corners radius up to the neighbouring cuts, #2332; Image Trace quick presets: wording corrected to the shipped behaviour, the Image Trace button opens the controls; batch exporting, #1935; document window scroll bars, #2322; Anchor Point tool: a click on a handle end retracts that handle only, #2267; scores unchanged; Smart Guides: the dragged end of a straight segment keeps to its extension at any angle, #2120; Warp Options controls verified by focused checks and original-image review; estimates unchanged; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; later 2026-10-14 merges: macOS save sheet Format menu back to missing, #1855, #866 reopened; anchor X/Y/W/H in the Transform panel, Properties and Control bar, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar labels, columns and the All Tools drawer, #1751; Actions panel selection, #1629 in part; scores unchanged; 2026-10-14 merges: anchor Align and Distribute, #1636; Delete on closed paths and whole objects, #1674; selection inside isolation, #1666; Escape in a Direct Selection drag and while typing, #1743, #1686; Pen clicks under 3 px, #1663; [ and ] on more tools, #1661 in part; fields commit on click away, #1787; command search keys, #1757; a guide's position in the Transform panel is a convenience with no parity claim, #1659; scores unchanged; intersect Smart Guides for drawn points and dragged anchors, #1602 in part; a ruler guide's position in the Transform panel, #1597 in part; Symbol Options before New Symbol, #1560; tool flyout widths, #1616; Pathfinder Trim, Merge and Crop strokes, #1570; scores unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; per-corner radius fields with a link toggle, split froLine truncated
How VectorCraft's tools, handles, snapping and panels behave next to Illustrator's. A vector illustrator's
trust is in the details: where a handle lands, what Shift and Alt do mid-drag, what a smart guide snaps to,
@@ -224,6 +224,7 @@ empty), without opening the dialog.
| 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 dragged onto a dock group's tabs join it; onto its upper or lower half they make a new group above or below it (a top/bottom split, resizable); onto the icon column they become icons, the tabbed group's panels too; a dock tab's context menu closes it (Window menu reopens it) | done | `window.panel.dock {anchor, zone}`, `window.panel.dock {icon: true}`, `window.panel.close` |
| A double-click on a dock group's tab collapses the group to its tabs (a group below it moves up into the room) and a second one expands it; a click on another of its tabs expands it showing that panel ([helpx: Workspace basics](https://helpx.adobe.com/illustrator/using/workspace-basics.html), panel groups) | done | `window.panel.collapse {panel, collapsed?}`; in the legacy dock and in the shared dock's tree (floating groups already did); a group beside another (a left/right split) doesn't collapse |
| Panels outside the app window (another monitor) | missing | single OS window |
| Edit Toolbar (•••): the All Tools drawer opens beside the toolbar by its button (Escape or a click outside closes it), dims the tools already in the toolbar; while it is open, tools drag from it onto the toolbar (between two tools, or onto one to join its group), from the toolbar into it, and within the toolbar; each toolbar keeps its edits | done | #1018; `window.editToolbar`, `toolbar.addTool`/`removeTool`/`reset`; the drawer's Reset (Illustrator has it in a drawer menu); dragging a toolbar button moves the tool it shows, not its whole group; New/Manage Toolbars missing; tool flyouts are as wide as their longest translated name and shortcut need (#1616) |
| Quick Image Trace presets trace at once and leave the panel as it was; the Image Trace button (Control bar, Properties) opens the editable controls | done | Per the public documentation, choosing a preset traces the image with it straight away, and the panel is opened with the Image Trace button in the Control panel or in Properties' Image Trace section (<https://helpx.adobe.com/illustrator/using/image-trace-results-optimization.html>). The controls show Threshold, Paths, Corners and Noise with Advanced open; `panels/image_trace.rs` headless tests (`a_quick_trace_leaves_the_panel_as_it_was`, `sketch_quick_trace_reveals_all_adjustments_and_keeps_the_source` for the button's route). No algorithm parity claim. |
@@ -283,6 +284,7 @@ sampling coexistence. Scores and estimates unchanged. See [development.md](devel
|---|---|---|
| 2026-10-21 | minor | Workspaces keep the layout changes made in them when switching away and back, and across restarts; Reset <name> returns to the saved layout; score unchanged (estimated) |
| 2026-10-21 | minor | Panels dragged by the mouse join a dock group as tabs, split it top/bottom or become icons in the icon column; dock tabs close from their context menu; score unchanged (estimated) |
| 2026-10-21 | minor | A double-click on a dock tab collapses its group to its tabs, as floating groups already did, and expands it again; another of its tabs expands it (`window.panel.collapse`); scores unchanged (estimated) |
| 2026-10-20 | minor | Workspaces keep the layout changes made in them when switching away and back, and across restarts; Reset <name> returns to the saved layout; score unchanged (estimated) |
| 2026-10-21 | minor | M3: highlighted-text Eyedropper sampling preserves ranges and rejects unsupported source/target transparency atomically; regression-tested; scores and estimates unchanged. |
| 2026-10-21 | minor | Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog. Scores and estimates unchanged. |