From 4f08f19ce4bdfd7d6d9236c53e2d39107a626a42 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 11 Nov 2026 19:10:02 -0800 Subject: [PATCH] Learn: a Learn panel lists tutorials by skill, opened from Explore tutorials on the app bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Window › Learn and a new Explore tutorials button beside the search icon on the app bar show and hide the Learn panel. Its For you tab holds a Get started section with a Quick skill guide card that opens the Skills tab. The Skills tab 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 one level. The tutorials live in a data catalog whose test checks unique ids, that every named tool and command exists and that no skill is empty. - window.learn {show?} shows, hides or toggles the panel; showing it also shows a hidden dock, and the panel counts as hidden while the dock is hidden or a full screen mode hides the panels. - learn.list lists the skills and tutorials; learn.show {tutorial} opens one. A missing, non-string or unknown id is an error and changes nothing. - New labels and messages have rows in every catalog. The tutorial text is English. Docs: docs/mcp.md, docs/target-app-parity.md, docs/ui-parity.md. --- ASSETS.md | 1 + assets/icons/graduation-cap.svg | 17 ++ crates/mcp/src/ui_catalog.rs | 18 ++ crates/ui-egui/src/chrome.rs | 17 +- crates/ui-egui/src/i18n/bg.tsv | 13 ++ crates/ui-egui/src/i18n/bs.tsv | 13 ++ crates/ui-egui/src/i18n/ca.tsv | 13 ++ crates/ui-egui/src/i18n/cs.tsv | 13 ++ crates/ui-egui/src/i18n/da.tsv | 13 ++ crates/ui-egui/src/i18n/de.tsv | 13 ++ crates/ui-egui/src/i18n/el.tsv | 13 ++ crates/ui-egui/src/i18n/es.tsv | 13 ++ crates/ui-egui/src/i18n/fi.tsv | 13 ++ crates/ui-egui/src/i18n/fr.tsv | 13 ++ crates/ui-egui/src/i18n/hi.tsv | 13 ++ crates/ui-egui/src/i18n/hu.tsv | 13 ++ crates/ui-egui/src/i18n/id.tsv | 13 ++ crates/ui-egui/src/i18n/it.tsv | 13 ++ crates/ui-egui/src/i18n/ja.tsv | 13 ++ crates/ui-egui/src/i18n/ka.tsv | 13 ++ crates/ui-egui/src/i18n/ko.tsv | 13 ++ crates/ui-egui/src/i18n/nl.tsv | 13 ++ crates/ui-egui/src/i18n/pl.tsv | 13 ++ crates/ui-egui/src/i18n/pt-br.tsv | 13 ++ crates/ui-egui/src/i18n/ru.tsv | 13 ++ crates/ui-egui/src/i18n/sv.tsv | 13 ++ crates/ui-egui/src/i18n/tr.tsv | 13 ++ crates/ui-egui/src/i18n/uk.tsv | 13 ++ crates/ui-egui/src/i18n/vi.tsv | 13 ++ crates/ui-egui/src/i18n/zh-hans.tsv | 13 ++ crates/ui-egui/src/i18n/zh-hant.tsv | 13 ++ crates/ui-egui/src/icon_data.rs | 1 + crates/ui-egui/src/menus/run.rs | 1 + crates/ui-egui/src/menus/state.rs | 1 + crates/ui-egui/src/menus/tree.rs | 1 + crates/ui-egui/src/menus/ui_commands.rs | 18 ++ crates/ui-egui/src/panels.rs | 2 + crates/ui-egui/src/panels/learn.rs | 214 +++++++++++++++++++ crates/ui-egui/src/panels/learn/catalog.rs | 185 +++++++++++++++++ crates/ui-egui/src/panels/learn/tests.rs | 231 +++++++++++++++++++++ crates/ui-egui/src/state.rs | 5 + docs/mcp.md | 20 ++ docs/target-app-parity.md | 1 + docs/ui-parity.md | 1 + 44 files changed, 1082 insertions(+), 3 deletions(-) create mode 100644 assets/icons/graduation-cap.svg create mode 100644 crates/ui-egui/src/panels/learn.rs create mode 100644 crates/ui-egui/src/panels/learn/catalog.rs create mode 100644 crates/ui-egui/src/panels/learn/tests.rs diff --git a/ASSETS.md b/ASSETS.md index 17ab680ff..adddbc695 100644 --- a/ASSETS.md +++ b/ASSETS.md @@ -265,6 +265,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`) | | diff --git a/assets/icons/graduation-cap.svg b/assets/icons/graduation-cap.svg new file mode 100644 index 000000000..fbce25edc --- /dev/null +++ b/assets/icons/graduation-cap.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/crates/mcp/src/ui_catalog.rs b/crates/mcp/src/ui_catalog.rs index e15244580..f8a728a21 100644 --- a/crates/mcp/src/ui_catalog.rs +++ b/crates/mcp/src/ui_catalog.rs @@ -628,6 +628,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", diff --git a/crates/ui-egui/src/chrome.rs b/crates/ui-egui/src/chrome.rs index f49851ad2..0cd041723 100644 --- a/crates/ui-egui/src/chrome.rs +++ b/crates/ui-egui/src/chrome.rs @@ -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); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index d83b5ac3c..4403477ba 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -6545,6 +6545,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 Отложени ефекти diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 663e95cea..b9214734d 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -6596,6 +6596,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 diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 9c60e87e0..811620833 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -6802,6 +6802,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 diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index e8f9666fa..8b20d7de4 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -6821,6 +6821,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 diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 31915ed6b..0055bcbcb 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -6559,6 +6559,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 diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 2c5424d81..23af70b20 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -6408,6 +6408,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 diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 6071ed5be..edf89b0cc 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -6773,6 +6773,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 Εφέ σε αναμονή diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 4a74e3ae3..0c0d394e2 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -6764,6 +6764,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 diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index f2ec64f60..432c21611 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -6856,6 +6856,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 diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 807b3363b..cf5d6fed8 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -6816,6 +6816,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 diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 572a19779..06cc0b814 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -8262,6 +8262,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 स्थगित इफ़ेक्ट्स diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 8c2d2339b..9b44d6440 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -6805,6 +6805,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 diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 606ffae97..eb560e47c 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -6559,6 +6559,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 diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index bf4c9e7b5..2c2008ae7 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -6442,6 +6442,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 diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 7a12afcad..047bd025b 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -6507,6 +6507,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 保留中の効果 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index c58480c42..d5721a45b 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -6935,6 +6935,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 გადადებული ეფექტები diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 39efdecf2..d32d0b9bb 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -6403,6 +6403,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 보류된 효과 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index dd1d22edc..adb810067 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -6610,6 +6610,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 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index dd850a3a4..2998d3987 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -6677,6 +6677,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 diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index f67ae6cda..19c74b85f 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -6894,6 +6894,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 diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 8eb542582..1025eb933 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -6910,6 +6910,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 Отложенные эффекты diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index fb2c463de..c855ad450 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -8306,6 +8306,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 diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index d45a171cc..a3a2116a4 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -6777,6 +6777,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 diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index acaf20b6b..9ca154c82 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -6422,6 +6422,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 Відкладені ефекти diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 4a2bbd5a6..1ae4d83b0 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -6423,6 +6423,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 diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 0eb7c7ebe..bcdbd8421 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -6659,6 +6659,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 延迟的效果 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 084a7e770..5a87d476b 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -6457,6 +6457,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 延後的效果 diff --git a/crates/ui-egui/src/icon_data.rs b/crates/ui-egui/src/icon_data.rs index 78eb6149d..b5bde30cd 100644 --- a/crates/ui-egui/src/icon_data.rs +++ b/crates/ui-egui/src/icon_data.rs @@ -195,6 +195,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")), diff --git a/crates/ui-egui/src/menus/run.rs b/crates/ui-egui/src/menus/run.rs index 1223e1e42..0ca31b80d 100644 --- a/crates/ui-egui/src/menus/run.rs +++ b/crates/ui-egui/src/menus/run.rs @@ -81,6 +81,7 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option Option { "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)) diff --git a/crates/ui-egui/src/menus/tree.rs b/crates/ui-egui/src/menus/tree.rs index 0c46950f3..b5bb7dac3 100644 --- a/crates/ui-egui/src/menus/tree.rs +++ b/crates/ui-egui/src/menus/tree.rs @@ -658,6 +658,7 @@ pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec)> { 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"), diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index a96f13e67..edf18fcc3 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -629,6 +629,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", diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index 1106406bc..899f02639 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -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), diff --git a/crates/ui-egui/src/panels/learn.rs b/crates/ui-egui/src/panels/learn.rs new file mode 100644 index 000000000..e49832c0d --- /dev/null +++ b/crates/ui-egui/src/panels/learn.rs @@ -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) -> 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> { + 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 { + 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 = 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::>() }) +} + +/// `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 { + 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; diff --git a/crates/ui-egui/src/panels/learn/catalog.rs b/crates/ui-egui/src/panels/learn/catalog.rs new file mode 100644 index 000000000..b1ffd897d --- /dev/null +++ b/crates/ui-egui/src/panels/learn/catalog.rs @@ -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))) +} diff --git a/crates/ui-egui/src/panels/learn/tests.rs b/crates/ui-egui/src/panels/learn/tests.rs new file mode 100644 index 000000000..d4dbf8f00 --- /dev/null +++ b/crates/ui-egui/src/panels/learn/tests.rs @@ -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, draw: fn(&mut VectorcraftApp, &mut egui::Ui)) -> Vec { + 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 { + 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 { + 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, ¶ms), Some(true), "Window › Learn is checked"); + app.run(cmd, params.clone()).unwrap(); + assert!(!shown(&app)); + assert_eq!(crate::menus::checked(&app, cmd, ¶ms), 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, ¶ms), 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::()); + 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)); + } +} diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 59553120c..d454da145 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -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`). @@ -1017,6 +1021,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, toolbar: true, diff --git a/docs/mcp.md b/docs/mcp.md index 5826c5c8c..e898e2ddb 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2266,6 +2266,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 diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index b4c949f74..c08d70217 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -801,6 +801,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - The Transform panel's Align to Pixel Grid checkbox (`object.setPixelAligned`) gives paths and compound paths a pixel-aligned property, saved in the native file: their horizontal and vertical straight segments sit on whole points (half points under an odd centered stroke) with whole stroke weights, and every move, scale, rotation, reflection, Transform Each, Align move, Transform panel change, move of direct-selected anchors or of one anchor, move of a compound path's path on its own, artboard move, resize or copy that carries the art, Duplicate or paste that moves a copy, stroke edit and appearance edit that adds, removes, hides, repaints or reweighs a stroke snaps them again, while diagonal segments and curves keep their shape; a compound path made from an aligned path or compound path is aligned and snaps its paths with its own stroke; the box shows a dash for a mixed selection and is unavailable for type, images and groups ([Pixel-aligned property](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/pixel-preview-mode-for-web-graphics.html); `tests::pixelalign::turning_it_on_snaps_the_path_in_one_undo_step`, `it_stays_aligned_through_transforms`, `it_stays_aligned_when_its_artboard_moves`, `diagonal_segments_and_curves_keep_their_shape`, `type_and_images_cannot_be_aligned`, `the_property_is_saved_and_read_back`, `stroke_edits_keep_it_aligned`, `a_compound_path_of_aligned_paths_is_aligned`, `a_compound_path_of_an_aligned_compound_path_is_aligned`, `a_stroke_without_paint_snaps_to_whole_points`, `moving_direct_selected_anchors_keeps_it_aligned`, `moving_one_anchor_keeps_it_aligned`, `moving_a_path_of_an_aligned_compound_path_keeps_it_aligned`, `artboard_options_and_artboard_copies_keep_it_aligned`, `appearance_edits_keep_it_aligned`, `placed_gradients_follow_the_snapped_path`, `duplicates_and_pastes_keep_it_aligned`, `paths_added_to_or_moved_in_an_aligned_compound_path_keep_it_aligned`, `the_art_of_a_linked_opacity_mask_keeps_it_aligned`, `a_linked_opacity_mask_follows_a_snap`, `aligning_direct_selected_anchors_keeps_it_aligned`, `global_edit_keeps_its_members_aligned`, `a_path_with_live_corners_snaps_its_inner_edges`, `geometry_edits_keep_it_aligned`, `other_path_edits_keep_it_aligned`, `panels::transform::tests::align_to_pixel_grid_shows_and_sets_the_property`). - 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. diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 296826671..35eec8335 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -371,6 +371,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 |