Skip to content

Commit 77bd970

Browse files
Prayingquran
andauthored
支持回滚字体(fallback font)设置 (#284)
* Add terminal fallback font setting * Fix terminal fallback font render path * Update fallback font Chinese labels * Fix fallback font handling --------- Co-authored-by: quran <quran@xiaomi.com>
1 parent 08e4312 commit 77bd970

17 files changed

Lines changed: 531 additions & 20 deletions

File tree

app/i18n/en/warp.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,8 @@ settings-appearance-font-agent-label = Agent font
13951395
settings-appearance-font-match-terminal = Match terminal
13961396
settings-appearance-font-ui-label = UI font
13971397
settings-appearance-font-terminal-label = Terminal font
1398+
settings-appearance-font-terminal-fallback-label = Fallback font
1399+
settings-appearance-font-fallback-system = System fallback
13981400
settings-appearance-font-view-all-system = View all available system fonts
13991401
settings-appearance-font-weight-label = Font weight
14001402
settings-appearance-font-size-label = Font size (px)

app/i18n/ja/warp.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,6 +1331,8 @@ settings-appearance-font-agent-label = エージェントフォント
13311331
settings-appearance-font-match-terminal = ターミナルに合わせる
13321332
settings-appearance-font-ui-label = UI フォント
13331333
settings-appearance-font-terminal-label = ターミナルフォント
1334+
settings-appearance-font-terminal-fallback-label = フォールバックフォント
1335+
settings-appearance-font-fallback-system = システムフォールバック
13341336
settings-appearance-font-view-all-system = 利用可能なシステムフォントをすべて表示
13351337
settings-appearance-font-weight-label = フォントの太さ
13361338
settings-appearance-font-size-label = フォントサイズ (px)

app/i18n/zh-CN/warp.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1372,6 +1372,8 @@ settings-appearance-font-agent-label = Agent 字体
13721372
settings-appearance-font-match-terminal = 匹配终端
13731373
settings-appearance-font-ui-label = UI 字体
13741374
settings-appearance-font-terminal-label = 终端字体
1375+
settings-appearance-font-terminal-fallback-label = 回滚字体
1376+
settings-appearance-font-fallback-system = 系统自动回滚
13751377
settings-appearance-font-view-all-system = 查看所有可用系统字体
13761378
settings-appearance-font-weight-label = 字重
13771379
settings-appearance-font-size-label = 字号(像素)

app/src/appearance.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@ impl AppearanceManager {
8585
});
8686
}
8787
}
88+
FontSettingsChangedEvent::MonospaceFallbackFontName { .. } => {
89+
let font_name = FontSettings::as_ref(ctx)
90+
.monospace_fallback_font_name
91+
.value()
92+
.clone();
93+
let new_family = get_or_load_optional_font_family(&font_name, ctx);
94+
Appearance::handle(ctx).update(ctx, |appearance, ctx| {
95+
appearance.set_terminal_fallback_font_family(new_family, ctx);
96+
});
97+
}
8898
FontSettingsChangedEvent::MonospaceFontSize { .. } => {
8999
let new_font_size = *FontSettings::as_ref(ctx).monospace_font_size.value();
90100
Appearance::handle(ctx).update(ctx, |appearance, ctx| {
@@ -421,6 +431,14 @@ fn get_or_load_font_family(font_name: &str, ctx: &mut AppContext) -> Option<Fami
421431
})
422432
}
423433

434+
fn get_or_load_optional_font_family(font_name: &str, ctx: &mut AppContext) -> Option<FamilyId> {
435+
if font_name.is_empty() {
436+
None
437+
} else {
438+
get_or_load_font_family(font_name, ctx)
439+
}
440+
}
441+
424442
fn build_appearance(ctx: &mut AppContext) -> Appearance {
425443
let default_monospace_font_family = load_default_monospace_font_family(ctx)
426444
.expect("unable to load default monospace font family");
@@ -429,8 +447,14 @@ fn build_appearance(ctx: &mut AppContext) -> Appearance {
429447
.value()
430448
.clone();
431449
let am_font_name = FontSettings::as_ref(ctx).ai_font_name.value().clone();
450+
let monospace_fallback_font_name = FontSettings::as_ref(ctx)
451+
.monospace_fallback_font_name
452+
.value()
453+
.clone();
432454

433455
let monospace_font_family_from_settings = get_or_load_font_family(&monospace_font_name, ctx);
456+
let monospace_fallback_font_family_from_settings =
457+
get_or_load_optional_font_family(&monospace_fallback_font_name, ctx);
434458

435459
let ui_font_name = FontSettings::as_ref(ctx).ui_font_name.value().clone();
436460
let ui_font_size = *FontSettings::as_ref(ctx).ui_font_size.value();
@@ -469,6 +493,7 @@ fn build_appearance(ctx: &mut AppContext) -> Appearance {
469493
ui_font_family,
470494
line_height_ratio,
471495
am_font_family_from_settings.unwrap_or(default_monospace_font_family),
496+
monospace_fallback_font_family_from_settings,
472497
password_font_family,
473498
ui_font_size.clamp(UI_FONT_SIZE_MIN, UI_FONT_SIZE_MAX),
474499
heading_multipliers,

app/src/settings/font.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use warpui::elements::{HeadingFontSizeMultipliers, DEFAULT_UI_LINE_HEIGHT_RATIO}
1515
use super::EnforceMinimumContrast as EnforceMinimumContrastEnum;
1616

1717
pub const DEFAULT_MONOSPACE_FONT_NAME: &str = "Hack";
18+
pub const DEFAULT_MONOSPACE_FALLBACK_FONT_NAME: &str = "";
1819

1920
// Markdown 标题字号倍率的合法区间(与 UI 层 clamp 保持一致)
2021
pub const MARKDOWN_HEADING_SCALE_MIN: f32 = 0.1;
@@ -36,6 +37,16 @@ define_settings_group!(FontSettings,
3637
toml_path: "appearance.text.font_name",
3738
description: "The monospace font used in the terminal.",
3839
},
40+
monospace_fallback_font_name: MonospaceFallbackFontName {
41+
type: String,
42+
default: DEFAULT_MONOSPACE_FALLBACK_FONT_NAME.to_string(),
43+
supported_platforms: SupportedPlatforms::ALL,
44+
sync_to_cloud: SyncToCloud::Never,
45+
private: false,
46+
storage_key: "TerminalFallbackFontName",
47+
toml_path: "appearance.text.fallback_font_name",
48+
description: "The fallback font used when the terminal font cannot render a character.",
49+
},
3950
monospace_font_size: MonospaceFontSize {
4051
type: f32,
4152
default: DEFAULT_MONOSPACE_FONT_SIZE,

app/src/settings_view/appearance_page.rs

Lines changed: 133 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ use crate::settings::{
3232
EnforceMinimumContrast, FocusPaneOnHover, FontSettings, FontSettingsChangedEvent, InputBoxType,
3333
InputModeSettings, InputModeState, MarkdownHeadingH1Scale, MarkdownHeadingH2Scale,
3434
MarkdownHeadingH3Scale, MarkdownHeadingH4Scale, MarkdownHeadingH5Scale, MarkdownHeadingH6Scale,
35-
MonospaceFontName, PaneSettings, ShouldDimInactivePanes, ThemeSettings, UiFontName,
36-
UseSystemTheme, DEFAULT_MONOSPACE_FONT_NAME, MARKDOWN_HEADING_SCALE_MAX,
37-
MARKDOWN_HEADING_SCALE_MIN, UI_FONT_SIZE_MAX, UI_FONT_SIZE_MIN,
35+
MonospaceFallbackFontName, MonospaceFontName, PaneSettings, ShouldDimInactivePanes,
36+
ThemeSettings, UiFontName, UseSystemTheme, DEFAULT_MONOSPACE_FONT_NAME,
37+
MARKDOWN_HEADING_SCALE_MAX, MARKDOWN_HEADING_SCALE_MIN, UI_FONT_SIZE_MAX, UI_FONT_SIZE_MIN,
3838
};
3939
use crate::settings::{CursorDisplayType, GPUSettings, InputSettings, InputSettingsChangedEvent};
4040
use crate::terminal::block_list_viewport::InputMode;
@@ -139,6 +139,17 @@ fn default_font_label(is_ai_font: bool) -> String {
139139
}
140140
}
141141

142+
fn fallback_font_dropdown_should_include_font(
143+
name: &str,
144+
font_type: FontType,
145+
view_font_type: FontType,
146+
selected_font_name: &str,
147+
) -> bool {
148+
matches!(view_font_type, FontType::Any)
149+
|| matches!(font_type, FontType::Monospace)
150+
|| name == selected_font_name
151+
}
152+
142153
pub fn init_actions_from_parent_view<T: Action + Clone>(
143154
app: &mut AppContext,
144155
context: &ContextPredicate,
@@ -440,6 +451,7 @@ pub enum AppearancePageAction {
440451
OpacitySliderDragged(f32),
441452
BlurSliderDragged(f32),
442453
SetFontFamily(String),
454+
SetFallbackFontFamily(String),
443455
SetAIFontFamily(String),
444456
SetUIFontFamily(String),
445457
SetUIFontSize,
@@ -514,6 +526,7 @@ pub struct AppearanceSettingsPageView {
514526
opacity_state: SliderStateHandle,
515527
blur_state: SliderStateHandle,
516528
font_family_dropdown: ViewHandle<FilterableDropdown<AppearancePageAction>>,
529+
fallback_font_family_dropdown: ViewHandle<FilterableDropdown<AppearancePageAction>>,
517530
font_weight_dropdown: ViewHandle<Dropdown<AppearancePageAction>>,
518531
#[allow(dead_code)]
519532
thin_strokes_dropdown: ViewHandle<Dropdown<AppearancePageAction>>,
@@ -575,6 +588,7 @@ impl TypedActionView for AppearanceSettingsPageView {
575588
SetOpacity(value) => self.set_opacity(*value, true, ctx),
576589
SetBlur(value) => self.set_blur(*value, true, ctx),
577590
SetFontFamily(name) => self.set_font_family(name, ctx),
591+
SetFallbackFontFamily(name) => self.set_fallback_font_family(name, ctx),
578592
SetAIFontFamily(name) => {
579593
self.set_ai_font_family(name, ctx);
580594
FontSettings::handle(ctx).update(ctx, |font_settings, ctx| {
@@ -1106,6 +1120,16 @@ impl AppearanceSettingsPageView {
11061120
dropdown
11071121
});
11081122

1123+
let fallback_font_family_dropdown = ctx.add_typed_action_view(|ctx| {
1124+
let mut dropdown = FilterableDropdown::new(ctx);
1125+
dropdown.set_top_bar_max_width(FONT_FAMILY_DROPDOWN_WIDTH);
1126+
dropdown.set_menu_width(FONT_FAMILY_DROPDOWN_WIDTH, ctx);
1127+
1128+
dropdown.add_items(vec![Self::default_fallback_font_item()], ctx);
1129+
dropdown.set_selected_by_index(0, ctx);
1130+
dropdown
1131+
});
1132+
11091133
let ai_font_family_dropdown = ctx.add_typed_action_view(|ctx| {
11101134
let mut dropdown = FilterableDropdown::new(ctx);
11111135
dropdown.set_top_bar_max_width(FONT_FAMILY_DROPDOWN_WIDTH);
@@ -1365,6 +1389,7 @@ impl AppearanceSettingsPageView {
13651389
opacity_state: Default::default(),
13661390
blur_state: Default::default(),
13671391
font_family_dropdown,
1392+
fallback_font_family_dropdown,
13681393
font_weight_dropdown,
13691394
thin_strokes_dropdown,
13701395
input_mode_dropdown,
@@ -1612,6 +1637,9 @@ impl AppearanceSettingsPageView {
16121637
AppearanceEvent::MonospaceFontFamilyChanged { .. } => {
16131638
self.update_font_dropdown(ctx);
16141639
}
1640+
AppearanceEvent::TerminalFallbackFontFamilyChanged { .. } => {
1641+
self.update_font_dropdown(ctx);
1642+
}
16151643
AppearanceEvent::MonospaceFontSizeChanged { .. } => {
16161644
let font_size = handle.as_ref(ctx).monospace_font_size();
16171645
self.font_size_editor.update(ctx, move |editor, ctx| {
@@ -1722,6 +1750,13 @@ impl AppearanceSettingsPageView {
17221750
initial_dropdown_item
17231751
}
17241752

1753+
fn default_fallback_font_item() -> DropdownItem<AppearancePageAction> {
1754+
DropdownItem::new(
1755+
crate::t!("settings-appearance-font-fallback-system"),
1756+
AppearancePageAction::SetFallbackFontFamily(MonospaceFallbackFontName::default_value()),
1757+
)
1758+
}
1759+
17251760
fn input_mode_dropdown_item_label(val: InputMode) -> String {
17261761
match val {
17271762
InputMode::PinnedToBottom => crate::t!("settings-appearance-input-mode-pinned-bottom"),
@@ -2219,6 +2254,7 @@ impl AppearanceSettingsPageView {
22192254

22202255
fn update_font_dropdown(&mut self, ctx: &mut ViewContext<Self>) {
22212256
let monospace_font_family = Appearance::as_ref(ctx).monospace_font_family();
2257+
let fallback_font_family = Appearance::as_ref(ctx).terminal_fallback_font_family();
22222258
let ai_font_family = Appearance::as_ref(ctx).ai_font_family();
22232259
let ui_font_family = Appearance::as_ref(ctx).ui_font_family();
22242260

@@ -2290,6 +2326,69 @@ impl AppearanceSettingsPageView {
22902326
}
22912327
});
22922328

2329+
self.fallback_font_family_dropdown
2330+
.update(ctx, |dropdown, ctx| {
2331+
let font_name = FontSettings::as_ref(ctx)
2332+
.monospace_fallback_font_name
2333+
.value()
2334+
.clone();
2335+
2336+
if let Some(fallback_font_family) = fallback_font_family {
2337+
if let Some(loaded_font_name) = ctx
2338+
.font_cache()
2339+
.load_family_name_from_id(fallback_font_family)
2340+
{
2341+
self.available_families
2342+
.entry(loaded_font_name)
2343+
.and_modify(|entry| entry.0 = Some(fallback_font_family))
2344+
.or_insert((Some(fallback_font_family), FontType::Monospace));
2345+
}
2346+
}
2347+
2348+
let mut items = self
2349+
.available_families
2350+
.iter()
2351+
.filter_map(|(name, (family, font_type))| {
2352+
let include_in_dropdown = fallback_font_dropdown_should_include_font(
2353+
name,
2354+
*font_type,
2355+
self.view_font_type,
2356+
&font_name,
2357+
);
2358+
if include_in_dropdown {
2359+
let name_move = name.clone();
2360+
let mut dropdown = DropdownItem::new(
2361+
name,
2362+
AppearancePageAction::SetFallbackFontFamily(name_move),
2363+
);
2364+
2365+
if cfg!(not(any(target_os = "linux", target_os = "freebsd"))) {
2366+
if let Some(family_id) = family {
2367+
dropdown = dropdown.with_font_override(*family_id)
2368+
}
2369+
}
2370+
2371+
Some(dropdown)
2372+
} else {
2373+
None
2374+
}
2375+
})
2376+
.collect::<Vec<_>>();
2377+
2378+
items.sort_by(|a, b| a.display_text.cmp(&b.display_text));
2379+
items.insert(0, Self::default_fallback_font_item());
2380+
dropdown.set_items(items, ctx);
2381+
2382+
if font_name.is_empty() {
2383+
dropdown.set_selected_by_name(
2384+
crate::t!("settings-appearance-font-fallback-system"),
2385+
ctx,
2386+
);
2387+
} else {
2388+
dropdown.set_selected_by_name(&font_name, ctx);
2389+
}
2390+
});
2391+
22932392
self.ai_font_family_dropdown.update(ctx, |dropdown, ctx| {
22942393
// Get the family name of the current agent mode font.
22952394
// We check the font_cache for the current agent mode family.
@@ -2441,6 +2540,14 @@ impl AppearanceSettingsPageView {
24412540
});
24422541
}
24432542

2543+
pub fn set_fallback_font_family(&mut self, name: &str, ctx: &mut ViewContext<Self>) {
2544+
FontSettings::handle(ctx).update(ctx, |font_settings, ctx| {
2545+
report_if_error!(font_settings
2546+
.monospace_fallback_font_name
2547+
.set_value(name.to_string(), ctx));
2548+
});
2549+
}
2550+
24442551
pub fn toggle_match_ai_font_to_terminal_font(&mut self, ctx: &mut ViewContext<Self>) {
24452552
FontSettings::handle(ctx).update(ctx, |font_settings, ctx| {
24462553
report_if_error!(font_settings
@@ -4480,7 +4587,7 @@ impl SettingsWidget for TerminalFontWidget {
44804587
type View = AppearanceSettingsPageView;
44814588

44824589
fn search_terms(&self) -> &str {
4483-
"text terminal font family font size line height monospace"
4590+
"text terminal font family fallback font size line height monospace"
44844591
}
44854592

44864593
fn render(
@@ -4512,6 +4619,24 @@ impl SettingsWidget for TerminalFontWidget {
45124619
.with_margin_bottom(10.)
45134620
.finish(),
45144621
);
4622+
terminal_font.add_child(render_body_item_label::<AppearancePageAction>(
4623+
crate::t!("settings-appearance-font-terminal-fallback-label"),
4624+
None,
4625+
None,
4626+
LocalOnlyIconState::for_setting(
4627+
MonospaceFallbackFontName::storage_key(),
4628+
MonospaceFallbackFontName::sync_to_cloud(),
4629+
&mut view.local_only_icon_tooltip_states.borrow_mut(),
4630+
app,
4631+
),
4632+
ToggleState::Enabled,
4633+
appearance,
4634+
));
4635+
terminal_font.add_child(
4636+
Container::new(ChildView::new(&view.fallback_font_family_dropdown).finish())
4637+
.with_margin_bottom(10.)
4638+
.finish(),
4639+
);
45154640
terminal_font.add_child(
45164641
Container::new(
45174642
Flex::row()
@@ -5987,3 +6112,7 @@ impl From<ViewHandle<AppearanceSettingsPageView>> for SettingsPageViewHandle {
59876112
SettingsPageViewHandle::Appearance(view_handle)
59886113
}
59896114
}
6115+
6116+
#[cfg(test)]
6117+
#[path = "appearance_page_tests.rs"]
6118+
mod tests;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use super::{fallback_font_dropdown_should_include_font, FontType};
2+
use crate::settings::{MonospaceFallbackFontName, DEFAULT_MONOSPACE_FONT_NAME};
3+
use settings::Setting as _;
4+
5+
#[test]
6+
fn fallback_font_dropdown_includes_default_monospace_font() {
7+
assert_eq!(MonospaceFallbackFontName::default_value(), "");
8+
assert!(fallback_font_dropdown_should_include_font(
9+
DEFAULT_MONOSPACE_FONT_NAME,
10+
FontType::Monospace,
11+
FontType::Monospace,
12+
"",
13+
));
14+
}

0 commit comments

Comments
 (0)