Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
parent
6cfed3a7dc
commit
67e3d9be9f
|
|
@ -1007,7 +1007,6 @@ pub const HIDDEN_UNTIL_IMPLEMENTED: &[&str] = &[
|
|||
"type.missingGlyphProtection",
|
||||
"type.showFontNamesInEnglish",
|
||||
"type.textEngine",
|
||||
"type.fontPreview",
|
||||
"type.recentFonts",
|
||||
"enhancedControls.scrubbySliderAcceleration",
|
||||
"enhancedControls.touchGestures",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use std::sync::PoisonError;
|
|||
use egui::{ColorImage, Rect, TextureHandle, TextureOptions, pos2, vec2};
|
||||
use photocraft_color::{ColorMode, PixelFormat, SampleType};
|
||||
use photocraft_doc::TextLayer;
|
||||
use photocraft_engine::prefs::FontPreview;
|
||||
use photocraft_geom::Affine;
|
||||
|
||||
const SAMPLE: &str = "AaBbCc";
|
||||
|
|
@ -13,16 +14,44 @@ const SAMPLE: &str = "AaBbCc";
|
|||
const ARABIC_CORE: [char; 4] = ['\u{627}', '\u{644}', '\u{645}', '\u{639}'];
|
||||
/// Short, recognisable, and joins letters, so it shows the face's shaping.
|
||||
const ARABIC_SAMPLE: &str = "أبجد هوز";
|
||||
/// The Latin and Arabic sample widths and the sample height at the menu's medium (18 pt).
|
||||
const WIDTH: f32 = 100.0;
|
||||
const ARABIC_WIDTH: f32 = 72.0;
|
||||
const GAP: f32 = 6.0;
|
||||
const HEIGHT: f32 = 24.0;
|
||||
/// The medium point size the widths above are measured at.
|
||||
const MEDIUM: f32 = 18.0;
|
||||
const CACHE_LIMIT: usize = 128;
|
||||
const CACHE_ID: &str = "font-menu-previews";
|
||||
|
||||
/// Family, display scale, and the font database generations, so a family installed or fetched
|
||||
/// (served fonts) after its first miss gets a sample once it is available.
|
||||
type Key = (String, u32, (u64, u64));
|
||||
/// A width or height measured at 18 pt, scaled to the sample's point size.
|
||||
fn scaled(v: f32, size: f32) -> f32 {
|
||||
(v * size / MEDIUM).round()
|
||||
}
|
||||
|
||||
/// The Latin sample's `[width, height]` box for a point size: [`WIDTH`]×[`HEIGHT`] at 18 pt (the
|
||||
/// menu's medium), proportional otherwise.
|
||||
fn box_for(size: f32) -> [f32; 2] {
|
||||
[scaled(WIDTH, size), scaled(HEIGHT, size)]
|
||||
}
|
||||
|
||||
/// The font menu's sample geometry for Preferences ▸ Type ▸ Font Preview Size: the sample's point
|
||||
/// size and its Latin box; `None` when the preview is off.
|
||||
pub(crate) fn sample(preview: FontPreview) -> Option<(f32, [f32; 2])> {
|
||||
let size = match preview {
|
||||
FontPreview::Off => return None,
|
||||
FontPreview::Small => 14.0,
|
||||
FontPreview::Medium => MEDIUM,
|
||||
FontPreview::Large => 24.0,
|
||||
FontPreview::ExtraLarge => 32.0,
|
||||
FontPreview::Huge => 40.0,
|
||||
};
|
||||
Some((size, box_for(size)))
|
||||
}
|
||||
|
||||
/// Family, display scale, the font database generations (so a family installed or fetched
|
||||
/// (served fonts) after its first miss gets a sample once it is available) and the point size.
|
||||
type Key = (String, u32, (u64, u64), u32);
|
||||
#[derive(Clone)]
|
||||
struct Samples {
|
||||
latin: TextureHandle,
|
||||
|
|
@ -48,15 +77,15 @@ fn sample_texts(chars: &[char]) -> (String, Option<&'static str>) {
|
|||
(SAMPLE.into(), arabic.then_some(ARABIC_SAMPLE))
|
||||
}
|
||||
|
||||
fn render(engine: &mut photocraft_text::TextEngine, family: &str, text: &str, slot: f32, scale: u32) -> Option<ColorImage> {
|
||||
fn render(engine: &mut photocraft_text::TextEngine, family: &str, text: &str, slot: f32, scale: u32, size: f32) -> Option<ColorImage> {
|
||||
if text.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let layer = TextLayer { text: text.into(), font_family: family.into(), size_pt: 18.0 * scale as f32, ..Default::default() };
|
||||
let layer = TextLayer { text: text.into(), font_family: family.into(), size_pt: size * scale as f32, ..Default::default() };
|
||||
let layout = engine.layout(&layer, 72.0);
|
||||
let bounds = layout.bounds()?;
|
||||
let width = (slot as u32 * scale) as usize;
|
||||
let height = (HEIGHT as u32 * scale) as usize;
|
||||
let height = (scaled(HEIGHT, size) as u32 * scale) as usize;
|
||||
let transform = Affine::translate(f64::from(-bounds[0]), f64::from((height as f32 - (bounds[3] - bounds[1])) / 2.0 - bounds[1]));
|
||||
// Reject oversized ink before the rasterizer allocates a surface (malformed installed fonts).
|
||||
let ink = photocraft_text::render::ink_rect(&layout, &transform);
|
||||
|
|
@ -69,38 +98,38 @@ fn render(engine: &mut photocraft_text::TextEngine, family: &str, text: &str, sl
|
|||
Some(ColorImage::from_rgba_unmultiplied([width, height], &rgba.into_iter().flat_map(|p| [255, 255, 255, p[3]]).collect::<Vec<_>>()))
|
||||
}
|
||||
|
||||
fn images(engine: &mut photocraft_text::TextEngine, family: &str, scale: u32) -> Option<SampleImages> {
|
||||
fn images(engine: &mut photocraft_text::TextEngine, family: &str, scale: u32, size: f32) -> Option<SampleImages> {
|
||||
// Unknown/unreadable families keep their plain menu label, rather than a misleading fallback.
|
||||
if !engine.fonts.has_family(family) {
|
||||
return None;
|
||||
}
|
||||
let (latin, arabic) = sample_texts(&engine.fonts.charmap(family, 400, false));
|
||||
let latin = render(engine, family, &latin, WIDTH, scale)?;
|
||||
let arabic = arabic.and_then(|text| render(engine, family, text, ARABIC_WIDTH, scale));
|
||||
let latin = render(engine, family, &latin, scaled(WIDTH, size), scale, size)?;
|
||||
let arabic = arabic.and_then(|text| render(engine, family, text, scaled(ARABIC_WIDTH, size), scale, size));
|
||||
Some(SampleImages { latin, arabic })
|
||||
}
|
||||
|
||||
fn key(ctx: &egui::Context, family: &str) -> Key {
|
||||
fn key(ctx: &egui::Context, family: &str, size: f32) -> Key {
|
||||
let scale = ctx.pixels_per_point().ceil().clamp(1.0, 4.0) as u32;
|
||||
(family.to_owned(), scale, (photocraft_text::fonts::generation(), photocraft_text::served::generation()))
|
||||
(family.to_owned(), scale, (photocraft_text::fonts::generation(), photocraft_text::served::generation()), size as u32)
|
||||
}
|
||||
|
||||
/// Width a row reserves for its samples: wider once a painted row showed the family covers
|
||||
/// Arabic, so the label truncates before the samples. Never renders, so rows scrolled out of
|
||||
/// view cost nothing.
|
||||
pub(crate) fn slot_width(ctx: &egui::Context, family: &str) -> f32 {
|
||||
let key = key(ctx, family);
|
||||
pub(crate) fn slot_width(ctx: &egui::Context, family: &str, size: f32) -> f32 {
|
||||
let key = key(ctx, family, size);
|
||||
let arabic = ctx.data_mut(|d| {
|
||||
let cache = d.get_temp_mut_or_default::<Cache>(egui::Id::new(CACHE_ID));
|
||||
cache.0.iter().any(|(k, samples)| k == &key && samples.as_ref().is_some_and(|s| s.arabic.is_some()))
|
||||
});
|
||||
if arabic { WIDTH + GAP + ARABIC_WIDTH } else { WIDTH }
|
||||
if arabic { scaled(WIDTH, size) + GAP + scaled(ARABIC_WIDTH, size) } else { scaled(WIDTH, size) }
|
||||
}
|
||||
|
||||
pub(crate) fn paint(ui: &egui::Ui, family: &str, row: Rect) {
|
||||
pub(crate) fn paint(ui: &egui::Ui, family: &str, row: Rect, size: f32) {
|
||||
let ctx = ui.ctx();
|
||||
let scale = ctx.pixels_per_point().ceil().clamp(1.0, 4.0) as u32;
|
||||
let key = key(ctx, family);
|
||||
let key = key(ctx, family, size);
|
||||
let id = egui::Id::new(CACHE_ID);
|
||||
let cached = ctx.data_mut(|d| {
|
||||
let cache = d.get_temp_mut_or_default::<Cache>(id);
|
||||
|
|
@ -111,7 +140,7 @@ pub(crate) fn paint(ui: &egui::Ui, family: &str, row: Rect) {
|
|||
Some(samples)
|
||||
});
|
||||
let samples = cached.unwrap_or_else(|| {
|
||||
let samples = images(&mut photocraft_text::shared().lock().unwrap_or_else(PoisonError::into_inner), family, scale).map(|img| {
|
||||
let samples = images(&mut photocraft_text::shared().lock().unwrap_or_else(PoisonError::into_inner), family, scale, size).map(|img| {
|
||||
let load = |image| ctx.load_texture(format!("font-sample-{family}"), image, TextureOptions::LINEAR);
|
||||
Samples { latin: load(img.latin), arabic: img.arabic.map(load) }
|
||||
});
|
||||
|
|
@ -131,10 +160,12 @@ pub(crate) fn paint(ui: &egui::Ui, family: &str, row: Rect) {
|
|||
if let Some(samples) = samples {
|
||||
let tint = crate::theme::Tokens::get(ctx).text;
|
||||
let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
|
||||
let latin = Rect::from_min_size(pos2(row.right() - WIDTH - 8.0, row.center().y - HEIGHT / 2.0), vec2(WIDTH, HEIGHT));
|
||||
let [w, h] = box_for(size);
|
||||
let latin = Rect::from_min_size(pos2(row.right() - w - 8.0, row.center().y - h / 2.0), vec2(w, h));
|
||||
ui.painter().image(samples.latin.id(), latin, uv, tint);
|
||||
if let Some(arabic) = samples.arabic {
|
||||
let rect = Rect::from_min_size(pos2(latin.left() - GAP - ARABIC_WIDTH, latin.top()), vec2(ARABIC_WIDTH, HEIGHT));
|
||||
let aw = scaled(ARABIC_WIDTH, size);
|
||||
let rect = Rect::from_min_size(pos2(latin.left() - GAP - aw, latin.top()), vec2(aw, h));
|
||||
ui.painter().image(arabic.id(), rect, uv, tint);
|
||||
}
|
||||
}
|
||||
|
|
@ -145,7 +176,7 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
fn image(engine: &mut photocraft_text::TextEngine, family: &str, scale: u32) -> Option<ColorImage> {
|
||||
images(engine, family, scale).map(|i| i.latin)
|
||||
images(engine, family, scale, MEDIUM).map(|i| i.latin)
|
||||
}
|
||||
|
||||
fn charmap(text: &str) -> Vec<char> {
|
||||
|
|
@ -166,14 +197,27 @@ mod tests {
|
|||
assert!(image(&mut engine, "Missing font", 1).is_none());
|
||||
}
|
||||
|
||||
/// Preferences ▸ Type ▸ Font Preview Size: Small…Huge scale the sample, Off removes it.
|
||||
#[test]
|
||||
fn the_preview_size_preference_scales_the_sample() {
|
||||
assert_eq!(sample(FontPreview::Off), None);
|
||||
assert_eq!(sample(FontPreview::Medium), Some((18.0, [100.0, 24.0])));
|
||||
let Some((_, [w, h])) = sample(FontPreview::Huge) else { panic!("Huge previews") };
|
||||
assert!(w > 100.0 && h > 24.0, "Huge is bigger than Medium: {w}×{h}");
|
||||
assert!(sample(FontPreview::Small).unwrap().1[0] < sample(FontPreview::ExtraLarge).unwrap().1[0], "sizes are ordered");
|
||||
let mut engine = photocraft_text::TextEngine::new();
|
||||
let big = images(&mut engine, "Inter", 1, 32.0).unwrap().latin;
|
||||
assert_eq!(big.size, [178, 43], "the box follows the size");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn latin_only_families_get_no_arabic_sample() {
|
||||
let mut engine = photocraft_text::TextEngine::new();
|
||||
for family in ["Inter", "JetBrains Mono"] {
|
||||
assert!(images(&mut engine, family, 1).expect("bundled").arabic.is_none(), "{family}");
|
||||
assert!(images(&mut engine, family, 1, MEDIUM).expect("bundled").arabic.is_none(), "{family}");
|
||||
}
|
||||
assert!(images(&mut engine, "", 1).is_none());
|
||||
assert!(images(&mut engine, "Missing font", 1).is_none());
|
||||
assert!(images(&mut engine, "", 1, MEDIUM).is_none());
|
||||
assert!(images(&mut engine, "Missing font", 1, MEDIUM).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -202,7 +246,7 @@ mod tests {
|
|||
eprintln!("skipped: no installed family covers both Latin and Arabic");
|
||||
return;
|
||||
};
|
||||
let images = images(&mut engine, &family, 1).expect(&family);
|
||||
let images = images(&mut engine, &family, 1, MEDIUM).expect(&family);
|
||||
let arabic = images.arabic.expect("Arabic sample");
|
||||
assert_eq!(arabic.size, [ARABIC_WIDTH as usize, HEIGHT as usize]);
|
||||
assert!(arabic.pixels.iter().any(|p| p.a() > 0), "{family}");
|
||||
|
|
|
|||
|
|
@ -5,23 +5,29 @@ use egui::accesskit::Role;
|
|||
use egui::{Key, Modifiers, Rect, vec2};
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use photocraft_engine::prefs::FontPreview;
|
||||
|
||||
struct State {
|
||||
current: String,
|
||||
families: Vec<String>,
|
||||
popup: egui::Id,
|
||||
preview: FontPreview,
|
||||
}
|
||||
|
||||
fn harness() -> Harness<'static, State> {
|
||||
harness_with(FontPreview::Medium)
|
||||
}
|
||||
|
||||
fn harness_with(preview: FontPreview) -> Harness<'static, State> {
|
||||
// Enough families that the unfiltered menu hits its maximum height and scrolls.
|
||||
let mut families: Vec<String> = (0..60).map(|i| format!("Family {i:02}")).collect();
|
||||
families.push("Zeta Sans".into());
|
||||
let state = State { current: "Family 00".into(), families, popup: egui::Id::NULL };
|
||||
let state = State { current: "Family 00".into(), families, popup: egui::Id::NULL, preview };
|
||||
let mut h = Harness::builder().with_size(vec2(600.0, 900.0)).build_ui_state(
|
||||
|ui, s: &mut State| {
|
||||
s.popup = ui.make_persistent_id(egui::IdSalt::new("type-font")).with("popup");
|
||||
let fams = s.families.clone();
|
||||
font_picker_in(ui, &mut s.current, 170.0, &fams);
|
||||
font_picker_in(ui, &mut s.current, 170.0, &fams, s.preview);
|
||||
},
|
||||
state,
|
||||
);
|
||||
|
|
@ -376,3 +382,21 @@ fn hovering_a_font_previews_only_the_selected_characters() {
|
|||
assert_eq!(fonts(&preview), ["JetBrains Mono", "JetBrains Mono", "Inter", "Inter", "Inter", "Inter"]);
|
||||
assert_eq!(fonts(&app.session.active().unwrap().doc), ["JetBrains Mono", "JetBrains Mono", "Inter", "Inter", "Inter", "Inter"]);
|
||||
}
|
||||
|
||||
/// Preferences ▸ Type ▸ Font Preview Size: Extra Large grows the sample rows, Off drops the
|
||||
/// samples (rows shrink to the plain height).
|
||||
#[test]
|
||||
fn the_preview_size_preference_resizes_or_removes_the_samples() {
|
||||
let mut h = harness_with(FontPreview::Medium);
|
||||
open(&mut h);
|
||||
let medium = h.get_by_label("Family 00").rect().height();
|
||||
assert!((medium - 28.0).abs() < 0.5, "medium rows hold the 24 px sample: {medium}");
|
||||
let mut h = harness_with(FontPreview::ExtraLarge);
|
||||
open(&mut h);
|
||||
let extra = h.get_by_label("Family 00").rect().height();
|
||||
assert!(extra > medium, "extra large rows are taller: {extra} vs {medium}");
|
||||
let mut h = harness_with(FontPreview::Off);
|
||||
open(&mut h);
|
||||
let off = h.get_by_label("Family 00").rect().height();
|
||||
assert!((off - 28.0).abs() < 0.5, "off keeps the plain row height: {off}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -371,6 +371,8 @@ fn options_editor(app: &mut PhotocraftApp, ui: &mut egui::Ui, paragraph: bool, s
|
|||
}
|
||||
});
|
||||
let defined = |attrs: &Value, k: &str| attrs.get(k).is_some();
|
||||
// Preferences ▸ Type ▸ Font Preview Size sizes the family picker's samples.
|
||||
let preview = app.session.prefs().type_.font_preview;
|
||||
// Preferences ▸ Units & Rulers ▸ Type: the model stores points; the fields may show pixels
|
||||
// (by the document's resolution) or millimetres.
|
||||
let (type_unit, unit_label) = crate::type_tool::type_unit(app);
|
||||
|
|
@ -397,7 +399,7 @@ fn options_editor(app: &mut PhotocraftApp, ui: &mut egui::Ui, paragraph: bool, s
|
|||
ui.add_enabled_ui(on, |ui| match kind {
|
||||
"font" => {
|
||||
let mut f = v.as_str().unwrap_or("").to_string();
|
||||
if family_picker(ui, &format!("style-font-{paragraph}-{id}"), &mut f) {
|
||||
if family_picker(ui, &format!("style-font-{paragraph}-{id}"), &mut f, preview) {
|
||||
set = Some(json!({ panel: f }));
|
||||
}
|
||||
}
|
||||
|
|
@ -506,9 +508,9 @@ fn options_editor(app: &mut PhotocraftApp, ui: &mut egui::Ui, paragraph: bool, s
|
|||
}
|
||||
|
||||
/// Searchable family combo box.
|
||||
fn family_picker(ui: &mut egui::Ui, salt: &str, current: &mut String) -> bool {
|
||||
fn family_picker(ui: &mut egui::Ui, salt: &str, current: &mut String, preview: photocraft_engine::prefs::FontPreview) -> bool {
|
||||
let mut shown = if current.is_empty() { photocraft_text::fonts::DEFAULT_FAMILY.to_string() } else { current.clone() };
|
||||
let changed = crate::type_tool::family_picker_in(ui, salt, &mut shown, 140.0, &crate::type_tool::families());
|
||||
let changed = crate::type_tool::family_picker_in(ui, salt, &mut shown, 140.0, &crate::type_tool::families(), preview);
|
||||
if changed {
|
||||
*current = shown;
|
||||
}
|
||||
|
|
@ -598,7 +600,7 @@ pub fn glyphs_panel(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
|
|||
// Font family + style.
|
||||
ui.horizontal(|ui| {
|
||||
let mut f = family.clone();
|
||||
if family_picker(ui, "glyph-family", &mut f) {
|
||||
if family_picker(ui, "glyph-family", &mut f, app.session.prefs().type_.font_preview) {
|
||||
app.ui.type_panels.glyph_family = f;
|
||||
app.ui.type_panels.glyph_style = tl!("Regular").into();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -844,8 +844,8 @@ pub fn style_label(style: &str) -> String {
|
|||
}
|
||||
|
||||
/// Searchable font-family combo box.
|
||||
fn font_picker(ui: &mut egui::Ui, current: &mut String, width: f32) -> bool {
|
||||
family_picker_in(ui, "type-font", current, width, &families())
|
||||
fn font_picker(ui: &mut egui::Ui, current: &mut String, width: f32, preview: photocraft_engine::prefs::FontPreview) -> bool {
|
||||
family_picker_in(ui, "type-font", current, width, &families(), preview)
|
||||
}
|
||||
|
||||
/// Maximum height of the font menu.
|
||||
|
|
@ -853,13 +853,20 @@ const FONT_MENU_HEIGHT: f32 = 460.0;
|
|||
|
||||
/// [`font_picker`] over a given family list (tests pass their own).
|
||||
#[cfg(test)]
|
||||
fn font_picker_in(ui: &mut egui::Ui, current: &mut String, width: f32, families: &[String]) -> bool {
|
||||
family_picker_in(ui, "type-font", current, width, families)
|
||||
fn font_picker_in(ui: &mut egui::Ui, current: &mut String, width: f32, families: &[String], preview: photocraft_engine::prefs::FontPreview) -> bool {
|
||||
family_picker_in(ui, "type-font", current, width, families, preview)
|
||||
}
|
||||
|
||||
/// Shared by the Type options, Character, style and Glyphs panels. Selection changes still
|
||||
/// flow through each caller's existing engine command.
|
||||
pub(crate) fn family_picker_in(ui: &mut egui::Ui, salt: &str, current: &mut String, width: f32, families: &[String]) -> bool {
|
||||
pub(crate) fn family_picker_in(
|
||||
ui: &mut egui::Ui,
|
||||
salt: &str,
|
||||
current: &mut String,
|
||||
width: f32,
|
||||
families: &[String],
|
||||
preview: photocraft_engine::prefs::FontPreview,
|
||||
) -> bool {
|
||||
let mut changed = false;
|
||||
let search_id = ui.id().with(("font-search", salt));
|
||||
let combo = egui::ComboBox::from_id_salt(salt)
|
||||
|
|
@ -917,20 +924,24 @@ pub(crate) fn family_picker_in(ui: &mut egui::Ui, salt: &str, current: &mut Stri
|
|||
ui.data_mut(|d| d.remove::<String>(search_id));
|
||||
ui.close();
|
||||
}
|
||||
let preview = crate::font_preview::sample(preview);
|
||||
let row_h = preview.map_or(28.0, |(_, [_, h])| (h + 4.0).max(28.0));
|
||||
|
||||
let mut hovered: Option<String> = None;
|
||||
for f in filtered {
|
||||
let response = ui.add_sized(
|
||||
[330.0, 28.0],
|
||||
egui::Button::selectable(f == current, f)
|
||||
.truncate()
|
||||
.right_text(egui::Atom::custom(ui.id().with(("font-sample", f)), egui::vec2(crate::font_preview::slot_width(ui.ctx(), f), 24.0))),
|
||||
);
|
||||
let mut button = egui::Button::selectable(f == current, f).truncate();
|
||||
if let Some((size, [_, h])) = preview {
|
||||
button =
|
||||
button.right_text(egui::Atom::custom(ui.id().with(("font-sample", f)), egui::vec2(crate::font_preview::slot_width(ui.ctx(), f, size), h)));
|
||||
}
|
||||
let response = ui.add_sized([330.0, row_h], button);
|
||||
if (down || up) && f == current {
|
||||
response.scroll_to_me(Some(egui::Align::Center));
|
||||
}
|
||||
if ui.is_rect_visible(response.rect) {
|
||||
crate::font_preview::paint(ui, f, response.rect);
|
||||
if let Some((size, _)) = preview
|
||||
&& ui.is_rect_visible(response.rect)
|
||||
{
|
||||
crate::font_preview::paint(ui, f, response.rect, size);
|
||||
}
|
||||
if response.hovered() {
|
||||
hovered = Some(f.clone());
|
||||
|
|
@ -1095,7 +1106,7 @@ pub fn options_bar(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
|
|||
app.ui.status_error = true;
|
||||
}
|
||||
}
|
||||
if font_picker(ui, &mut fam, 170.0) {
|
||||
if font_picker(ui, &mut fam, 170.0, app.session.prefs().type_.font_preview) {
|
||||
app.ui.tool_options.type_font = fam.clone();
|
||||
let st = styles(&fam);
|
||||
style = if st.contains(&style) { style } else { st.first().cloned().unwrap_or_else(|| "Regular".into()) };
|
||||
|
|
@ -1398,7 +1409,7 @@ fn type_sections(app: &mut PhotocraftApp, ui: &mut egui::Ui, character: bool, pa
|
|||
let w = field_width(full, 2, LABEL_W);
|
||||
let mut fam = c.font_family.clone();
|
||||
row(ui, &mut |ui| {
|
||||
if font_picker(ui, &mut fam, full) {
|
||||
if font_picker(ui, &mut fam, full, app.session.prefs().type_.font_preview) {
|
||||
app.ui.tool_options.type_font = fam.clone();
|
||||
apply(app, ui.ctx(), json!({"font": fam}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js
|
|||
| [Performance](#performance) | 3 | 22 | 22 | 56 scenarios: 3 within budget, 22 over budget or failing, 9 tracked only, 16 not run, 6 not measurable yet (baseline 2026-10-05) |
|
||||
| [File compatibility](#file-compatibility) | 5 | 11 | 11 | io corpus: oracle ≥ 146/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 236/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 133/256, round trip ≥ 258/256 |
|
||||
| [Tools](#tools) | 21 | 17 | 4 | |
|
||||
| [Workspace and UI](#workspace-and-ui) | 6 | 7 | 10 | settings that do nothing: 38 of 152 (target 0) |
|
||||
| [Workspace and UI](#workspace-and-ui) | 6 | 7 | 10 | settings that do nothing: 37 of 152 (target 0) |
|
||||
| [Type](#type) | 0 | 3 | 6 | |
|
||||
| [Automation](#automation) | 3 | 2 | 6 | 630 of 630 menu items live (100.0%) |
|
||||
| [Reliability](#reliability) | 5 | 2 | 0 | never-crash lints on 28 of 28 crates; panic_hunt present |
|
||||
|
|
@ -175,9 +175,9 @@ Corpus floors (`crates/io/tests/corpus.rs`): the enforced minimums; the corpus t
|
|||
|
||||
## Workspace and UI
|
||||
|
||||
**Settings that do nothing: 38 of 152** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't.
|
||||
**Settings that do nothing: 37 of 152** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't.
|
||||
|
||||
Unread: `general.color_picker`, `general.always_create_smart_objects_when_placing`, `general.animated_zoom`, `general.zoom_resizes_windows`, `workspace.auto_collapse_icon_panels`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `workspace.enable_narrow_options_bar`, `tools.enable_flick_panning`, `tools.show_transformation_values`, `file_handling.image_previews`, `file_handling.save_in_background`, `file_handling.ignore_exif_profile_tag`, `file_handling.maximize_psd_compatibility`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `units_and_rulers.print_resolution`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.missing_glyph_protection`, `type_.show_font_names_in_english`, `type_.text_engine`, `type_.font_preview`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `raw_defaults.color_space`, `raw_defaults.bit_depth`, `raw_defaults.resolution`, `raw_defaults.sharpen_for`, `raw_defaults.open_as_smart_object`, `raw_defaults.apply_auto_tone`, `integrations.allow_agent_control`, `integrations.control_port`.
|
||||
Unread: `general.color_picker`, `general.always_create_smart_objects_when_placing`, `general.animated_zoom`, `general.zoom_resizes_windows`, `workspace.auto_collapse_icon_panels`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `workspace.enable_narrow_options_bar`, `tools.enable_flick_panning`, `tools.show_transformation_values`, `file_handling.image_previews`, `file_handling.save_in_background`, `file_handling.ignore_exif_profile_tag`, `file_handling.maximize_psd_compatibility`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `units_and_rulers.print_resolution`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.missing_glyph_protection`, `type_.show_font_names_in_english`, `type_.text_engine`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `raw_defaults.color_space`, `raw_defaults.bit_depth`, `raw_defaults.resolution`, `raw_defaults.sharpen_for`, `raw_defaults.open_as_smart_object`, `raw_defaults.apply_auto_tone`, `integrations.allow_agent_control`, `integrations.control_port`.
|
||||
|
||||
| Id | Target | Status | Issue | Note |
|
||||
|---|---|---|---|---|
|
||||
|
|
|
|||
Loading…
Reference in New Issue