Skip to content

Commit

Permalink
Tweak Ice Bar rehiding logic
Browse files Browse the repository at this point in the history
Don't hide items when showing Ice Bar. Let them rehide on their own. Each new item that is shown extends the timer.
  • Loading branch information
jordanbaird committed Jul 15, 2024
1 parent 98e2a8b commit d7e5914
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 87 deletions.
149 changes: 67 additions & 82 deletions Ice/MenuBar/MenuBarItemManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,11 @@ class MenuBarItemManager: ObservableObject {
}
}

private class TempShownItemContext {
private struct TempShownItemContext {
let item: MenuBarItem
let returnDestination: MoveDestination
let shownInterfaceWindow: WindowInfo?

private weak var itemManager: MenuBarItemManager?

private var timer: Timer?

var isShowingInterface: Bool {
guard let currentWindow = shownInterfaceWindow.flatMap({ WindowInfo(windowID: $0.windowID) }) else {
return false
Expand All @@ -74,54 +70,6 @@ class MenuBarItemManager: ObservableObject {
return currentWindow.isOnScreen
}
}

init(item: MenuBarItem, returnDestination: MoveDestination, shownInterfaceWindow: WindowInfo?, itemManager: MenuBarItemManager) {
self.item = item
self.returnDestination = returnDestination
self.shownInterfaceWindow = shownInterfaceWindow
self.itemManager = itemManager
}

func scheduleTimer(for interval: TimeInterval) {
Logger.itemManager.debug("Running rehide timer for temp shown item \"\(self.item.logString)\" with interval: \(interval, format: .hybrid)")

invalidateTimer()
timer = .scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] timer in
guard let self else {
timer.invalidate()
return
}

Logger.itemManager.debug("Rehide timer fired")

guard !isShowingInterface else {
Logger.itemManager.debug("Item is showing its interface, so rescheduling timer")
scheduleTimer(for: 3)
return
}

Task {
do {
try await self.rehideItem()
if let itemManager = self.itemManager {
await itemManager.removeTempShownItemFromCache(with: self.item.info)
}
} catch {
Logger.itemManager.error("Failed to rehide \"\(self.item.logString)\": \(error)")
self.scheduleTimer(for: 3)
}
}
}
}

func invalidateTimer() {
timer?.invalidate()
timer = nil
}

func rehideItem() async throws {
try await itemManager?.slowMove(item: item, to: returnDestination)
}
}

@Published private(set) var itemCache = ItemCache()
Expand All @@ -130,7 +78,9 @@ class MenuBarItemManager: ObservableObject {

private var cachedItemWindowIDs = [CGWindowID]()

private var tempShownItemContexts = [MenuBarItemInfo: TempShownItemContext]()
private var tempShownItemContexts = [TempShownItemContext]()

private var tempShownItemsTimer: Timer?

private var isMouseButtonDown = false

Expand Down Expand Up @@ -1022,6 +972,26 @@ extension MenuBarItemManager {
return nil
}

/// Schedules a timer for the given interval, attempting to rehide the current
/// temporarily shown items when the timer fires.
private func runTempShownItemTimer(for interval: TimeInterval) {
Logger.itemManager.debug("Running rehide timer for temp shown items with interval: \(interval, format: .hybrid)")

tempShownItemsTimer?.invalidate()
tempShownItemsTimer = .scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] timer in
guard let self else {
timer.invalidate()
return
}

Logger.itemManager.debug("Rehide timer fired")

Task {
await self.rehideTempShownItems()
}
}
}

/// Temporarily shows the given item.
///
/// This method moves the given item to the right of the control item for
Expand All @@ -1038,9 +1008,9 @@ extension MenuBarItemManager {
func tempShowItem(_ item: MenuBarItem, clickWhenFinished: Bool, mouseButton: CGMouseButton) {
let rehideInterval: TimeInterval = 20

if let context = tempShownItemContexts[item.info] {
if tempShownItemContexts.contains(where: { $0.item.info == item.info }) {
Logger.itemManager.info("Item \"\(item.logString)\" is already temporarily shown, so extending timer")
context.scheduleTimer(for: rehideInterval)
runTempShownItemTimer(for: rehideInterval)
return
}

Expand Down Expand Up @@ -1098,52 +1068,67 @@ extension MenuBarItemManager {
let context = TempShownItemContext(
item: item,
returnDestination: destination,
shownInterfaceWindow: shownInterfaceWindow,
itemManager: self
shownInterfaceWindow: shownInterfaceWindow
)

tempShownItemContexts[item.info] = context
context.scheduleTimer(for: rehideInterval)
tempShownItemContexts.append(context)
runTempShownItemTimer(for: rehideInterval)
}
}

/// Rehides all temporarily shown items.
///
/// If an item is currently showing its menu, this method waits for the menu
/// to close before hiding the items.
func rehideTempShownItems() async {
for (_, context) in tempShownItemContexts {
do {
try await context.rehideItem()
removeTempShownItemFromCache(with: context.item.info)
} catch {
Logger.itemManager.error("Error rehiding item \"\(context.item.logString)\": \(error)")
continue
}
guard !tempShownItemContexts.isEmpty else {
return
}
}

/// Rehides all temporarily shown items.
///
/// If an item is currently showing its interface, this method waits for the
/// shown interface to close with the given timeout before hiding the items.
func rehideTempShownItems(interfaceCheckTimeout timeout: Duration) async {
let interfaceCheckTask = Task.detached(timeout: timeout) {
while await self.tempShownItemContexts.values.contains(where: { $0.isShowingInterface }) {
let interfaceCheckTask = Task.detached(timeout: .seconds(1)) {
while await self.tempShownItemContexts.contains(where: { $0.isShowingInterface }) {
try Task.checkCancellation()
try await Task.sleep(for: .milliseconds(10))
}
}
try? await interfaceCheckTask.value
await rehideTempShownItems()
do {
try await interfaceCheckTask.value
} catch is TaskTimeoutError {
Logger.itemManager.debug("Menu check task timed out. Switching to timer")
runTempShownItemTimer(for: 3)
return
} catch {
Logger.itemManager.error("ERROR: \(error)")
}

Logger.itemManager.info("Rehiding temp shown items")

var failedContexts = [TempShownItemContext]()

while let context = tempShownItemContexts.popLast() {
do {
try await move(item: context.item, to: context.returnDestination)
} catch {
Logger.itemManager.error("Failed to rehide \"\(context.item.logString)\": \(error)")
failedContexts.append(context)
}
}

if !failedContexts.isEmpty {
tempShownItemContexts = failedContexts
runTempShownItemTimer(for: 3)
}

tempShownItemsTimer?.invalidate()
tempShownItemsTimer = nil
}

/// Removes a temporarily shown item from the cache.
///
/// This has the effect of ensuring that the item will not be returned to
/// its previous location.
func removeTempShownItemFromCache(with info: MenuBarItemInfo) {
guard let context = tempShownItemContexts.removeValue(forKey: info) else {
return
}
context.invalidateTimer()
tempShownItemContexts.removeAll(where: { $0.item.info == info })
}
}

Expand Down
5 changes: 0 additions & 5 deletions Ice/MenuBar/MenuBarSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ final class MenuBarSection: ObservableObject {
/// Shows the status items in the section.
func show() {
guard
let appState,
let menuBarManager,
isHidden
else {
Expand All @@ -146,8 +145,6 @@ final class MenuBarSection: ObservableObject {
if let screenForIceBar {
await iceBarPanel?.show(section: .hidden, on: screenForIceBar)
}
try? await Task.sleep(for: .seconds(0.5))
await appState.itemManager.rehideTempShownItems(interfaceCheckTimeout: .seconds(0.5))
for section in menuBarManager.sections {
section.controlItem.state = .hideItems
}
Expand All @@ -157,8 +154,6 @@ final class MenuBarSection: ObservableObject {
if let screenForIceBar {
await iceBarPanel?.show(section: .alwaysHidden, on: screenForIceBar)
}
try? await Task.sleep(for: .seconds(0.5))
await appState.itemManager.rehideTempShownItems(interfaceCheckTimeout: .seconds(0.5))
for section in menuBarManager.sections {
section.controlItem.state = .hideItems
}
Expand Down

0 comments on commit d7e5914

Please sign in to comment.