M14.3: the Home button shows the Home screen over open documents

It used to open the New Document dialog. Now it runs the new app.home UI command:
the Home screen replaces the canvas while the documents stay open as tabs;
choosing a tab, or a document opening, closing or becoming active, returns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic 2026-10-05 22:43:26 +07:00
parent d4c4361b4a
commit 258337cf7c
7 changed files with 71 additions and 5 deletions

View File

@ -150,7 +150,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist
| M11 | Artboards & views (artboard panel/tool, Trim View, middle-button pan and print tiling done; multiple windows, presentation polish) | 🟡 | 15–25 |
| M12 | Advanced color & art (CMYK/ICC, separations, Gradient Mesh, Live Paint, Image Trace, Graphs) | 🟡 Gradient Mesh, Live Paint, Image Trace (12 presets, 18 ms/1k² image), Recolor Artwork, colour management (ICC, soft proofing, separations preview), Graphs (all 9 tools, Graph Data/Type, regenerate in place) done; graph Design/Column/Marker designs pending | 6–10 |
| M13 | Automation (Actions ✅ record/playback, persisted; variables, scripting, batch) | 🟡 | 18–27 |
| M14 | 1.0 polish (preferences, shortcut editor, workspaces, accessibility, packaging for all OSes) | 🟡 Preferences, shortcut editor, workspaces, a custom title bar on Windows/Linux, content-sized dialogs and menus that scroll when longer than the window done; accessibility, Windows/Linux packaging pending | 18–28 |
| M14 | 1.0 polish (preferences, shortcut editor, workspaces, accessibility, packaging for all OSes) | 🟡 Preferences, shortcut editor, workspaces, a custom title bar on Windows/Linux with a Home button, content-sized dialogs and menus that scroll when longer than the window done; accessibility, Windows/Linux packaging pending | 18–28 |
| — | Interaction fidelity pass (every tool's modifiers, Properties panel per context, isolation, nuance) | ⬜ | 60–90 |
| — | Hardening at scale (big-file corpus, fuzzing, cross-platform + browser QA) | 🟡 | 50–80 |
| | **Total to full parity** (one agent; re-derived from the [Parity estimate](#parity-estimate) table) | | **~335–520** |

View File

@ -127,7 +127,11 @@ pub fn fit(app: &mut VectorcraftApp, how: &str) {
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
let t = Tokens::get(ui.ctx());
let full = ui.available_rect_before_wrap();
if app.session.active().is_none() {
// A document that opened, closed or became active since Home was chosen replaces it.
if app.ui.home.is_some_and(|k| k != crate::menus::home_key(app)) {
app.ui.home = None;
}
if app.session.active().is_none() || app.ui.home.is_some() {
home(app, ui, full);
return;
}

View File

@ -27,8 +27,9 @@ pub fn app_bar(app: &mut VectorcraftApp, ui: &mut Ui) {
let (r, _) = ui.allocate_exact_size(vec2(22.0, 22.0), Sense::hover());
crate::brand::paint_mark(ui, r);
ui.add_space(4.0);
if widgets::icon_button(ui, "house", "Home", false, 24.0).clicked() {
crate::dialogs::open_new_document(app);
let on_home = app.ui.home.is_some() || app.session.active().is_none();
if widgets::icon_button(ui, "house", "Home", on_home, 24.0).clicked() {
app.run("app.home", json!({})).ok();
}
ui.add_space(2.0);
let menus_end = if app.native_menu {
@ -237,7 +238,8 @@ pub fn doc_tabs(app: &mut VectorcraftApp, ui: &mut Ui) {
for (i, d) in app.session.documents().iter().enumerate() {
let zoom = app.views.get(i).map(|v| v.zoom).unwrap_or(1.0);
let title = tab_title(d, zoom, app.ui.view.outline);
let is_active = Some(i) == active;
// On the Home screen no tab is the current one.
let is_active = Some(i) == active && app.ui.home.is_none();
let galley = ui.painter().layout_no_wrap(title, theme::semibold(12.5), if is_active { t.text_strong } else { t.text_dim });
let w = galley.size().x + 50.0;
let r = egui::Rect::from_min_size(egui::pos2(x, strip.top()), vec2(w, strip.height() - 1.0));
@ -265,6 +267,7 @@ pub fn doc_tabs(app: &mut VectorcraftApp, ui: &mut Ui) {
app.status(e);
}
} else if let Some(i) = activate {
app.ui.home = None;
app.session.set_active(i);
}
// Isolation mode breadcrumb bar.

View File

@ -46,6 +46,8 @@ mod tests_clipboard;
#[cfg(test)]
mod tests_docsetup;
#[cfg(test)]
mod tests_home;
#[cfg(test)]
mod tests_labels;
#[cfg(test)]
mod tests_nativeoptions;

View File

@ -111,6 +111,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
),
("file.documentSetup", "Document Setup…", "Cmd+Alt+P", "{}"),
("file.newDialog", "New…", "Cmd+N", "{} opens the New Document dialog"),
("app.home", "Home", "", "{} shows the Home screen (new file presets, Open) over the open documents; choosing a document tab returns to it"),
("edit.preferences", "Preferences…", "Cmd+K", "{category?} open Preferences (engine: prefs.get / prefs.set / prefs.list)"),
("edit.keyboardShortcuts", "Keyboard Shortcuts…", "Cmd+Alt+Shift+K", "{}"),
("shortcuts.set", "Set Keyboard Shortcut", "", "{id: command id or tool:<id>, shortcut: \"Cmd+Shift+K\" | \"\" (none) | null (default), force?}"),
@ -549,6 +550,10 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
crate::dialogs::open_new_document(app);
Ok(Value::Null)
}
"app.home" => {
app.ui.home = Some(home_key(app));
Ok(Value::Null)
}
"file.open" => match s("path") {
Some(path) => io::open_path(app, &path).map(|_| Value::Null),
None => io::open_dialog(app).map(|_| Value::Null),
@ -1919,6 +1924,12 @@ fn render_items(app: &VectorcraftApp, ui: &mut egui::Ui, items: &[Item], clicked
}
}
/// What the Home screen remembers when it opens (`app.home`): the active document and the
/// document count. When either changes, the Home screen gives way to the document.
pub(crate) fn home_key(app: &VectorcraftApp) -> (Option<u64>, usize) {
(app.session.active().map(|d| d.uid), app.session.documents().len())
}
fn label_of(it: &Item) -> &'static str {
match it {
Item::Cmd(l, _, _) => l,

View File

@ -298,6 +298,11 @@ pub struct UiState {
/// never used).
#[serde(default)]
pub dxf_import: Value,
/// The Home screen is shown over the open documents (`app.home`): the active document's uid
/// and the document count when it opened. Choosing a tab, or a document opening, closing or
/// becoming active, leaves it.
#[serde(skip)]
pub home: Option<(Option<u64>, usize)>,
}
impl UiState {
@ -359,6 +364,7 @@ impl Default for UiState {
dxf_options: Value::Null,
eps_options: Value::Null,
dxf_import: Value::Null,
home: None,
}
}
}

View File

@ -0,0 +1,40 @@
//! The Home icon shows the Home screen over open documents.
use serde_json::json;
use crate::{VectorcraftApp, canvas};
fn app_with_doc() -> VectorcraftApp {
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({})).unwrap();
app
}
/// Draw one canvas frame; whether it drew the document (else the Home screen).
fn shows_document(app: &mut VectorcraftApp) -> bool {
app.canvas_rect = None;
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(800.0, 600.0))), ..Default::default() };
let mut out = ctx.run_ui(raw, |ui| canvas::show(app, ui));
out.textures_delta.clear();
app.canvas_rect.is_some()
}
#[test]
fn home_shows_over_open_documents_until_a_document_is_chosen() {
let mut app = app_with_doc();
assert!(shows_document(&mut app));
app.run("app.home", json!({})).unwrap();
assert!(!shows_document(&mut app), "Home replaces the canvas");
assert_eq!(app.session.documents().len(), 1, "the document stays open");
assert!(app.ui.dialog.is_none(), "Home is not the New Document dialog");
// Choosing the document's tab returns to it.
app.ui.home = None;
assert!(shows_document(&mut app));
// A new document (from Home's presets, New… or Open) replaces Home too.
app.run("app.home", json!({})).unwrap();
app.run("file.new", json!({})).unwrap();
assert!(shows_document(&mut app));
assert!(app.ui.home.is_none());
}