M11.4: View → Hide/Show Corner Widget; Type → Recent Fonts (last 10 applied fonts, persisted)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-10-01 23:46:20 -04:00
co-authored by Claude Opus 5.5
parent 69be79bbf1
commit c878ac994c
4 changed files with 78 additions and 2 deletions
+1
View File
@@ -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)
+8
View File
@@ -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
+62 -2
View File
@@ -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<R
"view.outline" => 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::<usize>().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::<usize>().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<Item>)> {
"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<Item>)> {
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<Item> {
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()));
+7
View File
@@ -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<String>,
/// Type → Recent Fonts, most recent first.
#[serde(default)]
pub recent_fonts: Vec<String>,
/// 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,
}
}