diff --git a/crates/ui-egui/src/native_menu.rs b/crates/ui-egui/src/native_menu.rs index c8d046a68..8d1b7ee08 100644 --- a/crates/ui-egui/src/native_menu.rs +++ b/crates/ui-egui/src/native_menu.rs @@ -590,7 +590,8 @@ pub fn sync(app: &mut VectorcraftApp, ctx: &egui::Context) { /// What the menus' rows depend on, cheaply (no allocation): commands run, the active document, /// its revision (edits, selection and view changes), history and clipboard, the language, -/// shortcuts, plug-ins and an open file dialog. `input` forces a change on frames with a click or +/// shortcuts, plug-ins, an open file dialog and the fonts (Type › Font lists the loaded fonts +/// until the installed ones are scanned, then all of them, #5483). `input` forces a change on frames with a click or /// a key press, which covers what the hash doesn't list (panels shown, a tool picked). While an /// interaction is open (for example a move, scale or shape drag, a dialog's or the font menu's /// preview, or a typing session), the revision, which each of its previews changes, is left out: @@ -609,5 +610,7 @@ fn state_hash(app: &VectorcraftApp, input: Option) -> u64 { app.file_dialog_open().hash(&mut h); (crate::i18n::current().code(), menus::plugin_revision()).hash(&mut h); crate::shortcut_editor::GENERATION.load(std::sync::atomic::Ordering::Relaxed).hash(&mut h); + let fonts = vectorcraft_text::FontDb::global(); + (fonts.generation(), fonts.installed_fonts_ready()).hash(&mut h); h.finish() } diff --git a/crates/ui-egui/src/tests/nativemenu.rs b/crates/ui-egui/src/tests/nativemenu.rs index cc6e4b346..5d1d1c26f 100644 --- a/crates/ui-egui/src/tests/nativemenu.rs +++ b/crates/ui-egui/src/tests/nativemenu.rs @@ -388,12 +388,20 @@ fn click(at: Pos2, pressed: bool) -> Event { } /// Run `drag` on a new app with the fake native menu, and run it again on another new app until -/// the shortcut and plug-in generations hold still through a run (at most 20 runs). Return that app -/// and what `drag` returned. The generations are process-wide and only grow. A test running -/// alongside can bump them, and the app then reads the menus again, so every step whose menu -/// reads a test counts runs inside `drag`. +/// the shortcut, plug-in and font generations hold still through a run (at most 20 runs). Return +/// that app and what `drag` returned. The generations are process-wide and only grow. A test +/// running alongside can bump them (a font it adds, the installed fonts' scan ending), and the app +/// then reads the menus again, so every step whose menu reads a test counts runs inside `drag`. fn undisturbed(drag: impl Fn(&mut Mac) -> R) -> (Mac, R) { - let generations = || (crate::shortcut_editor::GENERATION.load(std::sync::atomic::Ordering::Relaxed), crate::menus::plugin_revision()); + let fonts = vectorcraft_text::FontDb::global(); + let generations = || { + ( + crate::shortcut_editor::GENERATION.load(std::sync::atomic::Ordering::Relaxed), + crate::menus::plugin_revision(), + fonts.generation(), + fonts.installed_fonts_ready(), + ) + }; for _ in 0..20 { let mut m = Mac::new(true); let before = generations(); @@ -420,10 +428,18 @@ fn key_equivalents_run_through_the_normal_shortcut_path_once() { m.key_equivalent("Cmd+Z"); assert_eq!(undo(&m), before - 1, "⌘Z undid one step"); assert!(m.fake.synced() >= 1, "the menus were built"); - // Idle frames don't read the menus again. The shortcut and plug-in generations are - // process-wide, and tests running alongside with other shortcuts bump them (rightly reading - // the menus again), so judge two idle frames in which they held still. - let globals = || (crate::shortcut_editor::GENERATION.load(std::sync::atomic::Ordering::Relaxed), crate::menus::plugin_revision()); + // Idle frames don't read the menus again. The shortcut, plug-in and font generations are + // process-wide, and tests running alongside with other shortcuts or fonts bump them (rightly + // reading the menus again), so judge two idle frames in which they held still. + let fonts = vectorcraft_text::FontDb::global(); + let globals = || { + ( + crate::shortcut_editor::GENERATION.load(std::sync::atomic::Ordering::Relaxed), + crate::menus::plugin_revision(), + fonts.generation(), + fonts.installed_fonts_ready(), + ) + }; let quiet = (0..50).any(|_| { let (before, synced) = (globals(), m.fake.synced()); m.frame(vec![]); @@ -540,6 +556,23 @@ fn applying_a_dialog_preview_reads_the_menus() { assert_eq!(find(&m.fake.last(), "edit.undo").label, "Undo Rotate"); } +/// Fonts that come in after the menus were read (the installed fonts' scan ending, #5483) make the +/// next frame read them again, with no click or key, so Type › Font lists them. +#[test] +fn fonts_that_load_read_the_menus_again() { + use vectorcraft_text::test_fonts as t; + let db = vectorcraft_text::FontDb::global(); + // A test font no other test loaded yet, so adding it changes the fonts. + let fonts = [t::vertical_font(true), t::two_axis_font(), t::variable_font()]; + let mut m = Mac::new(true); + let synced = m.fake.synced(); + if !fonts.into_iter().flatten().any(|data| db.add_font(data) > 0) { + return; + } + m.frame(vec![]); + assert!(m.fake.synced() > synced, "the menus were read again"); +} + /// The font menu previews each font it highlights on the selected type, in one interaction: the /// menus are read as the preview begins and as it ends, and not for the fonts between. #[test]