Merge pull request #7372 from dotCooCoo/feat/home-learn-section

Home: a Learn section lists the tutorials and the Quick skill guide
This commit is contained in:
BFlat
2026-11-14 13:42:47 +07:00
committed by GitHub
5 changed files with 148 additions and 5 deletions
+36 -1
View File
@@ -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) {
+16 -3
View File
@@ -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!({}))
{
+87 -1
View File
@@ -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();
+8
View File
@@ -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"}}}
+1
View File
@@ -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`).