49 lines
1.5 KiB
Swift
49 lines
1.5 KiB
Swift
import Foundation
|
|
|
|
/// Monotonic timestamps are supplied by the window; tests never wait on a real clock.
|
|
public struct CommandNumberShortcuts: Sendable {
|
|
private var heldSince: TimeInterval?
|
|
private var cancelledUntilRelease = false
|
|
public private(set) var isVisible = false
|
|
public var isHolding: Bool { heldSince != nil }
|
|
|
|
public init() {}
|
|
|
|
/// Samples an existing hold too, including when results first become eligible.
|
|
public mutating func update(commandOnly: Bool, eligible: Bool, at time: TimeInterval) -> Bool {
|
|
guard commandOnly else { reset(); return false }
|
|
guard eligible && !cancelledUntilRelease else {
|
|
heldSince = nil
|
|
isVisible = false
|
|
return false
|
|
}
|
|
guard heldSince == nil else { return false }
|
|
heldSince = time
|
|
return true
|
|
}
|
|
|
|
public mutating func reveal(at time: TimeInterval) -> Bool {
|
|
guard let heldSince, time - heldSince >= 1 else { return false }
|
|
isVisible = true
|
|
return true
|
|
}
|
|
|
|
public func index(for key: String) -> Int? {
|
|
guard isVisible, key.utf8.count == 1, let digit = key.utf8.first,
|
|
(49...57).contains(digit) else { return nil }
|
|
return Int(digit - 49)
|
|
}
|
|
|
|
/// An action cancels this hold; modifier sampling must not restart it.
|
|
public mutating func cancel() {
|
|
reset()
|
|
cancelledUntilRelease = true
|
|
}
|
|
|
|
public mutating func reset() {
|
|
heldSince = nil
|
|
isVisible = false
|
|
cancelledUntilRelease = false
|
|
}
|
|
}
|