Skip to content

Commit 56ce5e8

Browse files
committed
Wait for modifier keys to release before moving items
As a result of this change, we can remove the command key check in the layout bar. This commit also simplifies other waiters belonging to the menu bar item manager.
1 parent 14d8a60 commit 56ce5e8

File tree

3 files changed

+53
-39
lines changed

3 files changed

+53
-39
lines changed

Ice/MenuBar/MenuBarItems/MenuBarItemManager.swift

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -479,26 +479,33 @@ extension MenuBarItemManager {
479479
// MARK: - Async Waiters
480480

481481
extension MenuBarItemManager {
482+
/// Waits asynchronously for the given operation to complete.
483+
///
484+
/// - Parameters:
485+
/// - timeout: Amount of time to wait before throwing an error.
486+
/// - operation: The operation to perform.
487+
private func waitWithTask(timeout: Duration?, operation: @escaping @Sendable () async throws -> Void) async throws {
488+
let task = if let timeout {
489+
Task(timeout: timeout, operation: operation)
490+
} else {
491+
Task(operation: operation)
492+
}
493+
try await task.value
494+
}
495+
482496
/// Waits asynchronously for all menu bar items to stop moving.
483497
///
484498
/// - Parameter timeout: Amount of time to wait before throwing an error.
485499
func waitForItemsToStopMoving(timeout: Duration? = nil) async throws {
486-
let taskBody: @Sendable () async throws -> Void = {
487-
while await self.isMovingItem {
500+
try await waitWithTask(timeout: timeout) { [weak self] in
501+
guard let self else {
502+
return
503+
}
504+
while await isMovingItem {
488505
try Task.checkCancellation()
489506
try await Task.sleep(for: .milliseconds(10))
490507
}
491508
}
492-
let checkTask = if let timeout {
493-
Task(timeout: timeout) {
494-
try await taskBody()
495-
}
496-
} else {
497-
Task {
498-
try await taskBody()
499-
}
500-
}
501-
try await checkTask.value
502509
}
503510

504511
/// Waits asynchronously for the mouse to stop moving.
@@ -507,10 +514,13 @@ extension MenuBarItemManager {
507514
/// - threshold: A threshold to use to determine whether the mouse has stopped moving.
508515
/// - timeout: Amount of time to wait before throwing an error.
509516
func waitForMouseToStopMoving(threshold: TimeInterval = 0.1, timeout: Duration? = nil) async throws {
510-
let taskBody: @Sendable () async throws -> Void = {
517+
try await waitWithTask(timeout: timeout) { [weak self] in
518+
guard let self else {
519+
return
520+
}
511521
while true {
512522
try Task.checkCancellation()
513-
guard let date = await self.lastMouseMoveStartDate else {
523+
guard let date = await lastMouseMoveStartDate else {
514524
break
515525
}
516526
if Date.now.timeIntervalSince(date) > threshold {
@@ -519,16 +529,34 @@ extension MenuBarItemManager {
519529
try await Task.sleep(for: .milliseconds(10))
520530
}
521531
}
522-
let checkTask = if let timeout {
523-
Task(timeout: timeout) {
524-
try await taskBody()
532+
}
533+
534+
/// Waits asynchronously until no modifier keys are pressed.
535+
///
536+
/// - Parameter timeout: Amount of time to wait before throwing an error.
537+
func waitForNoModifiersPressed(timeout: Duration? = nil) async throws {
538+
try await waitWithTask(timeout: timeout) {
539+
// Return early if no flags are pressed.
540+
if NSEvent.modifierFlags.isEmpty {
541+
return
525542
}
526-
} else {
527-
Task {
528-
try await taskBody()
543+
544+
var cancellable: AnyCancellable?
545+
546+
await withCheckedContinuation { continuation in
547+
cancellable = Publishers.Merge(
548+
UniversalEventMonitor.publisher(for: .flagsChanged),
549+
RunLoopLocalEventMonitor.publisher(for: .flagsChanged, mode: .eventTracking)
550+
)
551+
.removeDuplicates()
552+
.sink { _ in
553+
if NSEvent.modifierFlags.isEmpty {
554+
cancellable?.cancel()
555+
continuation.resume()
556+
}
557+
}
529558
}
530559
}
531-
try await checkTask.value
532560
}
533561
}
534562

@@ -1051,6 +1079,9 @@ extension MenuBarItemManager {
10511079
}
10521080

10531081
do {
1082+
// Order of these waiters matters, as the modifiers could be released
1083+
// while the mouse is still moving.
1084+
try await waitForNoModifiersPressed()
10541085
try await waitForMouseToStopMoving()
10551086
} catch {
10561087
throw EventError(code: .couldNotComplete, item: item)

Ice/UI/LayoutBar/LayoutBarItemView.swift

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,6 @@ final class LayoutBarItemView: NSView {
121121
return alert
122122
}
123123

124-
/// Provides an alert to display when the user is pressing the Command key
125-
/// while moving a menu bar item.
126-
func provideAlertForCommandKeyDown() -> NSAlert {
127-
let alert = NSAlert()
128-
alert.messageText = "Do not hold the Command key while moving a menu bar item."
129-
return alert
130-
}
131-
132124
override func draw(_ dirtyRect: NSRect) {
133125
if !isDraggingPlaceholder {
134126
image?.draw(
@@ -160,12 +152,6 @@ final class LayoutBarItemView: NSView {
160152
override func mouseDragged(with event: NSEvent) {
161153
super.mouseDragged(with: event)
162154

163-
guard !event.modifierFlags.contains(.command) else {
164-
let alert = provideAlertForCommandKeyDown()
165-
alert.runModal()
166-
return
167-
}
168-
169155
guard isEnabled else {
170156
let alert = provideAlertForDisabledItem()
171157
alert.runModal()

Ice/UI/LayoutBar/LayoutBarPaddingView.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,7 @@ final class LayoutBarPaddingView: NSView {
9191
}
9292
}
9393

94-
guard
95-
sender.draggingSourceOperationMask == .move,
96-
let draggingSource = sender.draggingSource as? LayoutBarItemView
97-
else {
94+
guard let draggingSource = sender.draggingSource as? LayoutBarItemView else {
9895
return false
9996
}
10097

0 commit comments

Comments
 (0)