Merge pull request #6770 from dotCooCoo/feat/learn-panel

Learn: a Learn panel lists tutorials by skill, opened from Explore tutorials on the app bar
This commit is contained in:
BFlat
2026-11-12 16:02:15 +07:00
committed by GitHub
44 changed files with 1082 additions and 3 deletions
+1
View File
@@ -267,6 +267,7 @@ Generated-in-code art is original and has no file to list. This covers the defau
| `assets/icons/flip-vertical-2.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
| `assets/icons/folder-open.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
| `assets/icons/frame.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
| `assets/icons/graduation-cap.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | Learn panel, Explore tutorials button |
| `assets/icons/grid-3x3.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
| `assets/icons/group.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
| `assets/icons/hand.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
+17
View File
@@ -0,0 +1,17 @@
<!-- @license lucide-static v1.49.0 - ISC -->
<svg
class="lucide lucide-graduation-cap"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z" />
<path d="M22 10v6" />
<path d="M6 12.5V16a6 3 0 0 0 12 0v-3.5" />
</svg>

After

Width:  |  Height:  |  Size: 497 B

+18
View File
@@ -634,6 +634,24 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"",
"{collapsed?: bool} collapse every expanded dock column to icons (true), expand the outer dock column, Properties | Layers | Libraries (false), or toggle the outer column (omitted), as the double arrow at the top of the dock does; returns the new state",
),
(
"window.learn",
"Learn",
"",
"{show?: bool} show (true), hide (false) or toggle (omitted or null) the Learn panel, as Window › Learn and the app bar's Explore tutorials button do; any other value is an error and changes nothing; returns whether the panel shows",
),
(
"learn.list",
"List Tutorials",
"",
"{} the Learn panel's skills in the order its Skills tab lists them: {skills: [{id, title, tutorials: [{id, title, steps: number of steps}]}]}",
),
(
"learn.show",
"Show Tutorial",
"",
"{tutorial: id (see learn.list)} show the Learn panel on its Skills tab with that tutorial's numbered steps, as choosing its skill and then the tutorial does; a missing, non-string or unknown id is an error and changes nothing; returns {skill, tutorial, title, steps: [text]}",
),
("window.brightness", "UI Brightness", "", "{brightness: dark|mediumDark|mediumLight|light}"),
(
"window.workspace",
+14 -3
View File
@@ -72,8 +72,9 @@ pub fn app_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
// With the macOS menu bar the menus are at the top of the screen instead.
let menus_end = if app.services.native_menu.is_some() { ui.cursor().min.x } else { menus::menu_bar(app, ui) };
// The right-side group fills the space after the menus from the right; when it runs
// short, Discord goes first (it is also under Help), then the search icon (the palette
// stays under its shortcut), then the workspace switcher narrows.
// short, Discord goes first (it is also under Help), then Explore tutorials (also
// Window › Learn), then the search icon (the palette stays under its shortcut), then
// the workspace switcher narrows.
let full = ui.max_rect();
let right_edge = if custom { full.right() - titlebar::WIDTH - 10.0 } else { full.right() };
let right = egui::Rect::from_min_max(egui::pos2(menus_end + 8.0, full.top()), egui::pos2(right_edge, full.bottom()));
@@ -97,7 +98,9 @@ pub fn app_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
let icon = 28.0;
let search = switcher && room >= 64.0 + 8.0 + gap + icon;
let ws_w = ws_w.min(room - 8.0 - gap - if search { icon } else { 0.0 }).max(64.0);
let discord = search && room >= ws_w + 8.0 + gap + icon + 10.0 + gap + crate::community::discord_width(ui, false);
// Explore tutorials, beside the search icon, opens the Learn panel.
let learn = search && room >= ws_w + 8.0 + 2.0 * (gap + icon);
let discord = learn && room >= ws_w + 8.0 + 2.0 * (gap + icon) + 10.0 + gap + crate::community::discord_width(ui, false);
let mut rui = ui.new_child(egui::UiBuilder::new().max_rect(right).layout(egui::Layout::right_to_left(egui::Align::Center)));
let ui = &mut rui;
// Workspace switcher: shows the current workspace, opens Window → Workspace.
@@ -127,6 +130,14 @@ pub fn app_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
app.ui.palette_query.clear();
}
}
if learn {
let shown = crate::panels::learn::visible(&app.ui);
if widgets::icon_button(ui, "graduation-cap", tl!("Explore tutorials"), shown, icon).clicked()
&& let Err(e) = app.run("window.learn", json!({}))
{
app.ui.status = e;
}
}
if discord {
ui.add_space(10.0);
crate::community::discord_button(app, ui, false);
+13
View File
@@ -6632,6 +6632,19 @@ breakText Glyphs Глифи
@plural {n} earlier line dropped|{n} earlier lines dropped {n} по-ранен ред премахнат|{n} по-ранни реда премахнати
@plural {n} line cut|{n} lines cut {n} ред съкратен|{n} реда съкратени
# Learn panel
Learn Обучение
Explore tutorials Разгледайте уроците
For you За вас
Skills Умения
Get started Първи стъпки
Quick skill guide Бързо ръководство за умения
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Кратки практически уроци за основните инструменти, групирани по умения. Изберете умение, за да видите уроците му.
List Tutorials Извеждане на уроци
Show Tutorial Показване на урок
@msg missing `tutorial` (see learn.list) липсва `tutorial` (вижте learn.list)
@msg unknown tutorial `{id}` (see learn.list) непознат урок `{id}` (вижте learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Обновяване на отложените ефекти
Deferred Effects Отложени ефекти
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6684,6 +6684,19 @@ shape Open Otvoren
@plural {n} earlier line dropped|{n} earlier lines dropped {n} raniji red uklonjen|{n} ranija reda uklonjena|{n} ranijih redova uklonjeno
@plural {n} line cut|{n} lines cut {n} red skraćen|{n} reda skraćena|{n} redova skraćeno
# Learn panel
Learn Učenje
Explore tutorials Istražite tutorijale
For you Za vas
Skills Vještine
Get started Prvi koraci
Quick skill guide Brzi vodič kroz vještine
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Kratki praktični tutorijali za osnovne alate, grupisani po vještinama. Odaberite vještinu da vidite njene tutorijale.
List Tutorials Izlistaj tutorijale
Show Tutorial Prikaži tutorijal
@msg missing `tutorial` (see learn.list) nedostaje `tutorial` (vidi learn.list)
@msg unknown tutorial `{id}` (see learn.list) nepoznat tutorijal `{id}` (vidi learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Osvježi odgođene efekte
Deferred Effects Odgođeni efekti
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6920,6 +6920,19 @@ breakText Glyphs Glifs
@plural {n} line cut|{n} lines cut {n} línia tallada|{n} línies tallades
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` i `last` han de ser números de línies que la consola conserva, `first` abans de `last`
# Learn panel
Learn Aprèn
Explore tutorials Explora els tutorials
For you Per a vós
Skills Habilitats
Get started Primers passos
Quick skill guide Guia ràpida d’habilitats
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Tutorials pràctics breus de les eines essencials, agrupats per habilitat. Trieu una habilitat per veure’n els tutorials.
List Tutorials Llista els tutorials
Show Tutorial Mostra el tutorial
@msg missing `tutorial` (see learn.list) falta `tutorial` (vegeu learn.list)
@msg unknown tutorial `{id}` (see learn.list) tutorial desconegut `{id}` (vegeu learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Actualitza els efectes ajornats
Deferred Effects Efectes ajornats
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6939,6 +6939,19 @@ breakText Glyphs Glyfy
@plural {n} line cut|{n} lines cut {n} řádek zkrácen|{n} řádky zkráceny|{n} řádků zkráceno
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` a `last` musí být čísla řádků, které konzole uchovává, `first` před `last`
# Learn panel
Learn Výuka
Explore tutorials Prozkoumat výukové kurzy
For you Pro vás
Skills Dovednosti
Get started Začínáme
Quick skill guide Rychlý průvodce dovednostmi
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Krátké praktické výukové kurzy k základním nástrojům, seřazené podle dovedností. Vyberte dovednost a zobrazí se její kurzy.
List Tutorials Vypsat výukové kurzy
Show Tutorial Zobrazit výukový kurz
@msg missing `tutorial` (see learn.list) chybí `tutorial` (viz learn.list)
@msg unknown tutorial `{id}` (see learn.list) neznámý výukový kurz `{id}` (viz learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Obnovit odložené efekty
Deferred Effects Odložené efekty
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6646,6 +6646,19 @@ breakText Glyphs Glyffer
@plural {n} earlier line dropped|{n} earlier lines dropped {n} tidligere linje fjernet|{n} tidligere linjer fjernet
@plural {n} line cut|{n} lines cut {n} linje afkortet|{n} linjer afkortet
# Learn panel
Learn Lær
Explore tutorials Udforsk vejledninger
For you Til dig
Skills Færdigheder
Get started Kom i gang
Quick skill guide Hurtig færdighedsguide
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Korte praktiske vejledninger til de vigtigste værktøjer, ordnet efter færdighed. Vælg en færdighed for at se dens vejledninger.
List Tutorials Vis vejledninger
Show Tutorial Vis vejledning
@msg missing `tutorial` (see learn.list) `tutorial` mangler (se learn.list)
@msg unknown tutorial `{id}` (see learn.list) ukendt vejledning `{id}` (se learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Opdatér udskudte effekter
Deferred Effects Udskudte effekter
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6534,6 +6534,19 @@ breakText Glyphs Glyphen
@plural {n} line cut|{n} lines cut {n} Zeile gekürzt|{n} Zeilen gekürzt
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` und `last` müssen Nummern von Zeilen sein, die die Konsole enthält, `first` vor `last`
# Learn panel
Learn Lernen
Explore tutorials Lernprogramme entdecken
For you Für Sie
Skills Fertigkeiten
Get started Erste Schritte
Quick skill guide Kurzanleitung zu Fertigkeiten
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Kurze praktische Lernprogramme zu den wichtigsten Werkzeugen, nach Fertigkeiten geordnet. Wählen Sie eine Fertigkeit, um ihre Lernprogramme zu sehen.
List Tutorials Lernprogramme auflisten
Show Tutorial Lernprogramm anzeigen
@msg missing `tutorial` (see learn.list) `tutorial` fehlt (siehe learn.list)
@msg unknown tutorial `{id}` (see learn.list) unbekanntes Lernprogramm `{id}` (siehe learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Zurückgestellte Effekte aktualisieren
Deferred Effects Zurückgestellte Effekte
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6891,6 +6891,19 @@ breakText Glyphs Γλύφοι
@plural {n} line cut|{n} lines cut {n} γραμμή περικόπηκε|{n} γραμμές περικόπηκαν
@msg `first` and `last` must be numbers of lines the console holds, first before last Τα `first` και `last` πρέπει να είναι αριθμοί γραμμών που κρατά η κονσόλα, το `first` πριν από το `last`
# Learn panel
Learn Εκμάθηση
Explore tutorials Εξερεύνηση οδηγών εκμάθησης
For you Για εσάς
Skills Δεξιότητες
Get started Πρώτα βήματα
Quick skill guide Γρήγορος οδηγός δεξιοτήτων
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Σύντομοι πρακτικοί οδηγοί για τα βασικά εργαλεία, ομαδοποιημένοι ανά δεξιότητα. Επιλέξτε μια δεξιότητα για να δείτε τους οδηγούς της.
List Tutorials Λίστα οδηγών εκμάθησης
Show Tutorial Εμφάνιση οδηγού εκμάθησης
@msg missing `tutorial` (see learn.list) λείπει το `tutorial` (δείτε learn.list)
@msg unknown tutorial `{id}` (see learn.list) άγνωστος οδηγός εκμάθησης `{id}` (δείτε learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Ανανέωση εφέ σε αναμονή
Deferred Effects Εφέ σε αναμονή
Can't render this file because it is too large.
+13
View File
@@ -6882,6 +6882,19 @@ breakText Glyphs Glifos
@plural {n} line cut|{n} lines cut {n} línea recortada|{n} líneas recortadas
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` y `last` deben ser números de líneas que la consola conserva, `first` antes que `last`
# Learn panel
Learn Aprender
Explore tutorials Explorar tutoriales
For you Para ti
Skills Habilidades
Get started Primeros pasos
Quick skill guide Guía rápida de habilidades
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Tutoriales prácticos breves sobre las herramientas esenciales, agrupados por habilidad. Elige una habilidad para ver sus tutoriales.
List Tutorials Enumerar tutoriales
Show Tutorial Mostrar tutorial
@msg missing `tutorial` (see learn.list) falta `tutorial` (consulta learn.list)
@msg unknown tutorial `{id}` (see learn.list) tutorial desconocido `{id}` (consulta learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Actualizar efectos aplazados
Deferred Effects Efectos aplazados
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6974,6 +6974,19 @@ breakText Glyphs Merkit
@plural {n} line cut|{n} lines cut {n} rivi katkaistu|{n} riviä katkaistu
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` ja `last` on oltava konsolin säilyttämien rivien numeroita, `first` ennen arvoa `last`
# Learn panel
Learn Opi
Explore tutorials Tutustu opetusohjelmiin
For you Sinulle
Skills Taidot
Get started Aloitus
Quick skill guide Pikaopas taitoihin
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Lyhyitä käytännön opetusohjelmia tärkeimmistä työkaluista taidoittain ryhmiteltyinä. Valitse taito nähdäksesi sen opetusohjelmat.
List Tutorials Luettele opetusohjelmat
Show Tutorial Näytä opetusohjelma
@msg missing `tutorial` (see learn.list) `tutorial` puuttuu (ks. learn.list)
@msg unknown tutorial `{id}` (see learn.list) tuntematon opetusohjelma `{id}` (ks. learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Päivitä lykätyt tehosteet
Deferred Effects Lykätyt tehosteet
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6934,6 +6934,19 @@ breakText Glyphs Glyphes
@plural {n} line cut|{n} lines cut {n} ligne coupée|{n} lignes coupées
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` et `last` doivent être des numéros de lignes que la console contient, `first` avant `last`
# Learn panel
Learn Apprendre
Explore tutorials Découvrir les tutoriels
For you Pour vous
Skills Compétences
Get started Prise en main
Quick skill guide Guide rapide des compétences
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Courts tutoriels pratiques sur les outils essentiels, regroupés par compétence. Choisissez une compétence pour voir ses tutoriels.
List Tutorials Lister les tutoriels
Show Tutorial Afficher le tutoriel
@msg missing `tutorial` (see learn.list) `tutorial` manquant (voir learn.list)
@msg unknown tutorial `{id}` (see learn.list) tutoriel inconnu `{id}` (voir learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Actualiser les effets différés
Deferred Effects Effets différés
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -8372,6 +8372,19 @@ theme Appearance अपीयरेंस
@plural {n} line cut|{n} lines cut {n} पंक्ति काटी गई|{n} पंक्तियाँ काटी गईं
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` और `last` कंसोल में रखी पंक्तियों की संख्याएँ होनी चाहिए, `first` पहले और `last` बाद में
# Learn panel
Learn सीखें
Explore tutorials ट्यूटोरियल देखें
For you आपके लिए
Skills कौशल
Get started शुरू करें
Quick skill guide त्वरित कौशल गाइड
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. आवश्यक टूल के लिए छोटे व्यावहारिक ट्यूटोरियल, कौशल के अनुसार समूहित। किसी कौशल के ट्यूटोरियल देखने के लिए उसे चुनें।
List Tutorials ट्यूटोरियल की सूची
Show Tutorial ट्यूटोरियल दिखाएँ
@msg missing `tutorial` (see learn.list) लापता `tutorial` (learn.list देखें)
@msg unknown tutorial `{id}` (see learn.list) अज्ञात ट्यूटोरियल `{id}` (learn.list देखें)
# Deferred plug-in effects
Refresh Deferred Effects स्थगित इफ़ेक्ट्स रिफ्रेश करें
Deferred Effects स्थगित इफ़ेक्ट्स
Can't render this file because it is too large.
+13
View File
@@ -6923,6 +6923,19 @@ breakText Glyphs Glifák
@plural {n} line cut|{n} lines cut {n} sor levágva
@msg `first` and `last` must be numbers of lines the console holds, first before last A `first` és a `last` a konzolban tárolt sorok számai legyenek, a `first` a `last` előtt
# Learn panel
Learn Tanulás
Explore tutorials Oktatóanyagok felfedezése
For you Önnek
Skills Készségek
Get started Első lépések
Quick skill guide Gyors készségkalauz
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Rövid gyakorlati oktatóanyagok az alapvető eszközökhöz, készségek szerint csoportosítva. Válasszon egy készséget az oktatóanyagai megtekintéséhez.
List Tutorials Oktatóanyagok listázása
Show Tutorial Oktatóanyag megjelenítése
@msg missing `tutorial` (see learn.list) hiányzik: `tutorial` (lásd: learn.list)
@msg unknown tutorial `{id}` (see learn.list) ismeretlen oktatóanyag: `{id}` (lásd: learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Elhalasztott effektusok frissítése
Deferred Effects Elhalasztott effektusok
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6646,6 +6646,19 @@ breakText Glyphs Glif
@plural {n} earlier line dropped|{n} earlier lines dropped {n} baris lama dibuang
@plural {n} line cut|{n} lines cut {n} baris dipotong
# Learn panel
Learn Belajar
Explore tutorials Jelajahi tutorial
For you Untuk Anda
Skills Keterampilan
Get started Memulai
Quick skill guide Panduan cepat keterampilan
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Tutorial praktik singkat untuk alat-alat utama, dikelompokkan menurut keterampilan. Pilih keterampilan untuk melihat tutorialnya.
List Tutorials Daftar tutorial
Show Tutorial Tampilkan tutorial
@msg missing `tutorial` (see learn.list) `tutorial` tidak ada (lihat learn.list)
@msg unknown tutorial `{id}` (see learn.list) tutorial `{id}` tidak dikenal (lihat learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Segarkan efek yang ditunda
Deferred Effects Efek yang ditunda
Can't render this file because it contains an unexpected character in line 2380 and column 5.
+13
View File
@@ -6568,6 +6568,19 @@ breakText Glyphs Glifi
@plural {n} line cut|{n} lines cut {n} riga tagliata|{n} righe tagliate
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` e `last` devono essere numeri di righe presenti nella console, `first` prima di `last`
# Learn panel
Learn Impara
Explore tutorials Esplora i tutorial
For you Per te
Skills Competenze
Get started Per iniziare
Quick skill guide Guida rapida alle competenze
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Brevi tutorial pratici sugli strumenti essenziali, raggruppati per competenza. Scegli una competenza per vederne i tutorial.
List Tutorials Elenca tutorial
Show Tutorial Mostra tutorial
@msg missing `tutorial` (see learn.list) manca `tutorial` (vedi learn.list)
@msg unknown tutorial `{id}` (see learn.list) tutorial sconosciuto `{id}` (vedi learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Aggiorna effetti rimandati
Deferred Effects Effetti rimandati
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6633,6 +6633,19 @@ breakText Glyphs 字形
@plural {n} line cut|{n} lines cut {n} 行を切り詰め
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` と `last` には、コンソールが保持している行の番号を、`first` が `last` より前になるよう指定してください
# Learn panel
Learn 学ぶ
Explore tutorials チュートリアルを見る
For you おすすめ
Skills スキル
Get started はじめに
Quick skill guide クイックスキルガイド
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. 基本ツールを学ぶ短い実践チュートリアルを、スキルごとにまとめています。スキルを選ぶとチュートリアルが表示されます。
List Tutorials チュートリアルの一覧
Show Tutorial チュートリアルを表示
@msg missing `tutorial` (see learn.list) `tutorial` が指定されていません (learn.list を参照)
@msg unknown tutorial `{id}` (see learn.list) `{id}` は不明なチュートリアルです (learn.list を参照)
# Deferred plug-in effects
Refresh Deferred Effects 保留中の効果を更新
Deferred Effects 保留中の効果
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -7053,6 +7053,19 @@ font-script Japanese იაპონური
@plural {n} line cut|{n} lines cut {n} სტრიქონი შემოკლდა
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` და `last` უნდა იყოს კონსოლში შენახული სტრიქონების ნომრები და `first` უნდა იყოს `last`-ზე წინ
# Learn panel
Learn სწავლა
Explore tutorials გაკვეთილების დათვალიერება
For you თქვენთვის
Skills უნარები
Get started დაწყება
Quick skill guide უნარების სწრაფი გზამკვლევი
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. მოკლე პრაქტიკული გაკვეთილები ძირითადი ხელსაწყოებისთვის, დაჯგუფებული უნარების მიხედვით. აირჩიეთ უნარი მისი გაკვეთილების სანახავად.
List Tutorials გაკვეთილების სია
Show Tutorial გაკვეთილის ჩვენება
@msg missing `tutorial` (see learn.list) აკლია `tutorial` (იხ. learn.list)
@msg unknown tutorial `{id}` (see learn.list) უცნობი გაკვეთილი `{id}` (იხ. learn.list)
# Deferred plug-in effects
Refresh Deferred Effects გადადებული ეფექტების განახლება
Deferred Effects გადადებული ეფექტები
Can't render this file because it is too large.
+13
View File
@@ -6529,6 +6529,19 @@ breakText Glyphs 글리프
@plural {n} line cut|{n} lines cut 줄 {n}개 잘림
@msg `first` and `last` must be numbers of lines the console holds, first before last `first`와 `last`는 콘솔에 있는 줄 번호여야 하며 `first`가 `last`보다 앞서야 합니다
# Learn panel
Learn 학습
Explore tutorials 튜토리얼 살펴보기
For you 추천
Skills 기술
Get started 시작하기
Quick skill guide 빠른 기술 가이드
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. 기본 도구를 익히는 짧은 실습 튜토리얼을 기술별로 모았습니다. 기술을 선택하면 해당 튜토리얼이 표시됩니다.
List Tutorials 튜토리얼 목록
Show Tutorial 튜토리얼 표시
@msg missing `tutorial` (see learn.list) `tutorial`이 없습니다(learn.list 참조)
@msg unknown tutorial `{id}` (see learn.list) 알 수 없는 튜토리얼 `{id}`(learn.list 참조)
# Deferred plug-in effects
Refresh Deferred Effects 보류된 효과 새로 고침
Deferred Effects 보류된 효과
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6697,6 +6697,19 @@ breakText Glyphs Glyphs
@plural {n} earlier line dropped|{n} earlier lines dropped {n} eerdere regel verwijderd|{n} eerdere regels verwijderd
@plural {n} line cut|{n} lines cut {n} regel ingekort|{n} regels ingekort
# Learn panel
Learn Leren
Explore tutorials Tutorials verkennen
For you Voor jou
Skills Vaardigheden
Get started Aan de slag
Quick skill guide Snelgids voor vaardigheden
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Korte praktische tutorials voor de belangrijkste gereedschappen, gegroepeerd per vaardigheid. Kies een vaardigheid om de tutorials ervan te zien.
List Tutorials Tutorials weergeven
Show Tutorial Tutorial tonen
@msg missing `tutorial` (see learn.list) `tutorial` ontbreekt (zie learn.list)
@msg unknown tutorial `{id}` (see learn.list) onbekende tutorial `{id}` (zie learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Uitgestelde effecten vernieuwen
Deferred Effects Uitgestelde effecten
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6795,6 +6795,19 @@ breakText Glyphs Glify
@plural {n} line cut|{n} lines cut {n} skrócony wiersz|{n} skrócone wiersze|{n} skróconych wierszy
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` i `last` muszą być numerami wierszy przechowywanych w konsoli, `first` przed `last`
# Learn panel
Learn Nauka
Explore tutorials Przeglądaj samouczki
For you Dla Ciebie
Skills Umiejętności
Get started Pierwsze kroki
Quick skill guide Krótki przewodnik po umiejętnościach
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Krótkie praktyczne samouczki dotyczące podstawowych narzędzi, pogrupowane według umiejętności. Wybierz umiejętność, aby zobaczyć jej samouczki.
List Tutorials Wyświetl samouczki
Show Tutorial Pokaż samouczek
@msg missing `tutorial` (see learn.list) brak `tutorial` (patrz learn.list)
@msg unknown tutorial `{id}` (see learn.list) nieznany samouczek `{id}` (patrz learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Odśwież odroczone efekty
Deferred Effects Odroczone efekty
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -7012,6 +7012,19 @@ swatch Yellow Green Verde-amarelado
@plural {n} line cut|{n} lines cut {n} linha cortada|{n} linhas cortadas
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` e `last` devem ser números de linhas que o console mantém, `first` antes de `last`
# Learn panel
Learn Aprender
Explore tutorials Explorar tutoriais
For you Para você
Skills Habilidades
Get started Primeiros passos
Quick skill guide Guia rápido de habilidades
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Tutoriais práticos curtos das ferramentas essenciais, agrupados por habilidade. Escolha uma habilidade para ver os tutoriais dela.
List Tutorials Listar Tutoriais
Show Tutorial Mostrar Tutorial
@msg missing `tutorial` (see learn.list) falta `tutorial` (veja learn.list)
@msg unknown tutorial `{id}` (see learn.list) tutorial desconhecido `{id}` (veja learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Atualizar Efeitos Adiados
Deferred Effects Efeitos Adiados
Can't render this file because it contains an unexpected character in line 6 and column 47.
+13
View File
@@ -7028,6 +7028,19 @@ shape Open Открытый
@plural {n} line cut|{n} lines cut {n} строка обрезана|{n} строки обрезаны|{n} строк обрезано
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` и `last` должны быть номерами строк, которые хранит консоль, причём `first` не позже `last`
# Learn panel
Learn Обучение
Explore tutorials Обзор уроков
For you Для вас
Skills Навыки
Get started Начало работы
Quick skill guide Краткое руководство по навыкам
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Короткие практические уроки по основным инструментам, сгруппированные по навыкам. Выберите навык, чтобы увидеть его уроки.
List Tutorials Перечислить уроки
Show Tutorial Показать урок
@msg missing `tutorial` (see learn.list) отсутствует `tutorial` (см. learn.list)
@msg unknown tutorial `{id}` (see learn.list) неизвестный урок `{id}` (см. learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Обновить отложенные эффекты
Deferred Effects Отложенные эффекты
Can't render this file because it is too large.
+13
View File
@@ -8417,6 +8417,19 @@ digits Hindi Hindi
@plural {n} line cut|{n} lines cut {n} rad avkortad|{n} rader avkortade
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` och `last` måste vara nummer på rader som konsolen har kvar, `first` före `last`
# Learn panel
Learn Lär dig
Explore tutorials Utforska självstudier
For you För dig
Skills Färdigheter
Get started Kom igång
Quick skill guide Snabbguide till färdigheter
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Korta praktiska självstudier för de viktigaste verktygen, ordnade efter färdighet. Välj en färdighet för att se dess självstudier.
List Tutorials Lista självstudier
Show Tutorial Visa självstudie
@msg missing `tutorial` (see learn.list) `tutorial` saknas (se learn.list)
@msg unknown tutorial `{id}` (see learn.list) okänd självstudie `{id}` (se learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Uppdatera uppskjutna effekter
Deferred Effects Uppskjutna effekter
Can't render this file because it contains an unexpected character in line 5 and column 25.
+13
View File
@@ -6895,6 +6895,19 @@ breakText Glyphs Glifler
@plural {n} line cut|{n} lines cut {n} satır kesildi
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` ve `last`, konsolun tuttuğu satırların numaraları olmalı ve `first`, `last` değerinden önce gelmeli
# Learn panel
Learn Öğren
Explore tutorials Öğreticileri Keşfet
For you Sizin için
Skills Beceriler
Get started Başlarken
Quick skill guide Hızlı beceri rehberi
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Temel araçlar için becerilere göre gruplanmış kısa uygulamalı öğreticiler. Öğreticilerini görmek için bir beceri seçin.
List Tutorials Öğreticileri Listele
Show Tutorial Öğreticiyi Göster
@msg missing `tutorial` (see learn.list) `tutorial` eksik (bkz. learn.list)
@msg unknown tutorial `{id}` (see learn.list) bilinmeyen öğretici `{id}` (bkz. learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Ertelenen Efektleri Yenile
Deferred Effects Ertelenen Efektler
Can't render this file because it contains an unexpected character in line 5 and column 25.
+13
View File
@@ -6548,6 +6548,19 @@ breakText Glyphs Гліфи
@plural {n} line cut|{n} lines cut {n} рядок обрізано|{n} рядки обрізано|{n} рядків обрізано
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` і `last` мають бути номерами рядків, які зберігає консоль, причому `first` не пізніше `last`
# Learn panel
Learn Навчання
Explore tutorials Переглянути уроки
For you Для вас
Skills Навички
Get started Початок роботи
Quick skill guide Короткий посібник із навичок
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Короткі практичні уроки з основних інструментів, згруповані за навичками. Виберіть навичку, щоб побачити її уроки.
List Tutorials Перелічити уроки
Show Tutorial Показати урок
@msg missing `tutorial` (see learn.list) відсутній `tutorial` (див. learn.list)
@msg unknown tutorial `{id}` (see learn.list) невідомий урок `{id}` (див. learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Оновити відкладені ефекти
Deferred Effects Відкладені ефекти
Can't render this file because it contains an unexpected character in line 182 and column 5.
+13
View File
@@ -6549,6 +6549,19 @@ breakText Glyphs Glyph
@plural {n} line cut|{n} lines cut đã cắt {n} dòng
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` và `last` phải là số thứ tự của các dòng mà bảng điều khiển đang giữ, `first` đứng trước `last`
# Learn panel
Learn Học
Explore tutorials Khám phá hướng dẫn
For you Dành cho bạn
Skills Kỹ năng
Get started Bắt đầu
Quick skill guide Hướng dẫn nhanh về kỹ năng
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. Các hướng dẫn thực hành ngắn cho những công cụ thiết yếu, xếp theo kỹ năng. Chọn một kỹ năng để xem các hướng dẫn của nó.
List Tutorials Liệt kê hướng dẫn
Show Tutorial Hiện hướng dẫn
@msg missing `tutorial` (see learn.list) thiếu `tutorial` (xem learn.list)
@msg unknown tutorial `{id}` (see learn.list) hướng dẫn không xác định `{id}` (xem learn.list)
# Deferred plug-in effects
Refresh Deferred Effects Làm mới hiệu ứng bị hoãn
Deferred Effects Hiệu ứng bị hoãn
Can't render this file because it contains an unexpected character in line 204 and column 5.
+13
View File
@@ -6777,6 +6777,19 @@ breakText Glyphs 字形
@plural {n} line cut|{n} lines cut 已截断 {n} 行
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` 和 `last` 必须是控制台保留的行号,且 `first` 不能在 `last` 之后
# Learn panel
Learn 学习
Explore tutorials 浏览教程
For you 为你推荐
Skills 技能
Get started 入门
Quick skill guide 快速技能指南
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. 针对基本工具的简短实操教程,按技能分组。选择一项技能即可查看其教程。
List Tutorials 列出教程
Show Tutorial 显示教程
@msg missing `tutorial` (see learn.list) 缺少 `tutorial` (请参阅 learn.list)
@msg unknown tutorial `{id}` (see learn.list) 未知教程 `{id}` (请参阅 learn.list)
# Deferred plug-in effects
Refresh Deferred Effects 刷新延迟的效果
Deferred Effects 延迟的效果
Can't render this file because it contains an unexpected character in line 2 and column 73.
+13
View File
@@ -6583,6 +6583,19 @@ breakText Glyphs 字符
@plural {n} line cut|{n} lines cut 已截斷 {n} 行
@msg `first` and `last` must be numbers of lines the console holds, first before last `first` 和 `last` 必須是主控台保留的行號,且 `first` 不能在 `last` 之後
# Learn panel
Learn 學習
Explore tutorials 瀏覽教學課程
For you 為你推薦
Skills 技能
Get started 入門
Quick skill guide 快速技能指南
Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials. 針對基本工具的簡短實作教學課程,依技能分組。選擇一項技能即可查看其教學課程。
List Tutorials 列出教學課程
Show Tutorial 顯示教學課程
@msg missing `tutorial` (see learn.list) 未提供 `tutorial` (請參閱 learn.list)
@msg unknown tutorial `{id}` (see learn.list) 未知的教學課程 `{id}` (請參閱 learn.list)
# Deferred plug-in effects
Refresh Deferred Effects 重新整理延後的效果
Deferred Effects 延後的效果
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -197,6 +197,7 @@ pub static ICONS: &[(&str, &[u8])] = &[
("flip-vertical-2", include_bytes!("../../../assets/icons/flip-vertical-2.svg")),
("folder-open", include_bytes!("../../../assets/icons/folder-open.svg")),
("frame", include_bytes!("../../../assets/icons/frame.svg")),
("graduation-cap", include_bytes!("../../../assets/icons/graduation-cap.svg")),
("grid-3x3", include_bytes!("../../../assets/icons/grid-3x3.svg")),
("group", include_bytes!("../../../assets/icons/group.svg")),
("hand", include_bytes!("../../../assets/icons/hand.svg")),
+1
View File
@@ -114,6 +114,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
.or_else(|| crate::workspaces::run_command(app, id, p))
.or_else(|| crate::edit_toolbar::command(app, id, p))
.or_else(|| crate::custom_toolbars::command(app, id, p))
.or_else(|| crate::panels::learn::command(app, id, p))
{
return Some(r);
}
+1
View File
@@ -93,6 +93,7 @@ pub fn checked(app: &VectorcraftApp, id: &str, p: &Value) -> Option<bool> {
"window.taskBar" => app.ui.task_bar,
"window.pathfinder.compoundShapes" => app.ui.shape_modes_compound,
"window.taskBar.pin" => app.ui.task_bar_place.pinned,
"window.learn" => crate::panels::learn::visible(&app.ui),
"window.panel" => {
let panel = p.get("panel").and_then(Value::as_str).unwrap_or("");
normalize_panel(panel).is_some_and(|id| crate::panel_docking::showing(&app.ui, id))
+1
View File
@@ -667,6 +667,7 @@ pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec<Item>)> {
panel("Image Trace", "imageTrace"),
panel("Info", "info"),
panel("Layers", "layers"),
cp("Learn", "window.learn", json!({})),
panel("Libraries", "libraries"),
panel("Links", crate::panels::links::ID),
panel("Magic Wand", "magicWand"),
+18
View File
@@ -635,6 +635,24 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"",
"{collapsed?: bool} collapse every expanded dock column to icons (true), expand the outer dock column, Properties | Layers | Libraries (false), or toggle the outer column (omitted), as the double arrow at the top of the dock does; returns the new state",
),
(
"window.learn",
"Learn",
"",
"{show?: bool} show (true), hide (false) or toggle (omitted or null) the Learn panel, as Window › Learn and the app bar's Explore tutorials button do; any other value is an error and changes nothing; returns whether the panel shows",
),
(
"learn.list",
"List Tutorials",
"",
"{} the Learn panel's skills in the order its Skills tab lists them: {skills: [{id, title, tutorials: [{id, title, steps: number of steps}]}]}",
),
(
"learn.show",
"Show Tutorial",
"",
"{tutorial: id (see learn.list)} show the Learn panel on its Skills tab with that tutorial's numbered steps, as choosing its skill and then the tutorial does; a missing, non-string or unknown id is an error and changes nothing; returns {skill, tutorial, title, steps: [text]}",
),
("window.brightness", "UI Brightness", "", "{brightness: dark|mediumDark|mediumLight|light}"),
(
"window.workspace",
+2
View File
@@ -26,6 +26,7 @@ pub mod history;
pub mod image_trace;
pub mod info;
pub mod layers;
pub mod learn;
pub mod libraries;
pub mod library_panel;
pub mod links;
@@ -185,6 +186,7 @@ pub fn show_icon_panel(app: &mut VectorcraftApp, ui: &mut Ui, id: &str) {
css_properties::ID => css_properties::show(app, ui),
three_d::ID => three_d::show(app, ui),
script_console::ID => script_console::show(app, ui),
learn::ID => learn::show(app, ui),
crate::addons::cleanup::ID => crate::addons::cleanup::show(app, ui),
crate::addons::color::ID => crate::addons::color::show(app, ui),
crate::addons::pathtools::ID => crate::addons::pathtools::show(app, ui),
+214
View File
@@ -0,0 +1,214 @@
//! Learn panel (Window › Learn, and Explore tutorials at the right end of the app bar): short
//! hands-on tutorials grouped by skill ([`catalog::SKILLS`]). The For you tab holds the Get started
//! section with the Quick skill guide card, which opens the Skills tab. The Skills tab lists each
//! skill with an empty progress ring and its count of tutorials; a skill lists its tutorials, and a
//! tutorial previews its numbered steps. Back goes up one level. `learn.list` lists the tutorials,
//! `learn.show` opens one and `window.learn` shows or hides the panel.
pub mod catalog;
use egui::{Sense, Stroke, Ui, vec2};
use serde_json::{Value, json};
use crate::VectorcraftApp;
use crate::theme::Tokens;
pub const ID: &str = "learn";
/// The height of a skill or tutorial row.
const ROW_HEIGHT: f32 = 28.0;
/// The panel's tabs.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Tab {
#[default]
ForYou,
Skills,
}
/// What the panel shows: its tab and, on the Skills tab, the skill and the tutorial open.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct View {
pub tab: Tab,
pub skill: Option<&'static str>,
pub tutorial: Option<&'static str>,
}
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
ui.horizontal(|ui| {
for (tab, label) in [(Tab::ForYou, tl!("For you")), (Tab::Skills, tl!("Skills"))] {
if ui.selectable_label(app.ui.learn.tab == tab, label).clicked() {
app.ui.learn.tab = tab;
}
}
});
crate::widgets::divider(ui);
match app.ui.learn.tab {
Tab::ForYou => for_you(app, ui),
Tab::Skills => skills(app, ui),
}
}
/// The For you tab: Get started, with the Quick skill guide card.
fn for_you(app: &mut VectorcraftApp, ui: &mut Ui) {
let t = Tokens::get(ui.ctx());
crate::widgets::subheader(ui, tl!("Get started"));
ui.add_space(4.0);
let card = egui::Frame::NONE
.fill(t.panel_darker)
.stroke(Stroke::new(1.0, t.border))
.corner_radius(egui::CornerRadius::same(t.radius))
.inner_margin(egui::Margin::same(10))
.show(ui, |ui| {
ui.set_width(ui.available_width());
ui.horizontal(|ui| {
crate::icons::icon(ui, "graduation-cap", 18.0, t.text_strong);
ui.label(egui::RichText::new(tl!("Quick skill guide")).size(13.0).color(t.text_strong));
});
ui.add_space(4.0);
ui.add(egui::Label::new(
egui::RichText::new(tl!("Short hands-on tutorials for the essential tools, grouped by skill. Choose a skill to see its tutorials."))
.size(11.5)
.color(t.text_dim),
));
});
let resp = ui.interact(card.response.rect, ui.scope_id().with("quick-skill-guide"), Sense::click());
resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), tl!("Quick skill guide")));
if resp.clicked() {
app.ui.learn = View { tab: Tab::Skills, skill: None, tutorial: None };
}
}
/// The Skills tab: the skills, a skill's tutorials, or a tutorial's steps.
fn skills(app: &mut VectorcraftApp, ui: &mut Ui) {
let open = app.ui.learn.tutorial.and_then(catalog::tutorial);
if let Some((skill, tutorial)) = open {
if back(ui) {
app.ui.learn.skill = Some(skill.id);
app.ui.learn.tutorial = None;
}
steps(ui, tutorial);
return;
}
if let Some(skill) = app.ui.learn.skill.and_then(catalog::skill) {
if back(ui) {
app.ui.learn.skill = None;
}
crate::widgets::subheader(ui, skill.title);
for tutorial in skill.tutorials {
if row(ui, tutorial.title, None).clicked() {
app.ui.learn.tutorial = Some(tutorial.id);
}
}
return;
}
for skill in catalog::SKILLS {
if row(ui, skill.title, Some(skill.tutorials.len())).clicked() {
app.ui.learn.skill = Some(skill.id);
}
}
}
/// The Back button above a skill's tutorials and a tutorial's steps; true when it was clicked.
fn back(ui: &mut Ui) -> bool {
crate::widgets::icon_button(ui, "chevron-left", tl!("Back"), false, 22.0).clicked()
}
/// A tutorial's title and its numbered steps.
fn steps(ui: &mut Ui, tutorial: &catalog::Tutorial) {
let t = Tokens::get(ui.ctx());
ui.label(egui::RichText::new(tutorial.title).size(13.0).color(t.text_strong));
ui.add_space(4.0);
for (i, step) in tutorial.steps.iter().enumerate() {
ui.horizontal_top(|ui| {
ui.label(egui::RichText::new(format!("{}.", i + 1)).color(t.text_dim));
ui.add(egui::Label::new(egui::RichText::new(step.text).color(t.text)).wrap());
});
ui.add_space(2.0);
}
}
/// A clickable row: `title`, and for a skill (`tutorials`) a progress ring before it and the
/// count of tutorials done of all after it. No progress is kept yet, so the ring is empty.
fn row(ui: &mut Ui, title: &str, tutorials: Option<usize>) -> egui::Response {
let t = Tokens::get(ui.ctx());
let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width(), ROW_HEIGHT), Sense::click());
if resp.hovered() {
ui.painter().rect_filled(rect, egui::CornerRadius::same(4), t.hover);
}
let mut left = rect.left() + 6.0;
let mut right = rect.right() - 6.0;
let chevron = egui::Rect::from_center_size(egui::pos2(right - 6.0, rect.center().y), vec2(12.0, 12.0));
crate::icons::paint(ui, "chevron-right", chevron, t.text_dim);
right = chevron.left() - 6.0;
if let Some(total) = tutorials {
ui.painter().circle_stroke(egui::pos2(left + 8.0, rect.center().y), 7.0, Stroke::new(2.0, t.border));
left += 24.0;
let count = ui.painter().layout_no_wrap(format!("0/{total}"), egui::FontId::proportional(11.5), t.text_dim);
right -= count.size().x;
ui.painter().galley(egui::pos2(right, rect.center().y - count.size().y / 2.0), count, t.text_dim);
right -= 6.0;
}
let name = ui.painter().layout(title.to_string(), egui::FontId::proportional(12.5), t.text, f32::INFINITY);
let clip = egui::Rect::from_min_max(egui::pos2(left, rect.top()), egui::pos2(right.max(left), rect.bottom()));
ui.painter().with_clip_rect(clip).galley(egui::pos2(left, rect.center().y - name.size().y / 2.0), name, t.text);
resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), title));
resp.on_hover_cursor(egui::CursorIcon::PointingHand)
}
/// The Learn panel's UI commands: `window.learn`, `learn.list` and `learn.show`.
pub(crate) fn command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<Value, String>> {
Some(match id {
"window.learn" => toggle(app, p),
"learn.list" => Ok(list()),
"learn.show" => open(app, p),
_ => return None,
})
}
/// Whether the panel can be seen: it is the front panel, the dock is not hidden and no full screen
/// mode hides the panels.
pub(crate) fn visible(ui: &crate::state::UiState) -> bool {
ui.dock && ui.screen_mode < 3 && crate::panel_docking::showing(ui, ID)
}
/// `window.learn {show?}`: show the panel (true), hide it (false) or toggle it (omitted), as the
/// app bar's Explore tutorials button and Window › Learn do → whether it shows ([`visible`]).
/// Showing it also shows a hidden dock; in a full screen mode it makes the panel the front one.
fn toggle(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
let shown = visible(&app.ui);
let show = crate::menus::opt_bool(p, "show")?.unwrap_or(!shown);
if show && !shown {
crate::panel_docking::reveal_panel(app, ID)?;
} else if !show && shown {
app.run("window.panel", json!({ "panel": ID, "toggle": true }))?;
}
Ok(json!(visible(&app.ui)))
}
/// `learn.list`: every skill with its tutorials and their step counts.
fn list() -> Value {
let skills = catalog::SKILLS.iter().map(|s| {
let tutorials: Vec<Value> = s.tutorials.iter().map(|t| json!({"id": t.id, "title": t.title, "steps": t.steps.len()})).collect();
json!({"id": s.id, "title": s.title, "tutorials": tutorials})
});
json!({ "skills": skills.collect::<Vec<_>>() })
}
/// `learn.show {tutorial}`: show the panel on the Skills tab with that tutorial's steps → the
/// tutorial. A missing or unknown id is an error and changes nothing.
fn open(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
let key = "tutorial";
let id = match p.get(key) {
None | Some(Value::Null) => return Err("missing `tutorial` (see learn.list)".into()),
Some(Value::String(id)) => id.as_str(),
Some(_) => return Err(format!("`{key}` must be a string")),
};
let (skill, tutorial) = catalog::tutorial(id).ok_or_else(|| format!("unknown tutorial `{id}` (see learn.list)"))?;
crate::panel_docking::reveal_panel(app, ID)?;
app.ui.learn = View { tab: Tab::Skills, skill: Some(skill.id), tutorial: Some(tutorial.id) };
let steps: Vec<&str> = tutorial.steps.iter().map(|s| s.text).collect();
Ok(json!({"skill": skill.id, "tutorial": tutorial.id, "title": tutorial.title, "steps": steps}))
}
#[cfg(test)]
mod tests;
+185
View File
@@ -0,0 +1,185 @@
//! The Learn panel's tutorials, VectorCraft's own short lessons, grouped by skill. The text is
//! English data: the panel shows it as written in every interface language.
/// One step of a tutorial: what to do, and the tool it chooses or the command it runs, if any.
pub struct Step {
pub text: &'static str,
/// A tool id ([`vectorcraft_tools::catalog::tool_info`]) the step names.
pub tool: Option<&'static str>,
/// A command id the step names.
pub command: Option<&'static str>,
}
/// A tutorial: an id for `learn.show`, its title and its numbered steps.
pub struct Tutorial {
pub id: &'static str,
pub title: &'static str,
pub steps: &'static [Step],
}
/// A skill and its tutorials, in the order the panel lists them.
pub struct Skill {
pub id: &'static str,
pub title: &'static str,
pub tutorials: &'static [Tutorial],
}
/// A step that names no tool or command.
const fn say(text: &'static str) -> Step {
Step { text, tool: None, command: None }
}
/// A step that uses tool `id`.
const fn tool(text: &'static str, id: &'static str) -> Step {
Step { text, tool: Some(id), command: None }
}
/// A step that runs command `id`.
const fn command(text: &'static str, id: &'static str) -> Step {
Step { text, tool: None, command: Some(id) }
}
/// Every skill, in the order the Skills tab lists them.
pub const SKILLS: &[Skill] = &[
Skill {
id: "essentials",
title: "Best of VectorCraft",
tutorials: &[
Tutorial {
id: "draw-and-color",
title: "Draw and color a shape",
steps: &[
tool("Choose the Rectangle tool in the toolbar.", "rectangle"),
say("Drag on the artboard to draw a rectangle."),
say("With the rectangle still selected, click a color in the Swatches panel to fill it."),
command("Click the Stroke box under the tools, then click another swatch to color the outline.", "paint.toggleActive"),
],
},
Tutorial {
id: "combine-shapes",
title: "Combine shapes into one",
steps: &[
tool("Choose the Ellipse tool and draw two circles that overlap.", "ellipse"),
tool("Choose the Selection tool and drag across both circles to select them.", "selection"),
tool("Choose the Shape Builder tool and drag across the parts you want to merge.", "shapeBuilder"),
],
},
Tutorial {
id: "add-headline",
title: "Add a headline",
steps: &[
tool("Choose the Type tool and click on the artboard.", "type"),
say("Type a few words, then press Escape to finish."),
say("Pick a font and a size in the Character panel."),
],
},
],
},
Skill {
id: "draw",
title: "Draw and build",
tutorials: &[
Tutorial {
id: "pen-paths",
title: "Draw paths with the Pen",
steps: &[
tool("Choose the Pen tool.", "pen"),
say("Click to place corner points, or drag to pull out a curve."),
say("Click the first point again to close the shape, or press Enter to leave it open."),
],
},
Tutorial {
id: "smooth-curves",
title: "Draw smooth curves",
steps: &[
tool("Choose the Curvature tool.", "curvature"),
say("Click to place points: the path bends smoothly through each one."),
tool("Choose the Direct Selection tool and drag a point to reshape the curve.", "directSelection"),
],
},
Tutorial {
id: "pencil-sketch",
title: "Sketch with the Pencil",
steps: &[
tool("Choose the Pencil tool.", "pencil"),
say("Drag to draw a freehand line."),
tool("Choose the Smooth tool and drag along the line to even it out.", "smooth"),
],
},
],
},
Skill {
id: "color",
title: "Apply color",
tutorials: &[
Tutorial {
id: "fill-and-stroke",
title: "Fill and stroke",
steps: &[
tool("Select an object with the Selection tool.", "selection"),
say("Click the Fill box under the tools, then pick a color in the Color panel."),
command("Click the double arrow beside the boxes to swap the fill and the stroke.", "paint.swap"),
],
},
Tutorial {
id: "gradient",
title: "Add a gradient",
steps: &[
tool("Select an object with the Selection tool.", "selection"),
tool("Choose the Gradient tool and click the object to give it a gradient.", "gradient"),
say("Drag across the object to set the gradient's direction and length."),
],
},
Tutorial {
id: "copy-colors",
title: "Copy colors from other art",
steps: &[
tool("Select the object to color with the Selection tool.", "selection"),
tool("Choose the Eyedropper tool and click the art whose colors you want.", "eyedropper"),
],
},
],
},
Skill {
id: "type",
title: "Set type",
tutorials: &[
Tutorial {
id: "area-type",
title: "Flow text into a shape",
steps: &[
tool("Draw a rectangle for the text to fill.", "rectangle"),
tool("Choose the Area Type tool and click the rectangle's edge.", "areaType"),
say("Type or paste your text: it wraps inside the shape."),
],
},
Tutorial {
id: "type-on-path",
title: "Type along a path",
steps: &[
tool("Draw a curved path with the Pen tool.", "pen"),
tool("Choose the Type on a Path tool and click the path.", "typeOnPath"),
say("Type your text: it follows the path."),
],
},
Tutorial {
id: "outline-type",
title: "Turn type into shapes",
steps: &[
tool("Select a text object with the Selection tool.", "selection"),
command("Choose Type › Create Outlines to turn its letters into paths.", "type.createOutlines"),
],
},
],
},
];
/// The skill whose id is `id`.
pub fn skill(id: &str) -> Option<&'static Skill> {
SKILLS.iter().find(|s| s.id == id)
}
/// The tutorial whose id is `id`, with its skill.
pub fn tutorial(id: &str) -> Option<(&'static Skill, &'static Tutorial)> {
SKILLS.iter().find_map(|s| s.tutorials.iter().find(|t| t.id == id).map(|t| (s, t)))
}
+231
View File
@@ -0,0 +1,231 @@
use std::collections::HashSet;
use egui::accesskit::Node;
use serde_json::json;
use vectorcraft_engine::Session;
use super::{ID, Tab, View, catalog, show};
use crate::VectorcraftApp;
use crate::tests_labels::painted_text;
const SCREEN: egui::Rect = egui::Rect::from_min_max(egui::Pos2::ZERO, egui::pos2(1400.0, 800.0));
fn app() -> VectorcraftApp {
crate::i18n::set_current(crate::i18n::Lang::EN);
VectorcraftApp::new(Session::new(), Default::default())
}
/// One frame of `draw` with screen-reader nodes on → the nodes.
fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec<egui::Event>, draw: fn(&mut VectorcraftApp, &mut egui::Ui)) -> Vec<Node> {
let mut out = ctx.run_ui(egui::RawInput { events, screen_rect: Some(SCREEN), ..Default::default() }, |ui| draw(app, ui));
out.textures_delta.clear();
out.platform_output.accesskit_update.map(|u| u.nodes.into_iter().map(|(_, n)| n).collect()).unwrap_or_default()
}
/// The center of the node labeled `name`.
fn center(nodes: &[Node], name: &str) -> Option<egui::Pos2> {
center_where(nodes, |label| label == name)
}
/// The center of the first node whose label `pick` takes.
fn center_where(nodes: &[Node], pick: impl Fn(&str) -> bool) -> Option<egui::Pos2> {
let b = nodes.iter().find(|n| n.label().is_some_and(&pick))?.bounds()?;
Some(egui::pos2(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32))
}
/// Click the node labeled `name` in what `draw` draws.
fn click(app: &mut VectorcraftApp, ctx: &egui::Context, name: &str, draw: fn(&mut VectorcraftApp, &mut egui::Ui)) {
frame(app, ctx, vec![], draw);
let at = center(&frame(app, ctx, vec![], draw), name).unwrap_or_else(|| panic!("{name} is drawn"));
let press = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
frame(app, ctx, vec![egui::Event::PointerMoved(at), press(true)], draw);
frame(app, ctx, vec![press(false)], draw);
}
fn context() -> egui::Context {
let ctx = egui::Context::default();
ctx.enable_accesskit();
crate::theme::install_fonts(&ctx);
ctx
}
fn app_bar(app: &mut VectorcraftApp, ui: &mut egui::Ui) {
crate::chrome::app_bar(app, ui);
}
fn shown(app: &VectorcraftApp) -> bool {
crate::panel_docking::showing(&app.ui, ID)
}
/// Every skill has tutorials and every tutorial steps; ids are unique; every tool and command a
/// step names exists.
#[test]
fn the_catalog_is_valid() {
let mut skills = HashSet::new();
let mut tutorials = HashSet::new();
assert!(!catalog::SKILLS.is_empty());
for skill in catalog::SKILLS {
assert!(skills.insert(skill.id), "skill id {} is used twice", skill.id);
assert!(!skill.title.is_empty() && !skill.tutorials.is_empty(), "skill {} is empty", skill.id);
for tutorial in skill.tutorials {
assert!(tutorials.insert(tutorial.id), "tutorial id {} is used twice", tutorial.id);
assert!(!tutorial.title.is_empty() && !tutorial.steps.is_empty(), "tutorial {} is empty", tutorial.id);
for step in tutorial.steps {
assert!(step.text.ends_with('.'), "{}: {:?}", tutorial.id, step.text);
if let Some(tool) = step.tool {
assert!(vectorcraft_tools::catalog::tool_info(tool).is_some(), "{}: no tool {tool}", tutorial.id);
}
if let Some(cmd) = step.command {
let known =
crate::menus::UI_COMMANDS.iter().any(|c| c.0 == cmd) || vectorcraft_engine::cmd::command_specs().iter().any(|c| c.id == cmd);
assert!(known, "{}: no command {cmd}", tutorial.id);
}
}
}
}
}
/// Explore tutorials at the right end of the app bar shows the Learn panel, and hides it again.
#[test]
fn explore_tutorials_on_the_app_bar_shows_and_hides_the_learn_panel() {
let mut app = app();
let ctx = context();
assert!(!shown(&app));
let nodes = frame(&mut app, &ctx, vec![], app_bar);
let search = center_where(&nodes, |label| label.starts_with("Search commands and tools"));
let learn = center(&nodes, "Explore tutorials");
let (search, learn) = (search.expect("the search icon shows"), learn.expect("the app bar has Explore tutorials"));
assert!(learn.x < search.x && (learn.y - search.y).abs() < 1.0, "beside the search icon: {learn:?}, {search:?}");
click(&mut app, &ctx, "Explore tutorials", app_bar);
assert!(shown(&app), "{}", app.ui.status);
click(&mut app, &ctx, "Explore tutorials", app_bar);
assert!(!shown(&app));
}
/// The command and params of Window › Learn.
fn window_learn() -> (&'static str, serde_json::Value) {
use crate::menus::Item;
let window = crate::menus::menu_tree().into_iter().find(|(title, _)| *title == "Window").map(|(_, items)| items).unwrap();
window
.iter()
.find_map(|item| match item {
Item::Cmd("Learn", cmd, p) => Some((*cmd, p.clone())),
_ => None,
})
.expect("Window › Learn")
}
/// Window › Learn shows and hides the panel, as `window.learn` does.
#[test]
fn window_learn_shows_and_hides_the_panel() {
let mut app = app();
let (cmd, params) = window_learn();
app.run(cmd, params.clone()).unwrap();
assert!(shown(&app));
assert_eq!(crate::menus::checked(&app, cmd, &params), Some(true), "Window › Learn is checked");
app.run(cmd, params.clone()).unwrap();
assert!(!shown(&app));
assert_eq!(crate::menus::checked(&app, cmd, &params), Some(false));
assert_eq!(app.run("window.learn", json!({"show": true})).unwrap(), json!(true));
assert_eq!(app.run("window.learn", json!({"show": true})).unwrap(), json!(true), "shown stays shown");
assert_eq!(app.run("window.learn", json!({})).unwrap(), json!(false));
assert!(app.run("window.learn", json!({"show": "yes"})).is_err());
assert!(!shown(&app));
}
/// With the dock hidden, Learn as the front panel is not visible: `window.learn`, Window › Learn
/// and Explore tutorials show the dock instead of closing the panel.
#[test]
fn window_learn_shows_a_hidden_dock() {
let mut app = app();
let (cmd, params) = window_learn();
assert_eq!(app.run("window.learn", json!({"show": true})).unwrap(), json!(true));
app.ui.dock = false;
assert_eq!(crate::menus::checked(&app, cmd, &params), Some(false), "Window › Learn is unchecked");
assert_eq!(app.run("window.learn", json!({"show": false})).unwrap(), json!(false));
assert!(shown(&app) && !app.ui.dock, "hiding a panel that cannot be seen changes nothing");
app.run(cmd, params).unwrap();
assert!(app.ui.dock && shown(&app), "Window › Learn shows the dock");
// A full screen mode hides the panels: the panel stays the front one and shows when it ends.
app.ui.screen_mode = 3;
assert_eq!(app.run("window.learn", json!({})).unwrap(), json!(false));
assert!(shown(&app));
app.ui.screen_mode = 0;
assert_eq!(app.run("window.learn", json!({})).unwrap(), json!(false));
assert!(!shown(&app));
}
/// The For you tab's Quick skill guide card opens the Skills tab, which lists every skill with its
/// count of tutorials.
#[test]
fn the_skills_tab_lists_every_skill() {
let mut app = app();
let ctx = context();
let text = painted_text(&mut app, show);
assert!(text.contains("Get started") && text.contains("Quick skill guide"), "{text}");
click(&mut app, &ctx, "Quick skill guide", show);
assert_eq!(app.ui.learn.tab, Tab::Skills);
let text = painted_text(&mut app, show);
for skill in catalog::SKILLS {
assert!(text.contains(skill.title), "{} in {text}", skill.title);
assert!(text.contains(&format!("0/{}", skill.tutorials.len())), "{text}");
}
let skill = &catalog::SKILLS[1];
click(&mut app, &ctx, skill.title, show);
let text = painted_text(&mut app, show);
for tutorial in skill.tutorials {
assert!(text.contains(tutorial.title), "{} in {text}", tutorial.title);
}
}
/// Choosing a tutorial previews its numbered steps; Back goes to its skill, then to the skills.
#[test]
fn a_tutorial_previews_its_steps() {
let mut app = app();
let ctx = context();
let (skill, tutorial) = catalog::tutorial("pen-paths").unwrap();
app.ui.learn = View { tab: Tab::Skills, skill: Some(skill.id), tutorial: None };
click(&mut app, &ctx, tutorial.title, show);
assert_eq!(app.ui.learn.tutorial, Some(tutorial.id));
let text = painted_text(&mut app, show);
for (i, step) in tutorial.steps.iter().enumerate() {
assert!(text.contains(&format!("{}.", i + 1)) && text.contains(step.text), "{} in {text}", step.text);
}
click(&mut app, &ctx, "Back", show);
assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some(skill.id), tutorial: None });
click(&mut app, &ctx, "Back", show);
assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: None, tutorial: None });
}
/// `learn.show` opens the panel on a tutorial's steps; `learn.list` lists every tutorial.
#[test]
fn learn_show_opens_a_tutorial_and_learn_list_lists_them() {
let mut app = app();
let shown_tutorial = app.run("learn.show", json!({"tutorial": "gradient"})).unwrap();
assert_eq!(shown_tutorial["skill"], "color");
assert_eq!(shown_tutorial["steps"].as_array().map(Vec::len), Some(3));
assert!(shown(&app));
assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("color"), tutorial: Some("gradient") });
let list = app.run("learn.list", json!({})).unwrap();
let skills = list["skills"].as_array().unwrap();
assert_eq!(skills.len(), catalog::SKILLS.len());
let count: usize = skills.iter().map(|s| s["tutorials"].as_array().map_or(0, Vec::len)).sum();
assert_eq!(count, catalog::SKILLS.iter().map(|s| s.tutorials.len()).sum::<usize>());
assert_eq!(skills[2]["tutorials"][1], json!({"id": "gradient", "title": "Add a gradient", "steps": 3}));
}
/// A missing, mistyped or unknown tutorial id is an error and leaves the panel as it was.
#[test]
fn learn_show_rejects_a_missing_or_unknown_tutorial() {
let mut app = app();
let before = app.ui.learn.clone();
for (params, error) in [
(json!({}), "missing `tutorial` (see learn.list)"),
(json!({"tutorial": 3}), "`tutorial` must be a string"),
(json!({"tutorial": "nope"}), "unknown tutorial `nope` (see learn.list)"),
] {
assert_eq!(app.run("learn.show", params).unwrap_err(), error);
assert_eq!(app.ui.learn, before);
assert!(!shown(&app));
}
}
+5
View File
@@ -209,6 +209,7 @@ pub const ICON_PANELS: &[(&str, &str, &str)] = &[
(crate::panels::three_d::ID, "3D and Materials", "dc-cube"),
(crate::panels::variables::ID, "Variables", "chart-column"),
(crate::panels::script_console::ID, "Script Console", "message-circle"),
(crate::panels::learn::ID, "Learn", "graduation-cap"),
(crate::addons::cleanup::ID, "Artwork Cleanup", "sparkles"),
(crate::addons::color::ID, "Color Effects", "sun"),
(crate::addons::pathtools::ID, "Anchor Inspector", "dc-anchor-smooth"),
@@ -401,6 +402,9 @@ pub struct UiState {
/// Type › Find Font…'s last Find In choice (`find_font::FIND_IN`), kept while the app runs.
#[serde(skip)]
pub find_font_in: String,
/// The Learn panel's tab, skill and tutorial, kept while the app runs.
#[serde(skip)]
pub learn: crate::panels::learn::View,
pub control_bar: bool,
/// The Control bar is docked to the bottom of the window instead of the top
/// (`window.control.dock`).
@@ -1031,6 +1035,7 @@ impl Default for UiState {
transient_focus: false,
color_settings_scroll: false,
find_font_in: String::new(),
learn: Default::default(),
control_bar: false,
control_bar_bottom: false,
control_bar_hidden_groups: Default::default(),
+20
View File
@@ -2486,6 +2486,26 @@ kept with the workspace, and built-in workspaces and workspaces saved without it
{"name":"invoke_menu","arguments":{"command":"window.toolsBesideCanvas","params":{"on":true}}}
```
## Learn panel
Window › Learn and the Explore tutorials button beside the search icon at the right end of the app bar show and hide
the Learn panel. Its For you tab holds a Get started section with a Quick skill guide card; choosing the card opens
the Skills tab. The Skills tab lists VectorCraft's own skills, each with its number of tutorials and an empty progress
ring. Choosing a skill lists its tutorials, choosing a tutorial shows its numbered steps, and Back goes up one level.
The tutorial text is English. `window.learn {show?}` shows the panel (`show: true`), hides it (`show: false`) or
toggles it (`show` omitted or null), and answers whether it shows; Window › Learn and Explore tutorials run it.
The panel counts as hidden while the dock is hidden or a full screen mode hides the panels. Showing it shows a hidden
dock; in a full screen mode it makes Learn the front panel, which shows when the mode ends, and answers false. Any
other value of `show` is an error and changes nothing. `learn.list {}` → `{skills: [{id, title, tutorials: [{id, title, steps}]}]}`, in the order the Skills tab
lists them, where `steps` is the number of steps. `learn.show {tutorial}` shows the panel on its Skills tab with that
tutorial's steps → `{skill, tutorial, title, steps: [text]}`. A missing, non-string or unknown `tutorial` is an error
and changes nothing. Behavior reference:
[Quick skill guide](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html).
```json
{"name":"invoke_menu","arguments":{"command":"learn.show","params":{"tutorial":"pen-paths"}}}
```
## Ruler guides
Each ruler guide is an object of a layer or sublayer, directly in it and above its art, and takes an id from the
+1
View File
@@ -827,6 +827,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Align New Objects to Pixel Grid (`document.setup {alignNewObjectsToPixelGrid}`, a checkbox in New Document's Advanced Options and a checked item in the Transform panel menu) gives every path and compound path drawn in the document, the paths of a new grid or flare included, the pixel-aligned property, snapped in the step that draws it; it is on in the Mobile and Web presets and off in the others, saved presets and Recent keep it, and the document saves it. Type, images and groups do not take it, and art already in the document, pasted, duplicated or placed keeps its own property (VectorCraft's own rules) ([Pixel-aligned property](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/pixel-preview-mode-for-web-graphics.html); `tests::pixelalign::align_new_objects_makes_new_paths_pixel_aligned`, `align_new_objects_reaches_grids_flares_and_add_on_shapes`, `align_new_objects_is_saved_and_read_back`, `tests::newdoc::align_new_objects_to_pixel_grid_follows_the_preset`, `panels::transform::tests::align_new_objects_menu_item_toggles_the_active_document`, `dialogs::new_document::tests::align_new_objects_checkbox_follows_the_preset_and_reaches_the_document`).
- Object › Transform › Reset Bounding Box squares the box of every object inside the selected ones as well: the children of groups, compound paths, clip groups, blends, envelopes, repeats and intertwines, and the art of linked opacity masks. Ungroup and the release commands then bring back boxes square to the page (VectorCraft's own behavior; `bboxrotate::reset_bounding_box_squares_the_objects_inside_a_group`, `reset_bounding_box_squares_the_turned_members_of_a_square_group`, `reset_bounding_box_then_release_clipping_mask` and the other `reset_bounding_box_then_*` tests, `reset_bounding_box_leaves_the_art_of_an_unlinked_opacity_mask`).
- Native macOS menu bar, vector tool cursors, a ⌘K command palette, middle-button panning with any tool. Native menus explicitly end tracking on command selection, outside window presses and Escape; menu updates wait until tracking ends, and a dismissing press never starts a canvas gesture (M14.8).
- The Learn panel (Window › Learn, or Explore tutorials beside the search icon on the app bar) has a For you tab with a Get started section and a Quick skill guide card, and a Skills tab that lists VectorCraft's own skills with their tutorial counts and empty progress rings; a skill lists its tutorials, a tutorial shows its numbered steps, and Back goes up a level; `learn.list` and `learn.show {tutorial}` script it ([the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html); `panels::learn::tests`).
- The errors of the blend spine commands (Move Spine Point, Reshape Spine Segment, Add Spine Point and Delete Spine Point) show in the status bar in the interface language: each has a row in every catalog that translates all status and error messages and in Bulgarian (`blend_spine_param_errors_are_translated`).
- The errors of the parameter checks that many commands share show in the status bar in the interface language: a value that is not a number where a number goes (`object.move` with `"dx": "60 mm"`, and `object.rotate`, `object.setBounds` and the other commands that call `check_numbers`), a missing number or index, `ids` that are not a list of object ids, an object id that is not a non-negative integer or names no object, and a date that is not Unix seconds. Each has a row in every catalog that translates all status and error messages, and in Bulgarian, Danish, Indonesian and Dutch (`shared_param_check_errors_are_translated`).
- Export commands run through MCP, the control channel or the CLI refuse a key that no format takes, and a command that writes one format (`document.exportPdf`, `document.exportEps`, `document.exportDxf`) or takes one format's options (`document.pdfSettings`, `pdf.preset.save`) refuses a key that format doesn't take. An unknown key inside `marks`, `bleed`, `compression`, `output`, `advanced`, `security` or `flattener` is refused by its path.
+1
View File
@@ -373,6 +373,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
|---|---|---|
| Effect > Warp Options: style previews, orientation choices and percent controls | done | #1760; focused input, mixed-style and history checks passed; original captures reviewed. No geometry or optional Properties changes. |
| Command search: Up and Down move through the results, Enter runs the highlighted one | done | #1798 (#1757) |
| Learn panel: Window › Learn and Explore tutorials on the app bar open it; For you holds a Quick skill guide card, Skills lists VectorCraft's own skills with tutorial counts and empty progress rings, a tutorial shows its numbered steps, Back goes up a level | done | [the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html); `learn.list`, `learn.show`; `panels::learn::tests` |
| Pathfinder panel: a group selected alone counts its nested shapes, so an expanded stroked object keeps the Shape Modes enabled | done | #1642 (#1635) |
| Selected Grid Repeat shows row, column and corner handles and spacing sliders; bounding-box handles take priority where they meet | done | #2691, `tools/src/repeat_grid.rs`; [public behavior reference](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-patterns/set-grid-repeat-options.html); drags preview and commit `object.repeat.options` as one undo step, Escape cancels; Radial Repeat has no handles yet |
| Grid Repeat Options (Object › Repeat › Options…) has a Preview checkbox: the repeat on the canvas follows the fields while the dialog is open, Cancel puts the original back without a history step, OK keeps the previewed values as one undo step | done | #2651; [public behavior reference](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-patterns/set-grid-repeat-options.html) lists the Preview option for the grid options only, so Radial and Mirror Repeat Options have none; `dialogs/command.rs` tests |