From 3792561e1a0c01234857ad3bad22ceaaf6c779cb Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 13 Nov 2026 00:51:37 -0800 Subject: [PATCH 1/2] Learn: View more opens a tutorial's practice sample, and Next Up shows the next tutorial Under a tutorial's steps, View more opens the tutorial's practice sample as a new untitled document, which becomes the active one; open documents stay as they are, and Save asks where to save the copy. Each of the 12 tutorials has its own small SVG drawing in panels/learn/samples.rs, read only when View more is pressed. Once the sample is open, Finish tutorial marks the tutorial finished. A finished tutorial shows Next Up with the next tutorial's title in the same skill, and Next Up shows that tutorial without opening its sample. After a skill's last tutorial the panel says so and shows Back to skills; Next Up never moves to another skill. Finished tutorials are kept while the app runs; the skill rings and counts do not show them yet. The help page's short video is not part of this change. - learn.openSample, learn.finish and learn.next run the three controls and are listed in the UI command catalogs; a missing, non-string or unknown tutorial id, or a step out of order, is an error and changes nothing. - New labels and messages have rows in every catalog. - Docs: docs/mcp.md, docs/target-app-parity.md, docs/ui-parity.md and the ASSETS.md line for art generated in code. --- ASSETS.md | 2 +- crates/mcp/src/ui_catalog.rs | 18 ++ crates/ui-egui/src/i18n/bg.tsv | 12 ++ crates/ui-egui/src/i18n/bs.tsv | 12 ++ crates/ui-egui/src/i18n/ca.tsv | 12 ++ crates/ui-egui/src/i18n/cs.tsv | 12 ++ crates/ui-egui/src/i18n/da.tsv | 12 ++ crates/ui-egui/src/i18n/de.tsv | 12 ++ crates/ui-egui/src/i18n/el.tsv | 12 ++ crates/ui-egui/src/i18n/es.tsv | 12 ++ crates/ui-egui/src/i18n/fi.tsv | 12 ++ crates/ui-egui/src/i18n/fr.tsv | 12 ++ crates/ui-egui/src/i18n/hi.tsv | 12 ++ crates/ui-egui/src/i18n/hu.tsv | 12 ++ crates/ui-egui/src/i18n/id.tsv | 12 ++ crates/ui-egui/src/i18n/it.tsv | 12 ++ crates/ui-egui/src/i18n/ja.tsv | 12 ++ crates/ui-egui/src/i18n/ka.tsv | 12 ++ crates/ui-egui/src/i18n/ko.tsv | 12 ++ crates/ui-egui/src/i18n/nl.tsv | 12 ++ crates/ui-egui/src/i18n/pl.tsv | 12 ++ crates/ui-egui/src/i18n/pt-br.tsv | 12 ++ crates/ui-egui/src/i18n/ru.tsv | 12 ++ crates/ui-egui/src/i18n/sv.tsv | 12 ++ crates/ui-egui/src/i18n/tr.tsv | 12 ++ crates/ui-egui/src/i18n/uk.tsv | 12 ++ crates/ui-egui/src/i18n/vi.tsv | 12 ++ crates/ui-egui/src/i18n/zh-hans.tsv | 12 ++ crates/ui-egui/src/i18n/zh-hant.tsv | 12 ++ crates/ui-egui/src/menus/ui_commands.rs | 18 ++ crates/ui-egui/src/panels/learn.rs | 135 ++++++++++++- crates/ui-egui/src/panels/learn/catalog.rs | 7 + crates/ui-egui/src/panels/learn/samples.rs | 103 ++++++++++ crates/ui-egui/src/panels/learn/tests.rs | 216 ++++++++++++++++++++- crates/ui-egui/src/state.rs | 5 + docs/mcp.md | 20 +- docs/target-app-parity.md | 1 + docs/ui-parity.md | 2 +- 38 files changed, 837 insertions(+), 14 deletions(-) create mode 100644 crates/ui-egui/src/panels/learn/samples.rs diff --git a/ASSETS.md b/ASSETS.md index 5658882d48..dbdd8b84d8 100644 --- a/ASSETS.md +++ b/ASSETS.md @@ -20,7 +20,7 @@ Every non-code asset in this repository (icons, images, fonts, example art, colo **Policy (mandatory):** VectorCraft contains **no Adobe iconography, images, artwork, presets, swatch/brush/symbol/pattern libraries or ICC profiles**. Every asset is either original work by VectorCraft contributors, or third-party material under an open licence (OSI open source, public domain / CC0, or Creative Commons that allows redistribution), and it is attributed below. Screenshots of Adobe software are never committed. The one exception is the ArtCraft name, wordmark and logos in `docs/brand/`: ArtCraft trademarks, not open source, usable only under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt). -Generated-in-code art is original and has no file to list. This covers the default swatches, brushes, symbols, patterns, graphic styles, image-trace presets and the vector tool cursors (`crates/ui-egui/src/cursors.rs`). +Generated-in-code art is original and has no file to list. This covers the default swatches, brushes, symbols, patterns, graphic styles, image-trace presets, the vector tool cursors (`crates/ui-egui/src/cursors.rs`) and the Learn panel's practice samples (`crates/ui-egui/src/panels/learn/samples.rs`). **Font files are never committed here.** Fonts shared by the Crafting Apps live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts). The small Latin UI/document fonts below predate that rule and stay. diff --git a/crates/mcp/src/ui_catalog.rs b/crates/mcp/src/ui_catalog.rs index 342feb568c..bffe47bfa7 100644 --- a/crates/mcp/src/ui_catalog.rs +++ b/crates/mcp/src/ui_catalog.rs @@ -658,6 +658,24 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{tool: a tool id, as tool.select takes} mark where that tool is for 3 seconds without choosing it, as the link on a tool's name in a Learn tutorial step does: its toolbar button, a row of its collapsed group's flyout (which opens), its group's torn-off strip (which comes to the front), or, when no shown toolbar holds it, its row in the All Tools drawer (which opens); a Tools panel hidden with Window › Tools shows again; Escape or the next locate removes the mark; a missing, non-string or unknown id is an error and changes nothing; returns {tool, location: toolbar|flyout|strip|allTools|unavailable, toolbar: the name of the toolbar made with New Toolbar that holds it, or null}, with reason when Presentation Mode or Tab hides the toolbars", ), + ( + "learn.openSample", + "Open Tutorial Sample", + "", + "{tutorial: id (see learn.list)} open that tutorial's practice sample as a new untitled document, which becomes the active one, and move the Learn view to the tutorial, as its View more button does; each call opens another copy and open documents stay as they are; a missing, non-string or unknown id is an error and changes nothing; returns {tutorial, index, title} of the new document", + ), + ( + "learn.finish", + "Finish Tutorial", + "", + "{tutorial: id (see learn.list)} mark a tutorial whose sample learn.openSample opened as finished, as its Finish tutorial button does; finishing it again keeps one entry; a missing, non-string or unknown id, or a tutorial whose sample was not opened, is an error and changes nothing; returns {tutorial, finished: true, next: id of the next tutorial in its skill, or null after the skill's last one}", + ), + ( + "learn.next", + "Next Tutorial", + "", + "{tutorial: id (see learn.list)} show the Learn panel on the tutorial after a finished one in the same skill, as its Next Up button does, without opening a sample; a missing, non-string or unknown id, a tutorial not finished, or a skill's last tutorial is an error and changes nothing; returns {skill, tutorial, title} of the next tutorial", + ), ("window.brightness", "UI Brightness", "", "{brightness: dark|mediumDark|mediumLight|light}"), ( "window.workspace", diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 3828d9ba2d..0cc6e106ce 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -6744,6 +6744,18 @@ breakText Glyphs Глифи Show where this tool is Показване къде е този инструмент @msg missing `tutorial` (see learn.list) липсва `tutorial` (вижте learn.list) @msg unknown tutorial `{id}` (see learn.list) непознат урок `{id}` (вижте learn.list) + View more Вижте повече + Finish tutorial Завършване на урока + Next Up Следващ + You finished the last tutorial in this skill. Завършихте последния урок в това умение. + Back to skills Обратно към уменията + Open Tutorial Sample Отваряне на примерния файл на урока + Finish Tutorial Завършване на урок + Next Tutorial Следващ урок +@msg tutorial `{id}` has no practice sample урокът `{id}` няма примерен файл за упражнение +@msg open the practice sample of `{id}` first (see learn.openSample) първо отворете примерния файл за упражнение на `{id}` (вижте learn.openSample) +@msg finish `{id}` first (see learn.finish) първо завършете `{id}` (вижте learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` е последният урок в своето умение # Deferred plug-in effects Refresh Deferred Effects Обновяване на отложените ефекти diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 81fee3a079..2df8131afe 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -6798,6 +6798,18 @@ shape Open Otvoren Show where this tool is Prikaži gdje je ovaj alat @msg missing `tutorial` (see learn.list) nedostaje `tutorial` (vidi learn.list) @msg unknown tutorial `{id}` (see learn.list) nepoznat tutorijal `{id}` (vidi learn.list) + View more Prikaži više + Finish tutorial Završi tutorijal + Next Up Sljedeće + You finished the last tutorial in this skill. Završili ste posljednji tutorijal u ovoj vještini. + Back to skills Nazad na vještine + Open Tutorial Sample Otvori uzorak tutorijala + Finish Tutorial Završi tutorijal + Next Tutorial Sljedeći tutorijal +@msg tutorial `{id}` has no practice sample tutorijal `{id}` nema uzorak za vježbu +@msg open the practice sample of `{id}` first (see learn.openSample) prvo otvorite uzorak za vježbu tutorijala `{id}` (vidi learn.openSample) +@msg finish `{id}` first (see learn.finish) prvo završite `{id}` (vidi learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` je posljednji tutorijal u svojoj vještini # Deferred plug-in effects Refresh Deferred Effects Osvježi odgođene efekte diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 02ae0f4a09..71959a3767 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -7321,6 +7321,18 @@ breakText Glyphs Glifs Show where this tool is Mostra on és aquesta eina @msg missing `tutorial` (see learn.list) falta `tutorial` (vegeu learn.list) @msg unknown tutorial `{id}` (see learn.list) tutorial desconegut `{id}` (vegeu learn.list) + View more Mostra’n més + Finish tutorial Acaba el tutorial + Next Up A continuació + You finished the last tutorial in this skill. Heu acabat l’últim tutorial d’aquesta habilitat. + Back to skills Torna a les habilitats + Open Tutorial Sample Obre la mostra del tutorial + Finish Tutorial Acaba el tutorial + Next Tutorial Tutorial següent +@msg tutorial `{id}` has no practice sample el tutorial `{id}` no té cap mostra de pràctica +@msg open the practice sample of `{id}` first (see learn.openSample) obriu primer la mostra de pràctica de `{id}` (vegeu learn.openSample) +@msg finish `{id}` first (see learn.finish) acabeu primer `{id}` (vegeu learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` és l’últim tutorial de la seva habilitat # Deferred plug-in effects Refresh Deferred Effects Actualitza els efectes ajornats diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 7e3794e8a1..ca6c1cb875 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -7340,6 +7340,18 @@ breakText Glyphs Glyfy Show where this tool is Zobrazit, kde je tento nástroj @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) + View more Zobrazit více + Finish tutorial Dokončit výukový kurz + Next Up Další + You finished the last tutorial in this skill. Dokončili jste poslední výukový kurz této dovednosti. + Back to skills Zpět na dovednosti + Open Tutorial Sample Otevřít ukázku výukového kurzu + Finish Tutorial Dokončit výukový kurz + Next Tutorial Další výukový kurz +@msg tutorial `{id}` has no practice sample výukový kurz `{id}` nemá cvičnou ukázku +@msg open the practice sample of `{id}` first (see learn.openSample) nejprve otevřete cvičnou ukázku kurzu `{id}` (viz learn.openSample) +@msg finish `{id}` first (see learn.finish) nejprve dokončete `{id}` (viz learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` je poslední výukový kurz své dovednosti # Deferred plug-in effects Refresh Deferred Effects Obnovit odložené efekty diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index f7011e23bf..70411cbd00 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -6760,6 +6760,18 @@ breakText Glyphs Glyffer Show where this tool is Vis, hvor dette værktøj er @msg missing `tutorial` (see learn.list) `tutorial` mangler (se learn.list) @msg unknown tutorial `{id}` (see learn.list) ukendt vejledning `{id}` (se learn.list) + View more Vis mere + Finish tutorial Afslut vejledning + Next Up Næste + You finished the last tutorial in this skill. Du har afsluttet den sidste vejledning i denne færdighed. + Back to skills Tilbage til færdigheder + Open Tutorial Sample Åbn eksempel til vejledning + Finish Tutorial Afslut vejledning + Next Tutorial Næste vejledning +@msg tutorial `{id}` has no practice sample vejledningen `{id}` har intet øvelseseksempel +@msg open the practice sample of `{id}` first (see learn.openSample) åbn først øvelseseksemplet til `{id}` (se learn.openSample) +@msg finish `{id}` first (see learn.finish) afslut først `{id}` (se learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` er den sidste vejledning i sin færdighed # Deferred plug-in effects Refresh Deferred Effects Opdatér udskudte effekter diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 4174537f87..eceefc5fb0 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -6935,6 +6935,18 @@ breakText Glyphs Glyphen Show where this tool is Zeigen, wo sich dieses Werkzeug befindet @msg missing `tutorial` (see learn.list) `tutorial` fehlt (siehe learn.list) @msg unknown tutorial `{id}` (see learn.list) unbekanntes Lernprogramm `{id}` (siehe learn.list) + View more Mehr anzeigen + Finish tutorial Lernprogramm abschließen + Next Up Als Nächstes + You finished the last tutorial in this skill. Sie haben das letzte Lernprogramm dieser Fertigkeit abgeschlossen. + Back to skills Zurück zu den Fertigkeiten + Open Tutorial Sample Übungsdatei des Lernprogramms öffnen + Finish Tutorial Lernprogramm abschließen + Next Tutorial Nächstes Lernprogramm +@msg tutorial `{id}` has no practice sample das Lernprogramm `{id}` hat keine Übungsdatei +@msg open the practice sample of `{id}` first (see learn.openSample) öffnen Sie zuerst die Übungsdatei von `{id}` (siehe learn.openSample) +@msg finish `{id}` first (see learn.finish) schließen Sie zuerst `{id}` ab (siehe learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` ist das letzte Lernprogramm seiner Fertigkeit # Deferred plug-in effects Refresh Deferred Effects Zurückgestellte Effekte aktualisieren diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index bde2fbee78..79e834ebee 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -7292,6 +7292,18 @@ breakText Glyphs Γλύφοι Show where this tool is Εμφάνιση της θέσης αυτού του εργαλείου @msg missing `tutorial` (see learn.list) λείπει το `tutorial` (δείτε learn.list) @msg unknown tutorial `{id}` (see learn.list) άγνωστος οδηγός εκμάθησης `{id}` (δείτε learn.list) + View more Προβολή περισσότερων + Finish tutorial Ολοκλήρωση οδηγού + Next Up Επόμενο + You finished the last tutorial in this skill. Ολοκληρώσατε τον τελευταίο οδηγό εκμάθησης αυτής της δεξιότητας. + Back to skills Πίσω στις δεξιότητες + Open Tutorial Sample Άνοιγμα δείγματος οδηγού εκμάθησης + Finish Tutorial Ολοκλήρωση οδηγού εκμάθησης + Next Tutorial Επόμενος οδηγός εκμάθησης +@msg tutorial `{id}` has no practice sample ο οδηγός εκμάθησης `{id}` δεν έχει δείγμα εξάσκησης +@msg open the practice sample of `{id}` first (see learn.openSample) ανοίξτε πρώτα το δείγμα εξάσκησης του `{id}` (δείτε learn.openSample) +@msg finish `{id}` first (see learn.finish) ολοκληρώστε πρώτα το `{id}` (δείτε learn.finish) +@msg `{id}` is the last tutorial in its skill το `{id}` είναι ο τελευταίος οδηγός εκμάθησης της δεξιότητάς του # Deferred plug-in effects Refresh Deferred Effects Ανανέωση εφέ σε αναμονή diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index ff28d9dfc5..905221dc68 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -7283,6 +7283,18 @@ breakText Glyphs Glifos Show where this tool is Mostrar dónde está esta herramienta @msg missing `tutorial` (see learn.list) falta `tutorial` (consulta learn.list) @msg unknown tutorial `{id}` (see learn.list) tutorial desconocido `{id}` (consulta learn.list) + View more Ver más + Finish tutorial Terminar tutorial + Next Up A continuación + You finished the last tutorial in this skill. Has terminado el último tutorial de esta habilidad. + Back to skills Volver a las habilidades + Open Tutorial Sample Abrir muestra del tutorial + Finish Tutorial Terminar tutorial + Next Tutorial Tutorial siguiente +@msg tutorial `{id}` has no practice sample el tutorial `{id}` no tiene muestra de práctica +@msg open the practice sample of `{id}` first (see learn.openSample) abre primero la muestra de práctica de `{id}` (consulta learn.openSample) +@msg finish `{id}` first (see learn.finish) termina primero `{id}` (consulta learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` es el último tutorial de su habilidad # Deferred plug-in effects Refresh Deferred Effects Actualizar efectos aplazados diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index bb74188ed8..2227a31c33 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -7375,6 +7375,18 @@ breakText Glyphs Merkit Show where this tool is Näytä, missä tämä työkalu on @msg missing `tutorial` (see learn.list) `tutorial` puuttuu (ks. learn.list) @msg unknown tutorial `{id}` (see learn.list) tuntematon opetusohjelma `{id}` (ks. learn.list) + View more Näytä lisää + Finish tutorial Viimeistele opetusohjelma + Next Up Seuraavaksi + You finished the last tutorial in this skill. Viimeistelit tämän taidon viimeisen opetusohjelman. + Back to skills Takaisin taitoihin + Open Tutorial Sample Avaa opetusohjelman harjoitustiedosto + Finish Tutorial Viimeistele opetusohjelma + Next Tutorial Seuraava opetusohjelma +@msg tutorial `{id}` has no practice sample opetusohjelmalla `{id}` ei ole harjoitustiedostoa +@msg open the practice sample of `{id}` first (see learn.openSample) avaa ensin opetusohjelman `{id}` harjoitustiedosto (ks. learn.openSample) +@msg finish `{id}` first (see learn.finish) viimeistele ensin `{id}` (ks. learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` on taitonsa viimeinen opetusohjelma # Deferred plug-in effects Refresh Deferred Effects Päivitä lykätyt tehosteet diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 09c9c35b0d..22d5e3d376 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -7335,6 +7335,18 @@ breakText Glyphs Glyphes Show where this tool is Afficher l'emplacement de cet outil @msg missing `tutorial` (see learn.list) `tutorial` manquant (voir learn.list) @msg unknown tutorial `{id}` (see learn.list) tutoriel inconnu `{id}` (voir learn.list) + View more Voir plus + Finish tutorial Terminer le tutoriel + Next Up À suivre + You finished the last tutorial in this skill. Vous avez terminé le dernier tutoriel de cette compétence. + Back to skills Retour aux compétences + Open Tutorial Sample Ouvrir le fichier d’exercice du tutoriel + Finish Tutorial Terminer le tutoriel + Next Tutorial Tutoriel suivant +@msg tutorial `{id}` has no practice sample le tutoriel `{id}` n’a pas de fichier d’exercice +@msg open the practice sample of `{id}` first (see learn.openSample) ouvrez d’abord le fichier d’exercice de `{id}` (voir learn.openSample) +@msg finish `{id}` first (see learn.finish) terminez d’abord `{id}` (voir learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` est le dernier tutoriel de sa compétence # Deferred plug-in effects Refresh Deferred Effects Actualiser les effets différés diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 9aa4bd7b99..996e534ef0 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -8733,6 +8733,18 @@ theme Appearance अपीयरेंस Show where this tool is दिखाएँ कि यह टूल कहाँ है @msg missing `tutorial` (see learn.list) लापता `tutorial` (learn.list देखें) @msg unknown tutorial `{id}` (see learn.list) अज्ञात ट्यूटोरियल `{id}` (learn.list देखें) + View more और देखें + Finish tutorial ट्यूटोरियल पूरा करें + Next Up आगे + You finished the last tutorial in this skill. आपने इस कौशल का अंतिम ट्यूटोरियल पूरा कर लिया है। + Back to skills कौशल पर वापस जाएँ + Open Tutorial Sample ट्यूटोरियल का नमूना खोलें + Finish Tutorial ट्यूटोरियल पूरा करें + Next Tutorial अगला ट्यूटोरियल +@msg tutorial `{id}` has no practice sample ट्यूटोरियल `{id}` का कोई अभ्यास नमूना नहीं है +@msg open the practice sample of `{id}` first (see learn.openSample) पहले `{id}` का अभ्यास नमूना खोलें (learn.openSample देखें) +@msg finish `{id}` first (see learn.finish) पहले `{id}` पूरा करें (learn.finish देखें) +@msg `{id}` is the last tutorial in its skill `{id}` अपने कौशल का अंतिम ट्यूटोरियल है # Deferred plug-in effects Refresh Deferred Effects स्थगित इफ़ेक्ट्स रिफ्रेश करें diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index af6f586880..5c38da2fb4 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -7324,6 +7324,18 @@ breakText Glyphs Glifák Show where this tool is Megmutatja, hol van ez az eszköz @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) + View more Továbbiak + Finish tutorial Oktatóanyag befejezése + Next Up Következő + You finished the last tutorial in this skill. Befejezte a készség utolsó oktatóanyagát. + Back to skills Vissza a készségekhez + Open Tutorial Sample Oktatóanyag mintafájljának megnyitása + Finish Tutorial Oktatóanyag befejezése + Next Tutorial Következő oktatóanyag +@msg tutorial `{id}` has no practice sample nincs gyakorló mintafájl ehhez az oktatóanyaghoz: `{id}` +@msg open the practice sample of `{id}` first (see learn.openSample) előbb nyissa meg a gyakorló mintafájlt ehhez: `{id}` (lásd: learn.openSample) +@msg finish `{id}` first (see learn.finish) előbb fejezze be ezt: `{id}` (lásd: learn.finish) +@msg `{id}` is the last tutorial in its skill a készsége utolsó oktatóanyaga: `{id}` # Deferred plug-in effects Refresh Deferred Effects Elhalasztott effektusok frissítése diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index e0ba31ecd5..75b03775f3 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -6760,6 +6760,18 @@ breakText Glyphs Glif Show where this tool is Tampilkan letak alat ini @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) + View more Lihat selengkapnya + Finish tutorial Selesaikan tutorial + Next Up Berikutnya + You finished the last tutorial in this skill. Anda telah menyelesaikan tutorial terakhir dalam keterampilan ini. + Back to skills Kembali ke keterampilan + Open Tutorial Sample Buka contoh tutorial + Finish Tutorial Selesaikan tutorial + Next Tutorial Tutorial berikutnya +@msg tutorial `{id}` has no practice sample tutorial `{id}` tidak memiliki contoh latihan +@msg open the practice sample of `{id}` first (see learn.openSample) buka contoh latihan `{id}` terlebih dahulu (lihat learn.openSample) +@msg finish `{id}` first (see learn.finish) selesaikan `{id}` terlebih dahulu (lihat learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` adalah tutorial terakhir dalam keterampilannya # Deferred plug-in effects Refresh Deferred Effects Segarkan efek yang ditunda diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index ce0bc58ce5..1304d8cf61 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -6969,6 +6969,18 @@ breakText Glyphs Glifi Show where this tool is Mostra dove si trova questo strumento @msg missing `tutorial` (see learn.list) manca `tutorial` (vedi learn.list) @msg unknown tutorial `{id}` (see learn.list) tutorial sconosciuto `{id}` (vedi learn.list) + View more Mostra altro + Finish tutorial Termina il tutorial + Next Up Prossimo + You finished the last tutorial in this skill. Hai terminato l’ultimo tutorial di questa competenza. + Back to skills Torna alle competenze + Open Tutorial Sample Apri l’esempio del tutorial + Finish Tutorial Termina il tutorial + Next Tutorial Tutorial successivo +@msg tutorial `{id}` has no practice sample il tutorial `{id}` non ha un esempio di esercitazione +@msg open the practice sample of `{id}` first (see learn.openSample) apri prima l’esempio di esercitazione di `{id}` (vedi learn.openSample) +@msg finish `{id}` first (see learn.finish) termina prima `{id}` (vedi learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` è l’ultimo tutorial della sua competenza # Deferred plug-in effects Refresh Deferred Effects Aggiorna effetti rimandati diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 1ecce4618a..1d5d882a82 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -7034,6 +7034,18 @@ breakText Glyphs 字形 Show where this tool is このツールの場所を表示 @msg missing `tutorial` (see learn.list) `tutorial` が指定されていません (learn.list を参照) @msg unknown tutorial `{id}` (see learn.list) `{id}` は不明なチュートリアルです (learn.list を参照) + View more 詳細を見る + Finish tutorial チュートリアルを完了 + Next Up 次へ + You finished the last tutorial in this skill. このスキルの最後のチュートリアルを完了しました。 + Back to skills スキルに戻る + Open Tutorial Sample チュートリアルのサンプルを開く + Finish Tutorial チュートリアルを完了 + Next Tutorial 次のチュートリアル +@msg tutorial `{id}` has no practice sample チュートリアル `{id}` には練習用サンプルがありません +@msg open the practice sample of `{id}` first (see learn.openSample) 先に `{id}` の練習用サンプルを開いてください (learn.openSample を参照) +@msg finish `{id}` first (see learn.finish) 先に `{id}` を完了してください (learn.finish を参照) +@msg `{id}` is the last tutorial in its skill `{id}` はこのスキルの最後のチュートリアルです # Deferred plug-in effects Refresh Deferred Effects 保留中の効果を更新 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 3d6425c373..8e4de4c62d 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -7454,6 +7454,18 @@ font-script Japanese იაპონური Show where this tool is ამ ხელსაწყოს მდებარეობის ჩვენება @msg missing `tutorial` (see learn.list) აკლია `tutorial` (იხ. learn.list) @msg unknown tutorial `{id}` (see learn.list) უცნობი გაკვეთილი `{id}` (იხ. learn.list) + View more მეტის ნახვა + Finish tutorial გაკვეთილის დასრულება + Next Up შემდეგი + You finished the last tutorial in this skill. თქვენ დაასრულეთ ამ უნარის ბოლო გაკვეთილი. + Back to skills უნარებზე დაბრუნება + Open Tutorial Sample გაკვეთილის ნიმუშის გახსნა + Finish Tutorial გაკვეთილის დასრულება + Next Tutorial შემდეგი გაკვეთილი +@msg tutorial `{id}` has no practice sample გაკვეთილს `{id}` არ აქვს სავარჯიშო ნიმუში +@msg open the practice sample of `{id}` first (see learn.openSample) ჯერ გახსენით `{id}` გაკვეთილის სავარჯიშო ნიმუში (იხ. learn.openSample) +@msg finish `{id}` first (see learn.finish) ჯერ დაასრულეთ `{id}` (იხ. learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` თავისი უნარის ბოლო გაკვეთილია # Deferred plug-in effects Refresh Deferred Effects გადადებული ეფექტების განახლება diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index fb87488d64..7d8f77477b 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -6930,6 +6930,18 @@ breakText Glyphs 글리프 Show where this tool is 이 도구의 위치 표시 @msg missing `tutorial` (see learn.list) `tutorial`이 없습니다(learn.list 참조) @msg unknown tutorial `{id}` (see learn.list) 알 수 없는 튜토리얼 `{id}`(learn.list 참조) + View more 자세히 보기 + Finish tutorial 튜토리얼 완료 + Next Up 다음 + You finished the last tutorial in this skill. 이 기술의 마지막 튜토리얼을 완료했습니다. + Back to skills 기술로 돌아가기 + Open Tutorial Sample 튜토리얼 샘플 열기 + Finish Tutorial 튜토리얼 완료 + Next Tutorial 다음 튜토리얼 +@msg tutorial `{id}` has no practice sample 튜토리얼 `{id}`에 연습용 샘플이 없습니다 +@msg open the practice sample of `{id}` first (see learn.openSample) 먼저 `{id}`의 연습용 샘플을 여세요(learn.openSample 참조) +@msg finish `{id}` first (see learn.finish) 먼저 `{id}`을(를) 완료하세요(learn.finish 참조) +@msg `{id}` is the last tutorial in its skill `{id}`은(는) 해당 기술의 마지막 튜토리얼입니다 # Deferred plug-in effects Refresh Deferred Effects 보류된 효과 새로 고침 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index b3388fa82a..db793697a6 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -6811,6 +6811,18 @@ breakText Glyphs Glyphs Show where this tool is Tonen waar dit gereedschap zich bevindt @msg missing `tutorial` (see learn.list) `tutorial` ontbreekt (zie learn.list) @msg unknown tutorial `{id}` (see learn.list) onbekende tutorial `{id}` (zie learn.list) + View more Meer weergeven + Finish tutorial Tutorial voltooien + Next Up Hierna + You finished the last tutorial in this skill. Je hebt de laatste tutorial van deze vaardigheid voltooid. + Back to skills Terug naar vaardigheden + Open Tutorial Sample Oefenbestand van tutorial openen + Finish Tutorial Tutorial voltooien + Next Tutorial Volgende tutorial +@msg tutorial `{id}` has no practice sample tutorial `{id}` heeft geen oefenbestand +@msg open the practice sample of `{id}` first (see learn.openSample) open eerst het oefenbestand van `{id}` (zie learn.openSample) +@msg finish `{id}` first (see learn.finish) voltooi eerst `{id}` (zie learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` is de laatste tutorial van zijn vaardigheid # Deferred plug-in effects Refresh Deferred Effects Uitgestelde effecten vernieuwen diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 0729e35c79..372697fdef 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -7196,6 +7196,18 @@ breakText Glyphs Glify Show where this tool is Pokaż, gdzie jest to narzędzie @msg missing `tutorial` (see learn.list) brak `tutorial` (patrz learn.list) @msg unknown tutorial `{id}` (see learn.list) nieznany samouczek `{id}` (patrz learn.list) + View more Pokaż więcej + Finish tutorial Zakończ samouczek + Next Up Następny + You finished the last tutorial in this skill. Ukończono ostatni samouczek tej umiejętności. + Back to skills Powrót do umiejętności + Open Tutorial Sample Otwórz przykład samouczka + Finish Tutorial Zakończ samouczek + Next Tutorial Następny samouczek +@msg tutorial `{id}` has no practice sample samouczek `{id}` nie ma przykładu do ćwiczeń +@msg open the practice sample of `{id}` first (see learn.openSample) najpierw otwórz przykład do ćwiczeń samouczka `{id}` (patrz learn.openSample) +@msg finish `{id}` first (see learn.finish) najpierw zakończ `{id}` (patrz learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` to ostatni samouczek swojej umiejętności # Deferred plug-in effects Refresh Deferred Effects Odśwież odroczone efekty diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 36f778dbd5..728f11dafa 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -7413,6 +7413,18 @@ swatch Yellow Green Verde-amarelado Show where this tool is Mostrar onde está esta ferramenta @msg missing `tutorial` (see learn.list) falta `tutorial` (veja learn.list) @msg unknown tutorial `{id}` (see learn.list) tutorial desconhecido `{id}` (veja learn.list) + View more Ver mais + Finish tutorial Concluir tutorial + Next Up A seguir + You finished the last tutorial in this skill. Você concluiu o último tutorial desta habilidade. + Back to skills Voltar às habilidades + Open Tutorial Sample Abrir Amostra do Tutorial + Finish Tutorial Concluir Tutorial + Next Tutorial Próximo Tutorial +@msg tutorial `{id}` has no practice sample o tutorial `{id}` não tem amostra de prática +@msg open the practice sample of `{id}` first (see learn.openSample) abra primeiro a amostra de prática de `{id}` (veja learn.openSample) +@msg finish `{id}` first (see learn.finish) conclua primeiro `{id}` (veja learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` é o último tutorial da sua habilidade # Deferred plug-in effects Refresh Deferred Effects Atualizar Efeitos Adiados diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 442a9612d4..2f5708c436 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -7429,6 +7429,18 @@ shape Open Открытый Show where this tool is Показать, где находится этот инструмент @msg missing `tutorial` (see learn.list) отсутствует `tutorial` (см. learn.list) @msg unknown tutorial `{id}` (see learn.list) неизвестный урок `{id}` (см. learn.list) + View more Подробнее + Finish tutorial Завершить урок + Next Up Далее + You finished the last tutorial in this skill. Вы завершили последний урок этого навыка. + Back to skills Назад к навыкам + Open Tutorial Sample Открыть пример урока + Finish Tutorial Завершить урок + Next Tutorial Следующий урок +@msg tutorial `{id}` has no practice sample у урока `{id}` нет учебного примера +@msg open the practice sample of `{id}` first (see learn.openSample) сначала откройте учебный пример урока `{id}` (см. learn.openSample) +@msg finish `{id}` first (see learn.finish) сначала завершите `{id}` (см. learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` является последним уроком своего навыка # Deferred plug-in effects Refresh Deferred Effects Обновить отложенные эффекты diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index cc871ad8a3..a05aa652d6 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -8779,6 +8779,18 @@ digits Hindi Hindi Show where this tool is Visa var det här verktyget finns @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) + View more Visa mer + Finish tutorial Slutför självstudien + Next Up Nästa + You finished the last tutorial in this skill. Du har slutfört den sista självstudien i den här färdigheten. + Back to skills Tillbaka till färdigheter + Open Tutorial Sample Öppna övningsexempel för självstudie + Finish Tutorial Slutför självstudie + Next Tutorial Nästa självstudie +@msg tutorial `{id}` has no practice sample självstudien `{id}` har inget övningsexempel +@msg open the practice sample of `{id}` first (see learn.openSample) öppna först övningsexemplet för `{id}` (se learn.openSample) +@msg finish `{id}` first (see learn.finish) slutför först `{id}` (se learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` är den sista självstudien i sin färdighet # Deferred plug-in effects Refresh Deferred Effects Uppdatera uppskjutna effekter diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index ef8773c31b..1c310c7f6e 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -7296,6 +7296,18 @@ breakText Glyphs Glifler Show where this tool is Bu aracın nerede olduğunu 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) + View more Daha fazlasını gör + Finish tutorial Öğreticiyi bitir + Next Up Sıradaki + You finished the last tutorial in this skill. Bu becerinin son öğreticisini bitirdiniz. + Back to skills Becerilere dön + Open Tutorial Sample Öğretici Örneğini Aç + Finish Tutorial Öğreticiyi Bitir + Next Tutorial Sonraki Öğretici +@msg tutorial `{id}` has no practice sample `{id}` öğreticisinin alıştırma örneği yok +@msg open the practice sample of `{id}` first (see learn.openSample) önce `{id}` öğreticisinin alıştırma örneğini açın (bkz. learn.openSample) +@msg finish `{id}` first (see learn.finish) önce `{id}` öğreticisini bitirin (bkz. learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}`, becerisinin son öğreticisidir # Deferred plug-in effects Refresh Deferred Effects Ertelenen Efektleri Yenile diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 0bccbd8cb6..5f7de5c52f 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -6949,6 +6949,18 @@ breakText Glyphs Гліфи Show where this tool is Показати, де розташований цей інструмент @msg missing `tutorial` (see learn.list) відсутній `tutorial` (див. learn.list) @msg unknown tutorial `{id}` (see learn.list) невідомий урок `{id}` (див. learn.list) + View more Докладніше + Finish tutorial Завершити урок + Next Up Далі + You finished the last tutorial in this skill. Ви завершили останній урок цієї навички. + Back to skills Назад до навичок + Open Tutorial Sample Відкрити приклад уроку + Finish Tutorial Завершити урок + Next Tutorial Наступний урок +@msg tutorial `{id}` has no practice sample урок `{id}` не має навчального прикладу +@msg open the practice sample of `{id}` first (see learn.openSample) спочатку відкрийте навчальний приклад уроку `{id}` (див. learn.openSample) +@msg finish `{id}` first (see learn.finish) спочатку завершіть `{id}` (див. learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` є останнім уроком своєї навички # Deferred plug-in effects Refresh Deferred Effects Оновити відкладені ефекти diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 4f084029db..5fad3e2493 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -6950,6 +6950,18 @@ breakText Glyphs Glyph Show where this tool is Hiện vị trí của công cụ này @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) + View more Xem thêm + Finish tutorial Hoàn tất hướng dẫn + Next Up Tiếp theo + You finished the last tutorial in this skill. Bạn đã hoàn tất hướng dẫn cuối cùng của kỹ năng này. + Back to skills Quay lại kỹ năng + Open Tutorial Sample Mở tệp mẫu của hướng dẫn + Finish Tutorial Hoàn tất hướng dẫn + Next Tutorial Hướng dẫn tiếp theo +@msg tutorial `{id}` has no practice sample hướng dẫn `{id}` không có tệp mẫu thực hành +@msg open the practice sample of `{id}` first (see learn.openSample) hãy mở tệp mẫu thực hành của `{id}` trước (xem learn.openSample) +@msg finish `{id}` first (see learn.finish) hãy hoàn tất `{id}` trước (xem learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` là hướng dẫn cuối cùng trong kỹ năng của nó # Deferred plug-in effects Refresh Deferred Effects Làm mới 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 b04e65a65b..f6e69e53c5 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -7178,6 +7178,18 @@ breakText Glyphs 字形 Show where this tool is 显示此工具的位置 @msg missing `tutorial` (see learn.list) 缺少 `tutorial` (请参阅 learn.list) @msg unknown tutorial `{id}` (see learn.list) 未知教程 `{id}` (请参阅 learn.list) + View more 查看更多 + Finish tutorial 完成教程 + Next Up 下一个 + You finished the last tutorial in this skill. 你已完成此技能的最后一个教程。 + Back to skills 返回技能 + Open Tutorial Sample 打开教程示例 + Finish Tutorial 完成教程 + Next Tutorial 下一个教程 +@msg tutorial `{id}` has no practice sample 教程 `{id}` 没有练习示例 +@msg open the practice sample of `{id}` first (see learn.openSample) 请先打开 `{id}` 的练习示例 (请参阅 learn.openSample) +@msg finish `{id}` first (see learn.finish) 请先完成 `{id}` (请参阅 learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` 是其技能的最后一个教程 # Deferred plug-in effects Refresh Deferred Effects 刷新延迟的效果 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index be62fde8a7..c9d6631fb3 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -6984,6 +6984,18 @@ breakText Glyphs 字符 Show where this tool is 顯示此工具的位置 @msg missing `tutorial` (see learn.list) 未提供 `tutorial` (請參閱 learn.list) @msg unknown tutorial `{id}` (see learn.list) 未知的教學課程 `{id}` (請參閱 learn.list) + View more 檢視更多 + Finish tutorial 完成教學課程 + Next Up 接下來 + You finished the last tutorial in this skill. 你已完成此技能的最後一個教學課程。 + Back to skills 返回技能 + Open Tutorial Sample 開啟教學課程範例 + Finish Tutorial 完成教學課程 + Next Tutorial 下一個教學課程 +@msg tutorial `{id}` has no practice sample 教學課程 `{id}` 沒有練習範例 +@msg open the practice sample of `{id}` first (see learn.openSample) 請先開啟 `{id}` 的練習範例 (請參閱 learn.openSample) +@msg finish `{id}` first (see learn.finish) 請先完成 `{id}` (請參閱 learn.finish) +@msg `{id}` is the last tutorial in its skill `{id}` 是其技能的最後一個教學課程 # Deferred plug-in effects Refresh Deferred Effects 重新整理延後的效果 diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index 20a2c58314..8e4aaab55e 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -659,6 +659,24 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{tool: a tool id, as tool.select takes} mark where that tool is for 3 seconds without choosing it, as the link on a tool's name in a Learn tutorial step does: its toolbar button, a row of its collapsed group's flyout (which opens), its group's torn-off strip (which comes to the front), or, when no shown toolbar holds it, its row in the All Tools drawer (which opens); a Tools panel hidden with Window › Tools shows again; Escape or the next locate removes the mark; a missing, non-string or unknown id is an error and changes nothing; returns {tool, location: toolbar|flyout|strip|allTools|unavailable, toolbar: the name of the toolbar made with New Toolbar that holds it, or null}, with reason when Presentation Mode or Tab hides the toolbars", ), + ( + "learn.openSample", + "Open Tutorial Sample", + "", + "{tutorial: id (see learn.list)} open that tutorial's practice sample as a new untitled document, which becomes the active one, and move the Learn view to the tutorial, as its View more button does; each call opens another copy and open documents stay as they are; a missing, non-string or unknown id is an error and changes nothing; returns {tutorial, index, title} of the new document", + ), + ( + "learn.finish", + "Finish Tutorial", + "", + "{tutorial: id (see learn.list)} mark a tutorial whose sample learn.openSample opened as finished, as its Finish tutorial button does; finishing it again keeps one entry; a missing, non-string or unknown id, or a tutorial whose sample was not opened, is an error and changes nothing; returns {tutorial, finished: true, next: id of the next tutorial in its skill, or null after the skill's last one}", + ), + ( + "learn.next", + "Next Tutorial", + "", + "{tutorial: id (see learn.list)} show the Learn panel on the tutorial after a finished one in the same skill, as its Next Up button does, without opening a sample; a missing, non-string or unknown id, a tutorial not finished, or a skill's last tutorial is an error and changes nothing; returns {skill, tutorial, title} of the next tutorial", + ), ("window.brightness", "UI Brightness", "", "{brightness: dark|mediumDark|mediumLight|light}"), ( "window.workspace", diff --git a/crates/ui-egui/src/panels/learn.rs b/crates/ui-egui/src/panels/learn.rs index cce131c7a3..8941626a2d 100644 --- a/crates/ui-egui/src/panels/learn.rs +++ b/crates/ui-egui/src/panels/learn.rs @@ -3,11 +3,15 @@ //! 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. A tool's name in a step is a link that shows where the tool -//! is ([`locate`]). Back goes up one level. `learn.list` lists the tutorials, `learn.show` opens one, -//! `learn.locateTool` shows where a tool is and `window.learn` shows or hides the panel. +//! is ([`locate`]). Back goes up one level. Under the steps, View more opens the tutorial's practice +//! sample as a new untitled document; Finish tutorial then marks it finished, and Next Up shows the +//! next tutorial in the same skill. `learn.list` lists the tutorials, `learn.show` opens one, +//! `learn.locateTool` shows where a tool is, `learn.openSample`, `learn.finish` and `learn.next` run +//! the three practice controls, and `window.learn` shows or hides the panel. pub mod catalog; pub mod locate; +pub mod samples; use egui::{Sense, Stroke, Ui, vec2}; use serde_json::{Value, json}; @@ -35,6 +39,14 @@ pub struct View { pub tutorial: Option<&'static str>, } +/// The tutorials whose samples View more opened, and the tutorials finished, in the order they +/// were finished. Each id is listed once. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Practice { + pub opened: Vec<&'static str>, + pub finished: Vec<&'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"))] { @@ -89,6 +101,7 @@ fn skills(app: &mut VectorcraftApp, ui: &mut Ui) { app.ui.learn.tutorial = None; } steps(app, ui, tutorial); + practice(app, ui, tutorial); return; } if let Some(skill) = app.ui.learn.skill.and_then(catalog::skill) { @@ -163,6 +176,49 @@ fn tool_link(ui: &mut Ui, t: &Tokens, name: &str) -> egui::Response { resp.on_hover_cursor(egui::CursorIcon::PointingHand).on_hover_text(tl!("Show where this tool is")) } +/// The controls under a tutorial's steps: View more; Finish tutorial once its sample is open; and +/// once it is finished, Next Up with the next tutorial's title, or for the skill's last tutorial a +/// line saying so and Back to skills. +fn practice(app: &mut VectorcraftApp, ui: &mut Ui, tutorial: &'static catalog::Tutorial) { + let t = Tokens::get(ui.ctx()); + let id = tutorial.id; + let mut run = None; + ui.add_space(6.0); + ui.horizontal_wrapped(|ui| { + if ui.button(tl!("View more")).clicked() { + run = Some("learn.openSample"); + } + let done = &app.ui.learn_practice; + if done.opened.contains(&id) && !done.finished.contains(&id) && ui.button(tl!("Finish tutorial")).clicked() { + run = Some("learn.finish"); + } + }); + if app.ui.learn_practice.finished.contains(&id) { + ui.add_space(6.0); + match catalog::next(id) { + Some(next) => { + ui.horizontal_wrapped(|ui| { + if ui.button(tl!("Next Up")).clicked() { + run = Some("learn.next"); + } + ui.add(egui::Label::new(egui::RichText::new(next.title).color(t.text)).wrap()); + }); + } + None => { + ui.add(egui::Label::new(egui::RichText::new(tl!("You finished the last tutorial in this skill.")).color(t.text_dim)).wrap()); + if ui.button(tl!("Back to skills")).clicked() { + app.ui.learn = View { tab: Tab::Skills, skill: None, tutorial: None }; + } + } + } + } + if let Some(cmd) = run + && let Err(e) = app.run(cmd, json!({ "tutorial": id })) + { + app.status(e); + } +} + /// 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 { @@ -191,13 +247,17 @@ fn row(ui: &mut Ui, title: &str, tutorials: Option) -> egui::Response { resp.on_hover_cursor(egui::CursorIcon::PointingHand) } -/// The Learn panel's UI commands: `window.learn`, `learn.list`, `learn.show` and `learn.locateTool`. +/// The Learn panel's UI commands: `window.learn`, `learn.list`, `learn.show`, `learn.locateTool`, +/// `learn.openSample`, `learn.finish` and `learn.next`. 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), "learn.locateTool" => locate::locate_tool(app, p), + "learn.openSample" => open_sample(app, p), + "learn.finish" => finish(app, p), + "learn.next" => next(app, p), _ => return None, }) } @@ -234,18 +294,73 @@ fn list() -> Value { /// `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)"))?; + let (skill, tutorial) = tutorial_param(p)?; 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 = tutorial.steps.iter().map(catalog::Step::plain).collect(); Ok(json!({"skill": skill.id, "tutorial": tutorial.id, "title": tutorial.title, "steps": steps})) } +/// The tutorial `p`'s `tutorial` names, with its skill. A missing, non-string or unknown id is an +/// error. +fn tutorial_param(p: &Value) -> Result<(&'static catalog::Skill, &'static catalog::Tutorial), 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")), + }; + catalog::tutorial(id).ok_or_else(|| format!("unknown tutorial `{id}` (see learn.list)")) +} + +/// `learn.openSample {tutorial}`: View more. Open that tutorial's practice sample as a new untitled +/// document, which becomes the active one, and move the Learn view to the tutorial → `{tutorial, +/// index, title}` of the new document. Each call opens another copy; open documents stay as they +/// are. An id that is not valid, or a sample that does not read, is an error and changes nothing. +fn open_sample(app: &mut VectorcraftApp, p: &Value) -> Result { + let (skill, tutorial) = tutorial_param(p)?; + let id = tutorial.id; + let svg = samples::sample(id).ok_or_else(|| format!("tutorial `{id}` has no practice sample"))?; + let name = format!("{id}.svg"); + let opened = vectorcraft_engine::cmd::fileio::open_template(&mut app.session, &name, svg.as_bytes(), None).map_err(|e| e.to_string())?; + app.sync_views(); + if !app.ui.learn_practice.opened.contains(&id) { + app.ui.learn_practice.opened.push(id); + } + app.ui.learn = View { tab: Tab::Skills, skill: Some(skill.id), tutorial: Some(id) }; + Ok(json!({"tutorial": id, "index": opened["index"], "title": opened["title"]})) +} + +/// `learn.finish {tutorial}`: Finish tutorial. Mark a tutorial whose sample was opened finished → +/// `{tutorial, finished: true, next}`, where `next` is the id of the next tutorial in its skill, or +/// null after the skill's last one. Finishing it again keeps one entry. The Learn view stays as it is. +fn finish(app: &mut VectorcraftApp, p: &Value) -> Result { + let (_, tutorial) = tutorial_param(p)?; + let id = tutorial.id; + let practice = &mut app.ui.learn_practice; + if !practice.opened.contains(&id) { + return Err(format!("open the practice sample of `{id}` first (see learn.openSample)")); + } + if !practice.finished.contains(&id) { + practice.finished.push(id); + } + Ok(json!({"tutorial": id, "finished": true, "next": catalog::next(id).map(|t| t.id)})) +} + +/// `learn.next {tutorial}`: Next Up. Show the panel on the tutorial after a finished one in the +/// same skill → `{skill, tutorial, title}` of that tutorial. It opens no sample. An unfinished +/// tutorial, or a skill's last tutorial, is an error and changes nothing. +fn next(app: &mut VectorcraftApp, p: &Value) -> Result { + let (skill, tutorial) = tutorial_param(p)?; + let id = tutorial.id; + if !app.ui.learn_practice.finished.contains(&id) { + return Err(format!("finish `{id}` first (see learn.finish)")); + } + let after = catalog::next(id).ok_or_else(|| format!("`{id}` is the last tutorial in its skill"))?; + crate::panel_docking::reveal_panel(app, ID)?; + app.ui.learn = View { tab: Tab::Skills, skill: Some(skill.id), tutorial: Some(after.id) }; + Ok(json!({"skill": skill.id, "tutorial": after.id, "title": after.title})) +} + #[cfg(test)] mod tests; diff --git a/crates/ui-egui/src/panels/learn/catalog.rs b/crates/ui-egui/src/panels/learn/catalog.rs index 98f0d6f355..57a8f30e31 100644 --- a/crates/ui-egui/src/panels/learn/catalog.rs +++ b/crates/ui-egui/src/panels/learn/catalog.rs @@ -201,3 +201,10 @@ pub fn skill(id: &str) -> Option<&'static 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))) } + +/// The tutorial after the one whose id is `id` in the same skill; none for a skill's last tutorial. +pub fn next(id: &str) -> Option<&'static Tutorial> { + let (skill, _) = tutorial(id)?; + let at = skill.tutorials.iter().position(|t| t.id == id)?; + skill.tutorials.get(at + 1) +} diff --git a/crates/ui-egui/src/panels/learn/samples.rs b/crates/ui-egui/src/panels/learn/samples.rs new file mode 100644 index 0000000000..d5e2758f59 --- /dev/null +++ b/crates/ui-egui/src/panels/learn/samples.rs @@ -0,0 +1,103 @@ +//! The practice samples that a tutorial's View more opens: one small original SVG drawing for each +//! tutorial, with only the art its steps work on. Each one opens as a new untitled document. + +/// The largest sample, in bytes: the catalog test keeps every sample under it. +pub const MAX_SAMPLE_BYTES: usize = 16 * 1024; + +/// Every tutorial's sample, by tutorial id, in the catalog's order. +pub const SAMPLES: &[(&str, &str)] = &[ + ( + "draw-and-color", + r##" + + +"##, + ), + ( + "combine-shapes", + r##" + + + +"##, + ), + ( + "add-headline", + r##" + + +"##, + ), + ( + "pen-paths", + r##" + + + + + +"##, + ), + ( + "smooth-curves", + r##" + + +"##, + ), + ( + "pencil-sketch", + r##" + + +"##, + ), + ( + "fill-and-stroke", + r##" + + +"##, + ), + ( + "gradient", + r##" + + +"##, + ), + ( + "copy-colors", + r##" + + + +"##, + ), + ( + "area-type", + r##" + + +"##, + ), + ( + "type-on-path", + r##" + + +"##, + ), + ( + "outline-type", + r##" + Outline + +"##, + ), +]; + +/// The sample of the tutorial whose id is `id`. +pub fn sample(id: &str) -> Option<&'static str> { + SAMPLES.iter().find(|(tutorial, _)| *tutorial == id).map(|(_, svg)| *svg) +} diff --git a/crates/ui-egui/src/panels/learn/tests.rs b/crates/ui-egui/src/panels/learn/tests.rs index 7014785eda..f302d8f436 100644 --- a/crates/ui-egui/src/panels/learn/tests.rs +++ b/crates/ui-egui/src/panels/learn/tests.rs @@ -4,7 +4,7 @@ use egui::accesskit::Node; use serde_json::json; use vectorcraft_engine::Session; -use super::{ID, Tab, View, catalog, show}; +use super::{ID, Practice, Tab, View, catalog, samples, show}; use crate::VectorcraftApp; use crate::tests_labels::painted_text; @@ -228,6 +228,220 @@ fn learn_show_opens_a_tutorial_and_learn_list_lists_them() { assert_eq!(skills[2]["tutorials"][1], json!({"id": "gradient", "title": "Add a gradient", "steps": 3})); } +/// Every tutorial has one sample of its own, in the catalog's order, each one small and read +/// without warnings into art of its own. +#[test] +fn every_tutorial_has_its_own_readable_sample() { + let ids: Vec<&str> = catalog::SKILLS.iter().flat_map(|s| s.tutorials.iter().map(|t| t.id)).collect(); + let sampled: Vec<&str> = samples::SAMPLES.iter().map(|(id, _)| *id).collect(); + assert_eq!(sampled, ids, "one sample for each tutorial, in the catalog's order"); + let mut texts = HashSet::new(); + for (id, svg) in samples::SAMPLES { + assert!(svg.len() <= samples::MAX_SAMPLE_BYTES, "{id}: {} bytes", svg.len()); + assert!(texts.insert(*svg), "{id} reuses another tutorial's sample"); + let loaded = vectorcraft_engine::cmd::fileio::load(&format!("{id}.svg"), svg.as_bytes()).unwrap_or_else(|e| panic!("{id}: {e}")); + assert!(loaded.warnings.is_empty(), "{id}: {:?}", loaded.warnings); + let mut art = 0; + loaded.doc.walk(|n| art += usize::from(!n.is_layer())); + assert!(art > 0, "{id} has no art"); + } +} + +/// Next Up stays in the tutorial's skill: a skill's last tutorial, the catalog's last one +/// included, has no next tutorial. +#[test] +fn the_next_tutorial_is_the_next_one_in_the_same_skill() { + for skill in catalog::SKILLS { + for pair in skill.tutorials.windows(2) { + assert_eq!(catalog::next(pair[0].id).map(|t| t.id), Some(pair[1].id)); + } + let last = skill.tutorials.last().unwrap(); + assert!(catalog::next(last.id).is_none(), "{} is the last of {}", last.id, skill.id); + } + assert!(catalog::next("nope").is_none()); +} + +/// View more opens the tutorial's sample as a new untitled document beside a modified one, which +/// stays as it was; another tutorial opens another sample, and View more again opens another copy. +#[test] +fn view_more_opens_the_tutorial_s_sample_as_a_new_untitled_document() { + let mut app = app(); + let ctx = context(); + app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); + app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 30, "height": 20})).unwrap(); + let work = app.session.documents()[0].doc.node_count(); + let (skill, tutorial) = catalog::tutorial("pen-paths").unwrap(); + app.ui.learn = View { tab: Tab::Skills, skill: Some(skill.id), tutorial: Some(tutorial.id) }; + click(&mut app, &ctx, "View more", show); + let docs = app.session.documents(); + assert_eq!(docs.len(), 2, "{}", app.ui.status); + assert_eq!(app.session.active_index(), Some(1)); + assert_eq!(docs[0].doc.node_count(), work, "the open document stays as it was"); + assert!(docs[1].path.is_none() && docs[1].title().starts_with("Untitled-"), "{}", docs[1].title()); + let pen = docs[1].doc.node_count(); + assert_eq!(app.ui.learn_practice, Practice { opened: vec!["pen-paths"], finished: vec![] }); + + let r = app.run("learn.openSample", json!({"tutorial": "gradient"})).unwrap(); + assert_eq!(r["tutorial"], "gradient"); + assert_eq!(r["index"], 2); + assert_eq!(r["title"], json!(app.session.documents()[2].title())); + assert_ne!(app.session.documents()[2].doc.node_count(), pen, "another tutorial, another sample"); + assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("color"), tutorial: Some("gradient") }); + app.run("learn.openSample", json!({"tutorial": "gradient"})).unwrap(); + assert_eq!(app.session.documents().len(), 4, "View more again opens another copy"); + assert_eq!(app.ui.learn_practice.opened, ["pen-paths", "gradient"]); +} + +/// Finish tutorial shows once the sample is open; finishing shows Next Up with the next +/// tutorial's title, and Next Up shows that tutorial without opening its sample. +#[test] +fn finishing_a_tutorial_offers_next_up_in_its_skill() { + 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: Some(tutorial.id) }; + let text = painted_text(&mut app, show); + assert!(text.contains("View more") && !text.contains("Finish tutorial") && !text.contains("Next Up"), "{text}"); + click(&mut app, &ctx, "View more", show); + let text = painted_text(&mut app, show); + assert!(text.contains("Finish tutorial") && !text.contains("Next Up"), "{text}"); + click(&mut app, &ctx, "Finish tutorial", show); + assert_eq!(app.ui.learn_practice.finished, ["pen-paths"]); + let text = painted_text(&mut app, show); + assert!(text.contains("Next Up") && text.contains("Draw smooth curves") && !text.contains("Finish tutorial"), "{text}"); + let docs = app.session.documents().len(); + click(&mut app, &ctx, "Next Up", show); + assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("draw"), tutorial: Some("smooth-curves") }); + assert_eq!(app.session.documents().len(), docs, "Next Up opens no sample"); + let text = painted_text(&mut app, show); + assert!(text.contains("View more") && !text.contains("Finish tutorial") && !text.contains("Next Up"), "{text}"); +} + +/// One press and release of `key` in what `draw` draws. +fn key(app: &mut VectorcraftApp, ctx: &egui::Context, key: egui::Key, draw: fn(&mut VectorcraftApp, &mut egui::Ui)) { + let event = |pressed| egui::Event::Key { key, physical_key: None, pressed, repeat: false, modifiers: Default::default() }; + frame(app, ctx, vec![event(true)], draw); + frame(app, ctx, vec![event(false)], draw); +} + +/// Press Tab until the control labeled `name` has the keyboard focus. +fn tab_to(app: &mut VectorcraftApp, ctx: &egui::Context, name: &str, draw: fn(&mut VectorcraftApp, &mut egui::Ui)) { + for _ in 0..32 { + key(app, ctx, egui::Key::Tab, draw); + let at = center(&frame(app, ctx, vec![], draw), name).unwrap_or_else(|| panic!("{name} is drawn")); + if ctx.memory(|m| m.focused()).and_then(|id| ctx.read_response(id)).is_some_and(|r| r.rect.contains(at)) { + return; + } + } + panic!("Tab did not reach {name}"); +} + +/// View more, Finish tutorial and Next Up are reached with Tab and pressed with Enter. +#[test] +fn the_practice_controls_work_from_the_keyboard() { + 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: Some(tutorial.id) }; + frame(&mut app, &ctx, vec![], show); + tab_to(&mut app, &ctx, "View more", show); + key(&mut app, &ctx, egui::Key::Enter, show); + assert_eq!(app.session.documents().len(), 1, "{}", app.ui.status); + assert_eq!(app.ui.learn_practice.opened, ["pen-paths"]); + tab_to(&mut app, &ctx, "Finish tutorial", show); + key(&mut app, &ctx, egui::Key::Enter, show); + assert_eq!(app.ui.learn_practice.finished, ["pen-paths"]); + tab_to(&mut app, &ctx, "Next Up", show); + key(&mut app, &ctx, egui::Key::Enter, show); + assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("draw"), tutorial: Some("smooth-curves") }); + assert_eq!(app.session.documents().len(), 1, "Next Up opens no sample"); +} + +/// The panel 150 points wide. +fn narrow(app: &mut VectorcraftApp, ui: &mut egui::Ui) { + ui.allocate_ui(egui::vec2(150.0, 800.0), |ui| { + ui.set_max_width(150.0); + show(app, ui); + }); +} + +/// In a narrow panel the practice controls wrap inside it. +#[test] +fn the_practice_controls_fit_a_narrow_panel() { + let mut app = app(); + let ctx = context(); + app.run("learn.openSample", json!({"tutorial": "pen-paths"})).unwrap(); + frame(&mut app, &ctx, vec![], narrow); + let nodes = frame(&mut app, &ctx, vec![], narrow); + for name in ["View more", "Finish tutorial"] { + let node = nodes.iter().find(|n| n.label() == Some(name)).unwrap_or_else(|| panic!("{name} is drawn")); + let b = node.bounds().unwrap(); + assert!(b.x1 <= 151.0, "{name} ends at {}", b.x1); + } +} + +/// A skill's last tutorial, finished, says so and offers Back to skills instead of Next Up, even +/// when another skill follows; `learn.next` there is an error. +#[test] +fn the_last_tutorial_of_a_skill_offers_back_to_skills() { + let mut app = app(); + let ctx = context(); + for id in ["add-headline", "outline-type"] { + app.run("learn.openSample", json!({"tutorial": id})).unwrap(); + assert_eq!(app.run("learn.finish", json!({"tutorial": id})).unwrap(), json!({"tutorial": id, "finished": true, "next": null})); + let text = painted_text(&mut app, show); + assert!(text.contains("You finished the last tutorial in this skill.") && !text.contains("Next Up"), "{text}"); + let before = app.ui.learn.clone(); + assert_eq!(app.run("learn.next", json!({"tutorial": id})).unwrap_err(), format!("`{id}` is the last tutorial in its skill")); + assert_eq!(app.ui.learn, before); + click(&mut app, &ctx, "Back to skills", show); + assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: None, tutorial: None }); + } +} + +/// `learn.finish` needs the sample opened, and finishing again keeps one entry; `learn.next` needs +/// the tutorial finished and answers the next tutorial. +#[test] +fn learn_finish_and_learn_next_follow_the_practice() { + let mut app = app(); + let open = "open the practice sample of `gradient` first (see learn.openSample)"; + assert_eq!(app.run("learn.finish", json!({"tutorial": "gradient"})).unwrap_err(), open); + assert_eq!(app.run("learn.next", json!({"tutorial": "gradient"})).unwrap_err(), "finish `gradient` first (see learn.finish)"); + assert_eq!(app.ui.learn_practice, Practice::default()); + app.run("learn.openSample", json!({"tutorial": "gradient"})).unwrap(); + assert_eq!(app.run("learn.next", json!({"tutorial": "gradient"})).unwrap_err(), "finish `gradient` first (see learn.finish)"); + for _ in 0..2 { + let r = app.run("learn.finish", json!({"tutorial": "gradient"})).unwrap(); + assert_eq!(r, json!({"tutorial": "gradient", "finished": true, "next": "copy-colors"})); + } + assert_eq!(app.ui.learn_practice.finished, ["gradient"], "finishing again keeps one entry"); + let r = app.run("learn.next", json!({"tutorial": "gradient"})).unwrap(); + assert_eq!(r, json!({"skill": "color", "tutorial": "copy-colors", "title": "Copy colors from other art"})); + assert!(shown(&app)); + assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("color"), tutorial: Some("copy-colors") }); +} + +/// The practice commands reject a missing, mistyped or unknown tutorial id and change nothing. +#[test] +fn the_practice_commands_reject_a_missing_or_unknown_tutorial() { + let mut app = app(); + app.run("file.new", json!({"width": 400, "height": 300})).unwrap(); + for cmd in ["learn.openSample", "learn.finish", "learn.next"] { + for (params, error) in [ + (json!({}), "missing `tutorial` (see learn.list)"), + (json!({"tutorial": null}), "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(cmd, params).unwrap_err(), error, "{cmd}"); + assert_eq!(app.ui.learn, View::default()); + assert_eq!(app.ui.learn_practice, Practice::default()); + assert_eq!(app.session.documents().len(), 1); + assert!(!shown(&app)); + } + } +} + /// 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() { diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index f84b8f53b0..e7951f6d4f 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -424,6 +424,10 @@ pub struct UiState { /// is on screen. Not saved. #[serde(skip)] pub learn_locate: Option, + /// The tutorials whose practice samples were opened and the tutorials finished, kept while the + /// app runs. + #[serde(skip)] + pub learn_practice: crate::panels::learn::Practice, pub control_bar: bool, /// The Control bar is docked to the bottom of the window instead of the top /// (`window.control.dock`). @@ -1089,6 +1093,7 @@ impl Default for UiState { find_font_in: String::new(), learn: Default::default(), learn_locate: None, + learn_practice: Default::default(), control_bar: false, control_bar_bottom: false, control_bar_floating: false, diff --git a/docs/mcp.md b/docs/mcp.md index 1232ffb131..8e158a81a9 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2685,11 +2685,29 @@ dock; in a full screen mode it makes Learn the front panel, which shows when the 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: +and changes nothing. + +Under a tutorial's steps, View more opens the tutorial's practice sample, a small drawing of VectorCraft's own with the +art its steps work on, as a new untitled document that becomes the active one; Save asks where to save it. Once the +sample is open, Finish tutorial marks the tutorial finished. A finished tutorial shows Next Up with the title of the +next tutorial in the same skill, which Next Up shows without opening its sample. A skill's last tutorial, finished, +says so and shows Back to skills instead; Next Up never moves to another skill. Which tutorials are finished is kept +while the app runs, and the skill rings and counts do not show it yet. These are VectorCraft's own rules; the help +page also plays a short video, which VectorCraft does not have. `learn.openSample {tutorial}` runs View more → +`{tutorial, index, title}` of the new document, and moves the Learn view to that tutorial; each call opens another +copy and leaves the open documents as they are. `learn.finish {tutorial}` runs Finish tutorial → +`{tutorial, finished: true, next}`, where `next` is the next tutorial's id in the skill, or null after the skill's +last one; the tutorial's sample must have been opened, and finishing it again keeps one entry. `learn.next {tutorial}` +runs Next Up on a finished tutorial and shows the panel on the next one → `{skill, tutorial, title}`; on a tutorial +not finished, or a skill's last tutorial, it is an error. For all three, a missing, non-string or unknown `tutorial` +is an error, and an error 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"}}} +{"name":"invoke_menu","arguments":{"command":"learn.openSample","params":{"tutorial":"pen-paths"}}} +{"name":"invoke_menu","arguments":{"command":"learn.finish","params":{"tutorial":"pen-paths"}}} +{"name":"invoke_menu","arguments":{"command":"learn.next","params":{"tutorial":"pen-paths"}}} ``` A step that names a tool shows the tool's name as a blue link. A click on it, or Enter or Space while it has the diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 086d4cfc0d..4ac027eb78 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -864,6 +864,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - 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`). - A tool's name in a Learn tutorial step is a blue link that shows where the tool is without choosing it (`learn.locateTool {tool}`): its toolbar button is marked, a collapsed group's flyout opens with the tool's row marked, a torn-off strip comes to the front, and a tool no shown toolbar holds is marked in the All Tools drawer. The mark, its 3 seconds and the hidden-toolbar cases are VectorCraft's own rules ([the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html); `panels::learn::tests`, the `a_learn_link_*` tests in `toolbar::tests`). + - Under a tutorial's steps, View more opens the tutorial's own practice sample as a new untitled document, and once the tutorial is finished, Next Up moves to the next tutorial in the same skill, as the help page describes ([the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html)). The samples are VectorCraft's own drawings. A Finish tutorial button that shows once the sample is open, the untitled copy, Back to skills in place of Next Up after a skill's last tutorial, and Next Up showing the next tutorial without opening its sample are VectorCraft's own rules. `learn.openSample`, `learn.finish` and `learn.next` script them. Not done: the help page's short video, and progress kept between sessions and shown in the skill rings. Regression tests: `view_more_opens_the_tutorial_s_sample_as_a_new_untitled_document`, `finishing_a_tutorial_offers_next_up_in_its_skill`, `the_practice_controls_work_from_the_keyboard`, `the_last_tutorial_of_a_skill_offers_back_to_skills`, `learn_finish_and_learn_next_follow_the_practice`, `the_practice_commands_reject_a_missing_or_unknown_tutorial`, `every_tutorial_has_its_own_readable_sample` and `the_next_tutorial_is_the_next_one_in_the_same_skill` in `crates/ui-egui/src/panels/learn/tests.rs`. - 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 3D effects (Extrude & Bevel, Revolve, Inflate, Rotate and their Classic forms) show in the status bar in the interface language: the art a 3D effect cannot build a solid from, invalid light, graphics and surface settings, and the notes for an object that draws no 3D faces or more wireframe edges than the limit. Each has a row in every catalog that translates all status and error messages, and the effect's name and the values in the message are translated or kept as written (`three_d_refusals_are_translated_in_every_complete_catalog`, `three_d_refusals_translate_with_their_values`). - 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`). diff --git a/docs/ui-parity.md b/docs/ui-parity.md index b2dd7d23a1..86f05d0ad8 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -380,7 +380,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; a tool's name in a step is a link that marks the tool where the toolbars show it (its button, its group's flyout, a torn-off strip, a toolbar made with New Toolbar or the All Tools drawer) without choosing it | 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`, `learn.locateTool`; the mark's look and 3-second lifetime are VectorCraft's own rules; `panels::learn::tests`, `toolbar::tests` | +| 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; a tool's name in a step is a link that marks the tool where the toolbars show it (its button, its group's flyout, a torn-off strip, a toolbar made with New Toolbar or the All Tools drawer) without choosing it; View more opens the tutorial's practice sample as a new untitled document, Finish tutorial then marks it finished, and Next Up shows the next tutorial in the same skill (no video) | 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`, `learn.locateTool`, `learn.openSample`, `learn.finish`, `learn.next`; the mark's look and 3-second lifetime are VectorCraft's own rules; `panels::learn::tests`, `toolbar::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 | From d747f5b36574b9d3a779ed63387063aa25e71d76 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 13 Nov 2026 04:34:35 -0800 Subject: [PATCH 2/2] Learn: Back to skills and the Quick skill guide card run learn.skills learn.skills {} shows the Learn panel on its Skills tab with the list of skills, with no skill and no tutorial open, and returns what learn.list returns. Back to skills after a skill's last tutorial and the Quick skill guide card on the For you tab now run it, so the control channel and MCP can reach the same view. - The command is listed in both UI command catalogs, and its label has a row in every catalog. - Docs: docs/mcp.md, docs/target-app-parity.md, docs/ui-parity.md. --- crates/mcp/src/ui_catalog.rs | 6 ++++ crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/bs.tsv | 1 + crates/ui-egui/src/i18n/ca.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/da.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hi.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ka.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + crates/ui-egui/src/menus/ui_commands.rs | 6 ++++ crates/ui-egui/src/panels/learn.rs | 39 ++++++++++++++++-------- crates/ui-egui/src/panels/learn/tests.rs | 35 +++++++++++++++++++++ docs/mcp.md | 5 ++- docs/target-app-parity.md | 2 +- docs/ui-parity.md | 2 +- 34 files changed, 106 insertions(+), 16 deletions(-) diff --git a/crates/mcp/src/ui_catalog.rs b/crates/mcp/src/ui_catalog.rs index bffe47bfa7..2337e08a23 100644 --- a/crates/mcp/src/ui_catalog.rs +++ b/crates/mcp/src/ui_catalog.rs @@ -646,6 +646,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{} the Learn panel's skills in the order its Skills tab lists them: {skills: [{id, title, tutorials: [{id, title, steps: number of steps}]}]}", ), + ( + "learn.skills", + "Back to Skills", + "", + "{} show the Learn panel on its Skills tab with the list of skills, with no skill and no tutorial open, as Back to skills after a skill's last tutorial and the Quick skill guide card do; returns what learn.list returns", + ), ( "learn.show", "Show Tutorial", diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 0cc6e106ce..d78cc54a7f 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -6752,6 +6752,7 @@ breakText Glyphs Глифи Open Tutorial Sample Отваряне на примерния файл на урока Finish Tutorial Завършване на урок Next Tutorial Следващ урок + Back to Skills Обратно към уменията @msg tutorial `{id}` has no practice sample урокът `{id}` няма примерен файл за упражнение @msg open the practice sample of `{id}` first (see learn.openSample) първо отворете примерния файл за упражнение на `{id}` (вижте learn.openSample) @msg finish `{id}` first (see learn.finish) първо завършете `{id}` (вижте learn.finish) diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 2df8131afe..f0567886a3 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -6806,6 +6806,7 @@ shape Open Otvoren Open Tutorial Sample Otvori uzorak tutorijala Finish Tutorial Završi tutorijal Next Tutorial Sljedeći tutorijal + Back to Skills Nazad na vještine @msg tutorial `{id}` has no practice sample tutorijal `{id}` nema uzorak za vježbu @msg open the practice sample of `{id}` first (see learn.openSample) prvo otvorite uzorak za vježbu tutorijala `{id}` (vidi learn.openSample) @msg finish `{id}` first (see learn.finish) prvo završite `{id}` (vidi learn.finish) diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 71959a3767..ac567a79c9 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -7329,6 +7329,7 @@ breakText Glyphs Glifs Open Tutorial Sample Obre la mostra del tutorial Finish Tutorial Acaba el tutorial Next Tutorial Tutorial següent + Back to Skills Torna a les habilitats @msg tutorial `{id}` has no practice sample el tutorial `{id}` no té cap mostra de pràctica @msg open the practice sample of `{id}` first (see learn.openSample) obriu primer la mostra de pràctica de `{id}` (vegeu learn.openSample) @msg finish `{id}` first (see learn.finish) acabeu primer `{id}` (vegeu learn.finish) diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index ca6c1cb875..0a24396a38 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -7348,6 +7348,7 @@ breakText Glyphs Glyfy Open Tutorial Sample Otevřít ukázku výukového kurzu Finish Tutorial Dokončit výukový kurz Next Tutorial Další výukový kurz + Back to Skills Zpět na dovednosti @msg tutorial `{id}` has no practice sample výukový kurz `{id}` nemá cvičnou ukázku @msg open the practice sample of `{id}` first (see learn.openSample) nejprve otevřete cvičnou ukázku kurzu `{id}` (viz learn.openSample) @msg finish `{id}` first (see learn.finish) nejprve dokončete `{id}` (viz learn.finish) diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 70411cbd00..3cd2805bd1 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -6768,6 +6768,7 @@ breakText Glyphs Glyffer Open Tutorial Sample Åbn eksempel til vejledning Finish Tutorial Afslut vejledning Next Tutorial Næste vejledning + Back to Skills Tilbage til færdigheder @msg tutorial `{id}` has no practice sample vejledningen `{id}` har intet øvelseseksempel @msg open the practice sample of `{id}` first (see learn.openSample) åbn først øvelseseksemplet til `{id}` (se learn.openSample) @msg finish `{id}` first (see learn.finish) afslut først `{id}` (se learn.finish) diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index eceefc5fb0..86f53f6b93 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -6943,6 +6943,7 @@ breakText Glyphs Glyphen Open Tutorial Sample Übungsdatei des Lernprogramms öffnen Finish Tutorial Lernprogramm abschließen Next Tutorial Nächstes Lernprogramm + Back to Skills Zurück zu den Fertigkeiten @msg tutorial `{id}` has no practice sample das Lernprogramm `{id}` hat keine Übungsdatei @msg open the practice sample of `{id}` first (see learn.openSample) öffnen Sie zuerst die Übungsdatei von `{id}` (siehe learn.openSample) @msg finish `{id}` first (see learn.finish) schließen Sie zuerst `{id}` ab (siehe learn.finish) diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 79e834ebee..337e0d08a1 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -7300,6 +7300,7 @@ breakText Glyphs Γλύφοι Open Tutorial Sample Άνοιγμα δείγματος οδηγού εκμάθησης Finish Tutorial Ολοκλήρωση οδηγού εκμάθησης Next Tutorial Επόμενος οδηγός εκμάθησης + Back to Skills Πίσω στις δεξιότητες @msg tutorial `{id}` has no practice sample ο οδηγός εκμάθησης `{id}` δεν έχει δείγμα εξάσκησης @msg open the practice sample of `{id}` first (see learn.openSample) ανοίξτε πρώτα το δείγμα εξάσκησης του `{id}` (δείτε learn.openSample) @msg finish `{id}` first (see learn.finish) ολοκληρώστε πρώτα το `{id}` (δείτε learn.finish) diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 905221dc68..3717681302 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -7291,6 +7291,7 @@ breakText Glyphs Glifos Open Tutorial Sample Abrir muestra del tutorial Finish Tutorial Terminar tutorial Next Tutorial Tutorial siguiente + Back to Skills Volver a las habilidades @msg tutorial `{id}` has no practice sample el tutorial `{id}` no tiene muestra de práctica @msg open the practice sample of `{id}` first (see learn.openSample) abre primero la muestra de práctica de `{id}` (consulta learn.openSample) @msg finish `{id}` first (see learn.finish) termina primero `{id}` (consulta learn.finish) diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 2227a31c33..4a3e1750e1 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -7383,6 +7383,7 @@ breakText Glyphs Merkit Open Tutorial Sample Avaa opetusohjelman harjoitustiedosto Finish Tutorial Viimeistele opetusohjelma Next Tutorial Seuraava opetusohjelma + Back to Skills Takaisin taitoihin @msg tutorial `{id}` has no practice sample opetusohjelmalla `{id}` ei ole harjoitustiedostoa @msg open the practice sample of `{id}` first (see learn.openSample) avaa ensin opetusohjelman `{id}` harjoitustiedosto (ks. learn.openSample) @msg finish `{id}` first (see learn.finish) viimeistele ensin `{id}` (ks. learn.finish) diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 22d5e3d376..cc0ae10971 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -7343,6 +7343,7 @@ breakText Glyphs Glyphes Open Tutorial Sample Ouvrir le fichier d’exercice du tutoriel Finish Tutorial Terminer le tutoriel Next Tutorial Tutoriel suivant + Back to Skills Retour aux compétences @msg tutorial `{id}` has no practice sample le tutoriel `{id}` n’a pas de fichier d’exercice @msg open the practice sample of `{id}` first (see learn.openSample) ouvrez d’abord le fichier d’exercice de `{id}` (voir learn.openSample) @msg finish `{id}` first (see learn.finish) terminez d’abord `{id}` (voir learn.finish) diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 996e534ef0..4f7b6c3715 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -8741,6 +8741,7 @@ theme Appearance अपीयरेंस Open Tutorial Sample ट्यूटोरियल का नमूना खोलें Finish Tutorial ट्यूटोरियल पूरा करें Next Tutorial अगला ट्यूटोरियल + Back to Skills कौशल पर वापस जाएँ @msg tutorial `{id}` has no practice sample ट्यूटोरियल `{id}` का कोई अभ्यास नमूना नहीं है @msg open the practice sample of `{id}` first (see learn.openSample) पहले `{id}` का अभ्यास नमूना खोलें (learn.openSample देखें) @msg finish `{id}` first (see learn.finish) पहले `{id}` पूरा करें (learn.finish देखें) diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 5c38da2fb4..92067127aa 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -7332,6 +7332,7 @@ breakText Glyphs Glifák Open Tutorial Sample Oktatóanyag mintafájljának megnyitása Finish Tutorial Oktatóanyag befejezése Next Tutorial Következő oktatóanyag + Back to Skills Vissza a készségekhez @msg tutorial `{id}` has no practice sample nincs gyakorló mintafájl ehhez az oktatóanyaghoz: `{id}` @msg open the practice sample of `{id}` first (see learn.openSample) előbb nyissa meg a gyakorló mintafájlt ehhez: `{id}` (lásd: learn.openSample) @msg finish `{id}` first (see learn.finish) előbb fejezze be ezt: `{id}` (lásd: learn.finish) diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 75b03775f3..0effb51048 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -6768,6 +6768,7 @@ breakText Glyphs Glif Open Tutorial Sample Buka contoh tutorial Finish Tutorial Selesaikan tutorial Next Tutorial Tutorial berikutnya + Back to Skills Kembali ke keterampilan @msg tutorial `{id}` has no practice sample tutorial `{id}` tidak memiliki contoh latihan @msg open the practice sample of `{id}` first (see learn.openSample) buka contoh latihan `{id}` terlebih dahulu (lihat learn.openSample) @msg finish `{id}` first (see learn.finish) selesaikan `{id}` terlebih dahulu (lihat learn.finish) diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 1304d8cf61..5f2571e648 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -6977,6 +6977,7 @@ breakText Glyphs Glifi Open Tutorial Sample Apri l’esempio del tutorial Finish Tutorial Termina il tutorial Next Tutorial Tutorial successivo + Back to Skills Torna alle competenze @msg tutorial `{id}` has no practice sample il tutorial `{id}` non ha un esempio di esercitazione @msg open the practice sample of `{id}` first (see learn.openSample) apri prima l’esempio di esercitazione di `{id}` (vedi learn.openSample) @msg finish `{id}` first (see learn.finish) termina prima `{id}` (vedi learn.finish) diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 1d5d882a82..9603ed5d36 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -7042,6 +7042,7 @@ breakText Glyphs 字形 Open Tutorial Sample チュートリアルのサンプルを開く Finish Tutorial チュートリアルを完了 Next Tutorial 次のチュートリアル + Back to Skills スキルに戻る @msg tutorial `{id}` has no practice sample チュートリアル `{id}` には練習用サンプルがありません @msg open the practice sample of `{id}` first (see learn.openSample) 先に `{id}` の練習用サンプルを開いてください (learn.openSample を参照) @msg finish `{id}` first (see learn.finish) 先に `{id}` を完了してください (learn.finish を参照) diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 8e4de4c62d..d923131a58 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -7462,6 +7462,7 @@ font-script Japanese იაპონური Open Tutorial Sample გაკვეთილის ნიმუშის გახსნა Finish Tutorial გაკვეთილის დასრულება Next Tutorial შემდეგი გაკვეთილი + Back to Skills უნარებზე დაბრუნება @msg tutorial `{id}` has no practice sample გაკვეთილს `{id}` არ აქვს სავარჯიშო ნიმუში @msg open the practice sample of `{id}` first (see learn.openSample) ჯერ გახსენით `{id}` გაკვეთილის სავარჯიშო ნიმუში (იხ. learn.openSample) @msg finish `{id}` first (see learn.finish) ჯერ დაასრულეთ `{id}` (იხ. learn.finish) diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 7d8f77477b..1f277fca5c 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -6938,6 +6938,7 @@ breakText Glyphs 글리프 Open Tutorial Sample 튜토리얼 샘플 열기 Finish Tutorial 튜토리얼 완료 Next Tutorial 다음 튜토리얼 + Back to Skills 기술로 돌아가기 @msg tutorial `{id}` has no practice sample 튜토리얼 `{id}`에 연습용 샘플이 없습니다 @msg open the practice sample of `{id}` first (see learn.openSample) 먼저 `{id}`의 연습용 샘플을 여세요(learn.openSample 참조) @msg finish `{id}` first (see learn.finish) 먼저 `{id}`을(를) 완료하세요(learn.finish 참조) diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index db793697a6..2af19e1fa9 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -6819,6 +6819,7 @@ breakText Glyphs Glyphs Open Tutorial Sample Oefenbestand van tutorial openen Finish Tutorial Tutorial voltooien Next Tutorial Volgende tutorial + Back to Skills Terug naar vaardigheden @msg tutorial `{id}` has no practice sample tutorial `{id}` heeft geen oefenbestand @msg open the practice sample of `{id}` first (see learn.openSample) open eerst het oefenbestand van `{id}` (zie learn.openSample) @msg finish `{id}` first (see learn.finish) voltooi eerst `{id}` (zie learn.finish) diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 372697fdef..581e28b65b 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -7204,6 +7204,7 @@ breakText Glyphs Glify Open Tutorial Sample Otwórz przykład samouczka Finish Tutorial Zakończ samouczek Next Tutorial Następny samouczek + Back to Skills Powrót do umiejętności @msg tutorial `{id}` has no practice sample samouczek `{id}` nie ma przykładu do ćwiczeń @msg open the practice sample of `{id}` first (see learn.openSample) najpierw otwórz przykład do ćwiczeń samouczka `{id}` (patrz learn.openSample) @msg finish `{id}` first (see learn.finish) najpierw zakończ `{id}` (patrz learn.finish) diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 728f11dafa..81bf348c96 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -7421,6 +7421,7 @@ swatch Yellow Green Verde-amarelado Open Tutorial Sample Abrir Amostra do Tutorial Finish Tutorial Concluir Tutorial Next Tutorial Próximo Tutorial + Back to Skills Voltar às Habilidades @msg tutorial `{id}` has no practice sample o tutorial `{id}` não tem amostra de prática @msg open the practice sample of `{id}` first (see learn.openSample) abra primeiro a amostra de prática de `{id}` (veja learn.openSample) @msg finish `{id}` first (see learn.finish) conclua primeiro `{id}` (veja learn.finish) diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 2f5708c436..4ab210b59d 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -7437,6 +7437,7 @@ shape Open Открытый Open Tutorial Sample Открыть пример урока Finish Tutorial Завершить урок Next Tutorial Следующий урок + Back to Skills Назад к навыкам @msg tutorial `{id}` has no practice sample у урока `{id}` нет учебного примера @msg open the practice sample of `{id}` first (see learn.openSample) сначала откройте учебный пример урока `{id}` (см. learn.openSample) @msg finish `{id}` first (see learn.finish) сначала завершите `{id}` (см. learn.finish) diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index a05aa652d6..5c0ba43b1c 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -8787,6 +8787,7 @@ digits Hindi Hindi Open Tutorial Sample Öppna övningsexempel för självstudie Finish Tutorial Slutför självstudie Next Tutorial Nästa självstudie + Back to Skills Tillbaka till färdigheter @msg tutorial `{id}` has no practice sample självstudien `{id}` har inget övningsexempel @msg open the practice sample of `{id}` first (see learn.openSample) öppna först övningsexemplet för `{id}` (se learn.openSample) @msg finish `{id}` first (see learn.finish) slutför först `{id}` (se learn.finish) diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 1c310c7f6e..9cc2607820 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -7304,6 +7304,7 @@ breakText Glyphs Glifler Open Tutorial Sample Öğretici Örneğini Aç Finish Tutorial Öğreticiyi Bitir Next Tutorial Sonraki Öğretici + Back to Skills Becerilere Dön @msg tutorial `{id}` has no practice sample `{id}` öğreticisinin alıştırma örneği yok @msg open the practice sample of `{id}` first (see learn.openSample) önce `{id}` öğreticisinin alıştırma örneğini açın (bkz. learn.openSample) @msg finish `{id}` first (see learn.finish) önce `{id}` öğreticisini bitirin (bkz. learn.finish) diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 5f7de5c52f..593b863a16 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -6957,6 +6957,7 @@ breakText Glyphs Гліфи Open Tutorial Sample Відкрити приклад уроку Finish Tutorial Завершити урок Next Tutorial Наступний урок + Back to Skills Назад до навичок @msg tutorial `{id}` has no practice sample урок `{id}` не має навчального прикладу @msg open the practice sample of `{id}` first (see learn.openSample) спочатку відкрийте навчальний приклад уроку `{id}` (див. learn.openSample) @msg finish `{id}` first (see learn.finish) спочатку завершіть `{id}` (див. learn.finish) diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 5fad3e2493..1100b04ff9 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -6958,6 +6958,7 @@ breakText Glyphs Glyph Open Tutorial Sample Mở tệp mẫu của hướng dẫn Finish Tutorial Hoàn tất hướng dẫn Next Tutorial Hướng dẫn tiếp theo + Back to Skills Quay lại kỹ năng @msg tutorial `{id}` has no practice sample hướng dẫn `{id}` không có tệp mẫu thực hành @msg open the practice sample of `{id}` first (see learn.openSample) hãy mở tệp mẫu thực hành của `{id}` trước (xem learn.openSample) @msg finish `{id}` first (see learn.finish) hãy hoàn tất `{id}` trước (xem learn.finish) diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index f6e69e53c5..ca074f3497 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -7186,6 +7186,7 @@ breakText Glyphs 字形 Open Tutorial Sample 打开教程示例 Finish Tutorial 完成教程 Next Tutorial 下一个教程 + Back to Skills 返回技能 @msg tutorial `{id}` has no practice sample 教程 `{id}` 没有练习示例 @msg open the practice sample of `{id}` first (see learn.openSample) 请先打开 `{id}` 的练习示例 (请参阅 learn.openSample) @msg finish `{id}` first (see learn.finish) 请先完成 `{id}` (请参阅 learn.finish) diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index c9d6631fb3..756277bca9 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -6992,6 +6992,7 @@ breakText Glyphs 字符 Open Tutorial Sample 開啟教學課程範例 Finish Tutorial 完成教學課程 Next Tutorial 下一個教學課程 + Back to Skills 返回技能 @msg tutorial `{id}` has no practice sample 教學課程 `{id}` 沒有練習範例 @msg open the practice sample of `{id}` first (see learn.openSample) 請先開啟 `{id}` 的練習範例 (請參閱 learn.openSample) @msg finish `{id}` first (see learn.finish) 請先完成 `{id}` (請參閱 learn.finish) diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index 8e4aaab55e..bb4481e532 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -647,6 +647,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{} the Learn panel's skills in the order its Skills tab lists them: {skills: [{id, title, tutorials: [{id, title, steps: number of steps}]}]}", ), + ( + "learn.skills", + "Back to Skills", + "", + "{} show the Learn panel on its Skills tab with the list of skills, with no skill and no tutorial open, as Back to skills after a skill's last tutorial and the Quick skill guide card do; returns what learn.list returns", + ), ( "learn.show", "Show Tutorial", diff --git a/crates/ui-egui/src/panels/learn.rs b/crates/ui-egui/src/panels/learn.rs index 8941626a2d..ee7967c8a3 100644 --- a/crates/ui-egui/src/panels/learn.rs +++ b/crates/ui-egui/src/panels/learn.rs @@ -5,9 +5,10 @@ //! tutorial previews its numbered steps. A tool's name in a step is a link that shows where the tool //! is ([`locate`]). Back goes up one level. Under the steps, View more opens the tutorial's practice //! sample as a new untitled document; Finish tutorial then marks it finished, and Next Up shows the -//! next tutorial in the same skill. `learn.list` lists the tutorials, `learn.show` opens one, -//! `learn.locateTool` shows where a tool is, `learn.openSample`, `learn.finish` and `learn.next` run -//! the three practice controls, and `window.learn` shows or hides the panel. +//! next tutorial in the same skill. `learn.list` lists the tutorials, `learn.skills` shows the list +//! of skills, `learn.show` opens one tutorial, `learn.locateTool` shows where a tool is, +//! `learn.openSample`, `learn.finish` and `learn.next` run the three practice controls, and +//! `window.learn` shows or hides the panel. pub mod catalog; pub mod locate; @@ -87,8 +88,10 @@ fn for_you(app: &mut VectorcraftApp, ui: &mut Ui) { }); 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 }; + if resp.clicked() + && let Err(e) = app.run("learn.skills", json!({})) + { + app.status(e); } } @@ -182,15 +185,16 @@ fn tool_link(ui: &mut Ui, t: &Tokens, name: &str) -> egui::Response { fn practice(app: &mut VectorcraftApp, ui: &mut Ui, tutorial: &'static catalog::Tutorial) { let t = Tokens::get(ui.ctx()); let id = tutorial.id; + let params = json!({ "tutorial": id }); let mut run = None; ui.add_space(6.0); ui.horizontal_wrapped(|ui| { if ui.button(tl!("View more")).clicked() { - run = Some("learn.openSample"); + run = Some(("learn.openSample", params.clone())); } let done = &app.ui.learn_practice; if done.opened.contains(&id) && !done.finished.contains(&id) && ui.button(tl!("Finish tutorial")).clicked() { - run = Some("learn.finish"); + run = Some(("learn.finish", params.clone())); } }); if app.ui.learn_practice.finished.contains(&id) { @@ -199,7 +203,7 @@ fn practice(app: &mut VectorcraftApp, ui: &mut Ui, tutorial: &'static catalog::T Some(next) => { ui.horizontal_wrapped(|ui| { if ui.button(tl!("Next Up")).clicked() { - run = Some("learn.next"); + run = Some(("learn.next", params.clone())); } ui.add(egui::Label::new(egui::RichText::new(next.title).color(t.text)).wrap()); }); @@ -207,13 +211,13 @@ fn practice(app: &mut VectorcraftApp, ui: &mut Ui, tutorial: &'static catalog::T None => { ui.add(egui::Label::new(egui::RichText::new(tl!("You finished the last tutorial in this skill.")).color(t.text_dim)).wrap()); if ui.button(tl!("Back to skills")).clicked() { - app.ui.learn = View { tab: Tab::Skills, skill: None, tutorial: None }; + run = Some(("learn.skills", json!({}))); } } } } - if let Some(cmd) = run - && let Err(e) = app.run(cmd, json!({ "tutorial": id })) + if let Some((cmd, params)) = run + && let Err(e) = app.run(cmd, params) { app.status(e); } @@ -247,12 +251,13 @@ fn row(ui: &mut Ui, title: &str, tutorials: Option) -> egui::Response { resp.on_hover_cursor(egui::CursorIcon::PointingHand) } -/// The Learn panel's UI commands: `window.learn`, `learn.list`, `learn.show`, `learn.locateTool`, -/// `learn.openSample`, `learn.finish` and `learn.next`. +/// The Learn panel's UI commands: `window.learn`, `learn.list`, `learn.skills`, `learn.show`, +/// `learn.locateTool`, `learn.openSample`, `learn.finish` and `learn.next`. 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.skills" => skills_list(app), "learn.show" => open(app, p), "learn.locateTool" => locate::locate_tool(app, p), "learn.openSample" => open_sample(app, p), @@ -291,6 +296,14 @@ fn list() -> Value { json!({ "skills": skills.collect::>() }) } +/// `learn.skills`: Back to skills, and the Quick skill guide card. Show the panel on the Skills tab +/// with the list of skills, with no skill and no tutorial open → what `learn.list` answers. +fn skills_list(app: &mut VectorcraftApp) -> Result { + crate::panel_docking::reveal_panel(app, ID)?; + app.ui.learn = View { tab: Tab::Skills, skill: None, tutorial: None }; + Ok(list()) +} + /// `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 { diff --git a/crates/ui-egui/src/panels/learn/tests.rs b/crates/ui-egui/src/panels/learn/tests.rs index f302d8f436..4bd00648d1 100644 --- a/crates/ui-egui/src/panels/learn/tests.rs +++ b/crates/ui-egui/src/panels/learn/tests.rs @@ -421,6 +421,41 @@ fn learn_finish_and_learn_next_follow_the_practice() { assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("color"), tutorial: Some("copy-colors") }); } +/// `learn.skills` shows the panel on the Skills tab with the list of skills and answers what +/// `learn.list` answers. Back to skills and the Quick skill guide card run it: each one also shows +/// a panel that was hidden, as the command does. +#[test] +fn learn_skills_shows_the_list_of_skills() { + let skills = View { tab: Tab::Skills, skill: None, tutorial: None }; + assert!(crate::menus::UI_COMMANDS.iter().any(|c| c.0 == "learn.skills"), "learn.skills is in the UI command catalog"); + let mut app = app(); + app.run("learn.show", json!({"tutorial": "gradient"})).unwrap(); + app.run("window.learn", json!({"show": false})).unwrap(); + assert!(!shown(&app)); + let list = app.run("learn.list", json!({})).unwrap(); + assert_eq!(app.run("learn.skills", json!({})).unwrap(), list); + assert_eq!(app.ui.learn, skills); + assert!(shown(&app)); + + // Back to skills, after a skill's last tutorial. + let mut app = self::app(); + let ctx = context(); + app.run("learn.openSample", json!({"tutorial": "outline-type"})).unwrap(); + app.run("learn.finish", json!({"tutorial": "outline-type"})).unwrap(); + assert!(!shown(&app)); + click(&mut app, &ctx, "Back to skills", show); + assert_eq!(app.ui.learn, skills); + assert!(shown(&app), "Back to skills runs learn.skills"); + + // The Quick skill guide card on the For you tab. + let mut app = self::app(); + let ctx = context(); + assert!(!shown(&app)); + click(&mut app, &ctx, "Quick skill guide", show); + assert_eq!(app.ui.learn, skills); + assert!(shown(&app), "the Quick skill guide card runs learn.skills"); +} + /// The practice commands reject a missing, mistyped or unknown tutorial id and change nothing. #[test] fn the_practice_commands_reject_a_missing_or_unknown_tutorial() { diff --git a/docs/mcp.md b/docs/mcp.md index 8e158a81a9..47dd6f2014 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2683,7 +2683,9 @@ toggles it (`show` omitted or null), and answers whether it shows; Window › Le 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 +lists them, where `steps` is the number of steps. `learn.skills {}` shows the panel on its Skills tab with the list of +skills, with no skill and no tutorial open, as the Quick skill guide card and Back to skills do, and answers what +`learn.list` answers. `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. @@ -2708,6 +2710,7 @@ is an error, and an error changes nothing. Behavior reference: {"name":"invoke_menu","arguments":{"command":"learn.openSample","params":{"tutorial":"pen-paths"}}} {"name":"invoke_menu","arguments":{"command":"learn.finish","params":{"tutorial":"pen-paths"}}} {"name":"invoke_menu","arguments":{"command":"learn.next","params":{"tutorial":"pen-paths"}}} +{"name":"invoke_menu","arguments":{"command":"learn.skills","params":{}}} ``` A step that names a tool shows the tool's name as a blue link. A click on it, or Enter or Space while it has the diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 4ac027eb78..4b4137bd68 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -864,7 +864,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - 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`). - A tool's name in a Learn tutorial step is a blue link that shows where the tool is without choosing it (`learn.locateTool {tool}`): its toolbar button is marked, a collapsed group's flyout opens with the tool's row marked, a torn-off strip comes to the front, and a tool no shown toolbar holds is marked in the All Tools drawer. The mark, its 3 seconds and the hidden-toolbar cases are VectorCraft's own rules ([the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html); `panels::learn::tests`, the `a_learn_link_*` tests in `toolbar::tests`). - - Under a tutorial's steps, View more opens the tutorial's own practice sample as a new untitled document, and once the tutorial is finished, Next Up moves to the next tutorial in the same skill, as the help page describes ([the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html)). The samples are VectorCraft's own drawings. A Finish tutorial button that shows once the sample is open, the untitled copy, Back to skills in place of Next Up after a skill's last tutorial, and Next Up showing the next tutorial without opening its sample are VectorCraft's own rules. `learn.openSample`, `learn.finish` and `learn.next` script them. Not done: the help page's short video, and progress kept between sessions and shown in the skill rings. Regression tests: `view_more_opens_the_tutorial_s_sample_as_a_new_untitled_document`, `finishing_a_tutorial_offers_next_up_in_its_skill`, `the_practice_controls_work_from_the_keyboard`, `the_last_tutorial_of_a_skill_offers_back_to_skills`, `learn_finish_and_learn_next_follow_the_practice`, `the_practice_commands_reject_a_missing_or_unknown_tutorial`, `every_tutorial_has_its_own_readable_sample` and `the_next_tutorial_is_the_next_one_in_the_same_skill` in `crates/ui-egui/src/panels/learn/tests.rs`. + - Under a tutorial's steps, View more opens the tutorial's own practice sample as a new untitled document, and once the tutorial is finished, Next Up moves to the next tutorial in the same skill, as the help page describes ([the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html)). The samples are VectorCraft's own drawings. A Finish tutorial button that shows once the sample is open, the untitled copy, Back to skills in place of Next Up after a skill's last tutorial, and Next Up showing the next tutorial without opening its sample are VectorCraft's own rules. `learn.openSample`, `learn.finish`, `learn.next` and `learn.skills` (Back to skills) script them. Not done: the help page's short video, and progress kept between sessions and shown in the skill rings. Regression tests: `view_more_opens_the_tutorial_s_sample_as_a_new_untitled_document`, `finishing_a_tutorial_offers_next_up_in_its_skill`, `the_practice_controls_work_from_the_keyboard`, `the_last_tutorial_of_a_skill_offers_back_to_skills`, `learn_finish_and_learn_next_follow_the_practice`, `learn_skills_shows_the_list_of_skills`, `the_practice_commands_reject_a_missing_or_unknown_tutorial`, `every_tutorial_has_its_own_readable_sample` and `the_next_tutorial_is_the_next_one_in_the_same_skill` in `crates/ui-egui/src/panels/learn/tests.rs`. - 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 3D effects (Extrude & Bevel, Revolve, Inflate, Rotate and their Classic forms) show in the status bar in the interface language: the art a 3D effect cannot build a solid from, invalid light, graphics and surface settings, and the notes for an object that draws no 3D faces or more wireframe edges than the limit. Each has a row in every catalog that translates all status and error messages, and the effect's name and the values in the message are translated or kept as written (`three_d_refusals_are_translated_in_every_complete_catalog`, `three_d_refusals_translate_with_their_values`). - 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`). diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 86f05d0ad8..f82bbfc922 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -380,7 +380,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; a tool's name in a step is a link that marks the tool where the toolbars show it (its button, its group's flyout, a torn-off strip, a toolbar made with New Toolbar or the All Tools drawer) without choosing it; View more opens the tutorial's practice sample as a new untitled document, Finish tutorial then marks it finished, and Next Up shows the next tutorial in the same skill (no video) | 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`, `learn.locateTool`, `learn.openSample`, `learn.finish`, `learn.next`; the mark's look and 3-second lifetime are VectorCraft's own rules; `panels::learn::tests`, `toolbar::tests` | +| 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; a tool's name in a step is a link that marks the tool where the toolbars show it (its button, its group's flyout, a torn-off strip, a toolbar made with New Toolbar or the All Tools drawer) without choosing it; View more opens the tutorial's practice sample as a new untitled document, Finish tutorial then marks it finished, and Next Up shows the next tutorial in the same skill (no video) | done | [the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/learn-skills-quick-skill-guide.html); `learn.list`, `learn.skills`, `learn.show`, `learn.locateTool`, `learn.openSample`, `learn.finish`, `learn.next`; the mark's look and 3-second lifetime are VectorCraft's own rules; `panels::learn::tests`, `toolbar::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 |