diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 3b9da0e36..51be688a4 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -728,6 +728,7 @@ fn selection_overlay(app: &VectorcraftApp, p: &egui::Painter, xf: &Xf) { // Bounding box with handles (Selection tool). // Live corner widgets (Direct Selection on a single live rectangle). if tool == "directSelection" + && app.ui.view.corner_widgets && st.selection.len() == 1 && let Some(NodeKind::Path { live: Some(vectorcraft_doc::LiveShape::Rectangle { w, h, radii, xf: lxf }), .. }) = st.doc.node(st.selection.objects[0]).map(|n| &n.kind) diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index c0f5a6911..3aa4d8194 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -234,6 +234,14 @@ impl VectorcraftApp { if id == "file.new" { self.ui.status.clear(); } + if id == "text.setStyle" + && let Some(font) = params.get("font").and_then(Value::as_str) + { + let r = &mut self.ui.recent_fonts; + r.retain(|f| f != font); + r.insert(0, font.to_string()); + r.truncate(10); + } } } r diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 1256f085b..7fb0802d1 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -60,6 +60,16 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ ("file.openRecent8", "Open Recent File 8", "", "{}"), ("file.openRecent9", "Open Recent File 9", "", "{}"), ("file.openRecent10", "Open Recent File 10", "", "{}"), + ("type.recentFont1", "Recent Font 1", "", "{} apply the 1. most recently used font"), + ("type.recentFont2", "Recent Font 2", "", "{} apply the 2. most recently used font"), + ("type.recentFont3", "Recent Font 3", "", "{} apply the 3. most recently used font"), + ("type.recentFont4", "Recent Font 4", "", "{} apply the 4. most recently used font"), + ("type.recentFont5", "Recent Font 5", "", "{} apply the 5. most recently used font"), + ("type.recentFont6", "Recent Font 6", "", "{} apply the 6. most recently used font"), + ("type.recentFont7", "Recent Font 7", "", "{} apply the 7. most recently used font"), + ("type.recentFont8", "Recent Font 8", "", "{} apply the 8. most recently used font"), + ("type.recentFont9", "Recent Font 9", "", "{} apply the 9. most recently used font"), + ("type.recentFont10", "Recent Font 10", "", "{} apply the 10. most recently used font"), ("file.clearRecent", "Clear Recent Files", "", "{}"), ("type.findFont", "Find Font…", "", "{} open the Find Font dialog (engine: text.fonts / text.replaceFont / select.font)"), ("file.recentFiles", "Recent Files", "", "{} → [path…] most recent first"), @@ -80,6 +90,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ ("view.outline", "Outline", "Cmd+Y", "{} toggle Outline/Preview"), ("view.pixelPreview", "Pixel Preview", "Cmd+Alt+Y", "{}"), ("view.trimView", "Trim View", "", "{} toggle: hide everything outside the artboards"), + ("view.cornerWidget", "Hide Corner Widget", "", "{} toggle the live corner widgets"), ("view.zoomIn", "Zoom In", "Cmd+=", "{}"), ("view.zoomOut", "Zoom Out", "Cmd+-", "{}"), ("view.fitArtboard", "Fit Artboard in Window", "Cmd+0", "{}"), @@ -236,6 +247,14 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option flag(&mut app.ui.view.outline), "view.pixelPreview" => flag(&mut app.ui.view.pixel_preview), "view.trimView" => flag(&mut app.ui.view.trim_view), + "view.cornerWidget" => flag(&mut app.ui.view.corner_widgets), + id if id.starts_with("type.recentFont") => { + let n: usize = id["type.recentFont".len()..].parse().unwrap_or(0); + match n.checked_sub(1).and_then(|i| app.ui.recent_fonts.get(i)).cloned() { + Some(font) => app.run("text.setStyle", json!({ "font": font })), + None => Err("no such recent font".into()), + } + } "view.edges" => flag(&mut app.ui.view.edges), "view.artboards" => flag(&mut app.ui.view.artboards), "view.rulers" => flag(&mut app.ui.view.rulers), @@ -458,6 +477,11 @@ pub fn dynamic_label(app: &VectorcraftApp, id: &str, label: &str) -> String { "view.outline" => if v.outline { "Preview" } else { "Outline" }.into(), "window.workspace.reset" => format!("Reset {}", app.ui.workspace), "view.edges" => if v.edges { "Hide Edges" } else { "Show Edges" }.into(), + "view.cornerWidget" => if v.corner_widgets { "Hide Corner Widget" } else { "Show Corner Widget" }.into(), + id if id.starts_with("type.recentFont") => { + let n: usize = id["type.recentFont".len()..].parse().unwrap_or(0); + n.checked_sub(1).and_then(|i| app.ui.recent_fonts.get(i)).cloned().unwrap_or_else(|| "—".into()) + } "view.artboards" => if v.artboards { "Hide Artboards" } else { "Show Artboards" }.into(), "view.rulers" => if v.rulers { "Hide Rulers" } else { "Show Rulers" }.into(), "view.boundingBox" => if v.bounding_box { "Hide Bounding Box" } else { "Show Bounding Box" }.into(), @@ -492,6 +516,20 @@ const RECENT_IDS: [&str; 10] = [ "file.openRecent10", ]; +/// Type → Recent Fonts slots. +const RECENT_FONT_IDS: [&str; 10] = [ + "type.recentFont1", + "type.recentFont2", + "type.recentFont3", + "type.recentFont4", + "type.recentFont5", + "type.recentFont6", + "type.recentFont7", + "type.recentFont8", + "type.recentFont9", + "type.recentFont10", +]; + /// Effective shortcut of a command: the user's override (Edit → Keyboard Shortcuts) or the default. pub fn shortcut_of(id: &str) -> Option<&'static str> { crate::shortcut_editor::command_shortcut(id) @@ -521,6 +559,9 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool { id["file.openRecent".len()..].parse::().is_ok_and(|n| n >= 1 && n <= app.ui.recent_files.len()) } "file.clearRecent" => !app.ui.recent_files.is_empty(), + id if id.starts_with("type.recentFont") => { + id["type.recentFont".len()..].parse::().is_ok_and(|n| n >= 1 && n <= app.ui.recent_fonts.len()) && app.session.active().is_some() + } "effect.dialog" | "ui.recolorDialog" => app.session.active().is_some_and(|d| !d.selection.is_empty()), "effect.applyLast" => app.last_effect.is_some() && app.session.active().is_some_and(|d| !d.selection.is_empty()), "file.export.pdf" => app.session.active().is_some(), @@ -839,7 +880,7 @@ pub fn menu_tree() -> Vec<(&'static str, Vec)> { "Type", vec![ sub("Font", font_items()), - todo("Recent Fonts"), + sub("Recent Fonts", RECENT_FONT_IDS.iter().map(|id| c("Recent Font", id)).collect()), sub("Size", TYPE_SIZES.iter().map(|(l, n)| cp(l, "text.setStyle", json!({ "size": n }))).collect()), Sep, panel("Glyphs", "glyphs"), @@ -1001,7 +1042,7 @@ pub fn menu_tree() -> Vec<(&'static str, Vec)> { c("Show Transparency Grid", "view.transparencyGrid"), todos("Hide Text Threads", "Cmd+Shift+Y"), todos("Hide Gradient Annotator", "Cmd+Alt+G"), - todo("Hide Corner Widget"), + c("Hide Corner Widget", "view.cornerWidget"), Sep, sub( "Guides", @@ -1461,6 +1502,25 @@ fn effect_menu() -> Vec { mod tests { use super::*; + #[test] + fn recent_fonts_and_corner_widget_toggle() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", json!({})).unwrap(); + let fams = vectorcraft_text::FontDb::global().families(); + let (a, b) = (fams[0].clone(), fams[fams.len() - 1].clone()); + app.run("text.create", json!({"x": 10, "y": 10, "text": "Hi"})).unwrap(); + app.run("text.setStyle", json!({"font": a})).unwrap(); + app.run("text.setStyle", json!({"font": b})).unwrap(); + assert_eq!(app.ui.recent_fonts, vec![b.clone(), a.clone()]); + assert_eq!(dynamic_label(&app, "type.recentFont2", "Recent Font"), a); + assert!(enabled(&app, "type.recentFont2") && !enabled(&app, "type.recentFont3")); + app.run("type.recentFont2", json!({})).unwrap(); + assert_eq!(app.ui.recent_fonts[0], a); + assert_eq!(dynamic_label(&app, "view.cornerWidget", ""), "Hide Corner Widget"); + app.run("view.cornerWidget", json!({})).unwrap(); + assert_eq!(dynamic_label(&app, "view.cornerWidget", ""), "Show Corner Widget"); + } + #[test] fn recent_files_track_opens_and_saves() { let dir = std::env::temp_dir().join(format!("dc-recent-{}", std::process::id())); diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 25a188f55..235ab3aa8 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -117,6 +117,8 @@ pub struct ViewFlags { pub edges: bool, /// View → Trim View: hide everything outside the artboards. pub trim_view: bool, + /// View → Hide/Show Corner Widget (live corner widgets on rectangles). + pub corner_widgets: bool, } impl Default for ViewFlags { @@ -135,6 +137,7 @@ impl Default for ViewFlags { transparency_grid: false, edges: true, trim_view: false, + corner_widgets: true, } } } @@ -224,6 +227,9 @@ pub struct UiState { /// File → Open Recent Files, most recent first. #[serde(default)] pub recent_files: Vec, + /// Type → Recent Fonts, most recent first. + #[serde(default)] + pub recent_fonts: Vec, /// Engine preferences (Edit → Preferences), persisted alongside the UI state. #[serde(default)] pub engine_prefs: Value, @@ -276,6 +282,7 @@ impl Default for UiState { workspace: default_workspace(), custom_workspaces: vec![], recent_files: vec![], + recent_fonts: vec![], engine_prefs: Value::Null, } }