Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements autounhide when screen is wider than set width #413

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Ice/Main/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,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()

Expand Down Expand Up @@ -187,6 +190,7 @@ final class AppState: ObservableObject {
imageCache.performSetup()
updatesManager.performSetup()
userNotificationManager.performSetup()
autoExpander.performSetup()
}

/// Assigns the app delegate to the app state.
Expand Down
76 changes: 76 additions & 0 deletions Ice/MenuBar/MenuBarManagement/MenuBarAutoExpander.swift
Original file line number Diff line number Diff line change
@@ -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<AnyCancellable>()

/// 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<AnyCancellable>()

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 {
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()
}
}
}
}
.store(in: &c)
}

cancellables = c
}
}

// MARK: - Logger
private extension Logger {
static let autoExpander = Logger(category: "MenuBarAutoExpander")
}
23 changes: 22 additions & 1 deletion Ice/MenuBar/MenuBarManagement/MenuBarSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -196,6 +214,9 @@ final class MenuBarSection {
else {
return
}
if self.forceShowDueToWideScreen() {
return
}
iceBarPanel?.close()
switch name {
case _ where useIceBar:
Expand Down
23 changes: 23 additions & 0 deletions Ice/Settings/SettingsManagers/AdvancedSettingsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ final class AdvancedSettingsManager: ObservableObject {
/// 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<AnyCancellable>()

Expand All @@ -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() {
Expand Down Expand Up @@ -111,6 +120,20 @@ final class AdvancedSettingsManager: ObservableObject {
}
.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
}
}
Expand Down
36 changes: 36 additions & 0 deletions Ice/Settings/SettingsPanes/AdvancedSettingsPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ struct AdvancedSettingsPane: View {
}
}

private func formattedToPx(_ px: CGFloat) -> LocalizedStringKey {
let formatted = px.formatted()
return LocalizedStringKey(formatted + " px")
}

var body: some View {
IceForm {
IceSection {
Expand All @@ -41,6 +46,10 @@ struct AdvancedSettingsPane: View {
showOnHoverDelaySlider
tempShowIntervalSlider
}
IceSection {
activeScreenWidthToggle
activeScreenWidthSlider
}
}
}

Expand Down Expand Up @@ -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)
.annotation("This will always show the items in the menu bar (ignoring the \"Use Ice Bar\" setting.")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I forgot a closing paren here.

Suggested change
.annotation("This will always show the items in the menu bar (ignoring the \"Use Ice Bar\" setting.")
.annotation("This will always show the items in the menu bar (ignoring the \"Use Ice Bar\" setting).")

}

@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)
}
}
}
}
}

#Preview {
Expand Down
2 changes: 2 additions & 0 deletions Ice/Utilities/Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down