diff --git a/Package.resolved b/Package.resolved index f288005..9972721 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,7 +6,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenGraph", "state" : { "branch" : "main", - "revision" : "ed01e7196afe56bf953f2f14b0b463208dda9c8d" + "revision" : "ccb771c9fd939f1e1eefb46f69139e94ebb0dc82" } }, { diff --git a/Scripts/openswiftui_swiftinterface.sh b/Scripts/openswiftui_swiftinterface.sh index 9632409..a1b10b5 100755 --- a/Scripts/openswiftui_swiftinterface.sh +++ b/Scripts/openswiftui_swiftinterface.sh @@ -11,4 +11,4 @@ cd $OPENSWIFTUI_ROOT export OPENSWIFTUI_SWIFT_TESTING=0 export OPENGRAPH_SWIFT_TESTING=0 -swift build -c release -Xswiftc -emit-module-interface -Xswiftc -enable-library-evolution +swift build -Xswiftc -emit-module-interface -Xswiftc -enable-library-evolution diff --git a/Sources/OpenSwiftUI/App/ScenePhase.swift b/Sources/OpenSwiftUI/App/ScenePhase.swift new file mode 100644 index 0000000..56e5493 --- /dev/null +++ b/Sources/OpenSwiftUI/App/ScenePhase.swift @@ -0,0 +1,24 @@ +// +// ScenePhase.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: WIP +// ID: 130BB08D98602D712FD59CAC6992C14A + +public enum ScenePhase: Comparable, Hashable { + case background + case inactive + case active +} + +private struct ScenePhaseKey: EnvironmentKey { + static let defaultValue: ScenePhase = .background +} + +extension EnvironmentValues { + public var scenePhase: ScenePhase { + get { self[ScenePhaseKey.self] } + set { self[ScenePhaseKey.self] = newValue } + } +} diff --git a/Sources/OpenSwiftUI/Core/Data/EnvironmentKeys/ColorSchemeKey.swift b/Sources/OpenSwiftUI/Core/Data/EnvironmentKeys/ColorSchemeKey.swift deleted file mode 100644 index d0ccd7a..0000000 --- a/Sources/OpenSwiftUI/Core/Data/EnvironmentKeys/ColorSchemeKey.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// ColorSchemeKey.swift -// OpenSwiftUI -// -// Audited for RELEASE_2021 -// Status: WIP -// ID: 387C753F3FFD2899BCB77252214CFCC6 - -//private struct SystemColorSchemeModifier {} - -private struct ColorSchemeKey: EnvironmentKey { - static var defaultValue: ColorScheme = .light -} - -extension EnvironmentValues { - @inline(__always) - public var colorScheme: ColorScheme { - get { self[ColorSchemeKey.self] } - set { self[ColorSchemeKey.self] = newValue } - } -} - -//private struct SystemColorSchemeKey: EnvironmentKey { -// static var defaultValue: Bool { true } -//} -// -//private struct ExplicitPreferredColorSchemeKey: EnvironmentKey { -// static var defaultValue: Bool { true } -//} -// -//private struct ColorSchemeContrastKey: EnvironmentKey { -// static var defaultValue: Bool { true } -//} diff --git a/Sources/OpenSwiftUI/Core/Graph/GraphHost.swift b/Sources/OpenSwiftUI/Core/Graph/GraphHost.swift index 391c2cb..b86252f 100644 --- a/Sources/OpenSwiftUI/Core/Graph/GraphHost.swift +++ b/Sources/OpenSwiftUI/Core/Graph/GraphHost.swift @@ -20,8 +20,8 @@ class GraphHost { private(set) var hostPreferenceValues: OptionalAttribute private(set) var lastHostPreferencesSeed: VersionSeed = .invalid private var pendingTransactions: [AsyncTransaction] = [] - private(set) var inTransaction = false - private(set) var continuations: [() -> Void] = [] + /*private(set)*/ var inTransaction = false + /*private(set)*/ var continuations: [() -> Void] = [] private(set) var mayDeferUpdate = true private(set) var removedState: RemovedState = [] @@ -156,7 +156,8 @@ class GraphHost { } final func updatePreferences() -> Bool { - fatalError("TODO") + // fatalError("TODO") + return false } final func updateRemovedState() { diff --git a/Sources/OpenSwiftUI/Core/Log/Signpost.swift b/Sources/OpenSwiftUI/Core/Log/Signpost.swift index 682f0ca..3b24f5a 100644 --- a/Sources/OpenSwiftUI/Core/Log/Signpost.swift +++ b/Sources/OpenSwiftUI/Core/Log/Signpost.swift @@ -24,6 +24,8 @@ struct Signpost { } } + static let render = Signpost(style: .kdebug(0), stability: .published) + static let renderUpdate = Signpost(style: .kdebug(0), stability: .published) static let viewHost = Signpost(style: .kdebug(0), stability: .published) } diff --git a/Sources/OpenSwiftUI/Core/Render/DisplayLink.swift b/Sources/OpenSwiftUI/Core/Render/DisplayLink.swift new file mode 100644 index 0000000..bc3f09a --- /dev/null +++ b/Sources/OpenSwiftUI/Core/Render/DisplayLink.swift @@ -0,0 +1,68 @@ +// +// DisplayLink.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: WIP +// ID: D912470A6161D66810B373079EE9F26A + +#if canImport(Darwin) && os(iOS) // Disable macOS temporary due to CADisplayLink issue +import QuartzCore +#if os(iOS) +import UIKit +#elseif os(macOS) +import AppKit +#endif + +final class DisplayLink: NSObject { + private weak var host: AnyUIHostingView? + private var link: CADisplayLink? + private var nextUpdate: Time + private var currentUpdate: Time? + private var interval: Double + private var reasons: Set + private var currentThread: ThreadName + private var nextThread: ThreadName + + #if os(iOS) + init(host: AnyUIHostingView, window: UIWindow) { + fatalError("TODO") + } + #elseif os(macOS) + init(host: AnyUIHostingView, window: NSWindow) { + fatalError("TODO") + } + #endif + + var willRender: Bool { + nextUpdate < .infinity + } + + func setNextUpdate(delay: Double, interval: Double, reasons: Set) { + // TODO + } + + func invalidate() { + Update.lock.withLock { + // TODO + } + } + + @inline(__always) + func startAsyncRendering() { + nextThread = .async + } + + @inline(__always) + func cancelAsyncRendering() { + nextThread = .main + } +} + +extension DisplayLink { + enum ThreadName: Hashable { + case main + case async + } +} +#endif diff --git a/Sources/OpenSwiftUI/Core/Update/Update.swift b/Sources/OpenSwiftUI/Core/Update/Update.swift index 002476f..7043593 100644 --- a/Sources/OpenSwiftUI/Core/Update/Update.swift +++ b/Sources/OpenSwiftUI/Core/Update/Update.swift @@ -19,7 +19,7 @@ extension MovableLock { enum Update { static let trackHost: AnyObject = TraceHost() - private static let lock = MovableLock.create() + static let lock = MovableLock.create() private static var depth = 0 private static var actions: [() -> Void] = [] @@ -63,7 +63,7 @@ enum Update { } } - @inlinable + @inline(__always) static func dispatchActions() { // FIXME for action in actions { @@ -71,7 +71,7 @@ enum Update { } } - @inlinable + @inline(__always) static func syncMain(_ body: () -> Void) { // TODO fatalError("TODO") diff --git a/Sources/OpenSwiftUI/Core/Util/Time.swift b/Sources/OpenSwiftUI/Core/Util/Time.swift index 3958d00..8e7579c 100644 --- a/Sources/OpenSwiftUI/Core/Util/Time.swift +++ b/Sources/OpenSwiftUI/Core/Util/Time.swift @@ -5,6 +5,10 @@ // Audited for RELEASE_2021 // Status: Complete +#if canImport(QuartzCore) +import QuartzCore +#endif + struct Time: Comparable, Hashable { var seconds : Double @@ -14,4 +18,76 @@ struct Time: Comparable, Hashable { static let zero = Time(seconds: .zero) static let infinity = Time(seconds: .infinity) + + #if canImport(QuartzCore) + @inline(__always) + static var now: Time { + Time(seconds: CACurrentMediaTime()) + } + #endif + + @inline(__always) + private init(seconds: Double) { + self.seconds = seconds + } + + @inline(__always) + static func seconds(_ value: Double) -> Time { + Time(seconds: value) + } + + @inline(__always) + static func seconds(_ value: Int) -> Time { + Time(seconds: Double(value)) + } + + @inline(__always) + static func microseconds(_ value: Double) -> Time { + Time(seconds: value * 1e-3) + } + + @inline(__always) + static func milliseconds(_ value: Double) -> Time { + Time(seconds: value * 1e-6) + } + + @inline(__always) + static func nanoseconds(_ value: Double) -> Time { + Time(seconds: value * 1e-9) + } + + @inline(__always) + static func += (lhs: inout Time, rhs: Time) { + lhs.seconds = lhs.seconds + rhs.seconds + } + + @inline(__always) + static func + (lhs: Time, rhs: Time) -> Time { + Time(seconds: lhs.seconds + rhs.seconds) + } + + @inline(__always) + static func -= (lhs: inout Time, rhs: Time) { + lhs.seconds = lhs.seconds - rhs.seconds + } + + @inline(__always) + static func - (lhs: Time, rhs: Time) -> Time { + Time(seconds: lhs.seconds - rhs.seconds) + } + + @inline(__always) + mutating func advancing(by seconds: Double) { + self.seconds += seconds + } + + @inline(__always) + func advanced(by seconds: Double) -> Time { + Time(seconds: self.seconds + seconds) + } + + @inline(__always) + func distance(to other: Time) -> Double { + seconds.distance(to: other.seconds) + } } diff --git a/Sources/OpenSwiftUI/Core/View/ViewGraph.swift b/Sources/OpenSwiftUI/Core/View/ViewGraph.swift new file mode 100644 index 0000000..399b215 --- /dev/null +++ b/Sources/OpenSwiftUI/Core/View/ViewGraph.swift @@ -0,0 +1,214 @@ +// +// ViewGraph.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: WIP +// ID: D63C4EB7F2B205694B6515509E76E98B + +internal import OpenGraphShims +import Foundation + +final class ViewGraph: GraphHost { + let rootViewType: Any.Type + let makeRootView: (OGAttribute, _ViewInputs) -> _ViewOutputs + weak var delegate: ViewGraphDelegate? + var centersRootView: Bool = true + let rootView: OGAttribute + @Attribute var rootTransform: ViewTransform + @Attribute var zeroPoint: ViewOrigin + // TODO + @Attribute var proposedSize: ViewSize + // TODO + @Attribute var rootGeometry: ViewGeometry + @Attribute var position: ViewOrigin + @Attribute var dimensions: ViewSize + @Attribute var updateSeed: UInt32 + // TODO + var cachedSizeThatFits: CGSize = .invalidValue + var sizeThatFitsObserver: SizeThatFitsObserver? { + didSet { + guard let _ = sizeThatFitsObserver else { + return + } + guard requestedOutputs.contains(.layout) else { + fatalError("Cannot use sizeThatFits without layout output") + } + } + } + var requestedOutputs: Outputs + var disabledOutputs: Outputs = [] + var mainUpdates: Int = 0 + var needsFocusUpdate: Bool = false + var nextUpdate: (views: NextUpdate, gestures: NextUpdate) = (NextUpdate(time: .infinity), NextUpdate(time: .infinity)) + // TODO + + init(rootViewType: Body.Type, requestedOutputs: Outputs) { + #if canImport(Darwin) + self.rootViewType = rootViewType + self.requestedOutputs = requestedOutputs + + let data = GraphHost.Data() + OGSubgraph.current = data.globalSubgraph + rootView = Attribute(type: Body.self).identifier + _rootTransform = Attribute(RootTransform()) + _zeroPoint = Attribute(value: .zero) + // TODO + _proposedSize = Attribute(value: .zero) + // TODO + _rootGeometry = Attribute(RootGeometry()) // FIXME + _position = _rootGeometry.origin() + _dimensions = _rootGeometry.size() + _updateSeed = Attribute(value: .zero) + // FIXME + makeRootView = { view, inputs in + let rootView = _GraphValue(view.unsafeCast(to: Body.self)) + return Body._makeView(view: rootView, inputs: inputs) + } + super.init(data: data) + OGSubgraph.current = nil + #else + fatalError("TOOD") + #endif + } + + @inline(__always) + func updateOutputs(at time: Time) { + beginNextUpdate(at: time) + updateOutputs() + } + + private func beginNextUpdate(at time: Time) { + setTime(time) + updateSeed &+= 1 + mainUpdates = graph.mainUpdates + } + + private func updateOutputs() { + #if canImport(Darwin) + instantiateIfNeeded() + + let oldCachedSizeThatFits = cachedSizeThatFits + + var preferencesChanged = false + var observedSizeThatFitsChanged = false + var updatedOutputs: Outputs = [] + + var counter1 = 0 + repeat { + counter1 &+= 1 + inTransaction = true + var counter2 = 0 + repeat { + let conts = continuations + continuations = [] + for continuation in conts { + continuation() + } + counter2 &+= 1 + data.globalSubgraph.update(flags: .active) + } while (continuations.count != 0 && counter2 != 8) + inTransaction = false + preferencesChanged = preferencesChanged || updatePreferences() + observedSizeThatFitsChanged = observedSizeThatFitsChanged || updateObservedSizeThatFits() + updatedOutputs.formUnion(updateRequestedOutputs()) + } while (data.globalSubgraph.isDirty(1) && counter1 != 8) + + guard preferencesChanged || observedSizeThatFitsChanged || !updatedOutputs.isEmpty || needsFocusUpdate else { + return + } + if Thread.isMainThread { + if preferencesChanged { + delegate?.preferencesDidChange() + } + if observedSizeThatFitsChanged { + sizeThatFitsObserver?.callback(oldCachedSizeThatFits, self.cachedSizeThatFits) + } + if !requestedOutputs.isEmpty { + delegate?.outputsDidChange(outputs: updatedOutputs) + } + if needsFocusUpdate { + needsFocusUpdate = false + delegate?.focusDidChange() + } + } else { + fatalError("TODO") + } + mainUpdates &-= 1 + #endif + } + + private func updateObservedSizeThatFits() -> Bool { + // TODO + return false + } + + private func updateRequestedOutputs() -> Outputs { + // TODO + return [] + } + + // MARK: - Override Methods + + override var graphDelegate: GraphDelegate? { delegate } + override var parentHost: GraphHost? { + // TODO: _preferenceBridge + nil + } + + override func instantiateOutputs() { + // TODO + } + + override func uninstantiateOutputs() { + // TODO + } + + override func timeDidChange() { + // TODO + } + + override func isHiddenForReuseDidChange() { + // TODO + } +} + +extension ViewGraph { + struct NextUpdate { + var time: Time + var _interval: Double + var reasons: Set + + @inline(__always) + init(time: Time) { + self.time = time + _interval = .infinity + reasons = [] + } + } +} + +extension ViewGraph { + struct Outputs: OptionSet { + let rawValue: UInt8 + + static var layout: Outputs { .init(rawValue: 1 << 4) } + } +} + +private struct RootTransform: Rule { + var value: ViewTransform { + let graph = GraphHost.currentHost as! ViewGraph + guard let delegate = graph.delegate else { + return ViewTransform() + } + return delegate.rootTransform() + } +} + +struct RootGeometry: Rule { + var value: ViewGeometry { + // FIXME + .zero + } +} diff --git a/Sources/OpenSwiftUI/Core/View/ViewGraphDelegate.swift b/Sources/OpenSwiftUI/Core/View/ViewGraphDelegate.swift new file mode 100644 index 0000000..9f08b28 --- /dev/null +++ b/Sources/OpenSwiftUI/Core/View/ViewGraphDelegate.swift @@ -0,0 +1,14 @@ +// +// ViewGraphDelegate.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: Complete + +protocol ViewGraphDelegate: GraphDelegate { + func modifyViewInputs(_ inputs: inout _ViewInputs) + func updateViewGraph(body: (ViewGraph) -> Value) -> Value + func outputsDidChange(outputs: ViewGraph.Outputs) -> () + func focusDidChange() + func rootTransform() -> ViewTransform +} diff --git a/Sources/OpenSwiftUI/Core/View/ViewRendererHost.swift b/Sources/OpenSwiftUI/Core/View/ViewRendererHost.swift index 82d4dd3..8a5dfd0 100644 --- a/Sources/OpenSwiftUI/Core/View/ViewRendererHost.swift +++ b/Sources/OpenSwiftUI/Core/View/ViewRendererHost.swift @@ -1,11 +1,63 @@ -protocol ViewRendererHost {} +// +// ViewRendererHost.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: WIP + +internal import OpenGraphShims + +protocol ViewRendererHost: ViewGraphDelegate { + var viewGraph: ViewGraph { get } + var currentTimestamp: Time { get set } + var propertiesNeedingUpdate: ViewRendererHostProperties { get set } + var isRendering: Bool { get set } + func requestUpdate(after: Double) +} extension ViewRendererHost { + func invalidateProperties(_ properties: ViewRendererHostProperties, mayDeferUpdate: Bool) { + Update.lock.withLock { + guard !propertiesNeedingUpdate.contains(properties) else { + return + } + propertiesNeedingUpdate.insert(properties) + viewGraph.setNeedsUpdate(mayDeferUpdate: mayDeferUpdate) + requestUpdate(after: .zero) + } + } + func startProfiling() { - fatalError("TODO") + OGGraph.startProfiling(viewGraph.graph) } func stopProfiling() { - fatalError("TODO") + OGGraph.stopProfiling(viewGraph.graph) } + + func render(interval: Double, updateDisplayList: Bool = true) { + Update.begin() + defer { Update.end() } + guard !isRendering else { + return + } + let update = { [self] in + currentTimestamp.advancing(by: interval) + let time = currentTimestamp + viewGraph.flushTransactions() + // Signpost.renderUpdate + // TODO + viewGraph.updateOutputs(at: time) + } + if Signpost.render.isEnabled { + // TODO: Signpost related + update() + } else { + update() + } + } +} + +struct ViewRendererHostProperties: OptionSet { + let rawValue: UInt16 } diff --git a/Sources/OpenSwiftUI/Core/View/ViewTransform.swift b/Sources/OpenSwiftUI/Core/View/ViewTransform.swift deleted file mode 100644 index a830e06..0000000 --- a/Sources/OpenSwiftUI/Core/View/ViewTransform.swift +++ /dev/null @@ -1,30 +0,0 @@ -// ID: CE19A3CEA6B9730579C42CE4C3071E74 - -import Foundation - -struct ViewTransform { - private var chunks: ContiguousArray - var positionAdjustment: CGSize -} - -extension ViewTransform { - private class Chunk { - var tags: [Tag] - var values: [CGFloat] - var spaces: [AnyHashable] - init() { - fatalError() - } - - enum Tag { - case translation - case affine - case affine_inverse - case projection - case projection_inverse - case space - case sized_space - case scroll_layout - } - } -} diff --git a/Sources/OpenSwiftUI/Data/Model/DynamicProperty/DynamicPropertyCache.swift b/Sources/OpenSwiftUI/Data/Model/DynamicProperty/DynamicPropertyCache.swift index 97a0767..67dab5e 100644 --- a/Sources/OpenSwiftUI/Data/Model/DynamicProperty/DynamicPropertyCache.swift +++ b/Sources/OpenSwiftUI/Data/Model/DynamicProperty/DynamicPropertyCache.swift @@ -55,7 +55,7 @@ struct DynamicPropertyCache { fields = Fields(layout: .product([])) } if fields.behaviors.contains(.init(rawValue: 3)) { - Log.runtimeIssues("%s is marked async, but contains properties that require the main thread.", [_typeName(type)]) + Log.runtimeIssues("%s is marked async, but contains properties that require the main thread.", ["\(type)"]) } cache.wrappedValue[ObjectIdentifier(type)] = fields return fields diff --git a/Sources/OpenSwiftUI/Integration/UIKit/AnyUIHostingView.swift b/Sources/OpenSwiftUI/Integration/UIKit/AnyUIHostingView.swift new file mode 100644 index 0000000..6f1c0b7 --- /dev/null +++ b/Sources/OpenSwiftUI/Integration/UIKit/AnyUIHostingView.swift @@ -0,0 +1,12 @@ +// +// AnyUIHostingView.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: Complete + +#if os(iOS) +protocol AnyUIHostingView: AnyObject { + func displayLinkTimer(timestamp: Time, isAsyncThread: Bool) +} +#endif diff --git a/Sources/OpenSwiftUI/Integration/UIKit/_UIGraphicsView.swift b/Sources/OpenSwiftUI/Integration/UIKit/UIGraphicsView.swift similarity index 85% rename from Sources/OpenSwiftUI/Integration/UIKit/_UIGraphicsView.swift rename to Sources/OpenSwiftUI/Integration/UIKit/UIGraphicsView.swift index 0790c64..7d5e51b 100644 --- a/Sources/OpenSwiftUI/Integration/UIKit/_UIGraphicsView.swift +++ b/Sources/OpenSwiftUI/Integration/UIKit/UIGraphicsView.swift @@ -1,5 +1,5 @@ // -// _UIGraphicsView.swift +// UIGraphicsView.swift // OpenSwiftUI // // Audited for RELEASE_2021 @@ -9,7 +9,7 @@ internal import COpenSwiftUI import UIKit -class _UIGraphicsView: UIView { +class _UIGraphicsView: UIView { override func _shouldAnimateProperty(withKey key: String) -> Bool { if layer.hasBeenCommitted() { super._shouldAnimateProperty(withKey: key) diff --git a/Sources/OpenSwiftUI/Integration/UIKit/UIHostingController.swift b/Sources/OpenSwiftUI/Integration/UIKit/UIHostingController.swift index e8d2a5a..5865b0b 100644 --- a/Sources/OpenSwiftUI/Integration/UIKit/UIHostingController.swift +++ b/Sources/OpenSwiftUI/Integration/UIKit/UIHostingController.swift @@ -5,8 +5,65 @@ import UIKit @available(watchOS, unavailable) @MainActor(unsafe) open class UIHostingController : UIViewController where Content : View { + var host: _UIHostingView + override open dynamic var keyCommands: [UIKeyCommand]? { fatalError("Unimplemented") } + + public init(rootView: Content) { + // TODO + host = _UIHostingView(rootView: rootView) + super.init(nibName: nil, bundle: nil) + _commonInit() + } + + public init?(coder: NSCoder, rootView: Content) { + // TODO + host = _UIHostingView(rootView: rootView) + super.init(coder: coder) + _commonInit() + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) must be implemented in a subclass and call super.init(coder:, rootView:)") + } + + func _commonInit() { + host.viewController = self + // toolbar + // toolbar.addPreferences(to: ViewGraph) + // ... + // IsAppleInternalBuild + } + + open override func loadView() { + view = host + } + + public var rootView: Content { + get { host.rootView } + _modify { yield &host.rootView } + } +} + +@available(macOS, unavailable) +extension UIHostingController: _UIHostingViewable where Content == AnyView { } + +@available(macOS, unavailable) +public func _makeUIHostingController(_ view: AnyView) -> NSObject & _UIHostingViewable { + UIHostingController(rootView: view) +} + +@available(macOS, unavailable) +public func _makeUIHostingController(_ view: AnyView, tracksContentSize: Bool) -> NSObject & _UIHostingViewable { + let hostingController = UIHostingController(rootView: view) + if tracksContentSize { + // TODO: hostingController.host + // SizeThatFitsObserver + } + return hostingController +} + #endif diff --git a/Sources/OpenSwiftUI/Integration/UIKit/UIHostingView.swift b/Sources/OpenSwiftUI/Integration/UIKit/UIHostingView.swift new file mode 100644 index 0000000..28cd9a2 --- /dev/null +++ b/Sources/OpenSwiftUI/Integration/UIKit/UIHostingView.swift @@ -0,0 +1,180 @@ +// +// UIHostingView.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: WIP +// ID: FAF0B683EB49BE9BABC9009857940A1E + +#if os(iOS) +import UIKit + +@available(macOS, unavailable) +@available(watchOS, unavailable) +@MainActor(unsafe) +open class _UIHostingView: UIView where Content: View { + private var _rootView: Content + var viewGraph: ViewGraph + var isRendering: Bool = false + var inheritedEnvironment: EnvironmentValues? + var environmentOverride: EnvironmentValues? + weak var viewController: UIHostingController? + var displayLink: DisplayLink? + var lastRenderTime: Time = .zero + var canAdvanceTimeAutomatically = true + var allowLayoutWhenNotVisible = false + var isEnteringForeground = false + + public init(rootView: Content) { + // TODO + _rootView = rootView + viewGraph = ViewGraph(rootViewType: Content.self, requestedOutputs: []) // Fixme + // TODO + // FIXME + super.init(frame: .zero) + } + + @available(*, unavailable) + public required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var rootView: Content { + get { _rootView } + set { + _rootView = newValue + invalidateProperties(.init(rawValue: 1), mayDeferUpdate: true) + } + } + + + @available(macOS, unavailable) + @available(watchOS, unavailable) + final public func _viewDebugData() -> [_ViewDebug.Data] { + // TODO + [] + } + + open override func layoutSubviews() { + super.layoutSubviews() + guard updatesWillBeVisible || allowLayoutWhenNotVisible else { + return + } + guard canAdvanceTimeAutomatically else { + return + } + Update.lock.withLock { + cancelAsyncRendering() + let interval: Double + if let displayLink, displayLink.willRender { + interval = .zero + } else { + interval = renderInterval(timestamp: .now) / UIAnimationDragCoefficient() + } + render(interval: interval) + allowLayoutWhenNotVisible = false + } + } + + var updatesWillBeVisible: Bool { + guard let window, + let scene = window.windowScene else { + return false + } + let environment = inheritedEnvironment ?? traitCollection.baseEnvironment + switch scene.activationState { + case .unattached, .foregroundActive, .foregroundInactive: + return true + case .background: + fallthrough + @unknown default: + if isEnteringForeground { + return true + } + return environment.scenePhase != .background + } + } + + func cancelAsyncRendering() { + Update.lock.withLock { + displayLink?.cancelAsyncRendering() + } + } + + private func renderInterval(timestamp: Time) -> Double { + if lastRenderTime == .zero || lastRenderTime > timestamp { + lastRenderTime = timestamp - .microseconds(1) + } + let interval = timestamp - lastRenderTime + lastRenderTime = timestamp + return interval.seconds + } +} + +extension _UIHostingView: ViewRendererHost { + var currentTimestamp: Time { + get { + fatalError("TODO") + } + set { + fatalError("TODO") + } + } + + var propertiesNeedingUpdate: ViewRendererHostProperties { + get { + fatalError("TODO") + } + set { + fatalError("TODO") + } + } + + func requestUpdate(after: Double) { + // TODO + } + + func modifyViewInputs(_ inputs: inout _ViewInputs) { + // TODO + } + + func updateViewGraph(body: (ViewGraph) -> Value) -> Value { + fatalError("TODO") + } + + func outputsDidChange(outputs: ViewGraph.Outputs) { + // TODO + } + + func focusDidChange() { + // TODO + } + + func rootTransform() -> ViewTransform { + fatalError("TODO") + } + + func updateGraph(body: (GraphHost) -> V) -> V { + fatalError("TODO") + } + + func graphDidChange() { + // TODO + } + + func preferencesDidChange() { + // TODO + } +} + +extension UITraitCollection { + var baseEnvironment: EnvironmentValues { + // TODO + EnvironmentValues() + } +} + +@_silgen_name("UIAnimationDragCoefficient") +private func UIAnimationDragCoefficient() -> Double + +#endif diff --git a/Sources/OpenSwiftUI/Integration/UIKit/UIHostingViewable.swift b/Sources/OpenSwiftUI/Integration/UIKit/UIHostingViewable.swift new file mode 100644 index 0000000..889b2a0 --- /dev/null +++ b/Sources/OpenSwiftUI/Integration/UIKit/UIHostingViewable.swift @@ -0,0 +1,23 @@ +// +// UIHostingViewable.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: WIP + +#if os(iOS) + +import Foundation + +@available(macOS, unavailable) +public protocol _UIHostingViewable: AnyObject { +// var rootView: AnyView { get set } +// func _render(seconds: Double) +// func _forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void) +// func sizeThatFits(in size: CGSize) -> CGSize +// var _disableSafeArea: Bool { get set } +//// var _rendererConfiguration: _RendererConfiguration { get set } +// var _rendererObject: AnyObject? { get } +} + +#endif diff --git a/Sources/OpenSwiftUI/Integration/UIKit/_UIHostingView.swift b/Sources/OpenSwiftUI/Integration/UIKit/_UIHostingView.swift deleted file mode 100644 index e6a200c..0000000 --- a/Sources/OpenSwiftUI/Integration/UIKit/_UIHostingView.swift +++ /dev/null @@ -1,15 +0,0 @@ -#if os(iOS) -import UIKit - -@available(macOS, unavailable) -@available(watchOS, unavailable) -@MainActor(unsafe) -open class _UIHostingView: UIView where Content: View { - @available(macOS, unavailable) - @available(watchOS, unavailable) - final public func _viewDebugData() -> [_ViewDebug.Data] { - // TODO - [] - } -} -#endif diff --git a/Sources/OpenSwiftUI/Integration/UIKit/_UIHostingViewable.swift b/Sources/OpenSwiftUI/Integration/UIKit/_UIHostingViewable.swift deleted file mode 100644 index 0f8b4da..0000000 --- a/Sources/OpenSwiftUI/Integration/UIKit/_UIHostingViewable.swift +++ /dev/null @@ -1,29 +0,0 @@ -#if os(iOS) -import CoreGraphics - -@available(macOS, unavailable) -public protocol _UIHostingViewable: AnyObject { - var rootView: AnyView { get set } - func _render(seconds: Swift.Double) - func _forEachIdentifiedView(body: (_IdentifiedViewProxy) -> Void) - func sizeThatFits(in size: CGSize) -> CGSize - var _disableSafeArea: Bool { get set } -// var _rendererConfiguration: _RendererConfiguration { get set } - var _rendererObject: AnyObject? { get } -} - -//@available(macOS, unavailable) -//extension UIHostingController : _UIHostingViewable where Content == AnyView { -//} - -@available(macOS, unavailable) -public func _makeUIHostingController(_ view: AnyView) -> NSObject & _UIHostingViewable { - fatalError("TODO") - -} - -@available(macOS, unavailable) -public func _makeUIHostingController(_ view: AnyView, tracksContentSize: Swift.Bool) -> NSObject & _UIHostingViewable { - fatalError("TODO") -} -#endif diff --git a/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/ViewDimensions.swift b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/ViewDimensions.swift index 20f89d5..1a06505 100644 --- a/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/ViewDimensions.swift +++ b/Sources/OpenSwiftUI/Layout/LayoutAdjustments/Alignment/ViewDimensions.swift @@ -181,6 +181,9 @@ public struct ViewDimensions { subscript(explicit key: AlignmentKey) -> CGFloat? { guideComputer.delegate.explicitAlignment(key, at: size) } + + @inline(__always) + static var zero: ViewDimensions { ViewDimensions(guideComputer: .defaultValue, size: .zero) } } extension ViewDimensions: Equatable {} diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ProjectionTransform.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ProjectionTransform.swift new file mode 100644 index 0000000..2f0bef5 --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ProjectionTransform.swift @@ -0,0 +1,101 @@ +// +// ProjectionTransform.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: WIP + +import Foundation +#if canImport(QuartzCore) +import QuartzCore +#endif + +@frozen +public struct ProjectionTransform { + public var m11: CGFloat = 1.0, m12: CGFloat = 0.0, m13: CGFloat = 0.0 + public var m21: CGFloat = 0.0, m22: CGFloat = 1.0, m23: CGFloat = 0.0 + public var m31: CGFloat = 0.0, m32: CGFloat = 0.0, m33: CGFloat = 1.0 + + @inlinable + public init() {} + + #if canImport(QuartzCore) + @inlinable + public init(_ m: CGAffineTransform) { + m11 = m.a + m12 = m.b + m21 = m.c + m22 = m.d + m31 = m.tx + m32 = m.ty + } + + @inlinable public init(_ m: CATransform3D) { + m11 = m.m11 + m12 = m.m12 + m13 = m.m14 + m21 = m.m21 + m22 = m.m22 + m23 = m.m24 + m31 = m.m41 + m32 = m.m42 + m33 = m.m44 + } + #endif + + @inlinable + public var isIdentity: Bool { + self == ProjectionTransform() + } + + @inlinable + public var isAffine: Bool { + m13 == 0 && m23 == 0 && m33 == 1 + } + + public mutating func invert() -> Bool { + // TODO: + false + } + + public func inverted() -> ProjectionTransform { + // TODO: + self + } +} + +extension ProjectionTransform: Equatable {} + +extension ProjectionTransform { + @inline(__always) + @inlinable + func dot(_ a: (CGFloat, CGFloat, CGFloat), _ b: (CGFloat, CGFloat, CGFloat)) -> CGFloat { + a.0 * b.0 + a.1 * b.1 + a.2 * b.2 + } + + @inlinable + public func concatenating(_ rhs: ProjectionTransform) -> ProjectionTransform { + var m = ProjectionTransform() + m.m11 = dot((m11, m12, m13), (rhs.m11, rhs.m21, rhs.m31)) + m.m12 = dot((m11, m12, m13), (rhs.m12, rhs.m22, rhs.m32)) + m.m13 = dot((m11, m12, m13), (rhs.m13, rhs.m23, rhs.m33)) + m.m21 = dot((m21, m22, m23), (rhs.m11, rhs.m21, rhs.m31)) + m.m22 = dot((m21, m22, m23), (rhs.m12, rhs.m22, rhs.m32)) + m.m23 = dot((m21, m22, m23), (rhs.m13, rhs.m23, rhs.m33)) + m.m31 = dot((m31, m32, m33), (rhs.m11, rhs.m21, rhs.m31)) + m.m32 = dot((m31, m32, m33), (rhs.m12, rhs.m22, rhs.m32)) + m.m33 = dot((m31, m32, m33), (rhs.m13, rhs.m23, rhs.m33)) + return m + } +} + +extension CGPoint { + public func applying(_: ProjectionTransform) -> CGPoint { + // TODO: + self + } +} + +struct CodableProjectionTransform { + var base: ProjectionTransform +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/_ProposedSize.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ProposedSize.swift similarity index 64% rename from Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/_ProposedSize.swift rename to Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ProposedSize.swift index 2c22934..b883f9c 100644 --- a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/_ProposedSize.swift +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ProposedSize.swift @@ -1,5 +1,5 @@ // -// _ProposedSize.swift +// ProposedSize.swift // OpenSwiftUI // // Audited for RELEASE_2021 @@ -10,4 +10,6 @@ import Foundation struct _ProposedSize: Hashable { var width: CGFloat? var height: CGFloat? + + static let unspecified = _ProposedSize(width: nil, height: nil) } diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/SizeThatFitsObserver.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/SizeThatFitsObserver.swift new file mode 100644 index 0000000..805a71e --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/SizeThatFitsObserver.swift @@ -0,0 +1,13 @@ +// +// SizeThatFitsObserver.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: Complete + +import Foundation + +struct SizeThatFitsObserver { + var proposal: _ProposedSize + var callback: (CGSize, CGSize) -> Void +} diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewGeometry.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewGeometry.swift index dc108c7..a98858a 100644 --- a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewGeometry.swift +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewGeometry.swift @@ -6,8 +6,22 @@ // Status: Complete import Foundation +internal import OpenGraphShims struct ViewGeometry: Equatable { var origin: ViewOrigin var dimensions: ViewDimensions + + @inline(__always) + static var zero: ViewGeometry { ViewGeometry(origin: .zero, dimensions: .zero) } +} + +extension Attribute where Value == ViewGeometry { + func origin() -> Attribute { + self[keyPath: \.origin] + } + + func size() -> Attribute { + self[keyPath: \.dimensions.size] + } } diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewOrigin.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewOrigin.swift index 94c4232..5a3e118 100644 --- a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewOrigin.swift +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewOrigin.swift @@ -9,6 +9,9 @@ import Foundation struct ViewOrigin: Equatable { var value: CGPoint + + @inline(__always) + static var zero: ViewOrigin { ViewOrigin(value: .zero) } } extension ViewOrigin: Animatable { diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewSize.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewSize.swift index 9b3e925..b529023 100644 --- a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewSize.swift +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewSize.swift @@ -10,4 +10,11 @@ import Foundation struct ViewSize: Equatable { var value: CGSize var _proposal: CGSize + + @inline(__always) + static var zero: ViewSize { ViewSize(value: .zero, _proposal: .zero) } +} + +extension CGSize { + static let invalidValue: CGSize = CGSize(width: -.infinity, height: -.infinity) } diff --git a/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewTransform.swift b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewTransform.swift new file mode 100644 index 0000000..790389d --- /dev/null +++ b/Sources/OpenSwiftUI/Layout/LayoutFundamentals/internal/ViewTransform.swift @@ -0,0 +1,58 @@ +// ID: CE19A3CEA6B9730579C42CE4C3071E74 + +import Foundation + +struct ViewTransform { + private var chunks: ContiguousArray + var positionAdjustment: CGSize + + init() { + self.chunks = [] + self.positionAdjustment = .zero + } +} + +extension ViewTransform { + private class Chunk { + var tags: [Tag] = [] + var values: [CGFloat] = [] + var spaces: [AnyHashable] = [] + + enum Tag: UInt8 { + case translation + case affine + case affine_inverse + case projection + case projection_inverse + case space + case sized_space + case scroll_layout + } + + func appendTranslation(_ translation: CGSize) { + tags.append(.translation) + values.append(translation.width) + values.append(translation.height) + + } + } +} + +extension ViewTransform { + enum Item/*: Codable*/ { + case translation(CGSize) + #if canImport(Darwin) + case affineTransform(CGAffineTransform, inverse: Bool) + #endif + case projectionTransform(ProjectionTransform, inverse: Bool) + case coordinateSpace(name: AnyHashable) + case sizedSpace(name: AnyHashable, size: CGSize) + // case scrollLayout(_ScrollLayout) + + enum CodingKeys: CodingKey { + case translation + case affineTransform + case projection + } + } +} diff --git a/Sources/OpenSwiftUI/View/Graphic/Color/ColorSchema.swift b/Sources/OpenSwiftUI/View/Graphic/Color/ColorSchema.swift index 390c6b5..aee5e6b 100644 --- a/Sources/OpenSwiftUI/View/Graphic/Color/ColorSchema.swift +++ b/Sources/OpenSwiftUI/View/Graphic/Color/ColorSchema.swift @@ -4,8 +4,34 @@ // // Audited for RELEASE_2021 // Status: WIP +// ID: 387C753F3FFD2899BCB77252214CFCC6 public enum ColorScheme: Hashable, CaseIterable { case light case dark } + +//private struct SystemColorSchemeModifier {} + +private struct ColorSchemeKey: EnvironmentKey { + static let defaultValue: ColorScheme = .light +} + +extension EnvironmentValues { + public var colorScheme: ColorScheme { + get { self[ColorSchemeKey.self] } + set { self[ColorSchemeKey.self] = newValue } + } +} + +//private struct SystemColorSchemeKey: EnvironmentKey { +// static var defaultValue: Bool { true } +//} +// +//private struct ExplicitPreferredColorSchemeKey: EnvironmentKey { +// static var defaultValue: Bool { true } +//} +// +//private struct ColorSchemeContrastKey: EnvironmentKey { +// static var defaultValue: Bool { true } +//} diff --git a/Sources/OpenSwiftUI/Core/View/TODO/AnyView.swift b/Sources/OpenSwiftUI/View/View/AnyView.swift similarity index 100% rename from Sources/OpenSwiftUI/Core/View/TODO/AnyView.swift rename to Sources/OpenSwiftUI/View/View/AnyView.swift diff --git a/Sources/OpenSwiftUI/Core/View/TODO/EmptyView.swift b/Sources/OpenSwiftUI/View/View/EmptyView.swift similarity index 100% rename from Sources/OpenSwiftUI/Core/View/TODO/EmptyView.swift rename to Sources/OpenSwiftUI/View/View/EmptyView.swift diff --git a/Sources/OpenSwiftUI/Core/View/TODO/TupleView.swift b/Sources/OpenSwiftUI/View/View/TupleView.swift similarity index 100% rename from Sources/OpenSwiftUI/Core/View/TODO/TupleView.swift rename to Sources/OpenSwiftUI/View/View/TupleView.swift diff --git a/Sources/OpenSwiftUI/Core/View/TODO/UnaryView.swift b/Sources/OpenSwiftUI/View/View/UnaryView.swift similarity index 100% rename from Sources/OpenSwiftUI/Core/View/TODO/UnaryView.swift rename to Sources/OpenSwiftUI/View/View/UnaryView.swift diff --git a/Sources/OpenSwiftUI/Core/View/TODO/_VariadicView.swift b/Sources/OpenSwiftUI/View/View/_VariadicView.swift similarity index 100% rename from Sources/OpenSwiftUI/Core/View/TODO/_VariadicView.swift rename to Sources/OpenSwiftUI/View/View/_VariadicView.swift diff --git a/Tests/OpenSwiftUITests/Layout/LayoutFundamentals/ProposedSizeTests.swift b/Tests/OpenSwiftUITests/Layout/LayoutFundamentals/ProposedSizeTests.swift new file mode 100644 index 0000000..5867e14 --- /dev/null +++ b/Tests/OpenSwiftUITests/Layout/LayoutFundamentals/ProposedSizeTests.swift @@ -0,0 +1,28 @@ +// +// ProposedSizeTests.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: Complete + +import OpenGraphShims +@testable import OpenSwiftUI +import Testing + +@Suite +struct ProposedSizeTests { + @Test + func unspecified() { + let size = _ProposedSize.unspecified + #expect(size.width == nil) + #expect(size.height == nil) + } + + @Test + func hashable() { + let size1 = _ProposedSize(width: 20, height: 30) + let size2 = _ProposedSize(width: 30, height: 20) + #expect(size1 != size2) + #expect(size1.hashValue != size2.hashValue) + } +}