diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index 38125ea..d30c6da 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -1661,6 +1661,10 @@ } } }, + "±10 Seconds" : { + "comment" : "Displayed as the name of the music skip behavior option.", + "isCommentAutoGenerated" : true + }, "0" : { "comment" : "A label displayed in a circular gauge to indicate the minimum battery level.", "isCommentAutoGenerated" : true, @@ -2013,6 +2017,10 @@ } } }, + "Accent" : { + "comment" : "Display name for the \"Accent\" caps lock indicator tint mode.", + "isCommentAutoGenerated" : true + }, "Accent color" : { "localizations" : { "ar" : { @@ -2358,6 +2366,10 @@ } } }, + "Adaptive gradient" : { + "comment" : "Name of the timer icon color mode option that uses an adaptive gradient.", + "isCommentAutoGenerated" : true + }, "Add" : { "localizations" : { "ar" : { @@ -3977,6 +3989,10 @@ } } }, + "Apple Music" : { + "comment" : "Names for the different media player types.", + "isCommentAutoGenerated" : true + }, "Applies the notch-style parallax effect to the lock screen media widget album art." : { "localizations" : { "pt-BR" : { @@ -4409,6 +4425,10 @@ } } }, + "Bar" : { + "comment" : "Localized name for the \"Bar\" timer progress style.", + "isCommentAutoGenerated" : true + }, "Battery" : { "localizations" : { "ar" : { @@ -8798,6 +8818,10 @@ } } }, + "Digital" : { + "comment" : "The display name for the \"Digital\" reminder presentation style.", + "isCommentAutoGenerated" : true + }, "Dimensions" : { "localizations" : { "pt-BR" : { @@ -11451,6 +11475,9 @@ } } } + }, + "Font family" : { + }, "Font size" : { "comment" : "A label displayed above a slider that controls the font size of the terminal", @@ -12338,6 +12365,10 @@ } } }, + "Green" : { + "comment" : "Display name for the \"Green\" Caps Lock Indicator Tint Mode.", + "isCommentAutoGenerated" : true + }, "Height:" : { "extractionState" : "stale", "localizations" : { @@ -16121,7 +16152,6 @@ } }, "Minutes" : { - "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -18353,6 +18383,10 @@ } } }, + "Now Playing" : { + "comment" : "Localized name for each media controller type.", + "isCommentAutoGenerated" : true + }, "Number of lines kept in the scrollback buffer. Higher values use more memory." : { }, @@ -18755,6 +18789,10 @@ } } }, + "Panel" : { + "comment" : "Name for the \"Panel\" clipboard display mode.", + "isCommentAutoGenerated" : true + }, "Panel mode shows clipboard in a floating window near the notch." : { "comment" : "A description of the \"Panel\" clipboard display mode.", "isCommentAutoGenerated" : true, @@ -20456,6 +20494,10 @@ } } }, + "Ring" : { + "comment" : "Localized name for the \"Ring\" timer progress style.", + "isCommentAutoGenerated" : true + }, "Rotation:" : { "extractionState" : "stale", "localizations" : { @@ -21300,6 +21342,10 @@ } } }, + "Separate Tab" : { + "comment" : "Name of the clipboard display mode option that opens the clipboard in a separate tab.", + "isCommentAutoGenerated" : true + }, "Separate Tab mode integrates Copied Items and Notes into a single view. If both are enabled, Notes appear on the right and Clipboard on the left." : { "comment" : "A description of the \"Separate Tab\" clipboard display mode.", "isCommentAutoGenerated" : true, @@ -22391,6 +22437,14 @@ } } }, + "Shows screen assistant as a dropdown attached to the AI button" : { + "comment" : "Description of the \"popover\" display mode for the screen assistant.", + "isCommentAutoGenerated" : true + }, + "Shows screen assistant in a floating panel near the notch" : { + "comment" : "Description of the \"Panel\" screen assistant display mode.", + "isCommentAutoGenerated" : true + }, "Shows the model's reasoning process before the final answer" : { "localizations" : { "fr" : { @@ -22465,6 +22519,10 @@ } } }, + "Skip forward or backward by ten seconds" : { + "comment" : "Description of the \"Skip forward or backward by ten seconds\" music skip behavior option.", + "isCommentAutoGenerated" : true + }, "Slider color" : { "localizations" : { "ar" : { @@ -23033,6 +23091,10 @@ } } }, + "Spotify" : { + "comment" : "Name of a media controller type for Spotify.", + "isCommentAutoGenerated" : true + }, "Square" : { "localizations" : { "ar" : { @@ -23145,6 +23207,10 @@ }, "Standard notch media controls are hidden. Re-enable the toggle above to restore them." : { + }, + "Standard previous/next track controls" : { + "comment" : "Description of a music skip behavior option that uses standard previous/next track controls.", + "isCommentAutoGenerated" : true }, "Start" : { "localizations" : { @@ -23865,6 +23931,9 @@ } } } + }, + "System Monospaced" : { + }, "System Performance Monitor" : { "extractionState" : "stale", @@ -24549,6 +24618,10 @@ }, "shouldTranslate" : false }, + "Track Skip" : { + "comment" : "Name of the music skip behavior option that skips to the next track.", + "isCommentAutoGenerated" : true + }, "Transform" : { "extractionState" : "stale", "localizations" : { @@ -26557,6 +26630,10 @@ "comment" : "A footer note explaining the app's funding.", "isCommentAutoGenerated" : true }, + "Youtube Music" : { + "comment" : "Name for the \"Youtube Music\" media controller type in settings.", + "isCommentAutoGenerated" : true + }, "YouTube Music requires this third-party app to be installed: " : { "localizations" : { "ar" : { diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index e9eaa79..f974daf 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -7295,6 +7295,7 @@ struct TerminalSettings: View { @ObservedObject var terminalManager = TerminalManager.shared @Default(.enableTerminalFeature) var enableTerminalFeature @Default(.terminalShellPath) var terminalShellPath + @Default(.terminalFontFamily) var terminalFontFamily @Default(.terminalFontSize) var terminalFontSize @Default(.terminalOpacity) var terminalOpacity @Default(.terminalMaxHeightFraction) var terminalMaxHeightFraction @@ -7315,6 +7316,21 @@ struct TerminalSettings: View { "\(Int(terminalMaxHeightFraction * 100))% of screen" } + /// All monospaced font families available on the system. + private var monospacedFontFamilies: [String] { + NSFontManager.shared.availableFontFamilies.filter { family in + guard let font = NSFont(name: family, size: 12) else { return false } + return font.isFixedPitch + || font.fontDescriptor.symbolicTraits.contains(.monoSpace) + } + .sorted() + } + + /// Display name for the font picker — shows "System Monospaced" when no custom font is set. + private var fontDisplayName: String { + terminalFontFamily.isEmpty ? "System Monospaced" : terminalFontFamily + } + private var cursorStyleBinding: Binding { Binding( get: { TerminalCursorStyleOption(rawValue: terminalCursorStyle) ?? .blinkBlock }, @@ -7365,6 +7381,20 @@ struct TerminalSettings: View { // MARK: Appearance Section { + Picker("Font family", selection: $terminalFontFamily) { + Text("System Monospaced").tag("") + Divider() + ForEach(monospacedFontFamilies, id: \.self) { family in + Text(family) + .font(.custom(family, size: 13)) + .tag(family) + } + } + .onChange(of: terminalFontFamily) { _, newValue in + terminalManager.applyFontFamily(newValue) + } + .settingsHighlight(id: highlightID("Font family")) + VStack(alignment: .leading, spacing: 8) { HStack { Text("Font size") diff --git a/DynamicIsland/managers/TerminalManager.swift b/DynamicIsland/managers/TerminalManager.swift index 212c81b..de0aad7 100644 --- a/DynamicIsland/managers/TerminalManager.swift +++ b/DynamicIsland/managers/TerminalManager.swift @@ -181,15 +181,28 @@ class TerminalManager: ObservableObject { terminalTitle = title } + // MARK: - Font Resolution + + /// Resolves the terminal font from the user's chosen family and size. + /// + /// - If `family` is empty, returns the system monospaced font. + /// - Otherwise tries `NSFont(name:size:)` and falls back to system monospaced + /// when the name is invalid or the font is not installed. + private func resolveFont(family: String, size: CGFloat) -> NSFont { + if !family.isEmpty, let custom = NSFont(name: family, size: size) { + return custom + } + return NSFont.monospacedSystemFont(ofSize: size, weight: .regular) + } + // MARK: - Settings Application /// Applies all persisted settings to a terminal view at creation time. private func applyAllSettings(to view: LocalProcessTerminalView) { // Font let fontSize = CGFloat(Defaults[.terminalFontSize]) - if let font = NSFont.monospacedSystemFont(ofSize: fontSize, weight: .regular) as NSFont? { - view.font = font - } + let fontFamily = Defaults[.terminalFontFamily] + view.font = resolveFont(family: fontFamily, size: fontSize) // Opacity view.layer?.opacity = Float(Defaults[.terminalOpacity]) @@ -220,9 +233,15 @@ class TerminalManager: ObservableObject { /// Updates font size on the live terminal view. func applyFontSize(_ size: Double) { guard let view = terminalView else { return } - if let font = NSFont.monospacedSystemFont(ofSize: CGFloat(size), weight: .regular) as NSFont? { - view.font = font - } + let fontFamily = Defaults[.terminalFontFamily] + view.font = resolveFont(family: fontFamily, size: CGFloat(size)) + } + + /// Updates font family on the live terminal view. + func applyFontFamily(_ family: String) { + guard let view = terminalView else { return } + let fontSize = CGFloat(Defaults[.terminalFontSize]) + view.font = resolveFont(family: family, size: fontSize) } /// Updates opacity on the live terminal view. diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift index 2a4b0d3..cef66c7 100644 --- a/DynamicIsland/models/Constants.swift +++ b/DynamicIsland/models/Constants.swift @@ -915,6 +915,7 @@ extension Defaults.Keys { // MARK: Terminal Feature static let enableTerminalFeature = Key("enableTerminalFeature", default: false) static let terminalShellPath = Key("terminalShellPath", default: "/bin/zsh") + static let terminalFontFamily = Key("terminalFontFamily", default: "") static let terminalFontSize = Key("terminalFontSize", default: 12.0) static let terminalOpacity = Key("terminalOpacity", default: 1.0) static let terminalMaxHeightFraction = Key("terminalMaxHeightFraction", default: 0.4)