import Foundation
|
|
public enum SessionState: Equatable, Sendable {
|
case idle
|
case browsing
|
case quickSearch(SearchMode)
|
/// Quick Switch internal global-layout pane state; this is not EntryPoint.quickLayout.
|
case globalLayout
|
case settings
|
case modal
|
}
|
|
public enum SearchMode: Equatable, Sendable {
|
case inactive
|
case title
|
case tabs
|
case paths
|
case deep
|
}
|
|
public enum DismissReason: Equatable, Sendable {
|
case escape
|
case focusLost
|
case systemCriticalWindow
|
case timeout
|
case userClosed
|
}
|
|
public struct SpacePolicy: Equatable, Sendable {
|
public let isFullscreenSpace: Bool
|
public let avoidSpaceSwitch: Bool
|
|
public init(isFullscreenSpace: Bool = false, avoidSpaceSwitch: Bool = false) {
|
self.isFullscreenSpace = isFullscreenSpace
|
self.avoidSpaceSwitch = avoidSpaceSwitch
|
}
|
}
|
|
public struct OverlayContext: Equatable, Sendable {
|
public let entryPoint: EntryPoint
|
public let spacePolicy: SpacePolicy
|
public let targetDisplayUUID: String?
|
|
public init(
|
entryPoint: EntryPoint,
|
spacePolicy: SpacePolicy = SpacePolicy(),
|
targetDisplayUUID: String? = nil
|
) {
|
self.entryPoint = entryPoint
|
self.spacePolicy = spacePolicy
|
self.targetDisplayUUID = targetDisplayUUID
|
}
|
}
|
|
@MainActor
|
public protocol OverlaySessionDelegate: AnyObject {
|
func sessionDidChangeState(_ state: SessionState)
|
func sessionShouldDismiss(reason: DismissReason)
|
func sessionDidTimeout()
|
}
|
|
@MainActor
|
public final class OverlaySession {
|
public private(set) var state: SessionState = .idle {
|
didSet {
|
guard oldValue != state else { return }
|
delegate?.sessionDidChangeState(state)
|
}
|
}
|
|
public private(set) var context: OverlayContext?
|
public weak var delegate: OverlaySessionDelegate?
|
|
public init(delegate: OverlaySessionDelegate? = nil) {
|
self.delegate = delegate
|
}
|
|
public func openQuickSwitch(context: OverlayContext = OverlayContext(entryPoint: .quickSwitch)) {
|
self.context = context
|
state = .browsing
|
}
|
|
public func enterQuickSearch(mode: SearchMode = .title) {
|
guard state == .browsing else { return }
|
state = .quickSearch(mode)
|
}
|
|
/// Enters the Quick Switch global-layout pane; it must not launch Quick Layout in Round0/Round1.
|
public func enterGlobalLayout() {
|
guard state == .browsing else { return }
|
state = .globalLayout
|
}
|
|
public func openSettings() {
|
guard state == .browsing else { return }
|
state = .settings
|
}
|
|
public func enterModal() {
|
guard state != .idle else { return }
|
state = .modal
|
}
|
|
public func dismiss(reason: DismissReason) {
|
delegate?.sessionShouldDismiss(reason: reason)
|
state = .idle
|
context = nil
|
}
|
|
public func timeout() {
|
delegate?.sessionDidTimeout()
|
dismiss(reason: .timeout)
|
}
|
}
|