Apply the Interface UI Font Size preference (#616)
* Apply the Interface UI Font Size preference * Regenerate the scorecard after rebase (58 unread settings); drop the before/after screenshots Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
fee9ff9613
commit
3f637410cb
|
|
@ -804,7 +804,6 @@ pub const HIDDEN_UNTIL_IMPLEMENTED: &[&str] = &[
|
|||
"general.alwaysCreateSmartObjectsWhenPlacing",
|
||||
"general.animatedZoom",
|
||||
"general.zoomResizesWindows",
|
||||
"interface.uiFontSize",
|
||||
"interface.showChannelsInColor",
|
||||
"interface.dynamicColorSliders",
|
||||
"workspace.autoCollapseIconPanels",
|
||||
|
|
|
|||
|
|
@ -245,6 +245,7 @@ fn add_to_all_families(ctx: &egui::Context, name: String, mut data: FontData) {
|
|||
}
|
||||
}
|
||||
let families = families.into_iter().map(|family| InsertFontFamily { family, priority: FontPriority::Lowest }).collect();
|
||||
crate::theme::size_ui_font(ctx, &name, &mut data);
|
||||
ctx.add_font(FontInsert { name, data, families });
|
||||
}
|
||||
|
||||
|
|
@ -303,6 +304,31 @@ mod tests {
|
|||
use super::*;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[test]
|
||||
fn lazy_fallbacks_follow_ui_font_size_and_survive_size_changes() {
|
||||
use photocraft_engine::prefs::UiFontSize;
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
ctx.run_ui(Default::default(), |_| {}).textures_delta.clear();
|
||||
crate::theme::set_ui_font_size(&ctx, UiFontSize::Large);
|
||||
let name = "test-lazy-fallback".to_string();
|
||||
add_to_all_families(&ctx, name.clone(), FontData::from_static(photocraft_text::fonts::INTER_REGULAR));
|
||||
for (size, scale) in [(UiFontSize::Large, 16.0 / 12.0), (UiFontSize::Tiny, 10.0 / 12.0), (UiFontSize::Small, 1.0)] {
|
||||
crate::theme::set_ui_font_size(&ctx, size);
|
||||
for _ in 0..2 {
|
||||
ctx.run_ui(Default::default(), |_| {}).textures_delta.clear();
|
||||
}
|
||||
let fonts = ctx.fonts(|f| f.definitions().clone());
|
||||
let fallback = fonts.font_data.get(&name).unwrap();
|
||||
assert_eq!(fallback.tweak.scale, scale);
|
||||
assert_eq!(fallback.tweak.y_offset_factor, 0.0, "same face as Inter keeps its baseline");
|
||||
for stack in fonts.families.values() {
|
||||
assert_eq!(stack.iter().filter(|n| *n == &name).count(), 1);
|
||||
assert_eq!(stack.last(), Some(&name), "fallback stays at the lowest priority");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static DIR: Mutex<Option<PathBuf>> = Mutex::new(None);
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ pub fn tick(app: &mut PhotocraftApp, ctx: &egui::Context) {
|
|||
app.session.prefs.edit(|p| p.interface.theme = t);
|
||||
app.prefs_rt.theme_pref = Some(t);
|
||||
}
|
||||
crate::theme::set_ui_font_size(ctx, app.session.prefs().interface.ui_font_size);
|
||||
presets_store(app);
|
||||
sync_tooltips(app, ctx);
|
||||
app.sync_recent();
|
||||
|
|
@ -1603,6 +1604,31 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_font_size_commands_follow_themes_and_dpi_and_reset_live() {
|
||||
let (mut app, _) = app_with_store();
|
||||
let ctx = egui::Context::default();
|
||||
PhotocraftApp::setup_context(&ctx, ThemeKind::Pro);
|
||||
let mut input = egui::RawInput::default();
|
||||
input.viewports.get_mut(&egui::ViewportId::ROOT).unwrap().native_pixels_per_point = Some(1.5);
|
||||
for (theme, scale, expected) in
|
||||
[("pro", "100", 1.0), ("studio", "auto", 1.5), ("studioLight", "200", 2.0), ("proMedium", "100", 1.0), ("classic", "auto", 1.5)]
|
||||
{
|
||||
app.run("prefs.set", json!({"values": {"interface.uiFontSize": "large", "interface.theme": theme, "interface.uiScale": scale}})).unwrap();
|
||||
for _ in 0..3 {
|
||||
ctx.run_ui(input.clone(), |ui| tick(&mut app, ui.ctx())).textures_delta.clear();
|
||||
}
|
||||
assert!((ctx.pixels_per_point() - expected).abs() < 1e-4);
|
||||
assert_eq!(ctx.fonts(|f| f.definitions().font_data["Inter"].tweak.scale), 16.0 / 12.0);
|
||||
}
|
||||
app.run("prefs.reset", json!({"path": "interface.uiFontSize"})).unwrap();
|
||||
for _ in 0..2 {
|
||||
ctx.run_ui(input.clone(), |ui| tick(&mut app, ui.ctx())).textures_delta.clear();
|
||||
}
|
||||
assert_eq!(ctx.fonts(|f| f.definitions().font_data["Inter"].tweak.scale), 1.0);
|
||||
assert!((ctx.pixels_per_point() - 1.5).abs() < 1e-4, "resetting font size keeps UI Scale");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recent_files_survive_a_restart_and_honour_the_count() {
|
||||
let (mut app, store) = app_with_store();
|
||||
|
|
@ -1664,6 +1690,7 @@ mod tests {
|
|||
assert!(prefs::is_hidden("rawDefaults.applyAutoTone"));
|
||||
assert!(!prefs::is_hidden("general.autoShowHomeScreen"));
|
||||
assert!(!prefs::is_hidden("interface.uiScale"));
|
||||
assert!(!prefs::is_hidden("interface.uiFontSize"));
|
||||
// Hidden values still round-trip through the dialog untouched.
|
||||
let (mut app, _) = app_with_store();
|
||||
app.run("prefs.set", json!({"values": {"type.smartQuotes": false}})).unwrap();
|
||||
|
|
@ -1760,6 +1787,7 @@ mod tests {
|
|||
|
||||
let values = h.state_mut().ui.dialog_mut(id).unwrap().fields.get_mut("values").unwrap();
|
||||
values["interface"]["theme"] = json!("studioLight");
|
||||
values["interface"]["uiFontSize"] = json!("large");
|
||||
values["performance"]["historyStates"] = json!(12);
|
||||
h.run_steps(2);
|
||||
assert!(!h.get_by_label("Apply").accesskit_node().is_disabled());
|
||||
|
|
@ -1768,6 +1796,7 @@ mod tests {
|
|||
|
||||
assert_eq!(h.state().session.prefs().performance.history_states, 12);
|
||||
assert_eq!(h.state().ui.theme, ThemeKind::StudioLight);
|
||||
assert_eq!(h.ctx.fonts(|f| f.definitions().font_data["Inter"].tweak.scale), 16.0 / 12.0);
|
||||
assert!(!h.state().session.prefs().type_.smart_quotes, "hidden settings round-trip unchanged");
|
||||
let d = h.state().ui.dialogs.iter().find(|d| d.id == id).unwrap();
|
||||
assert_eq!(d.fields["section"], "interface");
|
||||
|
|
@ -1776,6 +1805,7 @@ mod tests {
|
|||
let saved: Value = serde_json::from_str(store.lock().unwrap().as_ref().unwrap()).unwrap();
|
||||
assert_eq!(saved["performance"]["historyStates"], 12);
|
||||
assert_eq!(saved["interface"]["theme"], "studioLight");
|
||||
assert_eq!(saved["interface"]["uiFontSize"], "large");
|
||||
|
||||
// Repeated Apply starts from the validated values, not the dialog's original snapshot.
|
||||
h.state_mut().ui.dialog_mut(id).unwrap().fields.get_mut("values").unwrap()["performance"]["historyStates"] = json!(22);
|
||||
|
|
@ -1784,16 +1814,19 @@ mod tests {
|
|||
h.run_steps(4);
|
||||
assert_eq!(h.state().session.prefs().performance.history_states, 22);
|
||||
h.state_mut().ui.dialog_mut(id).unwrap().fields.get_mut("values").unwrap()["performance"]["historyStates"] = json!(33);
|
||||
h.state_mut().ui.dialog_mut(id).unwrap().fields.get_mut("values").unwrap()["interface"]["uiFontSize"] = json!("tiny");
|
||||
h.run_steps(2);
|
||||
h.get_by_label("Cancel").click();
|
||||
h.run_steps(4);
|
||||
assert!(h.state().ui.dialogs.iter().all(|d| d.id != id));
|
||||
assert_eq!(h.state().session.prefs().performance.history_states, 22);
|
||||
assert_eq!(h.ctx.fonts(|f| f.definitions().font_data["Inter"].tweak.scale), 16.0 / 12.0);
|
||||
let saved = store.lock().unwrap().clone().unwrap();
|
||||
let (mut restarted, _) = app_with_saved(Some(saved));
|
||||
tick(&mut restarted, &egui::Context::default());
|
||||
assert_eq!(restarted.session.prefs().performance.history_states, 22);
|
||||
assert_eq!(restarted.ui.theme, ThemeKind::StudioLight);
|
||||
assert_eq!(restarted.session.prefs().interface.ui_font_size, prefs::UiFontSize::Large);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
//! Widgets read [`Tokens::get`] instead of hard-coding colours, so every theme applies everywhere.
|
||||
|
||||
use egui::{Color32, CornerRadius, FontData, FontDefinitions, FontFamily, FontId, Stroke, TextStyle, Visuals};
|
||||
use photocraft_engine::prefs::UiFontSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -342,12 +343,81 @@ pub fn install_fonts_with(ctx: &egui::Context, cjk: crate::cjk_fonts::Sources) {
|
|||
stack.extend(fallback.iter().cloned());
|
||||
fonts.families.insert(FontFamily::Name(fam.into()), stack);
|
||||
}
|
||||
let size = ui_font_size(ctx);
|
||||
for (name, data) in &mut fonts.font_data {
|
||||
size_ui_font(ctx, name, Arc::make_mut(data));
|
||||
}
|
||||
ctx.set_fonts(fonts);
|
||||
ctx.add_plugin(UiFontSizePlugin { applied: size });
|
||||
// Japanese / Chinese / Korean fallback fonts (craft-fonts' Japanese ones if built in, then
|
||||
// the system's) are registered on demand (cjk_fonts.rs).
|
||||
crate::cjk_fonts::install_with(ctx, cjk);
|
||||
}
|
||||
|
||||
fn ui_fonts_id() -> egui::Id {
|
||||
egui::Id::new("photocraft-ui-fonts")
|
||||
}
|
||||
|
||||
fn ui_font_size(ctx: &egui::Context) -> UiFontSize {
|
||||
ctx.data(|d| d.get_temp::<UiFontSize>(ui_fonts_id())).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn font_scale(size: UiFontSize) -> f32 {
|
||||
match size {
|
||||
UiFontSize::Tiny => 10.0 / 12.0,
|
||||
UiFontSize::Small => 1.0,
|
||||
UiFontSize::Medium => 14.0 / 12.0,
|
||||
UiFontSize::Large => 16.0 / 12.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the original multiplier before sizing a face, including lazy CJK fallbacks.
|
||||
pub(crate) fn size_ui_font(ctx: &egui::Context, name: &str, data: &mut FontData) {
|
||||
ctx.data_mut(|d| d.insert_temp(ui_fonts_id().with(name), data.tweak.scale));
|
||||
data.tweak.scale *= font_scale(ui_font_size(ctx));
|
||||
}
|
||||
|
||||
/// Apply Interface › UI Font Size independently of display/canvas zoom. Scaling the registered
|
||||
/// faces covers explicit RichText and painter font sizes as well as egui's text styles. egui
|
||||
/// 0.36 uses these tweaks in shaping and row metrics; the layout tests below guard that contract.
|
||||
pub(crate) fn set_ui_font_size(ctx: &egui::Context, size: UiFontSize) {
|
||||
if ui_font_size(ctx) == size {
|
||||
return;
|
||||
}
|
||||
ctx.data_mut(|d| d.insert_temp(ui_fonts_id(), size));
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
/// Font access is valid after the first pass, including when preferences load before it.
|
||||
/// Keep only the original multipliers, not a second copy of large system font files.
|
||||
struct UiFontSizePlugin {
|
||||
applied: UiFontSize,
|
||||
}
|
||||
|
||||
impl egui::Plugin for UiFontSizePlugin {
|
||||
fn debug_name(&self) -> &'static str {
|
||||
"photocraft-ui-font-size"
|
||||
}
|
||||
|
||||
fn output_hook(&mut self, ctx: &egui::Context, _output: &mut egui::FullOutput) {
|
||||
let size = ui_font_size(ctx);
|
||||
if size == self.applied {
|
||||
return;
|
||||
}
|
||||
// Start from the current stack so lazily registered fallback faces are retained.
|
||||
let mut fonts = ctx.fonts(|f| f.definitions().clone());
|
||||
for (name, data) in &mut fonts.font_data {
|
||||
let id = ui_fonts_id().with(name);
|
||||
let original = ctx.data(|d| d.get_temp::<f32>(id)).unwrap_or(data.tweak.scale / font_scale(self.applied));
|
||||
ctx.data_mut(|d| d.insert_temp(id, original));
|
||||
Arc::make_mut(data).tweak.scale = original * font_scale(size);
|
||||
}
|
||||
ctx.set_fonts(fonts);
|
||||
self.applied = size;
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn medium(size: f32) -> FontId {
|
||||
FontId::new(size, FontFamily::Name("medium".into()))
|
||||
}
|
||||
|
|
@ -443,6 +513,45 @@ pub fn canvas_bg(t: &Tokens) -> Color32 {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn text_sizes(ctx: &egui::Context) -> Vec<egui::Vec2> {
|
||||
let mut sizes = Vec::new();
|
||||
// The plugin queues changed fonts at the end of a pass; egui applies them next pass.
|
||||
ctx.run_ui(Default::default(), |_| {}).textures_delta.clear();
|
||||
ctx.run_ui(Default::default(), |ui| {
|
||||
// Include custom painter fonts and explicit RichText sizes, not just text styles.
|
||||
for font in [FontId::proportional(11.5), medium(12.0), semibold(15.0), mono(12.0), TextStyle::Body.resolve(ui.style())] {
|
||||
sizes.push(ui.painter().layout_no_wrap("Interface 123".into(), font, Color32::WHITE).size());
|
||||
}
|
||||
sizes.push(ui.label(egui::RichText::new("Interface 123").size(13.0)).rect.size());
|
||||
})
|
||||
.textures_delta
|
||||
.clear();
|
||||
sizes
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_font_sizes_scale_shaping_and_row_height_without_accumulating() {
|
||||
let ctx = egui::Context::default();
|
||||
install_fonts(&ctx);
|
||||
apply(&ctx, ThemeKind::Pro);
|
||||
let original = text_sizes(&ctx);
|
||||
let definitions = ctx.fonts(|f| f.definitions().clone());
|
||||
for size in [UiFontSize::Tiny, UiFontSize::Small, UiFontSize::Medium, UiFontSize::Large, UiFontSize::Tiny, UiFontSize::Small] {
|
||||
set_ui_font_size(&ctx, size);
|
||||
let measured = text_sizes(&ctx);
|
||||
for (before, after) in original.iter().zip(&measured) {
|
||||
let expected = *before * font_scale(size);
|
||||
assert!((after.x - expected.x).abs() < 1.0, "{size:?}: width {after:?} vs {expected:?}");
|
||||
// egui rounds ascent/descent and the final row height to pixels separately.
|
||||
assert!((after.y - expected.y).abs() < 1.1, "{size:?}: height {after:?} vs {expected:?}");
|
||||
}
|
||||
set_ui_font_size(&ctx, size);
|
||||
assert_eq!(text_sizes(&ctx), measured, "setting the same size twice is stable");
|
||||
}
|
||||
assert_eq!(ctx.fonts(|f| f.definitions().clone()), definitions, "Small restores original font tweaks exactly");
|
||||
assert_eq!(ctx.zoom_factor(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn theme_names_parse() {
|
||||
assert_eq!(ThemeKind::from_name("Classic"), Some(ThemeKind::Classic));
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ their #209–#211 targets (first baseline, taken on a heavily loaded machine), a
|
|||
nudge scenario crashes the GPU compositor; the open area issues' checklists (#203–#220) are
|
||||
almost entirely missing or partial.
|
||||
|
||||
2026-10-07: UI Font Size now applies to interface text, including CJK fallbacks, independently
|
||||
of display and canvas zoom (#532). The current preference audit drops from 59 to 58 unread
|
||||
settings out of 135; see the regenerated scorecard.
|
||||
|
||||
**Bottom line.** Two days after 0.2.0 we had merged ~96 PRs and closed ~48 issues, but **real
|
||||
Photoshop parity is still well below 50%**. The biggest gaps are AI, missing tools, professional
|
||||
workflow depth and the plug-in ecosystem. Most fixes since 0.2.0 have passed our tests but have
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js
|
|||
| [Performance](#performance) | 3 | 22 | 13 | 47 scenarios: 3 within budget, 22 over budget or failing, 15 tracked only, 6 not run, 7 not measurable yet (baseline 2026-10-05) |
|
||||
| [File compatibility](#file-compatibility) | 1 | 8 | 18 | io corpus: oracle ≥ 146/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 229/309, round trip ≥ 307/309; photoshop oracles: oracle ≥ 132/256, round trip ≥ 258/256 |
|
||||
| [Tools](#tools) | 6 | 13 | 11 | |
|
||||
| [Workspace and UI](#workspace-and-ui) | 0 | 5 | 18 | settings that do nothing: 59 of 135 (target 0) |
|
||||
| [Workspace and UI](#workspace-and-ui) | 0 | 5 | 18 | settings that do nothing: 58 of 135 (target 0) |
|
||||
| [Type](#type) | 0 | 3 | 6 | |
|
||||
| [Automation](#automation) | 1 | 3 | 7 | 626 of 626 menu items live (100.0%) |
|
||||
| [Reliability](#reliability) | 4 | 1 | 2 | never-crash lints on 27 of 27 crates; panic_hunt present |
|
||||
|
|
@ -154,9 +154,9 @@ Corpus floors (`crates/io/tests/corpus.rs`): the enforced minimums; the corpus t
|
|||
|
||||
## Workspace and UI
|
||||
|
||||
**Settings that do nothing: 59 of 135** (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: 58 of 135** (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.beep_when_done`, `general.export_clipboard`, `general.resize_image_during_place`, `general.always_create_smart_objects_when_placing`, `general.animated_zoom`, `general.zoom_resizes_windows`, `interface.ui_font_size`, `interface.show_channels_in_color`, `interface.dynamic_color_sliders`, `workspace.auto_collapse_icon_panels`, `workspace.auto_show_hidden_panels`, `workspace.open_documents_as_tabs`, `workspace.enable_floating_document_window_docking`, `workspace.large_tabs`, `workspace.enable_narrow_options_bar`, `tools.zoom_clicked_point_to_center`, `tools.enable_flick_panning`, `tools.vary_round_brush_hardness_on_hud`, `tools.show_transformation_values`, `tools.double_click_layer_mask_launches_select_and_mask`, `file_handling.image_previews`, `file_handling.lowercase_extension`, `file_handling.save_in_background`, `file_handling.ignore_exif_profile_tag`, `file_handling.ask_before_saving_layered_tiff`, `file_handling.maximize_psd_compatibility`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `cursors.brush_preview_color`, `units_and_rulers.type_units`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `units_and_rulers.print_resolution`, `units_and_rulers.screen_resolution`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.smart_quotes`, `type_.missing_glyph_protection`, `type_.show_font_names_in_english`, `type_.use_esc_to_commit`, `type_.text_engine`, `type_.font_preview`, `type_.fill_new_type_layers_with_placeholder`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `enhanced_controls.zoom_with_trackpad_pinch`, `enhanced_controls.rotate_view_with_trackpad`, `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.beep_when_done`, `general.export_clipboard`, `general.resize_image_during_place`, `general.always_create_smart_objects_when_placing`, `general.animated_zoom`, `general.zoom_resizes_windows`, `interface.show_channels_in_color`, `interface.dynamic_color_sliders`, `workspace.auto_collapse_icon_panels`, `workspace.auto_show_hidden_panels`, `workspace.open_documents_as_tabs`, `workspace.enable_floating_document_window_docking`, `workspace.large_tabs`, `workspace.enable_narrow_options_bar`, `tools.zoom_clicked_point_to_center`, `tools.enable_flick_panning`, `tools.vary_round_brush_hardness_on_hud`, `tools.show_transformation_values`, `tools.double_click_layer_mask_launches_select_and_mask`, `file_handling.image_previews`, `file_handling.lowercase_extension`, `file_handling.save_in_background`, `file_handling.ignore_exif_profile_tag`, `file_handling.ask_before_saving_layered_tiff`, `file_handling.maximize_psd_compatibility`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `cursors.brush_preview_color`, `units_and_rulers.type_units`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `units_and_rulers.print_resolution`, `units_and_rulers.screen_resolution`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.smart_quotes`, `type_.missing_glyph_protection`, `type_.show_font_names_in_english`, `type_.use_esc_to_commit`, `type_.text_engine`, `type_.font_preview`, `type_.fill_new_type_layers_with_placeholder`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `enhanced_controls.zoom_with_trackpad_pinch`, `enhanced_controls.rotate_view_with_trackpad`, `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 |
|
||||
|---|---|---|---|---|
|
||||
|
|
|
|||
|
|
@ -72,6 +72,12 @@ size is unavailable, Auto follows system DPI. The fixed choices (75% to 300%) se
|
|||
scale, allowing large 4K displays to use smaller controls when desired. Canvas zoom shortcuts
|
||||
continue to control the document independently of UI scaling.
|
||||
|
||||
Interface → UI Font Size changes interface text independently of UI Scale and canvas zoom.
|
||||
Tiny, Small (the default), Medium and Large use 10/12, 1, 14/12 and 16/12 of the theme's original
|
||||
font sizes, preserving the relative sizes of headings, captions and numeric fields. Apply or OK
|
||||
updates text immediately; the choice persists across launches and theme changes. CJK fallback
|
||||
fonts use the same size, including fonts loaded after the preference changes.
|
||||
|
||||
## Localisation
|
||||
|
||||
Strings in code stay English and are the default lookup keys. `crates/ui-egui/src/i18n` maps them
|
||||
|
|
|
|||
Loading…
Reference in New Issue