From 733f140e991258b9810447d3dc5ba98321a8fecb Mon Sep 17 00:00:00 2001 From: Michele Primavera Date: Mon, 21 Oct 2024 21:33:44 +0200 Subject: [PATCH 1/4] Implements autounhide when screen is wider than set width --- Ice/Main/AppState.swift | 4 + .../MenuBarAutoExpander.swift | 76 +++++++++++++++++++ .../AdvancedSettingsManager.swift | 23 ++++++ .../SettingsPanes/AdvancedSettingsPane.swift | 36 +++++++++ Ice/Utilities/Defaults.swift | 2 + 5 files changed, 141 insertions(+) create mode 100644 Ice/MenuBar/MenuBarManagement/MenuBarAutoExpander.swift diff --git a/Ice/Main/AppState.swift b/Ice/Main/AppState.swift index 77872bf2..f180b411 100644 --- a/Ice/Main/AppState.swift +++ b/Ice/Main/AppState.swift @@ -38,6 +38,9 @@ final class AppState: ObservableObject { /// Global cache for menu bar item images. private(set) lazy var imageCache = MenuBarItemImageCache(appState: self) + + /// Global observer for auto expanding the bar based on the screens width + private(set) lazy var autoExpander = MenuBarAutoExpander(appState: self) /// Manager for menu bar item spacing. let spacingManager = MenuBarItemSpacingManager() @@ -187,6 +190,7 @@ final class AppState: ObservableObject { imageCache.performSetup() updatesManager.performSetup() userNotificationManager.performSetup() + autoExpander.performSetup() } /// Assigns the app delegate to the app state. diff --git a/Ice/MenuBar/MenuBarManagement/MenuBarAutoExpander.swift b/Ice/MenuBar/MenuBarManagement/MenuBarAutoExpander.swift new file mode 100644 index 00000000..b14bd3be --- /dev/null +++ b/Ice/MenuBar/MenuBarManagement/MenuBarAutoExpander.swift @@ -0,0 +1,76 @@ +// +// MenuBarAutoExpander.swift +// Ice +// +// Created by Michele Primavera on 21/10/24. +// + +import Cocoa +import Combine + +final class MenuBarAutoExpander : ObservableObject { + /// The shared app state. + private weak var appState: AppState? + + /// Storage for internal observers. + private var cancellables = Set() + + /// Creates a cache with the given app state. + init(appState: AppState) { + self.appState = appState + } + + /// Sets up the cache. + @MainActor + func performSetup() { + configureCancellables() + } + + /// Configures the internal observers for the cache. + @MainActor + private func configureCancellables() { + var c = Set() + + if let appState { + Publishers.Merge( + NotificationCenter.default.publisher(for: NSApplication.didChangeScreenParametersNotification).mapToVoid(), + Just(()) + ) + .throttle(for: 10.0, scheduler: DispatchQueue.main, latest: false) + .sink { + let advancedSettingsManager = appState.settingsManager.advancedSettingsManager + + if(advancedSettingsManager.showHiddenSectionWhenWidthGreaterThanEnabled) { + Task.detached { + let mainScreen = NSScreen.main + if mainScreen != nil { + let mainScreenWidth = mainScreen!.frame.width + + guard let section = await appState.menuBarManager.section(withName: .hidden) else { + return + } + + let setting = await advancedSettingsManager.showHiddenSectionWhenWidthGreaterThan + + if (mainScreenWidth >= setting) { + Logger.autoExpander.info("Showing hidden section because mainScreenWidth (\(mainScreenWidth)) >= showHiddenSectionWhenWidthGreaterThan (\(setting)") + await section.show() + } else { + Logger.autoExpander.info("Hiding hidden section because mainScreenWidth (\(mainScreenWidth)) < showHiddenSectionWhenWidthGreaterThan (\(setting)") + await section.hide() + } + } + } + } + } + .store(in: &c) + } + + cancellables = c + } +} + +// MARK: - Logger +private extension Logger { + static let autoExpander = Logger(category: "MenuBarAutoExpander") +} diff --git a/Ice/Settings/SettingsManagers/AdvancedSettingsManager.swift b/Ice/Settings/SettingsManagers/AdvancedSettingsManager.swift index d1fed6a6..fefc4c22 100644 --- a/Ice/Settings/SettingsManagers/AdvancedSettingsManager.swift +++ b/Ice/Settings/SettingsManagers/AdvancedSettingsManager.swift @@ -33,6 +33,13 @@ final class AdvancedSettingsManager: ObservableObject { /// A Boolean value that indicates whether to show all sections when /// the user is dragging items in the menu bar. @Published var showAllSectionsOnUserDrag = true + + /// A Boolean value that indicates whether to show all sections when + /// the screen width is greater than showHiddenSectionWhenWidthGreaterThan + @Published var showHiddenSectionWhenWidthGreaterThanEnabled = false + + /// The minimum screen size showAllSectionOnScreenSize reacts to + @Published var showHiddenSectionWhenWidthGreaterThan: CGFloat = 3000 /// Storage for internal observers. private var cancellables = Set() @@ -57,6 +64,8 @@ final class AdvancedSettingsManager: ObservableObject { Defaults.ifPresent(key: .showOnHoverDelay, assign: &showOnHoverDelay) Defaults.ifPresent(key: .tempShowInterval, assign: &tempShowInterval) Defaults.ifPresent(key: .showAllSectionsOnUserDrag, assign: &showAllSectionsOnUserDrag) + Defaults.ifPresent(key: .showHiddenSectionWhenWidthGreaterThanEnabled, assign: &showHiddenSectionWhenWidthGreaterThanEnabled) + Defaults.ifPresent(key: .showHiddenSectionWhenWidthGreaterThan, assign: &showHiddenSectionWhenWidthGreaterThan) } private func configureCancellables() { @@ -110,6 +119,20 @@ final class AdvancedSettingsManager: ObservableObject { Defaults.set(showAll, forKey: .showAllSectionsOnUserDrag) } .store(in: &c) + + $showHiddenSectionWhenWidthGreaterThanEnabled + .receive(on: DispatchQueue.main) + .sink { showAll in + Defaults.set(showAll, forKey: .showHiddenSectionWhenWidthGreaterThanEnabled) + } + .store(in: &c) + + $showHiddenSectionWhenWidthGreaterThan + .receive(on: DispatchQueue.main) + .sink { width in + Defaults.set(width, forKey: .showHiddenSectionWhenWidthGreaterThan) + } + .store(in: &c) cancellables = c } diff --git a/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift b/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift index 4a7cda65..89474dfe 100644 --- a/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift +++ b/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift @@ -25,6 +25,11 @@ struct AdvancedSettingsPane: View { LocalizedStringKey(formatted + " seconds") } } + + private func formattedToPx(_ px: CGFloat) -> LocalizedStringKey { + let formatted = px.formatted() + return LocalizedStringKey(formatted + " px") + } var body: some View { IceForm { @@ -41,6 +46,10 @@ struct AdvancedSettingsPane: View { showOnHoverDelaySlider tempShowIntervalSlider } + IceSection { + activeScreenWidthToggle + activeScreenWidthSlider + } } } @@ -134,6 +143,33 @@ struct AdvancedSettingsPane: View { private var showAllSectionsOnUserDrag: some View { Toggle("Show all sections when Command + dragging menu bar items", isOn: manager.bindings.showAllSectionsOnUserDrag) } + + @ViewBuilder + private var activeScreenWidthToggle: some View { + Toggle("Automatically unhide when active screen width is higher than the value below", isOn: manager.bindings.showHiddenSectionWhenWidthGreaterThanEnabled) + } + + @ViewBuilder + private var activeScreenWidthSlider: some View { + if manager.showHiddenSectionWhenWidthGreaterThanEnabled { + IceLabeledContent { + IceSlider( + formattedToPx(manager.showHiddenSectionWhenWidthGreaterThan), + value: manager.bindings.showHiddenSectionWhenWidthGreaterThan, + in: 1000...6000, + step: 10 + ) + } label: { + Text("Active screen width in pixels") + .frame(minHeight: .compactSliderMinHeight) + .frame(minWidth: maxSliderLabelWidth, alignment: .leading) + .onFrameChange { frame in + maxSliderLabelWidth = max(maxSliderLabelWidth, frame.width) + } + } + .annotation("You may want to disable automatically rehide in General.") + } + } } #Preview { diff --git a/Ice/Utilities/Defaults.swift b/Ice/Utilities/Defaults.swift index f8eacb7c..c230e5ef 100644 --- a/Ice/Utilities/Defaults.swift +++ b/Ice/Utilities/Defaults.swift @@ -165,6 +165,8 @@ extension Defaults { case showOnHoverDelay = "ShowOnHoverDelay" case tempShowInterval = "TempShowInterval" case showAllSectionsOnUserDrag = "ShowAllSectionsOnUserDrag" + case showHiddenSectionWhenWidthGreaterThanEnabled = "ShowHiddenSectionWhenWidthGreaterThanEnabled" + case showHiddenSectionWhenWidthGreaterThan = "ShowHiddenSectionWhenWidthGreaterThan" // MARK: Menu Bar Appearance Settings From f1833a12064c3abf8f5d6764d3247a2b395a9a8f Mon Sep 17 00:00:00 2001 From: Michele Primavera Date: Mon, 21 Oct 2024 22:07:53 +0200 Subject: [PATCH 2/4] Make SwiftLint happy --- Ice/Main/AppState.swift | 2 +- .../MenuBarAutoExpander.swift | 42 +++++++++---------- .../AdvancedSettingsManager.swift | 8 ++-- .../SettingsPanes/AdvancedSettingsPane.swift | 6 +-- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Ice/Main/AppState.swift b/Ice/Main/AppState.swift index f180b411..c098471b 100644 --- a/Ice/Main/AppState.swift +++ b/Ice/Main/AppState.swift @@ -38,7 +38,7 @@ final class AppState: ObservableObject { /// Global cache for menu bar item images. private(set) lazy var imageCache = MenuBarItemImageCache(appState: self) - + /// Global observer for auto expanding the bar based on the screens width private(set) lazy var autoExpander = MenuBarAutoExpander(appState: self) diff --git a/Ice/MenuBar/MenuBarManagement/MenuBarAutoExpander.swift b/Ice/MenuBar/MenuBarManagement/MenuBarAutoExpander.swift index b14bd3be..c3ae8e9b 100644 --- a/Ice/MenuBar/MenuBarManagement/MenuBarAutoExpander.swift +++ b/Ice/MenuBar/MenuBarManagement/MenuBarAutoExpander.swift @@ -8,10 +8,10 @@ import Cocoa import Combine -final class MenuBarAutoExpander : ObservableObject { +final class MenuBarAutoExpander: ObservableObject { /// The shared app state. private weak var appState: AppState? - + /// Storage for internal observers. private var cancellables = Set() @@ -39,26 +39,26 @@ final class MenuBarAutoExpander : ObservableObject { .throttle(for: 10.0, scheduler: DispatchQueue.main, latest: false) .sink { let advancedSettingsManager = appState.settingsManager.advancedSettingsManager - - if(advancedSettingsManager.showHiddenSectionWhenWidthGreaterThanEnabled) { + + if advancedSettingsManager.showHiddenSectionWhenWidthGreaterThanEnabled { Task.detached { - let mainScreen = NSScreen.main - if mainScreen != nil { - let mainScreenWidth = mainScreen!.frame.width - - guard let section = await appState.menuBarManager.section(withName: .hidden) else { - return - } - - let setting = await advancedSettingsManager.showHiddenSectionWhenWidthGreaterThan - - if (mainScreenWidth >= setting) { - Logger.autoExpander.info("Showing hidden section because mainScreenWidth (\(mainScreenWidth)) >= showHiddenSectionWhenWidthGreaterThan (\(setting)") - await section.show() - } else { - Logger.autoExpander.info("Hiding hidden section because mainScreenWidth (\(mainScreenWidth)) < showHiddenSectionWhenWidthGreaterThan (\(setting)") - await section.hide() - } + guard let mainScreen = NSScreen.main else { + return + } + + guard let section = await appState.menuBarManager.section(withName: .hidden) else { + return + } + + let mainScreenWidth = mainScreen.frame.width + let setting = await advancedSettingsManager.showHiddenSectionWhenWidthGreaterThan + + if mainScreenWidth >= setting { + Logger.autoExpander.info("Showing hidden section because mainScreenWidth (\(mainScreenWidth)) >= showHiddenSectionWhenWidthGreaterThan (\(setting)") + await section.show() + } else { + Logger.autoExpander.info("Hiding hidden section because mainScreenWidth (\(mainScreenWidth)) < showHiddenSectionWhenWidthGreaterThan (\(setting)") + await section.hide() } } } diff --git a/Ice/Settings/SettingsManagers/AdvancedSettingsManager.swift b/Ice/Settings/SettingsManagers/AdvancedSettingsManager.swift index fefc4c22..4499c962 100644 --- a/Ice/Settings/SettingsManagers/AdvancedSettingsManager.swift +++ b/Ice/Settings/SettingsManagers/AdvancedSettingsManager.swift @@ -33,11 +33,11 @@ final class AdvancedSettingsManager: ObservableObject { /// A Boolean value that indicates whether to show all sections when /// the user is dragging items in the menu bar. @Published var showAllSectionsOnUserDrag = true - + /// A Boolean value that indicates whether to show all sections when /// the screen width is greater than showHiddenSectionWhenWidthGreaterThan @Published var showHiddenSectionWhenWidthGreaterThanEnabled = false - + /// The minimum screen size showAllSectionOnScreenSize reacts to @Published var showHiddenSectionWhenWidthGreaterThan: CGFloat = 3000 @@ -119,14 +119,14 @@ final class AdvancedSettingsManager: ObservableObject { Defaults.set(showAll, forKey: .showAllSectionsOnUserDrag) } .store(in: &c) - + $showHiddenSectionWhenWidthGreaterThanEnabled .receive(on: DispatchQueue.main) .sink { showAll in Defaults.set(showAll, forKey: .showHiddenSectionWhenWidthGreaterThanEnabled) } .store(in: &c) - + $showHiddenSectionWhenWidthGreaterThan .receive(on: DispatchQueue.main) .sink { width in diff --git a/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift b/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift index 89474dfe..71b99353 100644 --- a/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift +++ b/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift @@ -25,7 +25,7 @@ struct AdvancedSettingsPane: View { LocalizedStringKey(formatted + " seconds") } } - + private func formattedToPx(_ px: CGFloat) -> LocalizedStringKey { let formatted = px.formatted() return LocalizedStringKey(formatted + " px") @@ -143,12 +143,12 @@ struct AdvancedSettingsPane: View { private var showAllSectionsOnUserDrag: some View { Toggle("Show all sections when Command + dragging menu bar items", isOn: manager.bindings.showAllSectionsOnUserDrag) } - + @ViewBuilder private var activeScreenWidthToggle: some View { Toggle("Automatically unhide when active screen width is higher than the value below", isOn: manager.bindings.showHiddenSectionWhenWidthGreaterThanEnabled) } - + @ViewBuilder private var activeScreenWidthSlider: some View { if manager.showHiddenSectionWhenWidthGreaterThanEnabled { From bc7c50dd5ca77a6368483c40197ea5f4c35f6686 Mon Sep 17 00:00:00 2001 From: Lucas Garron Date: Sat, 23 Nov 2024 11:59:59 -0800 Subject: [PATCH 3/4] When showing items due to wide screen, always show inline and never hide. --- .../MenuBarManagement/MenuBarSection.swift | 23 ++++++++++++++++++- .../SettingsPanes/AdvancedSettingsPane.swift | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Ice/MenuBar/MenuBarManagement/MenuBarSection.swift b/Ice/MenuBar/MenuBarManagement/MenuBarSection.swift index b1157d37..aa12c0b9 100644 --- a/Ice/MenuBar/MenuBarManagement/MenuBarSection.swift +++ b/Ice/MenuBar/MenuBarManagement/MenuBarSection.swift @@ -127,6 +127,23 @@ final class MenuBarSection { self.init(name: name, controlItem: controlItem, appState: appState) } + func forceShowDueToWideScreen() -> Bool { + // TODO: deduplicate with `MenuBarAutoExpander`. + guard + let appState + else { + return false + } + let advancedSettingsManager = appState.settingsManager.advancedSettingsManager; + guard let mainScreen = NSScreen.main else { + return false + } + + let mainScreenWidth = mainScreen.frame.width; + let setting = advancedSettingsManager.showHiddenSectionWhenWidthGreaterThan; + return mainScreenWidth >= setting; + } + /// Shows the section. func show() { guard @@ -140,8 +157,9 @@ final class MenuBarSection { // TODO: Can we use isEnabled for this check? return } + let useIceBarExceptOnWideScreen = useIceBar && !self.forceShowDueToWideScreen(); switch name { - case .visible where useIceBar, .hidden where useIceBar: + case .visible where useIceBarExceptOnWideScreen, .hidden where useIceBarExceptOnWideScreen: Task { if let screenForIceBar { await iceBarPanel?.show(section: .hidden, on: screenForIceBar) @@ -196,6 +214,9 @@ final class MenuBarSection { else { return } + if self.forceShowDueToWideScreen() { + return; + } iceBarPanel?.close() switch name { case _ where useIceBar: diff --git a/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift b/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift index 71b99353..b7202ffa 100644 --- a/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift +++ b/Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift @@ -147,6 +147,7 @@ struct AdvancedSettingsPane: View { @ViewBuilder private var activeScreenWidthToggle: some View { Toggle("Automatically unhide when active screen width is higher than the value below", isOn: manager.bindings.showHiddenSectionWhenWidthGreaterThanEnabled) + .annotation("This will always show the items in the menu bar (ignoring the \"Use Ice Bar\" setting.") } @ViewBuilder @@ -167,7 +168,6 @@ struct AdvancedSettingsPane: View { maxSliderLabelWidth = max(maxSliderLabelWidth, frame.width) } } - .annotation("You may want to disable automatically rehide in General.") } } } From 27ebdfe807724c4e65385dc3a8684b32eceecd72 Mon Sep 17 00:00:00 2001 From: Michele Primavera Date: Sat, 23 Nov 2024 21:07:28 +0100 Subject: [PATCH 4/4] Swiftlint fix --- Ice/MenuBar/MenuBarManagement/MenuBarSection.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Ice/MenuBar/MenuBarManagement/MenuBarSection.swift b/Ice/MenuBar/MenuBarManagement/MenuBarSection.swift index aa12c0b9..1b017cc2 100644 --- a/Ice/MenuBar/MenuBarManagement/MenuBarSection.swift +++ b/Ice/MenuBar/MenuBarManagement/MenuBarSection.swift @@ -134,14 +134,14 @@ final class MenuBarSection { else { return false } - let advancedSettingsManager = appState.settingsManager.advancedSettingsManager; + let advancedSettingsManager = appState.settingsManager.advancedSettingsManager guard let mainScreen = NSScreen.main else { return false } - let mainScreenWidth = mainScreen.frame.width; - let setting = advancedSettingsManager.showHiddenSectionWhenWidthGreaterThan; - return mainScreenWidth >= setting; + let mainScreenWidth = mainScreen.frame.width + let setting = advancedSettingsManager.showHiddenSectionWhenWidthGreaterThan + return mainScreenWidth >= setting } /// Shows the section. @@ -157,7 +157,7 @@ final class MenuBarSection { // TODO: Can we use isEnabled for this check? return } - let useIceBarExceptOnWideScreen = useIceBar && !self.forceShowDueToWideScreen(); + let useIceBarExceptOnWideScreen = useIceBar && !self.forceShowDueToWideScreen() switch name { case .visible where useIceBarExceptOnWideScreen, .hidden where useIceBarExceptOnWideScreen: Task { @@ -215,7 +215,7 @@ final class MenuBarSection { return } if self.forceShowDueToWideScreen() { - return; + return } iceBarPanel?.close() switch name {