Refine Font size UI

This commit is contained in:
Kaede Akino committed 2026-09-18 16:58:32 +08:00
1 parent 37c0d6bdb7
commit 799128b59b
5 files changed
+173 -191

No files matched your search

+104 -164
View File
@@ -1,70 +1,6 @@
{
"sourceLanguage" : "en",
"strings" : {
"Invalid authentication challenge." : {
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "认证质询无效。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "驗證詢問無效。"
}
}
}
},
"Invalid authentication response." : {
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "认证响应无效。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "驗證回應無效。"
}
}
}
},
"This server requires interactive authentication." : {
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "此服务器需要交互式认证。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "此伺服器需要互動式驗證。"
}
}
}
},
"Interactive authentication" : {
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "交互式认证"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "互動式驗證"
}
}
}
},
"" : {
"localizations" : {
"zh-Hans" : {
@@ -7063,7 +6999,7 @@
}
},
"Custom" : {
"extractionState" : "stale",
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
@@ -7191,22 +7127,6 @@
}
}
},
"Dark Configuration" : {
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "深色配置"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "深色設定"
}
}
}
},
"Dark Custom Configuration" : {
"localizations" : {
"zh-Hans" : {
@@ -10660,6 +10580,7 @@
}
},
"Follow Theme" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
@@ -10692,22 +10613,6 @@
}
}
},
"Font Configuration" : {
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "字体配置"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "字型設定"
}
}
}
},
"Font Family" : {
"extractionState" : "manual",
"localizations" : {
@@ -10741,23 +10646,6 @@
}
}
},
"Font Size Follows Theme" : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "字号跟随主题"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "字級跟隨主題"
}
}
}
},
"For secrets, use the bearer token field above." : {
"localizations" : {
"zh-Hans" : {
@@ -12707,6 +12595,23 @@
}
}
},
"Interactive authentication" : {
"extractionState" : "stale",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "交互式认证"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "互動式驗證"
}
}
}
},
"Interactive partition editor." : {
"extractionState" : "stale",
"localizations" : {
@@ -12741,6 +12646,40 @@
}
}
},
"Invalid authentication challenge." : {
"extractionState" : "stale",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "认证质询无效。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "驗證詢問無效。"
}
}
}
},
"Invalid authentication response." : {
"extractionState" : "stale",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "认证响应无效。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "驗證回應無效。"
}
}
}
},
"Invalid SFTP copy source path." : {
"extractionState" : "manual",
"localizations" : {
@@ -13499,22 +13438,6 @@
}
}
},
"Light Configuration" : {
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "浅色配置"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "淺色設定"
}
}
}
},
"Light Custom Configuration" : {
"localizations" : {
"zh-Hans" : {
@@ -20785,6 +20708,23 @@
}
}
},
"Saved workspaces could not be restored. The saved data has been kept." : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "无法恢复已保存的工作区。已保留原有保存数据。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "無法回復已儲存的工作區。已保留原有儲存資料。"
}
}
}
},
"scratch files" : {
"extractionState" : "stale",
"localizations" : {
@@ -20852,23 +20792,6 @@
}
}
},
"Saved workspaces could not be restored. The saved data has been kept." : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "无法恢复已保存的工作区。已保留原有保存数据。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "無法回復已儲存的工作區。已保留原有儲存資料。"
}
}
}
},
"Search" : {
"comment" : "Placeholder text for a search field.",
"isCommentAutoGenerated" : true,
@@ -26027,23 +25950,6 @@
}
}
},
"This font is unavailable on this device. Following the theme instead." : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "此设备上无法使用该字体,已改为跟随主题。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "此裝置上無法使用此字型,已改為跟隨主題。"
}
}
}
},
"This address already has a different trusted fingerprint. Confirm adding this key." : {
"extractionState" : "stale",
"localizations" : {
@@ -26231,6 +26137,23 @@
}
}
},
"This font is unavailable on this device. Following the theme instead." : {
"extractionState" : "manual",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "此设备上无法使用该字体,已改为跟随主题。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "此裝置上無法使用此字型,已改為跟隨主題。"
}
}
}
},
"This forwarding will start after the host is saved." : {
"comment" : "A description of the state of a forwarding that is waiting for the host to be saved.",
"isCommentAutoGenerated" : true,
@@ -26488,6 +26411,23 @@
}
}
},
"This server requires interactive authentication." : {
"extractionState" : "stale",
"localizations" : {
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "此服务器需要交互式认证。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "此伺服器需要互動式驗證。"
}
}
}
},
"This session is not recording history. Enable Shell Integration and use bash, zsh, or fish." : {
"localizations" : {
"zh-Hans" : {
@@ -12,15 +12,25 @@ struct MobileTerminalSettingsView: View {
var body: some View {
Form {
Section("Font") {
TerminalFontPicker(family: $font.family, families: TerminalFontCatalog.shared.families)
Toggle("Font Size Follows Theme", isOn: $font.sizeFollowsTheme)
Stepper(value: $font.size, in: 8...36, step: 1) {
LabeledContent("Font Size", value: font.size.formatted())
}.disabled(font.sizeFollowsTheme)
}
themeSection(dark: false)
themeSection(dark: true)
Section("Font") {
TerminalFontPicker(family: $font.family, families: TerminalFontCatalog.shared.families)
VStack(alignment: .leading, spacing: 8) {
Picker("Font Size", selection: $font.sizeFollowsTheme) {
Text("Follow Theme").tag(true)
Text("Custom").tag(false)
}
.pickerStyle(.menu)
if !font.sizeFollowsTheme {
Stepper(value: $font.size, in: 8...36, step: 1) {
Text(font.size, format: .number.precision(.fractionLength(0))).monospacedDigit()
}
.accessibilityLabel("Font Size")
.accessibilityValue(font.size.formatted())
}
}
}
Section("Integration") { Toggle("Shell Integration", isOn: binding(\.shellIntegration)) }
if let error = model.storageError { Text(error).foregroundStyle(.red) }
}
+19 -5
View File
@@ -29,7 +29,9 @@ struct MobileTerminalFontTests {
.environment(\.locale, Locale(identifier: language))
.frame(width: 280, height: 320)
.background(.background)
try await capture(view, name: "mobile-terminal-font-options-\(language)")
try await withWindow(view) { window in
try await capture(window, name: "mobile-terminal-font-options-\(language)")
}
}
@Test(arguments: ["en", "zh-Hans", "zh-Hant"], ["", "Menlo"])
@@ -38,16 +40,24 @@ struct MobileTerminalFontTests {
let defaults = try #require(UserDefaults(suiteName: domain))
defer { defaults.removePersistentDomain(forName: domain) }
defaults.set(family, forKey: SettingsKey.terminalFontFamily)
defaults.set(family.isEmpty, forKey: SettingsKey.terminalFontFollowTheme)
defaults.set(18.0, forKey: SettingsKey.terminalFontSize)
let view = NavigationStack {
MobileTerminalSettingsView(model: TerminalSettingsModel())
}
.defaultAppStorage(defaults)
.environment(\.locale, Locale(identifier: language))
try await capture(view, name: "mobile-terminal-settings-\(language)-\(family.isEmpty ? "theme" : family)")
let name = "mobile-terminal-settings-\(language)-\(family.isEmpty ? "theme" : family)"
try await withWindow(view) { window in
for (index, followsTheme) in [false, true, false].enumerated() {
defaults.set(followsTheme, forKey: SettingsKey.terminalFontFollowTheme)
try await capture(window, name: "\(name)-\(followsTheme ? "theme-size" : "custom-size")-\(index)")
#expect(defaults.double(forKey: SettingsKey.terminalFontSize) == 18)
#expect(defaults.string(forKey: SettingsKey.terminalFontFamily) == family)
}
}
}
private func capture(_ view: some View, name: String) async throws {
private func withWindow(_ view: some View, perform: (UIWindow) async throws -> Void) async throws {
let scene = try #require(UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first)
let previousWindow = scene.keyWindow
let window = UIWindow(windowScene: scene)
@@ -60,7 +70,11 @@ struct MobileTerminalFontTests {
window.rootViewController = nil
previousWindow?.makeKey()
}
controller.view.layoutIfNeeded()
try await perform(window)
}
private func capture(_ window: UIWindow, name: String) async throws {
window.layoutIfNeeded()
try await Task.sleep(for: .milliseconds(500))
let image = UIGraphicsImageRenderer(bounds: window.bounds).image { _ in
window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)
+11 -6
View File
@@ -710,7 +710,7 @@ private struct TerminalSettings: View {
if let error = app.terminalSettings.storageError {
Text(error).foregroundStyle(.orange)
}
Section("Light Configuration") {
Section("Light") {
Picker("Theme", selection: binding(\.lightTheme)) {
ForEach(lightThemes) { Text($0.name).tag($0.name) }
}
@@ -728,19 +728,24 @@ private struct TerminalSettings: View {
editConfiguration(.dark)
}
} header: {
Text("Dark Configuration")
Text("Dark")
} footer: {
Text(
"Termind uses [Ghostty](https://ghostty.org) as its terminal engine. Custom configuration uses [Ghostty's configuration format](https://ghostty.org/docs/config/reference)."
)
}
Section("Font Configuration") {
Section("Font") {
TerminalFontPicker(family: $font.family, families: TerminalFontCatalog.shared.families)
Toggle("Font Size Follows Theme", isOn: $font.sizeFollowsTheme)
if !font.sizeFollowsTheme {
LabeledContent("Font Size") {
VStack(alignment: .leading, spacing: Metrics.Space.m) {
Picker("Font Size", selection: $font.sizeFollowsTheme) {
Text("Follow Theme").tag(true)
Text("Custom").tag(false)
}
.pickerStyle(.menu)
if !font.sizeFollowsTheme {
HStack {
Slider(value: $font.size, in: 9...24, step: 1)
.labelsHidden().accessibilityLabel("Font Size")
Text(font.size, format: .number.precision(.fractionLength(0)))
.monospacedDigit().frame(width: 24, alignment: .trailing)
}
+22 -9
View File
@@ -14,7 +14,7 @@ struct TerminalSettingsRenderingTests {
let defaults = try #require(UserDefaults(suiteName: domain))
defer { defaults.removePersistentDomain(forName: domain) }
defaults.set(family, forKey: SettingsKey.terminalFontFamily)
defaults.set(family.isEmpty, forKey: SettingsKey.terminalFontFollowTheme)
defaults.set(18.0, forKey: SettingsKey.terminalFontSize)
let app = AppModel(hosts: HostLibraryModel())
let model = WindowModel(workspaces: [])
model.openSettings(route: .category(.terminal))
@@ -23,9 +23,16 @@ struct TerminalSettingsRenderingTests {
.defaultAppStorage(defaults)
.environment(\.locale, Locale(identifier: language))
.frame(width: 640, height: 1100)
try await capture(
view, name: "terminal-settings-\(language)-\(family.isEmpty ? "theme" : family)",
size: NSSize(width: 640, height: 1100))
let name = "terminal-settings-\(language)-\(family.isEmpty ? "theme" : family)"
try await withWindow(view, size: NSSize(width: 640, height: 1100)) { hostedView in
for (index, followsTheme) in [false, true, false].enumerated() {
defaults.set(followsTheme, forKey: SettingsKey.terminalFontFollowTheme)
try await capture(
hostedView, name: "\(name)-\(followsTheme ? "theme-size" : "custom-size")-\(index)")
#expect(defaults.double(forKey: SettingsKey.terminalFontSize) == 18)
#expect(defaults.string(forKey: SettingsKey.terminalFontFamily) == family)
}
}
}
@Test(arguments: ["en", "zh-Hans", "zh-Hant"])
@@ -36,10 +43,12 @@ struct TerminalSettingsRenderingTests {
.environment(\.locale, Locale(identifier: language))
.frame(width: 280, height: 320)
.background(.background)
try await capture(view, name: "terminal-font-options-\(language)", size: NSSize(width: 280, height: 320))
try await withWindow(view, size: NSSize(width: 280, height: 320)) { hostedView in
try await capture(hostedView, name: "terminal-font-options-\(language)")
}
}
private func capture(_ view: some View, name: String, size: NSSize) async throws {
private func withWindow(_ view: some View, size: NSSize, perform: (NSView) async throws -> Void) async rethrows {
let controller = NSHostingController(rootView: view)
let window = NSWindow(
contentRect: NSRect(origin: .zero, size: size),
@@ -52,10 +61,14 @@ struct TerminalSettingsRenderingTests {
window.contentViewController = nil
window.close()
}
try await perform(controller.view)
}
private func capture(_ view: NSView, name: String) async throws {
try await Task.sleep(for: .milliseconds(500))
controller.view.layoutSubtreeIfNeeded()
let bitmap = try #require(controller.view.bitmapImageRepForCachingDisplay(in: controller.view.bounds))
controller.view.cacheDisplay(in: controller.view.bounds, to: bitmap)
view.layoutSubtreeIfNeeded()
let bitmap = try #require(view.bitmapImageRepForCachingDisplay(in: view.bounds))
view.cacheDisplay(in: view.bounds, to: bitmap)
let data = try #require(bitmap.representation(using: .png, properties: [:]))
Attachment.record(data, named: "\(name).png")
#expect(data.count > 5_000)