Fonts: drop the plain font dropdown now that no picker uses it (review of #397)

Find Font and the Glyphs panel were widgets::font_dropdown's last
callers; the font menu's own tests cover searching and Enter. ROADMAP:
the Glyphs panel and Find Font's Replace With use the font menu, without
its live preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic 2026-10-08 07:01:27 +07:00
parent 1d5e9cb239
commit d324138bc1
3 changed files with 3 additions and 105 deletions

View File

@ -164,7 +164,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist
| M4 | Files (native, SVG, PDF, raster, Export for Screens, clipboard interop) | ✅ done (M4.14–M4.98 on 2026-10-05): Save As/Save a Copy/Revert/templates, Data Recovery and background save, SVG import and export fidelity (units, text, symbols, filters, SVGZ, Preserve Editing, SVG Options), PDF import (colours, layers, masks, editable text, security) and export (presets, marks, bleed, ICC and output intent, raster effects, subset fonts, PDF/X-1a, X-3 and X-4, PDF layers, overprint, page thumbnails, fast web view, PDF 1.3), Place and Links, Package, EPS and DXF in and out, EMF/WMF, TIFF/BMP/Targa/PSD/GIF/PNG-8 export, Print and print presets, slices, Save for Web, Asset Export, clipboard flavours, File Info. Left: DWG (use DXF), PSD placement as layers, overprint read back from PDF, polish | 3–6 |
| M5 | Performance | 🟡 background render + caches + MT done; `vectorcraft-cli bench` and `vectorcraft-cli perf` (budget suite); file format v2 opens 3× faster (50k paths: 722 → 244 ms); raster effects (glows, shadows, blur, feather) no longer force the whole frame single-threaded (filtered offscreen per effect, verified equal to the single-threaded reference); effect-heavy demos need a clean-machine benchmark; dirty-region rendering, GPU backend spike pending | 15–25 |
| M6 | Path operations (Pathfinder, Shape Builder, offset…) | ✅ mostly done (Boolean precision on almost-horizontal edges fixed and the property tests made deterministic; Shape Builder gap detection, large-offset bug open) | 3–6 |
| M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | 🟡 Character/Paragraph Styles, Area Type Options (with Width/Height), type areas resized by their handles and reshaped with Direct Selection, threaded text, Fit Headline, Glyphs, OpenType panel, Find Font, installed system fonts in every font menu (searchable; the Character and Properties menus show each family's sample of the selected text, preview it live under the pointer or ↑/↓, filter by script and kind, list a family's styles, and star favourites) and found by name on open/import, variable fonts' named instances as styles, CJK names in the UI, input methods (marked text in place, candidate window at the caret; Japanese IME), Text Wrap (offset, invert, both sides of an object; follows edits), Type on a Path effects, tab stops + Tabs panel, Hebrew/Arabic bidirectional text and Paragraph Direction done; tab leaders, spell check, vertical type pending | 45–60 |
| M7 | Type (point/area/path, editing, styles, OpenType, threading, glyphs) | 🟡 Character/Paragraph Styles, Area Type Options (with Width/Height), type areas resized by their handles and reshaped with Direct Selection, threaded text, Fit Headline, Glyphs, OpenType panel, Find Font, installed system fonts in every font menu (searchable; the Character and Properties menus show each family's sample of the selected text, preview it live under the pointer or ↑/↓, filter by script and kind, list a family's styles, and star favourites; the Glyphs panel and Find Font's Replace With use the same menu without the live preview) and found by name on open/import, variable fonts' named instances as styles, CJK names in the UI, input methods (marked text in place, candidate window at the caret; Japanese IME), Text Wrap (offset, invert, both sides of an object; follows edits), Type on a Path effects, tab stops + Tabs panel, Hebrew/Arabic bidirectional text and Paragraph Direction done; tab leaders, spell check, vertical type pending | 45–60 |
| M8 | Transform & distort (Puppet Warp, Liquify tools, Envelopes, Blends, Perspective Grid) | ✅ done (M8.1–M8.20 on 2026-10-06, fidelity pass): Liquify Tool Options with one shared brush, pen pressure, hold-to-apply, a safe scope and incremental strokes; tool options that last; Puppet Warp rotation, multi-select, Control bar and a rest-shape session that follows Undo; Width tool on compound paths; Envelope dialogs with preview, Reset with Warp/Mesh, full Envelope Options, type/images/symbols/appearance/gradients/patterns distorted on canvas and in every export, warps that keep their frame, Edit Contents, mesh handles and Control bar; Blend Options, anchor-targeted Blend tool, editable spine, keys picked on the canvas, faithful interpolation and knockout; Perspective Grid with Define Grid, presets, lock/snap/rulers, widgets, the 1–4 plane switch, movable planes, Perspective Selection scale/copy/perpendicular/Transform Again, and type and symbols truly projected with Edit Text. Left: Anti-Alias and Preserve Shape output, warp-envelope point editing, brush interpolation in blends, Free Transform/Scale/Reflect along a rotated box | 2–4 |
| M9 | Live effects (+ 3D & Materials) | 🟡 2D effects done incl. Effect → Pathfinder and Color Adjustments; SVG Filters, Document Raster Effects Settings, 3D pending | 86–135 |
| M10 | Brushes, symbols, patterns, Repeat | 🟡 pattern swatches (5 tile types, Pattern Options, editing mode, SVG `<pattern>`/PDF export) and live Repeat (radial/grid/mirror) done; brushes/symbols in progress | 23–37 |

View File

@ -1,12 +1,10 @@
//! The font menus list the installed fonts without asking for them (#36), and the font dropdown
//! searches them.
//! The font menus list the installed fonts without asking for them (#36).
use serde_json::json;
use vectorcraft_engine::Session;
use vectorcraft_text::{FontDb, system_font_dirs};
use crate::tests_labels::shapes_text;
use crate::{VectorcraftApp, menus, widgets};
use crate::{VectorcraftApp, menus};
#[test]
fn type_font_menu_lists_every_available_family() {
@ -25,51 +23,6 @@ fn type_font_menu_lists_every_available_family() {
assert_eq!(fonts(&app), labels);
}
#[test]
fn the_font_dropdown_searches_the_families_and_enter_picks_the_first_match() {
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
// Each frame: what it painted, where the dropdown is and what it picked.
let frame = |events: Vec<egui::Event>| {
let input =
egui::RawInput { events, screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(600.0, 800.0))), ..Default::default() };
let (mut at, mut picked) = (egui::Pos2::ZERO, None);
let mut out = ctx.run_ui(input, |ui| {
at = ui.next_widget_position();
picked = widgets::font_dropdown(ui, "test-font", "Inter", 220.0);
});
out.textures_delta.clear();
(shapes_text(&out), at, picked)
};
let (closed, at, _) = frame(vec![]);
assert_eq!(closed.trim(), "Inter");
let click = at + egui::vec2(40.0, 10.0);
let button = |pressed| egui::Event::PointerButton { pos: click, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
frame(vec![egui::Event::PointerMoved(click), button(true)]);
frame(vec![button(false)]);
// The popup sizes itself invisibly in its first frame, then the list scrolls (animated).
for _ in 0..30 {
frame(vec![]);
}
// The families painted in the list (the button shows the current one, Inter).
let families = FontDb::global().families();
let listed = |text: &str| -> Vec<String> { text.lines().skip(1).filter(|l| families.iter().any(|f| f == l)).map(str::to_string).collect() };
let (open, ..) = frame(vec![]);
assert!(listed(&open).len() >= families.len().min(5), "{open}");
assert!(listed(&open).iter().any(|f| f == "Inter"), "the current font in view: {open}");
// The search field has the focus: typing filters the list.
frame(vec![egui::Event::Text("source serif".into())]);
let (filtered, ..) = frame(vec![]);
let shown = listed(&filtered);
assert!(shown.iter().any(|f| f == "Source Serif 4"), "{filtered}");
assert!(shown.iter().all(|f| f.to_lowercase().contains("source serif")), "{shown:?}");
let enter = |pressed| egui::Event::Key { key: egui::Key::Enter, physical_key: None, pressed, repeat: false, modifiers: Default::default() };
let (_, _, picked) = frame(vec![enter(true), enter(false)]);
assert_eq!(picked.as_deref(), Some("Source Serif 4"));
let (closed, ..) = frame(vec![]);
assert_eq!(closed.trim(), "Inter", "the list closes");
}
#[test]
fn the_character_panel_menu_refreshes_the_font_list() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());

View File

@ -530,61 +530,6 @@ pub(crate) fn combo<R>(
.inner
}
/// The font family dropdown: every family available (the installed fonts included), with a search
/// field over the list that filters it as you type (Enter picks the first match). Returns the
/// chosen family.
pub fn font_dropdown(ui: &mut Ui, id: impl std::hash::Hash + std::fmt::Debug, current: &str, width: f32) -> Option<String> {
let state = ui.id().with(&id).with("font-search");
combo(ui, id, current, width, true, |ui| {
// Each time the list opens: unfiltered, with the current font in view. Typing goes to the
// search field.
let pass = ui.ctx().cumulative_pass_nr();
// (the last pass the list was drawn in, the pass it opened in)
let (last, opened) = ui.data(|d| d.get_temp::<(u64, u64)>(state)).unwrap_or_default();
let opened = if last + 1 < pass || last == 0 { pass } else { opened };
let field = state.with("query");
ui.data_mut(|d| {
d.insert_temp(state, (pass, opened));
if opened == pass {
d.insert_temp(field, String::new());
}
});
// Its first two passes: a popup sizes itself in the first, invisibly.
let opening = pass <= opened + 1;
if ui.memory(|m| m.focused().is_none()) {
ui.memory_mut(|m| m.request_focus(field.with("edit")));
}
let query = search_field(ui, field, "Search").to_lowercase();
let enter = ui.input(|i| i.key_pressed(egui::Key::Enter));
let families = vectorcraft_text::FontDb::global().menu_family_list();
let (mut chosen, mut first) = (None, None);
// The search field stays put over the list as it scrolls.
menu_scroll(ui, |ui| {
for f in families.iter().filter(|f| query.is_empty() || f.to_lowercase().contains(&query)) {
first.get_or_insert(f);
let r = ui.add(egui::Button::selectable(f == current, f.as_str()));
if opening && f == current {
r.scroll_to_me(Some(egui::Align::Center));
}
if r.clicked() {
chosen = Some(f.clone());
}
}
});
match first {
None => {
dim_label(ui, "No matching fonts");
}
Some(f) if enter && chosen.is_none() => chosen = Some(f.clone()),
_ => {}
}
if chosen.is_some() {
ui.close();
}
chosen
})
}
/// The body of a menu or popup list: as tall as its items up to the bottom of the window, and
/// scrolling only past that, so long menus (Window, Effect, a panel's menu) stay reachable on
/// small windows.