diff --git a/DynamicIsland/DynamicIslandApp.swift b/DynamicIsland/DynamicIslandApp.swift index a193772..1413148 100644 --- a/DynamicIsland/DynamicIslandApp.swift +++ b/DynamicIsland/DynamicIslandApp.swift @@ -394,6 +394,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { self?.debouncedUpdateWindowSize() }.store(in: &cancellables) + Defaults.publisher(.openNotchWidth, options: []).sink { [weak self] _ in + self?.debouncedUpdateWindowSize() + }.store(in: &cancellables) + ReminderLiveActivityManager.shared.$activeWindowReminders .receive(on: RunLoop.main) .sink { [weak self] _ in diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index d7e9074..0e1a7cc 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -894,6 +894,10 @@ "comment" : "A text label displaying the current value of the \"Notify before\" slider. The argument is the current value of the slider, expressed in minutes.", "isCommentAutoGenerated" : true }, + "%lld px" : { + "comment" : "A label displaying the current expanded notch width value. The value is shown in pixels.", + "isCommentAutoGenerated" : true + }, "%lld s" : { "comment" : "A text view displaying the duration of the \"sneak peek\" feature in the reminder live activity section of the calendar settings view. The text is followed by a unit of measurement (\"s\").", "isCommentAutoGenerated" : true @@ -8567,6 +8571,10 @@ }, "Expand Notch with Animation" : { + }, + "Expanded notch width" : { + "comment" : "A label describing the expanded notch width setting.", + "isCommentAutoGenerated" : true }, "Extensions" : { "localizations" : { @@ -14533,6 +14541,10 @@ } } }, + "Notch Width" : { + "comment" : "A section header for adjusting the expanded notch width.", + "isCommentAutoGenerated" : true + }, "Notify before" : { "comment" : "A label describing the slider that allows the user to set a reminder lead time.", "isCommentAutoGenerated" : true @@ -15692,6 +15704,10 @@ } } }, + "Reset Width" : { + "comment" : "A button to reset the expanded notch width to its default value.", + "isCommentAutoGenerated" : true + }, "Restart" : { "localizations" : { "ar" : { diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index b7ce27f..d39c8c6 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -2173,6 +2173,8 @@ struct Appearance: View { @Default(.useMusicVisualizer) var useMusicVisualizer @Default(.customVisualizers) var customVisualizers @Default(.selectedVisualizer) var selectedVisualizer + @Default(.openNotchWidth) var openNotchWidth + @Default(.enableMinimalisticUI) var enableMinimalisticUI let icons: [String] = ["logo2"] @State private var selectedIcon: String = "logo2" @State private var selectedListVisualizer: CustomVisualizer? = nil @@ -2182,6 +2184,9 @@ struct Appearance: View { @State private var url: String = "" @State private var speed: CGFloat = 1.0 + private let notchWidthRange: ClosedRange = 640...900 + private let defaultOpenNotchWidth: CGFloat = 640 + private func highlightID(_ title: String) -> String { SettingsTab.appearance.highlightID(for: title) } @@ -2202,6 +2207,8 @@ struct Appearance: View { Text("General") } + notchWidthControls() + Section { Defaults.Toggle("Enable colored spectrograms", key: .coloredSpectrogram) .settingsHighlight(id: highlightID("Enable colored spectrograms")) @@ -2463,6 +2470,58 @@ struct Appearance: View { return false } + + @ViewBuilder + private func notchWidthControls() -> some View { + Section { + let widthBinding = Binding( + get: { Double(openNotchWidth) }, + set: { newValue in + let clamped = min(max(newValue, notchWidthRange.lowerBound), notchWidthRange.upperBound) + let value = CGFloat(clamped) + if openNotchWidth != value { + openNotchWidth = value + } + } + ) + + VStack(alignment: .leading, spacing: 10) { + Slider( + value: widthBinding, + in: notchWidthRange, + step: 10 + ) { + HStack { + Text("Expanded notch width") + Spacer() + Text("\(Int(openNotchWidth)) px") + .foregroundStyle(.secondary) + } + } + .disabled(enableMinimalisticUI) + .settingsHighlight(id: highlightID("Expanded notch width")) + + HStack { + Spacer() + Button("Reset Width") { + openNotchWidth = defaultOpenNotchWidth + } + .disabled(abs(openNotchWidth - defaultOpenNotchWidth) < 0.5) + .buttonStyle(.bordered) + } + + let description = enableMinimalisticUI + ? "Width adjustments apply only to the standard notch layout. Disable Minimalistic UI to edit this value." + : "Extend the notch span so the clipboard, colour picker, and other trailing icons remain visible on scaled displays (e.g. More Space)." + + Text(description) + .font(.caption) + .foregroundStyle(.secondary) + } + } header: { + Text("Notch Width") + } + } } struct LockScreenSettings: View { diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift index 97a06ee..c62ef82 100644 --- a/DynamicIsland/models/Constants.swift +++ b/DynamicIsland/models/Constants.swift @@ -400,6 +400,7 @@ extension Defaults.Keys { ) static let nonNotchHeight = Key("nonNotchHeight", default: 32) static let notchHeight = Key("notchHeight", default: 32) + static let openNotchWidth = Key("openNotchWidth", default: 640) //static let openLastTabByDefault = Key("openLastTabByDefault", default: false) // MARK: Appearance diff --git a/DynamicIsland/models/DynamicIslandViewModel.swift b/DynamicIsland/models/DynamicIslandViewModel.swift index a63464a..2e512dc 100644 --- a/DynamicIsland/models/DynamicIslandViewModel.swift +++ b/DynamicIsland/models/DynamicIslandViewModel.swift @@ -147,6 +147,29 @@ class DynamicIslandViewModel: NSObject, ObservableObject { } } .store(in: &cancellables) + + Defaults.publisher(.openNotchWidth, options: []) + .map { $0.newValue } + .removeDuplicates() + .receive(on: RunLoop.main) + .sink { [weak self] _ in + guard let self else { return } + guard self.notchState == .open else { return } + guard !Defaults[.enableMinimalisticUI] else { return } + let updatedTarget = self.calculateDynamicNotchSize() + guard self.notchSize != updatedTarget else { return } + withAnimation(.smooth) { + self.notchSize = updatedTarget + } + if let delegate = AppDelegate.shared { + delegate.ensureWindowSize( + addShadowPadding(to: updatedTarget, isMinimalistic: false), + animated: true, + force: false + ) + } + } + .store(in: &cancellables) } private func handleMinimalisticTimerHeightChange() { diff --git a/DynamicIsland/sizing/matters.swift b/DynamicIsland/sizing/matters.swift index 0e15796..6e1c505 100644 --- a/DynamicIsland/sizing/matters.swift +++ b/DynamicIsland/sizing/matters.swift @@ -14,7 +14,10 @@ import SwiftUI let downloadSneakSize: CGSize = .init(width: 65, height: 1) let batterySneakSize: CGSize = .init(width: 160, height: 1) -let openNotchSize: CGSize = .init(width: 640, height: 190) +var openNotchSize: CGSize { + let width = Defaults[.openNotchWidth] + return .init(width: width, height: 190) +} private let minimalisticBaseOpenNotchSize: CGSize = .init(width: 420, height: 180) private let minimalisticLyricsExtraHeight: CGFloat = 40 let minimalisticTimerCountdownTopPadding: CGFloat = 12