diff --git a/nebula_app/i18n/en-US.json b/nebula_app/i18n/en-US.json index 0b9882c..c5de905 100644 --- a/nebula_app/i18n/en-US.json +++ b/nebula_app/i18n/en-US.json @@ -79,6 +79,13 @@ "background_cover_description": "The image will extend beneath the title bar, window buttons, tabs, and SSH sidebar. Low-contrast images may make controls harder to see; the interface retains a minimum-opacity safeguard." }, "font": { + "ui_family": "Interface font", + "ui_family_description": "Enter an installed font family; leave empty for the default. Applies on Enter or leaving the field.", + "ui_size": "Interface font size", + "ui_size_description": "Changes interface text and component sizing independently of terminal text.", + "english": "English terminal font", + "chinese": "Chinese terminal font", + "chinese_description": "Used for Chinese and other full-width characters. Enter a font family or comma-separated families; leave empty to use the English font settings. Applies on Enter or leaving the field.", "fallback_description": "The first family is preferred; missing glyphs fall back through the remaining families, with bundled Maple as the final safe fallback.", "group_description": "The first family is primary; the rest are used in fallback order.", "loading": "Enumerating system fonts...", diff --git a/nebula_app/i18n/zh-CN.json b/nebula_app/i18n/zh-CN.json index 721e9d3..597cfba 100644 --- a/nebula_app/i18n/zh-CN.json +++ b/nebula_app/i18n/zh-CN.json @@ -79,6 +79,13 @@ "background_cover_description": "背景图会延伸到标题栏、窗口按钮、Tab 与 SSH 侧栏下方,低对比度图片可能影响操作可见性;界面仍会保留最低不透明度保护。" }, "font": { + "ui_family": "界面字体", + "ui_family_description": "输入已安装的字体名称;留空使用默认字体。按回车或离开输入框后生效。", + "ui_size": "界面字号", + "ui_size_description": "独立调整界面文字和组件字号,不影响终端文字。", + "english": "英文终端字体", + "chinese": "中文终端字体", + "chinese_description": "用于中文等全宽字符。输入字体名称,多个字体以逗号分隔;留空时使用英文字体设置。按回车或离开输入框后生效。", "fallback_description": "第 1 项优先显示;缺失字形依次使用后续字体,内置 Maple 是最终安全兜底。", "group_description": "第 1 项是主字体,其余按顺序回退", "loading": "正在枚举系统字体…", diff --git a/nebula_app/src/gpui_shell/config.rs b/nebula_app/src/gpui_shell/config.rs index 3f98075..03b3f26 100644 --- a/nebula_app/src/gpui_shell/config.rs +++ b/nebula_app/src/gpui_shell/config.rs @@ -42,6 +42,7 @@ pub struct Settings { /// 已解析的界面语言。GPUI 组件只读这个内存全局,渲染路径不得重复读盘。 pub ui_language: UiLanguage, pub font_family: String, + pub font_cjk: Option<[gpui::Font; 4]>, pub font_bold_family: String, pub font_italic_family: String, pub font_bold_italic_family: String, @@ -50,6 +51,7 @@ pub struct Settings { /// 配置文件的基准字号,不含设置页/Ctrl+滚轮持久化的终端缩放。 /// 启动窗口按它定形,和旧壳的 `window_size` 契约一致。 pub base_font_size_px: f32, + pub ui_font_size_px: f32, /// 字体 cell 的物理像素偏移;旧壳 Windows 默认 y=4,必须在设备像素 /// 域参与取整,才能在 125%/150% DPI 下保持同一行数。 pub font_offset_x: f32, @@ -165,6 +167,7 @@ impl Settings { font_bold_italic_family: secondary(&raw.font.bold_italic), font_size_px, base_font_size_px, + ui_font_size_px: runtime.ui_font_size_px.unwrap_or(base_font_size_px), font_offset_x: f32::from(offset.x), font_offset_y: f32::from(offset.y), palette, @@ -193,6 +196,20 @@ impl Settings { cjk_bold_regular: runtime.cjk_bold_regular, shell_id: runtime.shell.clone(), font_family: normal_family, + font_cjk: runtime.font_family_cjk.as_deref().map(|family| { + use gpui::{FontStyle, FontWeight}; + [ + (FontWeight::NORMAL, FontStyle::Normal), + (FontWeight::BOLD, FontStyle::Normal), + (FontWeight::NORMAL, FontStyle::Italic), + (FontWeight::BOLD, FontStyle::Italic), + ] + .map(|(weight, style)| gpui::Font { + weight, + style, + ..crate::font_install::gpui_font_with_fallbacks(family) + }) + }), load_notice, // 这里是唯一能正确合并「主题自带几何」与「用户显式覆盖」的地方: // `theme` 已是 follow_system 折算后的**生效**主题,runtime 是同一次 diff --git a/nebula_app/src/gpui_shell/settings_pane.rs b/nebula_app/src/gpui_shell/settings_pane.rs index fd7cd6c..06602fc 100644 --- a/nebula_app/src/gpui_shell/settings_pane.rs +++ b/nebula_app/src/gpui_shell/settings_pane.rs @@ -176,6 +176,8 @@ pub struct SettingsPane { /// GPUI text system 已注册的导入族名(启动扫描 + 本次导入累计)。 font_imported: Vec, font_family_input: Entity, + font_family_cjk_input: Entity, + ui_font_family_input: Entity, /// 字体输入框上一帧的窗口坐标。字体目录是宽弹层,不能把整条设置行当 /// 锚点;否则输入框在右侧、菜单却会从正文左缘展开。 font_picker_trigger_bounds: Option>, @@ -404,14 +406,9 @@ impl SettingsPane { self.sync_background_color_picker(window, cx); } - /// Workspace tab 与设置正文共用的主文字字号事实源;导航使用组件小字号。 - /// - /// 旧壳合同(display/mod.rs `ui_font_px`):chrome 排版锚定**配置字号** - /// (nebula.toml `font.size`,默认 11.25pt = 15px),终端的持久化缩放 - /// (设置 spinner / Ctrl+滚轮写入的 `font_size=`)只影响终端网格, - /// 不得放大侧栏与设置文字。 + /// 界面独立字号;未设置时保留原配置字号,终端缩放不影响这里。 fn font_size_px(&self, cx: &App) -> f32 { - cx.global::().base_font_size_px + cx.global::().ui_font_size_px } /// 终端字号(预览与「终端字号」步进行显示的值)。 @@ -419,11 +416,6 @@ impl SettingsPane { cx.global::().font_size_px } - fn set_font_size(&mut self, size: f32, cx: &mut Context) { - let size = size.clamp(4.0, 96.0); - self.persist(&[("font_size", format!("{size:.2}"))], cx); - } - /// 拖拽期间只做"改 alpha + 重绘"这两件必须的事,落盘与整套热应用 /// 交给 [`Self::schedule_slider_persist`]。旧壳拖 opacity 就是改内存 + 重绘, /// 这条快路径是为了对回那个手感。 diff --git a/nebula_app/src/gpui_shell/settings_pane/appearance.rs b/nebula_app/src/gpui_shell/settings_pane/appearance.rs index d965e97..7337207 100644 --- a/nebula_app/src/gpui_shell/settings_pane/appearance.rs +++ b/nebula_app/src/gpui_shell/settings_pane/appearance.rs @@ -88,45 +88,51 @@ impl SettingsPane { .into_any_element() } - pub(super) fn terminal_font_size_row(&self, cx: &Context) -> gpui::AnyElement { + pub(super) fn font_size_row(&self, ui: bool, cx: &Context) -> gpui::AnyElement { let language = crate::gpui_shell::config::ui_language(cx); - let size = self.terminal_font_size_px(cx); + let size = if ui { self.font_size_px(cx) } else { self.terminal_font_size_px(cx) }; + let (key, min, max) = + if ui { ("ui_font_size", 10.0, 24.0) } else { ("font_size", 4.0, 96.0) }; let stepper = h_flex() .w(px(142.0)) .h(px(36.0)) .items_center() .child( - Button::new("appearance-font-smaller") + Button::new(SharedString::from(format!("{key}-smaller"))) .icon(IconName::Minus) .ghost() .size(px(34.0)) - .disabled(size <= 4.0) + .disabled(size <= min) .tooltip(language.pick("减小字号", "Decrease font size")) - .on_click(cx.listener(|this, _, _, cx| { - this.set_font_size( - (this.terminal_font_size_px(cx).ceil() - 1.0).round(), - cx, - ); + .on_click(cx.listener(move |this, _, _, cx| { + let next = (size.ceil() - 1.0).clamp(min, max); + this.persist(&[(key, format!("{next:.2}"))], cx); })), ) .child(div().flex_1().text_center().child(format!("{size:.0} px"))) .child( - Button::new("appearance-font-larger") + Button::new(SharedString::from(format!("{key}-larger"))) .icon(IconName::Plus) .ghost() .size(px(34.0)) - .disabled(size >= 96.0) + .disabled(size >= max) .tooltip(language.pick("增大字号", "Increase font size")) - .on_click(cx.listener(|this, _, _, cx| { - this.set_font_size( - (this.terminal_font_size_px(cx).floor() + 1.0).round(), - cx, - ); + .on_click(cx.listener(move |this, _, _, cx| { + let next = (size.floor() + 1.0).clamp(min, max); + this.persist(&[(key, format!("{next:.2}"))], cx); })), ); self.row( - language.pick("终端字号(Ctrl+滚轮缩放)", "Terminal font size (Ctrl+wheel)"), - language.pick("只调整终端文字大小。", "Changes only the terminal text size."), + if ui { + language.text(crate::i18n::Message::SettingsFontUiSize) + } else { + language.pick("终端字号(Ctrl+滚轮缩放)", "Terminal font size (Ctrl+wheel)") + }, + if ui { + language.text(crate::i18n::Message::SettingsFontUiSizeDescription) + } else { + language.pick("只调整终端文字大小。", "Changes only the terminal text size.") + }, stepper, cx, ) diff --git a/nebula_app/src/gpui_shell/settings_pane/appearance_advanced.rs b/nebula_app/src/gpui_shell/settings_pane/appearance_advanced.rs index 741eb29..db2748a 100644 --- a/nebula_app/src/gpui_shell/settings_pane/appearance_advanced.rs +++ b/nebula_app/src/gpui_shell/settings_pane/appearance_advanced.rs @@ -61,6 +61,17 @@ impl SettingsPane { )); let interface = self .group(language.pick("界面", "Interface"), cx) + .child( + self.row( + language.text(crate::i18n::Message::SettingsFontUiFamily), + language.text(crate::i18n::Message::SettingsFontUiFamilyDescription), + Input::new(&self.ui_font_family_input) + .w(px(SETTINGS_SELECT_WIDTH)) + .aria_label(language.text(crate::i18n::Message::SettingsFontUiFamily)), + cx, + ), + ) + .child(self.font_size_row(true, cx)) .child(self.switch_row( "tab_close_visible", language.pick("显示标签关闭按钮", "Show tab close buttons"), @@ -96,12 +107,22 @@ impl SettingsPane { let terminal = self .group(language.pick("终端外观", "Terminal appearance"), cx) .child(self.row( - language.pick("终端字体", "Terminal font"), + language.text(crate::i18n::Message::SettingsFontEnglish), help("font_family", language), font_picker, cx, )) - .child(self.terminal_font_size_row(cx)) + .child( + self.row( + language.text(crate::i18n::Message::SettingsFontChinese), + language.text(crate::i18n::Message::SettingsFontChineseDescription), + Input::new(&self.font_family_cjk_input) + .w(px(SETTINGS_SELECT_WIDTH)) + .aria_label(language.text(crate::i18n::Message::SettingsFontChinese)), + cx, + ), + ) + .child(self.font_size_row(false, cx)) .child(self.select_row( "cell_width_mode", language.pick("字体间距", "Character spacing"), diff --git a/nebula_app/src/gpui_shell/settings_pane/font_picker.rs b/nebula_app/src/gpui_shell/settings_pane/font_picker.rs index 2a9b42c..ea23ee5 100644 --- a/nebula_app/src/gpui_shell/settings_pane/font_picker.rs +++ b/nebula_app/src/gpui_shell/settings_pane/font_picker.rs @@ -219,7 +219,7 @@ impl SettingsPane { cx.stop_propagation(); cx.notify(); }))) - .aria_label(language.pick("终端字体", "Terminal font")), + .aria_label(language.text(crate::i18n::Message::SettingsFontEnglish)), ) // 弹层仍须取输入框的真实窗口坐标,才能正确处理滚动、缩放与 DPI。 .child( @@ -246,7 +246,7 @@ impl SettingsPane { return control.into_any_element(); } self.row( - language.pick("终端字体", "Terminal font"), + language.text(crate::i18n::Message::SettingsFontEnglish), help("font_family", language), control, cx, diff --git a/nebula_app/src/gpui_shell/settings_pane/initialization.rs b/nebula_app/src/gpui_shell/settings_pane/initialization.rs index 76a2609..3c64625 100644 --- a/nebula_app/src/gpui_shell/settings_pane/initialization.rs +++ b/nebula_app/src/gpui_shell/settings_pane/initialization.rs @@ -439,6 +439,41 @@ impl SettingsPane { }, )); + let font_family_cjk_input = cx.new(|cx| { + InputState::new(window, cx) + .default_value(runtime.font_family_cjk.clone().unwrap_or_default()) + }); + let ui_font_family_input = cx.new(|cx| { + InputState::new(window, cx) + .default_value(runtime.ui_font_family.clone().unwrap_or_default()) + }); + for (key, input) in + [("font_family_cjk", &font_family_cjk_input), ("ui_font_family", &ui_font_family_input)] + { + subscriptions.push(cx.subscribe_in( + input, + window, + move |this: &mut Self, input, event: &InputEvent, window, cx| { + if matches!(event, InputEvent::PressEnter { .. } | InputEvent::Blur) { + let raw = input.read(cx).value(); + let (value, current) = if key == "ui_font_family" { + (raw.trim().to_owned(), &this.runtime.ui_font_family) + } else { + ( + crate::font_install::normalize_font_family_chain(&raw), + &this.runtime.font_family_cjk, + ) + }; + let changed = current.as_deref().unwrap_or_default() != value; + input.update(cx, |input, cx| input.set_value(value.clone(), window, cx)); + if changed { + this.persist(&[(key, value)], cx); + } + } + }, + )); + } + let bg_picker_hsv = { let term = crate::gpui_shell::theme::chrome_theme_resolved(cx).palette().term_bg; let rgb = runtime.background.unwrap_or([term.r, term.g, term.b]); @@ -549,6 +584,8 @@ impl SettingsPane { font_system: None, font_imported: Vec::new(), font_family_input, + font_family_cjk_input, + ui_font_family_input, font_picker_trigger_bounds: None, backup_selection: crate::encrypted_backup::BackupSelection::default(), backup_pass_input: cx.new(|cx| { diff --git a/nebula_app/src/gpui_shell/ssh_settings/library.rs b/nebula_app/src/gpui_shell/ssh_settings/library.rs index 65ea75c..a23a8c0 100644 --- a/nebula_app/src/gpui_shell/ssh_settings/library.rs +++ b/nebula_app/src/gpui_shell/ssh_settings/library.rs @@ -226,7 +226,7 @@ impl SettingsPane { let symbol_family: SharedString = crate::font_install::REQUIRED_FONT_FAMILY.into(); let font_px = cx .try_global::() - .map(|s| s.base_font_size_px) + .map(|s| s.ui_font_size_px) .unwrap_or(15.0); let title_h = font_px; let subtitle_h = font_px * 0.78; diff --git a/nebula_app/src/gpui_shell/terminal/element.rs b/nebula_app/src/gpui_shell/terminal/element.rs index 1955790..094e7a0 100644 --- a/nebula_app/src/gpui_shell/terminal/element.rs +++ b/nebula_app/src/gpui_shell/terminal/element.rs @@ -497,8 +497,14 @@ impl Element for TerminalElement { .try_global::() .map(|settings| settings.cjk_bold_regular) .unwrap_or(true); + let cjk_fonts = cx + .try_global::() + .and_then(|settings| settings.font_cjk.clone()); let pick_font = |bold: bool, italic: bool, wide: bool| { let bold = bold && !(wide && cjk_bold_regular); + if let Some(fonts) = cjk_fonts.as_ref().filter(|_| wide) { + return fonts[usize::from(bold) + 2 * usize::from(italic)].clone(); + } match (bold, italic) { (false, false) => font.clone(), (true, false) => bold_font.clone(), diff --git a/nebula_app/src/gpui_shell/terminal/ssh_connect_overlay.rs b/nebula_app/src/gpui_shell/terminal/ssh_connect_overlay.rs index bd1b60e..a1ba366 100644 --- a/nebula_app/src/gpui_shell/terminal/ssh_connect_overlay.rs +++ b/nebula_app/src/gpui_shell/terminal/ssh_connect_overlay.rs @@ -293,9 +293,9 @@ pub(super) fn overlay( // chrome 文本锚定配置字号(旧壳 ui_font 合同),不跟终端缩放。 let ui_px = cx .try_global::() - .map(|settings| settings.base_font_size_px) + .map(|settings| settings.ui_font_size_px) .unwrap_or(15.0); - let chrome_family = theme.mono_font_family.clone(); + let chrome_family = theme.font_family.clone(); let log_family: SharedString = crate::font_install::REQUIRED_FONT_FAMILY.into(); let ink_strong = theme.sidebar_accent_foreground; let ink = theme.foreground; diff --git a/nebula_app/src/gpui_shell/theme.rs b/nebula_app/src/gpui_shell/theme.rs index a221916..d23ebf0 100644 --- a/nebula_app/src/gpui_shell/theme.rs +++ b/nebula_app/src/gpui_shell/theme.rs @@ -550,6 +550,7 @@ fn soften(c: crate::display::color::Rgb, keep: f32) -> crate::display::color::Rg } fn apply_skin_tokens(chrome: NebulaTheme, cx: &mut App) { + let runtime = nebula_settings::RuntimeSettings::load(); let sk = chrome.skin(); let transparent = hsla(0.0, 0.0, 0.0, 0.0); let theme = Theme::global_mut(cx); @@ -672,8 +673,8 @@ fn apply_skin_tokens(chrome: NebulaTheme, cx: &mut App) { // 字号与圆角:控件 pill 档 = 旧壳 UI_CORNER_RADIUS_LOGICAL(8);浮层 // 12,低于终端卡的 14——三档呼应旧壳的圆角层级。 - theme.font_size = px(14.0); - theme.mono_font_size = px(13.0); + theme.font_size = px(runtime.ui_font_size_px.unwrap_or(14.0)); + theme.mono_font_size = theme.font_size * (13.0 / 14.0); // 整壳的兜底字体(fork `root.rs` 用 `theme.font_family` 给根容器)。上游 // 默认 `.SystemUIFont` 在 Windows 上没落到 UI 字体,中文最终回落进终端等 @@ -683,14 +684,17 @@ fn apply_skin_tokens(chrome: NebulaTheme, cx: &mut App) { // 我们是终端,所以等宽在这里是**语义标记**而不是全局字体:路径、键帽、 // 命令、数值这类"机器读、要逐字符对齐、要能整段复制"的东西显式走 mono; // 标题和说明是给人读的,走 sans。 - #[cfg(target_os = "windows")] - { - theme.font_family = "Microsoft YaHei UI".into(); - // UI 中的等宽语义也必须稳定。终端字体由 TerminalView 单独读取; - // 若把用户字体组写进全局 theme,tab、标题和代码字面量的字宽都会 - // 随终端主字体变化,进而破坏 chrome 的既定间距。 - theme.mono_font_family = crate::font_install::REQUIRED_FONT_FAMILY.into(); - } + let default_ui_font = + if crate::platform::Platform::current() == crate::platform::Platform::Windows { + // UI 中的等宽语义也必须稳定。终端字体由 TerminalView 单独读取; + // 若把用户字体组写进全局 theme,tab、标题和代码字面量的字宽都会 + // 随终端主字体变化,进而破坏 chrome 的既定间距。 + theme.mono_font_family = crate::font_install::REQUIRED_FONT_FAMILY.into(); + "Microsoft YaHei UI" + } else { + ".SystemUIFont" + }; + theme.font_family = runtime.ui_font_family.unwrap_or_else(|| default_ui_font.to_owned()).into(); theme.radius = px(crate::display::UI_CORNER_RADIUS_LOGICAL); theme.radius_lg = px(12.0); diff --git a/nebula_app/src/gpui_shell/workspace/pane_header.rs b/nebula_app/src/gpui_shell/workspace/pane_header.rs index 473c7fe..acf2402 100644 --- a/nebula_app/src/gpui_shell/workspace/pane_header.rs +++ b/nebula_app/src/gpui_shell/workspace/pane_header.rs @@ -258,9 +258,9 @@ impl NebulaWorkspace { let bar_bg = if focused { theme.sidebar_accent.opacity(0.55) } else { theme.muted.opacity(0.28) }; let settings = cx.try_global::(); - let chrome_family = theme.mono_font_family.clone(); + let chrome_family = theme.font_family.clone(); let symbol_family: SharedString = crate::font_install::REQUIRED_FONT_FAMILY.into(); - let label_px = settings.map(|settings| settings.base_font_size_px).unwrap_or(15.0); + let label_px = settings.map(|settings| settings.ui_font_size_px).unwrap_or(15.0); let title_px = label_px * 0.78; let PaneTitle { logo, glyph, text } = self.pane_title(view, cx, dark); let group: SharedString = format!("pane-header-{pane_id}").into(); diff --git a/nebula_app/src/gpui_shell/workspace/sidebar.rs b/nebula_app/src/gpui_shell/workspace/sidebar.rs index 9949947..85e01cd 100644 --- a/nebula_app/src/gpui_shell/workspace/sidebar.rs +++ b/nebula_app/src/gpui_shell/workspace/sidebar.rs @@ -240,13 +240,10 @@ impl NebulaWorkspace { let tab_reveal = settings .map(|settings| settings.tab_reveal) .unwrap_or(nebula_settings::TabRevealName::Slide); - let chrome_family = theme.mono_font_family.clone(); + let chrome_family = theme.font_family.clone(); let symbol_family: SharedString = crate::font_install::REQUIRED_FONT_FAMILY.into(); - // 旧壳合同(display/mod.rs `ui_font_px`):chrome 锚定**配置字号** - // (nebula.toml `font.size` 默认 11.25pt = 15px)。终端的持久化缩放 - // (`font_size=` 键)只影响终端网格,侧栏不得跟着变粗/变大; - // 固定 14px 的旧毛病(比旧壳小一号)也不能回潮。 - let label_px = settings.map(|settings| settings.base_font_size_px).unwrap_or(15.0); + // 界面字号独立于终端缩放。 + let label_px = settings.map(|settings| settings.ui_font_size_px).unwrap_or(15.0); let cell_w = self.sidebar_cell_width(window, &chrome_family, label_px); // 受约束拖拽的渲染参数:激活后被拖行骑指针位移,落点槽位由位移换算。 @@ -1044,9 +1041,9 @@ impl NebulaWorkspace { let (ink, dim, badge_fill) = (theme.foreground, theme.muted_foreground, theme.muted); let dark = theme.is_dark(); let settings = cx.try_global::(); - let chrome_family = theme.mono_font_family.clone(); + let chrome_family = theme.font_family.clone(); let symbol_family: SharedString = crate::font_install::REQUIRED_FONT_FAMILY.into(); - let label_px = settings.map(|settings| settings.base_font_size_px).unwrap_or(15.0); + let label_px = settings.map(|settings| settings.ui_font_size_px).unwrap_or(15.0); let TabPresentation { title, logo_image, program_glyph, pane_count, .. } = self.tab_presentation(self.active, cx, dark); slot.child( diff --git a/nebula_app/src/gpui_shell/workspace/top_tabs.rs b/nebula_app/src/gpui_shell/workspace/top_tabs.rs index d50dd06..d96d4ed 100644 --- a/nebula_app/src/gpui_shell/workspace/top_tabs.rs +++ b/nebula_app/src/gpui_shell/workspace/top_tabs.rs @@ -141,9 +141,9 @@ impl NebulaWorkspace { let tab_reveal = settings .map(|settings| settings.tab_reveal) .unwrap_or(nebula_settings::TabRevealName::Slide); - let chrome_family = theme.mono_font_family.clone(); + let chrome_family = theme.font_family.clone(); let symbol_family: SharedString = crate::font_install::REQUIRED_FONT_FAMILY.into(); - let label_px = settings.map(|settings| settings.base_font_size_px).unwrap_or(15.0); + let label_px = settings.map(|settings| settings.ui_font_size_px).unwrap_or(15.0); let tab_capacity_w = (f32::from(window.viewport_size().width) - TOP_TAB_RESERVED_W).max(TOP_TAB_MIN_W); let tab_w = tab_width(tab_capacity_w, self.top_tab_count()); diff --git a/nebula_settings/src/lib.rs b/nebula_settings/src/lib.rs index 7a5827c..7f765e1 100644 --- a/nebula_settings/src/lib.rs +++ b/nebula_settings/src/lib.rs @@ -941,6 +941,9 @@ pub struct RuntimeSettings { /// 系统外观变化时自动在主题家族的深浅成员间切换(默认关,尊重显式选择)。 pub follow_system_theme: bool, pub font_family: Option, + pub font_family_cjk: Option, + pub ui_font_family: Option, + pub ui_font_size_px: Option, /// **逻辑像素**(旧壳写盘语义:设置页 spinner 与 Ctrl+滚轮缩放持久化时 /// 已除以 scale factor)。`None` = 跟随 nebula.toml 的 `font.size`(pt)。 pub font_size_px: Option, @@ -1083,6 +1086,9 @@ impl RuntimeSettings { .unwrap_or_default(), follow_system_theme: raw.bool_on("follow_system_theme").unwrap_or(false), font_family: raw.value("font_family").map(str::to_owned), + font_family_cjk: raw.value("font_family_cjk").map(str::to_owned), + ui_font_family: raw.value("ui_font_family").map(str::to_owned), + ui_font_size_px: raw.f32("ui_font_size").map(|size| size.clamp(10.0, 24.0)), font_size_px: raw.f32("font_size").map(|size| size.clamp(4.0, 96.0)), cursor_shape: raw.value("cursor_shape").and_then(CursorShapeName::from_settings), cursor_blink: raw.bool_on("cursor_blink"), @@ -1308,6 +1314,9 @@ mod tests { completion_style=popup\n\ shell=pwsh\n\ font_family=Maple Mono Normal NF CN\n\ + ui_font_family=Arial\n\ + ui_font_size=18\n\ + font_family_cjk=PingFang SC\n\ font_size=16.3\n\ cursor_shape=beam\n\ cursor_blink=1\n\ @@ -1343,6 +1352,9 @@ mod tests { assert_eq!(settings.language, LanguagePref::ZhCn); assert_eq!(settings.theme, ThemeName::SilverLight); assert_eq!(settings.font_family.as_deref(), Some("Maple Mono Normal NF CN")); + assert_eq!(settings.font_family_cjk.as_deref(), Some("PingFang SC")); + assert_eq!(settings.ui_font_family.as_deref(), Some("Arial")); + assert_eq!(settings.ui_font_size_px, Some(18.0)); // font_size 键存的是逻辑像素(旧壳写盘语义),不做 pt 换算。 assert_eq!(settings.font_size_px, Some(16.3)); assert_eq!(settings.cursor_shape, Some(CursorShapeName::Beam)); @@ -1386,6 +1398,9 @@ mod tests { assert_eq!(settings.language, LanguagePref::System); assert_eq!(settings.theme, ThemeName::Nord); assert_eq!(settings.font_family, None); + assert_eq!(settings.font_family_cjk, None); + assert_eq!(settings.ui_font_family, None); + assert_eq!(settings.ui_font_size_px, None); assert!(!settings.copy_on_select); assert!(settings.multiline_paste_confirm, "多行粘贴确认默认开(上游兼容)"); assert!(settings.tab_close_visible, "标签关闭按钮默认可见(上游兼容)"); diff --git a/nebula_settings/src/reset.rs b/nebula_settings/src/reset.rs index 6562819..dfa66c8 100644 --- a/nebula_settings/src/reset.rs +++ b/nebula_settings/src/reset.rs @@ -10,6 +10,9 @@ const RESET_KEYS: &[&str] = &[ "app_icon", "follow_system_theme", "font_family", + "font_family_cjk", + "ui_font_family", + "ui_font_size", "font_size", "cursor_shape", "cursor_blink",