diff --git a/crates/ui-egui/src/canvas/home.rs b/crates/ui-egui/src/canvas/home.rs index cd2ac9c44a..bffab8f06c 100644 --- a/crates/ui-egui/src/canvas/home.rs +++ b/crates/ui-egui/src/canvas/home.rs @@ -22,7 +22,7 @@ pub(super) fn home(app: &mut VectorcraftApp, ui: &mut Ui, rect: egui::Rect) { }); } -/// The Home screen's column: welcome, New / Open, Recent Files, presets and community links. +/// The Home screen's column: welcome, New / Open, Recent Files, Learn, presets and community links. fn home_content(app: &mut VectorcraftApp, ui: &mut Ui) { let t = Tokens::get(ui.ctx()); // The welcome and the New / Open buttons sit centred, as PhotoCraft's Home screen does (#4684). @@ -46,6 +46,7 @@ fn home_content(app: &mut VectorcraftApp, ui: &mut Ui) { }); }); recent(app, ui); + learn(app, ui); ui.add_space(28.0); ui.label(egui::RichText::new(tl!("Quickly start a new file")).font(theme::semibold(14.0)).color(t.text)); ui.add_space(10.0); @@ -68,6 +69,40 @@ fn home_content(app: &mut VectorcraftApp, ui: &mut Ui) { }); } +/// The id of the Home screen's Quick skill guide card. +const LEARN_GUIDE_ID: &str = "home-quick-skill-guide"; + +/// The Home screen's Learn section: the Quick skill guide card, which shows the Learn panel's list +/// of skills, and the Learn panel's tutorials under their skills, each a button that shows the +/// panel at its steps (`learn.show`). Neither opens, closes or switches a document, and the panel +/// shows beside the Home screen. +fn learn(app: &mut VectorcraftApp, ui: &mut Ui) { + use crate::panels::learn::{catalog, guide_card}; + let t = Tokens::get(ui.ctx()); + ui.add_space(28.0); + ui.label(egui::RichText::new(tl!("Learn")).font(theme::semibold(14.0)).color(t.text)); + ui.add_space(10.0); + guide_card(app, ui, egui::Id::unique(LEARN_GUIDE_ID), t.panel); + let mut open = None; + for skill in catalog::SKILLS { + ui.add_space(10.0); + ui.label(egui::RichText::new(skill.title).size(12.0).color(t.text_dim)); + ui.add_space(4.0); + ui.horizontal_wrapped(|ui| { + for tutorial in skill.tutorials { + if widgets::secondary_button(ui, tutorial.title).clicked() { + open = Some(tutorial.id); + } + } + }); + } + if let Some(id) = open + && let Err(e) = app.run("learn.show", json!({ "tutorial": id })) + { + app.status(e); + } +} + /// The Home screen's Recent Files (#663): the first of File › Open Recent Files, each its name and /// folder; a click opens it. Nothing when there are none. fn recent(app: &mut VectorcraftApp, ui: &mut Ui) { diff --git a/crates/ui-egui/src/panels/learn.rs b/crates/ui-egui/src/panels/learn.rs index ee7967c8a3..5d24167c99 100644 --- a/crates/ui-egui/src/panels/learn.rs +++ b/crates/ui-egui/src/panels/learn.rs @@ -8,7 +8,8 @@ //! 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. +//! `window.learn` shows or hides the panel. The Home screen's Learn section shows the same card +//! ([`guide_card`]) and the tutorials, which open the panel. pub mod catalog; pub mod locate; @@ -68,8 +69,16 @@ fn for_you(app: &mut VectorcraftApp, ui: &mut Ui) { let t = Tokens::get(ui.ctx()); crate::widgets::subheader(ui, tl!("Get started")); ui.add_space(4.0); + guide_card(app, ui, ui.scope_id().with("quick-skill-guide"), t.panel_darker); +} + +/// The Quick skill guide card, filled with `fill`, as one button with the id `id`: a click, or Enter +/// or Space while it has the keyboard focus, shows the panel's list of skills (`learn.skills`). The +/// For you tab and the Home screen's Learn section show it. +pub(crate) fn guide_card(app: &mut VectorcraftApp, ui: &mut Ui, id: egui::Id, fill: egui::Color32) { + let t = Tokens::get(ui.ctx()); let card = egui::Frame::NONE - .fill(t.panel_darker) + .fill(fill) .stroke(Stroke::new(1.0, t.border)) .corner_radius(egui::CornerRadius::same(t.radius)) .inner_margin(egui::Margin::same(10)) @@ -86,8 +95,12 @@ fn for_you(app: &mut VectorcraftApp, ui: &mut Ui) { .color(t.text_dim), )); }); - let resp = ui.interact(card.response.rect, ui.scope_id().with("quick-skill-guide"), Sense::click()); + let resp = ui.interact(card.response.rect, id, Sense::click()); resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), tl!("Quick skill guide"))); + if resp.has_focus() { + ui.painter().rect_stroke(resp.rect, egui::CornerRadius::same(t.radius), Stroke::new(1.0, t.selection), egui::StrokeKind::Inside); + } + let resp = resp.on_hover_cursor(egui::CursorIcon::PointingHand); if resp.clicked() && let Err(e) = app.run("learn.skills", json!({})) { diff --git a/crates/ui-egui/src/tests/home.rs b/crates/ui-egui/src/tests/home.rs index d1c20991af..6cfdf78f38 100644 --- a/crates/ui-egui/src/tests/home.rs +++ b/crates/ui-egui/src/tests/home.rs @@ -299,6 +299,91 @@ fn home_scrolls_in_a_short_window() { assert_eq!(app.session.documents().len(), 1, "the card opened its preset"); } +/// The visible text `text` on a tall Home screen frame drawn at `time` → its center. +fn learn_text_at(app: &mut VectorcraftApp, ctx: &egui::Context, time: f64, text: &str) -> egui::Pos2 { + let texts = visible_home_texts(app, ctx, (1000.0, 2400.0), time, vec![]); + texts.iter().find(|(t, _)| t == text).map(|(_, r)| r.center()).unwrap_or_else(|| panic!("{text} is shown: {texts:?}")) +} + +/// A click at `at` on a tall Home screen frame, pressed at `time` and released a frame later. +fn click_home_at(app: &mut VectorcraftApp, ctx: &egui::Context, time: f64, at: egui::Pos2) { + let press = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() }; + visible_home_texts(app, ctx, (1000.0, 2400.0), time, vec![egui::Event::PointerMoved(at), press(true)]); + visible_home_texts(app, ctx, (1000.0, 2400.0), time + 0.05, vec![press(false)]); +} + +/// The Home screen has a Learn section: the Quick skill guide card and every tutorial of the Learn +/// panel under its skill. A tutorial shows the Learn panel at its steps and the card shows the list +/// of skills; a second choice keeps the panel shown, and no document is opened. +#[test] +fn home_has_a_learn_section_that_opens_the_learn_panel() { + use crate::panels::learn::{self, Tab, View, catalog}; + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default()); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let texts = visible_home_texts(&mut app, &ctx, (1000.0, 2400.0), 0.0, vec![]); + let has = |s: &str| texts.iter().any(|(t, _)| t == s); + assert!(has("Learn") && has("Quick skill guide"), "{texts:?}"); + for skill in catalog::SKILLS { + assert!(has(skill.title), "{} is shown", skill.title); + for tutorial in skill.tutorials { + assert!(has(tutorial.title), "{} is shown", tutorial.title); + } + } + assert!(!learn::visible(&app.ui), "the panel is not shown before a choice"); + let at = learn_text_at(&mut app, &ctx, 0.5, "Draw paths with the Pen"); + click_home_at(&mut app, &ctx, 1.0, at); + assert!(learn::visible(&app.ui), "a tutorial shows the Learn panel"); + assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("draw"), tutorial: Some("pen-paths") }); + assert!(app.session.documents().is_empty(), "no document is opened"); + assert!(crate::menus::home_showing(&app), "Home stays"); + let at = learn_text_at(&mut app, &ctx, 1.5, "Quick skill guide"); + click_home_at(&mut app, &ctx, 2.0, at); + assert!(learn::visible(&app.ui), "a second choice keeps the panel shown"); + assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: None, tutorial: None }, "the card shows the list of skills"); + assert!(app.session.documents().is_empty()); +} + +/// Home shown over open documents has the Learn section too; a tutorial chosen there leaves the +/// documents and the active one as they are. +#[test] +fn home_learn_section_over_open_documents_leaves_them_as_they_are() { + use crate::panels::learn::{self, Tab, View}; + let mut app = app_with_doc(); + app.run("file.new", json!({})).unwrap(); + app.run("document.activate", json!({"index": 0})).unwrap(); + app.run("app.home", json!({})).unwrap(); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let at = learn_text_at(&mut app, &ctx, 0.0, "Add a gradient"); + click_home_at(&mut app, &ctx, 0.5, at); + assert!(learn::visible(&app.ui)); + assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: Some("color"), tutorial: Some("gradient") }); + assert_eq!((app.session.documents().len(), app.session.active_index()), (2, Some(0)), "the documents stay as they are"); + assert!(crate::menus::home_showing(&app), "Home stays over the documents"); +} + +/// The Home screen's Quick skill guide card works from the keyboard: with the keyboard focus, Enter +/// shows the Learn panel's list of skills. +#[test] +fn home_quick_skill_guide_card_works_from_the_keyboard() { + use crate::panels::learn::{self, Tab, View}; + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default()); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + visible_home_texts(&mut app, &ctx, (1000.0, 2400.0), 0.0, vec![]); + let card = egui::Id::unique("home-quick-skill-guide"); + ctx.memory_mut(|m| m.request_focus(card)); + visible_home_texts(&mut app, &ctx, (1000.0, 2400.0), 0.1, vec![]); + assert!(ctx.memory(|m| m.has_focus(card)), "the card takes the keyboard focus"); + let key = |pressed| egui::Event::Key { key: egui::Key::Enter, physical_key: None, pressed, repeat: false, modifiers: Default::default() }; + visible_home_texts(&mut app, &ctx, (1000.0, 2400.0), 0.2, vec![key(true)]); + visible_home_texts(&mut app, &ctx, (1000.0, 2400.0), 0.3, vec![key(false)]); + assert!(learn::visible(&app.ui)); + assert_eq!(app.ui.learn, View { tab: Tab::Skills, skill: None, tutorial: None }); + assert!(app.session.documents().is_empty()); +} + /// A file that is already open is not opened again, however it is asked for: Home › Recent Files, /// File › Open Recent, File › Open, the Finder and a drop all focus its tab, unsaved edits and all /// (#3455). @@ -452,7 +537,8 @@ fn text_centre_x(app: &mut VectorcraftApp, text: &str) -> Option<(f32, f32)> { app.canvas_rect = None; let ctx = egui::Context::default(); crate::theme::install_fonts(&ctx); - let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1000.0, 900.0)); + // Tall enough to draw the whole screen, Learn section included. + let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1000.0, 1600.0)); let raw = egui::RawInput { screen_rect: Some(screen), ..Default::default() }; let mut out = ctx.run_ui(raw, |ui| canvas::show(app, ui)); out.textures_delta.clear(); diff --git a/docs/mcp.md b/docs/mcp.md index c770611d76..4be69e30b2 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -2880,6 +2880,14 @@ not finished, or a skill's last tutorial, it is an error. For all three, a missi 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). +The Home screen has a Learn section between Recent Files and the new-file presets, as +[the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/homescreen-overview.html) +lists Learn, with in-app tutorials, among the Home screen's options. The section holds the Quick skill guide card, +which runs `learn.skills`, and every tutorial of the Learn panel under its skill, each a button that runs +`learn.show {tutorial}`. The panel shows beside the Home screen; no document is opened, closed or switched, and a +second choice keeps the panel shown. The section's place, its layout and what a choice opens are VectorCraft's own +rules. + ```json {"name":"invoke_menu","arguments":{"command":"learn.show","params":{"tutorial":"pen-paths"}}} {"name":"invoke_menu","arguments":{"command":"learn.openSample","params":{"tutorial":"pen-paths"}}} diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index d3f31f8786..571b7810a6 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -920,6 +920,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - 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`, `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 Home screen has a Learn section with the in-app tutorials, as the help page lists among the Home screen's options ([the help page](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/homescreen-overview.html)): the Quick skill guide card shows the Learn panel's list of skills (`learn.skills`), and each tutorial, listed under its skill, shows the panel at its steps (`learn.show`). It shows with no document open and over open documents, and a choice leaves the documents as they are. Its place between Recent Files and the new-file presets, its layout and what a choice opens are VectorCraft's own rules (`tests::home::home_has_a_learn_section_that_opens_the_learn_panel`, `home_learn_section_over_open_documents_leaves_them_as_they_are`, `home_quick_skill_guide_card_works_from_the_keyboard`). - 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`).