#if canImport(Cocoa) && !canImport(UIKit) import Cocoa /** A `Coordinator` managing and sending events related to actions by the user. Coordinators act as a layer on top of view controllers, and manage preparing data for the view controllers (reaching out to any services), as well as reacting to events generated by the view controllers. In an MVC context, Coordinators essentially act as the ViewController of the Model layer, but specific for a given screen (in the Mobile context). E.G. you wouldn't have a "DatadaseCoordinator", though if you were writing a SQL browser, you might have a "TableCoordinator" or a "RowCoordinator". Coordinators should be initialized with everything they need to start work, and then actually start that work (invoke services, subscribe to events, present alerts if need be) in ``-start()`` */ @MainActor public protocol NSCoordinator: AnyObject { /// The child coordinators of this coordinator. var children: [NSCoordinator] { get } /// Whether this coordinator is active or not. var isActive: Bool { get } /// The singular view controller this Coordinator coordinates. var rootViewController: NSViewController { get } /** Adds the given coordinator as a child of the receiver. - Parameter coordinator: The Coordinator to make a child of the receiving Coordinator. */ func addChild(_ coordinator: NSCoordinator) /** Removes the given coordinator as a child of the receiver. - Parameter coordinator: The Coordinator to remove. */ func removeChild(_ coordinator: NSCoordinator) /** Starts work for this coordinator. This is analogous to `-viewDidLoad()` in `UIViewController`. Instead of putting any logic in `init`, logic should be set up and performed in `start()`. */ func start() /** Stops work for this coordinator. Implementers should prepare for this coordinator to be de-initialized, and cancel any outstanding publishers. - Note: It's possible for `-stop()` to be called, and then ``-start()`` to later be called again. This is completely valid. */ func stop() } extension NSCoordinator { /** Combines the work of adding a child coordinator and calling ``-start()`` on it. - Parameter child: The coordinator to start and add as a child to the receiver. If the coordinator is already a child of the receiver, then this method no-ops. - Note: This method first adds the coordinator as a child, then calls ``-start()`` on it. */ public func pushAndStart(child: NSCoordinator) { guard children.contains(where: { $0 === child }) == false else { return } addChild(child) child.start() } /** Combines the work of removing a child coordinator and calling ``-stop()`` on it. - Parameter child: The coordinator to stop and remove as a child of the receiver. If the "child" is not actually a child of the receiver, then this mthod no-ops. - Note: This method first calls ``-stop()`` on the child coordinator, then removes it as a child of the receiver. */ public func stopAndPop(child: NSCoordinator) { guard children.contains(where: { $0 === child }) else { return } child.stop() removeChild(child) } } /// A concrete implementation of Coordinator, using NSObject as the superclass (so that it, and all subclasses, can easily conform to delegates requiring `NSObjectProtocol`). @MainActor open class BaseNSCoordinator: NSObject, NSCoordinator { /// The list of child coordinators public private(set) var children: [NSCoordinator] = [] /// Whether the coordinator is active or not. public private(set) var isActive: Bool = false /// The root view controller of the coordinator. public let rootViewController: NSViewController /** Initializes the coordinator. - Parameter rootViewController: The `NSViewController` to set as the root view controller. - Note: BaseCoordinator doesn't actually do anything with the `rootViewController` except have a reference to it. */ public init(rootViewController: NSViewController) { self.rootViewController = rootViewController super.init() } /** Adds the given coordinator as a child of the receiver. - Parameter coordinator: The Coordinator to make a child of the receiving Coordinator. */ public func addChild(_ coordinator: NSCoordinator) { children.append(coordinator) } /** Removes the given coordinator as a child of the receiver. - Parameter coordinator: The Coordinator to remove. */ public func removeChild(_ coordinator: NSCoordinator) { children.removeAll(where: { $0 === coordinator }) } /** Starts work for the coordinator. Your `BaseCoordinator` subclasses should implement this method and super-up as one of the first things it does. - Note: This sets ``isActive`` to true, until ``-stop()`` is called. */ open func start() { isActive = true } /** Starts work for the coordinator. Your `BaseCoordinator` subclasses should implement this method and super-up as one of the last things it does. - Note: This sets ``isActive`` to false, until ``-start()`` is called again. */ open func stop() { isActive = false } } #endif