From 3a99bad78d8ef6b0d7dc38544d5afa9d5a17d975 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Thu, 5 Mar 2026 11:18:25 +0530 Subject: [PATCH] Update minimum sizing --- .../DynamicIslandViewCoordinator.swift | 24 +++++++ DynamicIsland/Localizable.xcstrings | 12 ++++ .../components/Settings/SettingsView.swift | 28 ++++++-- DynamicIsland/sizing/matters.swift | 68 ++++++++++++++++++- 4 files changed, 123 insertions(+), 9 deletions(-) diff --git a/DynamicIsland/DynamicIslandViewCoordinator.swift b/DynamicIsland/DynamicIslandViewCoordinator.swift index d578199..7c55430 100644 --- a/DynamicIsland/DynamicIslandViewCoordinator.swift +++ b/DynamicIsland/DynamicIslandViewCoordinator.swift @@ -219,6 +219,30 @@ class DynamicIslandViewCoordinator: ObservableObject { .store(in: &cancellables) handleExtensionExperienceSnapshot(extensionNotchExperienceManager.activeExperiences) + + // Observe all tab-affecting settings to enforce minimum notch width + Publishers.MergeMany( + Defaults.publisher(.showStandardMediaControls).map { _ in () }.eraseToAnyPublisher(), + Defaults.publisher(.showCalendar).map { _ in () }.eraseToAnyPublisher(), + Defaults.publisher(.showMirror).map { _ in () }.eraseToAnyPublisher(), + Defaults.publisher(.dynamicShelf).map { _ in () }.eraseToAnyPublisher(), + Defaults.publisher(.enableTimerFeature).map { _ in () }.eraseToAnyPublisher(), + Defaults.publisher(.timerDisplayMode).map { _ in () }.eraseToAnyPublisher(), + Defaults.publisher(.enableStatsFeature).map { _ in () }.eraseToAnyPublisher(), + Defaults.publisher(.enableNotes).map { _ in () }.eraseToAnyPublisher(), + Defaults.publisher(.enableClipboardManager).map { _ in () }.eraseToAnyPublisher(), + Defaults.publisher(.clipboardDisplayMode).map { _ in () }.eraseToAnyPublisher(), + Defaults.publisher(.enableTerminalFeature).map { _ in () }.eraseToAnyPublisher(), + Defaults.publisher(.enableMinimalisticUI).map { _ in () }.eraseToAnyPublisher() + ) + .debounce(for: .milliseconds(100), scheduler: DispatchQueue.main) + .sink { _ in + enforceMinimumNotchWidth() + } + .store(in: &cancellables) + + // Enforce minimum width on launch for existing configurations + enforceMinimumNotchWidth() } private func handleStatsTabTransition(from oldValue: NotchViews, to newValue: NotchViews) { diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index 412ac56..0fd79b0 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -1068,6 +1068,18 @@ "isCommentAutoGenerated" : true, "shouldTranslate" : false }, + "%lld tab%@ enabled · min %lld px" : { + "comment" : "A label displaying the number of enabled tabs, the recommended minimum notch width, and a button to reset the notch width to its recommended minimum value. The first argument is the count of enabled tabs. The second argument is the recommended minimum notch width as a formatted pixel count.", + "isCommentAutoGenerated" : true, + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$lld tab%2$@ enabled · min %3$lld px" + } + } + } + }, "%lld%%" : { "localizations" : { "ar" : { diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index c996f9a..eceac92 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -3468,8 +3468,12 @@ 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 var notchWidthRange: ClosedRange { + Double(currentRecommendedMinimumNotchWidth())...900 + } + private var defaultOpenNotchWidth: CGFloat { + currentRecommendedMinimumNotchWidth() + } private func highlightID(_ title: String) -> String { SettingsTab.appearance.highlightID(for: title) @@ -4019,10 +4023,14 @@ struct Appearance: View { @ViewBuilder private func notchWidthControls() -> some View { Section { + let recommendedMin = currentRecommendedMinimumNotchWidth() + let tabCount = enabledStandardTabCount() + let dynamicRange = Double(recommendedMin)...900 + let widthBinding = Binding( get: { Double(openNotchWidth) }, set: { newValue in - let clamped = min(max(newValue, notchWidthRange.lowerBound), notchWidthRange.upperBound) + let clamped = min(max(newValue, dynamicRange.lowerBound), dynamicRange.upperBound) let value = CGFloat(clamped) if openNotchWidth != value { openNotchWidth = value @@ -4033,7 +4041,7 @@ struct Appearance: View { VStack(alignment: .leading, spacing: 10) { Slider( value: widthBinding, - in: notchWidthRange, + in: dynamicRange, step: 10 ) { HStack { @@ -4047,22 +4055,28 @@ struct Appearance: View { .settingsHighlight(id: highlightID("Expanded notch width")) HStack { + Text("\(tabCount) tab\(tabCount == 1 ? "" : "s") enabled · min \(Int(recommendedMin)) px") + .font(.caption) + .foregroundStyle(.secondary) Spacer() Button("Reset Width") { - openNotchWidth = defaultOpenNotchWidth + openNotchWidth = recommendedMin } - .disabled(abs(openNotchWidth - defaultOpenNotchWidth) < 0.5) + .disabled(abs(openNotchWidth - recommendedMin) < 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)." + : "Recommended minimum width adjusts automatically based on the number of enabled tabs." Text(description) .font(.caption) .foregroundStyle(.secondary) } + .onAppear { + enforceMinimumNotchWidth() + } } header: { HStack { Text("Notch Width") diff --git a/DynamicIsland/sizing/matters.swift b/DynamicIsland/sizing/matters.swift index 3757150..d972b3d 100644 --- a/DynamicIsland/sizing/matters.swift +++ b/DynamicIsland/sizing/matters.swift @@ -28,8 +28,72 @@ let downloadSneakSize: CGSize = .init(width: 65, height: 1) let batterySneakSize: CGSize = .init(width: 160, height: 1) var openNotchSize: CGSize { - let width = Defaults[.openNotchWidth] - return .init(width: width, height: 200) // Adjusted to a better middle ground + let storedWidth = Defaults[.openNotchWidth] + let minWidth = currentRecommendedMinimumNotchWidth() + let width = max(storedWidth, minWidth) + return .init(width: width, height: 200) +} + +// MARK: - Tab-Based Notch Width + +/// Counts the number of currently enabled standard notch tabs. +/// Mirrors the tab-building logic in ``TabSelectionView``. +func enabledStandardTabCount() -> Int { + var count = 0 + + // Home tab + if Defaults[.showStandardMediaControls] || Defaults[.showCalendar] || Defaults[.showMirror] { + count += 1 + } + + // Shelf tab + if Defaults[.dynamicShelf] { + count += 1 + } + + // Timer tab (only in .tab display mode) + if Defaults[.enableTimerFeature] && Defaults[.timerDisplayMode] == .tab { + count += 1 + } + + // Stats tab + if Defaults[.enableStatsFeature] { + count += 1 + } + + // Notes / Clipboard tab + if Defaults[.enableNotes] || (Defaults[.enableClipboardManager] && Defaults[.clipboardDisplayMode] == .separateTab) { + count += 1 + } + + // Terminal tab + if Defaults[.enableTerminalFeature] { + count += 1 + } + + return count +} + +/// Returns the recommended minimum notch width for the given tab count. +func recommendedMinimumNotchWidth(forTabCount count: Int) -> CGFloat { + if count >= 6 { return 770 } + if count >= 5 { return 690 } + return 640 +} + +/// Returns the recommended minimum notch width for the current tab configuration. +func currentRecommendedMinimumNotchWidth() -> CGFloat { + recommendedMinimumNotchWidth(forTabCount: enabledStandardTabCount()) +} + +/// Enforces the minimum notch width based on current tab count. +/// Only adjusts when not in minimalistic mode. +func enforceMinimumNotchWidth() { + guard !Defaults[.enableMinimalisticUI] else { return } + let minWidth = currentRecommendedMinimumNotchWidth() + if Defaults[.openNotchWidth] < minWidth { + Defaults[.openNotchWidth] = minWidth + } } private let minimalisticBaseOpenNotchSize: CGSize = .init(width: 420, height: 180) private let minimalisticLyricsExtraHeight: CGFloat = 40