diff --git a/Example/Example.xcodeproj/xcshareddata/xcschemes/OSUI_Example.xcscheme b/Example/Example.xcodeproj/xcshareddata/xcschemes/OSUI_Example.xcscheme index f9ebc5585..b1b0b8e82 100644 --- a/Example/Example.xcodeproj/xcshareddata/xcschemes/OSUI_Example.xcscheme +++ b/Example/Example.xcodeproj/xcshareddata/xcschemes/OSUI_Example.xcscheme @@ -69,6 +69,11 @@ + + + + + + AnySequence { AnySequence(entries(within: range, mode: mode, limit: limit)) } +} + +extension TimelineSchedule { + package func nextEntry( + after date: Date, + mode: TimelineScheduleMode, + limit: UInt? + ) -> Date { + entries(from: date, mode: mode) + .lazy + .abort(after: limit) + .first { $0 > date } ?? .distantFuture + } private func entries( within range: Range, diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift index 2f9dda039..4647ad690 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift @@ -107,6 +107,24 @@ package struct DisplayList: Equatable { package var isEmpty: Bool { items.isEmpty } + + // TODO + + package func nextUpdate(after time: Time) -> Time { + guard !features.contains(.animations) else { + return time + } + var nextUpdate = Time.infinity + if features.contains(.dynamicContent) { + for item in items { + nextUpdate = min(nextUpdate, item.nextUpdate(after: time)) + if nextUpdate == time { + break + } + } + } + return nextUpdate + } } @available(*, unavailable) @@ -177,6 +195,38 @@ extension DisplayList { indirect case drawing(any ORBDisplayListContents, CGPoint, RasterizationOptions) indirect case view(any _DisplayList_ViewFactory) case placeholder(id: Identity) + + @inline(__always) + var caseName: String { + switch self { + case .backdrop: + return "backdrop" + case .color: + return "color" + case .chameleonColor: + return "chameleonColor" + case .image: + return "image" + case .shape: + return "shape" + case .shadow: + return "shadow" + case .platformView: + return "platformView" + case .platformLayer: + return "platformLayer" + case .text: + return "text" + case .flattened: + return "flattened" + case .drawing: + return "drawing" + case .view: + return "view" + case .placeholder: + return "placeholder" + } + } } package init(_ value: Content.Value, seed: Seed) { @@ -618,6 +668,8 @@ extension DisplayList.Item { extension DisplayList { // FIXME package class InterpolatorGroup { + var maxDuration: Double = .zero + private struct Contents { var list: DisplayList var origin: CGPoint @@ -664,6 +716,10 @@ extension DisplayList { [] } + func nextUpdate(after _: Time) -> Time { + .infinity + } + func rewriteDisplayList( _ list: inout DisplayList, time: Attribute, @@ -675,6 +731,104 @@ extension DisplayList { return false } } + + package class UnaryInterpolatorGroup: InterpolatorGroup { + var layer: InterpolatorLayer = .init() + + var contentsScale: Float = .zero + + var rasterizationOptions: RasterizationOptions = .init() + + override func nextUpdate(after time: Time) -> Time { + layer.nextUpdate(after: time) + } + } + + struct InterpolatorLayer { + struct Contents { + var list: DisplayList = .init() + var origin: CGPoint = .zero + var rbList: (any ORBDisplayListContents)? + var nextTime: Time = .infinity + var numericValue: Float? + } + + struct Removed { + var contents: Contents = .init() + var interpolator: ORBDisplayListInterpolator? + var transition: ORBTransition? + var animation: ORBAnimation = .init() + var listener: AnimationListener? + var begin: Time = .zero + var duration: Double = .zero + var phase: Phase = .pending + } + + enum Phase { + case pending + case first + case second + case running + } + + var contents: Contents = .init() + var removed: [Removed] = [] + var time: Time = .zero + var renderer: DisplayList.GraphicsRenderer? + var contentSeed: DisplayList.Seed = .init() + var supportsVFD: Bool = false + var needsUpdate: Bool = false + + @inline(__always) + func nextUpdate(after _: Time) -> Time { + removed.isEmpty ? contents.nextTime : time + } + } +} + +extension DisplayList.Item { + package func nextUpdate(after time: Time) -> Time { + var nextUpdate = Time.infinity + switch value { + case let .content(content): + switch content.value { + case let .text(text, _): + nextUpdate = min(nextUpdate, text.text.nextUpdate(after: time, equivalentDate: .now, reduceFrequency: false) + ) + case let .flattened(list, _, _): + nextUpdate = min(nextUpdate, list.nextUpdate(after: time)) + default: + break + } + case let .effect(effect, list): + nextUpdate = min(nextUpdate, list.nextUpdate(after: time)) + switch effect { + case let .mask(mask, _): + nextUpdate = min(nextUpdate, mask.nextUpdate(after: time)) + case .animation: + nextUpdate = time + case let .interpolatorLayer(group, _): + nextUpdate = min(nextUpdate, group.nextUpdate(after: time)) + default: + break + } + case let .states(states): + for (_, list) in states { + let nestedUpdate: Time + if list.features.contains(.animations) { + nestedUpdate = time + } else if list.features.contains(.dynamicContent) { + nestedUpdate = list.nextUpdate(after: time) + } else { + nestedUpdate = .infinity + } + nextUpdate = min(nextUpdate, nestedUpdate) + } + case .empty: + break + } + return nextUpdate + } } package struct AccessibilityNodeAttachment {} diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListAsyncLayer.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListAsyncLayer.swift index 8f89b0653..b1e2e64bd 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListAsyncLayer.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListAsyncLayer.swift @@ -60,5 +60,124 @@ extension DisplayList.ViewUpdater { var isContentGeometryEnabled: Bool { flags.contains(.contentGeometry) } + + @inline(__always) + mutating func update( + _ property: P.Type, + from oldValue: P.Value, + to newValue: P.Value + ) where P: Property, P.Value: Equatable { + guard oldValue != newValue else { + return + } + setValue(P.self, to: newValue) + } + + @inline(__always) + mutating func setValue( + _ property: P.Type, + to value: P.Value + ) where P: Property { + cache.pointee.setAsyncValue( + P.boxValue(value), + for: P.keyPath, + in: layer, + usingPresentationModifier: P.supportsPresentationModifier + ) + } } + + // MARK: - AsyncLayer.Property + + struct BackgroundColor: AsyncLayer.Property { + static let keyPath = "backgroundColor" + + static func boxValue(_ value: Color.Resolved) -> NSObject { + #if canImport(Darwin) + unsafeDowncast(value.cgColor, to: NSObject.self) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + + struct PositionLayer: AsyncLayer.Property { + static let keyPath = "position" + + static func boxValue(_ value: CGPoint) -> NSObject { + #if canImport(QuartzCore) + NSValue(point: value) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + + struct BoundsLayer: AsyncLayer.Property { + static let keyPath = "bounds" + + static func boxValue(_ value: CGRect) -> NSObject { + #if canImport(QuartzCore) + NSValue(rect: value) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + + struct AffineTransformLayer: AsyncLayer.Property { + static let keyPath = "transform" + + static func boxValue(_ value: CGAffineTransform) -> NSObject { + #if canImport(QuartzCore) + NSValue(caTransform3D: CATransform3DMakeAffineTransform(value)) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + + struct LayerProjectionTransform: AsyncLayer.Property { + static let keyPath = "transform" + + static func boxValue(_ value: ProjectionTransform) -> NSObject { + #if canImport(QuartzCore) + NSValue(caTransform3D: CATransform3D(value)) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + + struct OpacityLayer: AsyncLayer.Property { + static let keyPath = "opacity" + + static func boxValue(_ value: Float) -> NSObject { + NSNumber(value: value) + } + } + + struct CornerRadiusLayer: AsyncLayer.Property { + static let keyPath = "cornerRadius" + + static func boxValue(_ value: CGFloat) -> NSObject { + NSNumber(value: Double(value)) + } + } + + struct ContentsMultiplyColor: AsyncLayer.Property { + static let keyPath = "contentsMultiplyColor" + + static func boxValue(_ value: Color.Resolved?) -> NSObject { + #if canImport(Darwin) + guard let value else { + return NSNull() + } + return unsafeDowncast(value.cgColor, to: NSObject.self) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + } diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift index 20bb40b06..99aa232b0 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift @@ -3,7 +3,7 @@ // OpenSwiftUICore // // Audited for 6.5.4 -// Status: Blocked by GraphicsContext and Platform +// Status: Blocked by GraphicsContext // ID: 8BBC66CBE42B8A65F8A2F3799C81A349 (SwiftUICore) public import OpenQuartzCoreShims @@ -187,7 +187,7 @@ extension DisplayList.ViewUpdater { } } -// MARK: - DisplayList.ViewUpdater.Platform API [WIP] +// MARK: - DisplayList.ViewUpdater.Platform API extension DisplayList.ViewUpdater.Platform { package init(definition: PlatformViewDefinition.Type) { @@ -381,9 +381,312 @@ extension DisplayList.ViewUpdater.Platform { item: DisplayList.Item, state: UnsafePointer ) { - _openSwiftUIUnimplementedFailure() + var item = item + switch item.value { + case let .content(content): + guard viewInfo.seeds.content != content.seed else { + updateSizeDependentContent(&viewInfo, item: item, state: state) + return + } + var localState = state.pointee + var size = item.size + viewInfo.isInvalid = false + viewInfo.state.isContentGeometryEnabled = false + switch content.value { + case let .backdrop(effect): + if viewInfo.state.kind != .backdrop { + viewInfo = _makeItemView(item: item, state: state) + } + #if canImport(QuartzCore) + let layer = viewInfo.layer as! CABackdropLayer + let hasZeroScale = effect.scale == 0 + layer.scale = hasZeroScale ? 1.0 : CGFloat(effect.scale) + layer.allowsInPlaceFiltering = hasZeroScale + layer.backgroundColor = effect.color.cgColor + let groupID = state.pointee.backdropGroupID + layer.groupName = groupID == 0 ? nil : "OpenSwiftUI-\(groupID)" + #else + _openSwiftUIPlatformUnimplementedWarning() + #endif + case let .color(color): + if viewInfo.state.kind != .color { + viewInfo = _makeItemView(item: item, state: state) + } + #if canImport(QuartzCore) + viewInfo.layer.backgroundColor = color.cgColor + #else + _openSwiftUIPlatformUnimplementedWarning() + #endif + case .chameleonColor: + if viewInfo.state.kind != .chameleonColor { + viewInfo = _makeItemView(item: item, state: state) + } + case let .image(image): + if viewInfo.state.kind != .image { + viewInfo = _makeItemView(item: item, state: state) + } + #if canImport(QuartzCore) + let layer = viewInfo.layer as! ImageLayer + layer.update(image: image, size: size) + #else + _openSwiftUIPlatformUnimplementedWarning() + #endif + adjustImageContentGeometry( + image: image, + state: &localState, + size: &size + ) + viewInfo.state.isContentGeometryEnabled = true + case let .shape(path, paint, style): + if viewInfo.state.kind != .shape { + viewInfo = _makeItemView(item: item, state: state) + } + updateShapeView( + &viewInfo, + state: &localState, + size: &size, + path: path, + paint: paint, + style: style, + contentsChanged: true + ) + case let .shadow(path, shadow): + if viewInfo.state.kind != .shadow { + viewInfo = _makeItemView(item: item, state: state) + } + updateShadowView( + &viewInfo, + path: path, + shadow: shadow, + size: size + ) + case let .platformView(factory): + if viewInfo.state.kind != .platformView { + viewInfo = _makeItemView(item: item, state: state) + } + let oldView = viewInfo.view + factory.updatePlatformView(&viewInfo.view) + let newView = viewInfo.view + if oldView !== newView { + definition.makePlatformView(view: newView, kind: .platformView) + viewInfo.reset(platform: self) + } + case let .platformLayer(factory): + if viewInfo.state.kind != .platformLayer { + viewInfo = _makeItemView(item: item, state: state) + } + let layer = viewInfo.layer + #if canImport(QuartzCore) + layer.contentsScale = state.pointee.globals.pointee.environment.contentsScale + #endif + factory.updatePlatformLayer(layer) + case let .text(text, textSize): + if viewInfo.state.kind != .drawing { + viewInfo = _makeItemView(item: item, state: state) + } + var options = RasterizationOptions() + options.isAccelerated = text.needsDrawingGroup + updateDrawingView( + &viewInfo, + options: options, + contentsScale: state.pointee.globals.pointee.environment.contentsScale, + content: .platformCallback { size in + text.text.draw( + in: CGRect(origin: .zero, size: size), + with: textSize, + applyingMarginOffsets: true, + containsResolvable: text.text.isDynamic, + context: .shared, + renderer: text.renderer + ) + }, + sizeChanged: viewInfo.state.size != item.size + ) + viewInfo.nextUpdate = min( + viewInfo.nextUpdate, + text.text.nextUpdate( + after: state.pointee.globals.pointee.time, + equivalentDate: .now, + reduceFrequency: false + ) + ) + case let .flattened(list, offset, options): + if viewInfo.state.kind != .drawing { + viewInfo = _makeItemView(item: item, state: state) + } + let time = state.pointee.globals.pointee.time + updateDrawingView( + &viewInfo, + options: options, + contentsScale: state.pointee.globals.pointee.environment.contentsScale, + content: .displayList( + list, + offset, + time + ), + sizeChanged: viewInfo.state.size != item.size + ) + viewInfo.nextUpdate = min(viewInfo.nextUpdate, list.nextUpdate(after: time)) + case let .drawing(contents, offset, options): + if viewInfo.state.kind != .drawing { + viewInfo = _makeItemView(item: item, state: state) + } + updateDrawingView( + &viewInfo, + options: options, + contentsScale: state.pointee.globals.pointee.environment.contentsScale, + content: .rbDisplayList(contents, offset), + sizeChanged: viewInfo.state.size != item.size + ) + case .view, .placeholder: + _openSwiftUIUnreachableCode() + } + if viewInfo.state.isContentGeometryEnabled { + localState.versions.transform.combine(with: item.version) + } + if !viewInfo.isInvalid, viewInfo.nextUpdate == .infinity { + viewInfo.seeds.content = content.seed + } + withUnsafePointer(to: localState) { statePtr in + updateState( + &viewInfo, + item: item, + size: size, + state: statePtr + ) + } + case let .effect(effect, _): + let contentChanged = viewInfo.seeds.content != item.version.seed + var changed = contentChanged + if !changed { + let transformChanged: Bool + if case let .transform(transform) = effect, + transform.projectionTransform != nil, + viewInfo.seeds.transform != state.pointee.versions.transform.seed { + transformChanged = true + } else { + transformChanged = false + } + changed = changed || transformChanged + } + guard changed else { + updateSizeDependentContent(&viewInfo, item: item, state: state) + return + } + viewInfo.seeds.content = item.version.seed + switch effect { + case .geometryGroup: + if viewInfo.state.kind != .geometry { + viewInfo = _makeItemView(item: item, state: state) + } + case .compositingGroup: + if viewInfo.state.kind != .compositing { + viewInfo = _makeItemView(item: item, state: state) + } + case let .platformGroup(factory): + if viewInfo.state.kind != .platformGroup { + viewInfo = _makeItemView(item: item, state: state) + } + let oldView = viewInfo.view + factory.updatePlatformGroup(&viewInfo.view) + let newView = viewInfo.view + if oldView !== newView { + definition.makePlatformView(view: newView, kind: .platformGroup) + viewInfo.reset(platform: self) + } + viewInfo.container = factory.platformGroupContainer(newView) + case .mask: + if viewInfo.state.kind != .mask { + viewInfo = _makeItemView(item: item, state: state) + } + case let .transform(transform): + if let projectionTransform = transform.projectionTransform { + if viewInfo.state.kind != .projection { + viewInfo = _makeItemView(item: item, state: state) + } + definition.setProjectionTransform( + projectionTransform.concatenating(ProjectionTransform(state.pointee.transform)), + projectionView: viewInfo.view + ) + } + case .platform: + if viewInfo.state.kind != .platformEffect { + viewInfo = _makeItemView(item: item, state: state) + } + default: + _openSwiftUIUnreachableCode() + } + updateState( + &viewInfo, + item: item, + size: item.size, + state: state + ) + case .empty, .states: + _openSwiftUIUnreachableCode() + } } - + + @inline(__always) + private func updateSizeDependentContent( + _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, + item: DisplayList.Item, + state: UnsafePointer + ) { + guard viewInfo.state.isContentGeometryEnabled else { + if viewInfo.state.kind == .drawing && viewInfo.state.size != item.size { + let drawable = viewInfo.view as! PlatformDrawable + viewInfo.isInvalid = !drawable.update(content: nil, required: true) + } + updateState( + &viewInfo, + item: item, + size: item.size, + state: state + ) + return + } + guard case let .content(content) = item.value else { + _openSwiftUIUnreachableCode() + } + var size = item.size + var localState = state.pointee + switch content.value { + case let .image(image): + adjustImageContentGeometry( + image: image, + state: &localState, + size: &size + ) + case let .shape(path, paint, style): + updateShapeView( + &viewInfo, + state: &localState, + size: &size, + path: path, + paint: paint, + style: style, + contentsChanged: false + ) + default: + viewInfo.seeds.content = .init() + Log.internalError( + "Invalid size-dependent display list content: %s, %s", + content.value.caseName, + "\(viewInfo.state.kind)" + ) + } + localState.versions.transform.combine(with: item.version) + withUnsafePointer(to: localState) { statePtr in + updateState( + &viewInfo, + item: item, + size: size, + state: statePtr + ) + } + } + func updateItemViewAsync( layer: inout DisplayList.ViewUpdater.AsyncLayer, index: DisplayList.Index, @@ -392,7 +695,272 @@ extension DisplayList.ViewUpdater.Platform { newItem: DisplayList.Item, newState: UnsafePointer ) -> Bool { - _openSwiftUIUnimplementedFailure() + switch (oldItem.value, newItem.value) { + case let (.content(oldContent), .content(newContent)): + guard oldContent.seed != newContent.seed else { + return updateSizeDependentContentAsync( + layer: &layer, + oldItem: oldItem, + oldState: oldState, + newItem: newItem, + newState: newState + ) + } + var oldLocalState = oldState.pointee + var newLocalState = newState.pointee + var oldSize = oldItem.size + var newSize = newItem.size + layer.isInvalid = false + switch (oldContent.value, newContent.value) { + case let (.color(oldColor), .color(newColor)): + layer.update( + DisplayList.ViewUpdater.BackgroundColor.self, + from: oldColor, + to: newColor + ) + case let (.image(oldImage), .image(newImage)): + #if canImport(QuartzCore) + guard ImageLayer.updateAsync( + layer: &layer, + oldImage: oldImage, + oldSize: oldSize, + newImage: newImage, + newSize: newSize + ) else { + return false + } + #else + _openSwiftUIPlatformUnimplementedWarning() + return false + #endif + adjustImageContentGeometry( + image: oldImage, + state: &oldLocalState, + size: &oldSize + ) + adjustImageContentGeometry( + image: newImage, + state: &newLocalState, + size: &newSize + ) + case let (.shape(oldPath, oldPaint, oldStyle), .shape(newPath, newPaint, newStyle)): + guard updateShapeViewAsync( + layer: &layer, + oldState: &oldLocalState, + oldSize: &oldSize, + oldPath: oldPath, + oldPaint: oldPaint, + oldStyle: oldStyle, + newState: &newLocalState, + newSize: &newSize, + newPath: newPath, + newPaint: newPaint, + newStyle: newStyle, + contentsChanged: true + ) else { + return false + } + case let (.flattened(_, _, oldOptions), .flattened(newList, newOffset, newOptions)): + let time = newState.pointee.globals.pointee.time + guard updateDrawingViewAsync( + &layer, + oldOptions: oldOptions, + newOptions: newOptions, + content: .displayList(newList, newOffset, time), + sizeChanged: oldItem.size != newItem.size, + newSize: newItem.size, + newState: newState + ) else { + return false + } + layer.nextUpdate = min(layer.nextUpdate, newList.nextUpdate(after: time)) + case let (.drawing(_, _, oldOptions), .drawing(newContents, newOffset, newOptions)): + guard updateDrawingViewAsync( + &layer, + oldOptions: oldOptions, + newOptions: newOptions, + content: .rbDisplayList(newContents, newOffset), + sizeChanged: oldItem.size != newItem.size, + newSize: newItem.size, + newState: newState + ) else { + return false + } + default: + return false + } + return withUnsafePointer(to: oldLocalState) { oldStatePtr in + withUnsafePointer(to: newLocalState) { newStatePtr in + updateStateAsync( + layer: &layer, + oldItem: oldItem, + oldSize: oldSize, + oldState: oldStatePtr, + newItem: newItem, + newSize: newSize, + newState: newStatePtr + ) + } + } + case let (.effect(oldEffect, _), .effect(newEffect, _)): + let contentChanged = oldItem.version != newItem.version + var changed = contentChanged + if !changed { + let transformChanged: Bool + if case let .transform(oldTransform) = oldEffect, + oldTransform.projectionTransform != nil, + oldState.pointee.versions.transform != newState.pointee.versions.transform { + transformChanged = true + } else { + transformChanged = false + } + changed = changed || transformChanged + } + guard changed else { + return updateSizeDependentContentAsync( + layer: &layer, + oldItem: oldItem, + oldState: oldState, + newItem: newItem, + newState: newState + ) + } + switch (oldEffect, newEffect) { + case let (.platformGroup(oldFactory), .platformGroup(newFactory)): + guard !oldFactory.needsUpdateFor(newValue: newFactory) else { + return false + } + case let (.transform(oldTransform), .transform(newTransform)): + if let oldProjectionTransform = oldTransform.projectionTransform, + let newProjectionTransform = newTransform.projectionTransform { + layer.update( + DisplayList.ViewUpdater.LayerProjectionTransform.self, + from: oldProjectionTransform.concatenating(ProjectionTransform(oldState.pointee.transform)), + to: newProjectionTransform.concatenating(ProjectionTransform(newState.pointee.transform)) + ) + } + default: + break + } + return updateStateAsync( + layer: &layer, + oldItem: oldItem, + oldSize: oldItem.size, + oldState: oldState, + newItem: newItem, + newSize: newItem.size, + newState: newState + ) + default: + return false + } + } + + @inline(__always) + private func updateSizeDependentContentAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldItem: DisplayList.Item, + oldState: UnsafePointer, + newItem: DisplayList.Item, + newState: UnsafePointer + ) -> Bool { + guard layer.isContentGeometryEnabled else { + return updateStateAsync( + layer: &layer, + oldItem: oldItem, + oldSize: oldItem.size, + oldState: oldState, + newItem: newItem, + newSize: newItem.size, + newState: newState + ) + } + guard case let .content(oldContent) = oldItem.value, + case let .content(newContent) = newItem.value + else { + return false + } + var oldLocalState = oldState.pointee + var newLocalState = newState.pointee + var oldSize = oldItem.size + var newSize = newItem.size + switch (oldContent.value, newContent.value) { + case let (.image(oldImage), .image(newImage)): + adjustImageContentGeometry( + image: oldImage, + state: &oldLocalState, + size: &oldSize + ) + adjustImageContentGeometry( + image: newImage, + state: &newLocalState, + size: &newSize + ) + case let (.shape(oldPath, _, _), .shape(newPath, _, _)): + adjustShapeContentGeometry( + layer: layer, + state: &oldLocalState, + size: &oldSize, + path: oldPath + ) + adjustShapeContentGeometry( + layer: layer, + state: &newLocalState, + size: &newSize, + path: newPath + ) + default: + return false + } + return withUnsafePointer(to: oldLocalState) { oldStatePtr in + withUnsafePointer(to: newLocalState) { newStatePtr in + updateStateAsync( + layer: &layer, + oldItem: oldItem, + oldSize: oldSize, + oldState: oldStatePtr, + newItem: newItem, + newSize: newSize, + newState: newStatePtr + ) + } + } + } + + @inline(__always) + private func adjustImageContentGeometry( + image: GraphicsImage, + state: inout DisplayList.ViewUpdater.Model.State, + size: inout CGSize + ) { + let orientation = image.bitmapOrientation + if orientation != .up { + state.transform = CGAffineTransform( + orientation: orientation, + in: size + ).concatenating(state.transform) + size = size.apply(orientation) + } + } + + @inline(__always) + private func adjustShapeContentGeometry( + layer: DisplayList.ViewUpdater.AsyncLayer, + state: inout DisplayList.ViewUpdater.Model.State, + size: inout CGSize, + path: Path + ) { + let bounds = ShapeLayerHelper.makeLayerBounds( + size: size, + path: path, + layerType: type(of: layer.layer), + contentsScale: state.globals.pointee.environment.contentsScale + ) + state.transform = state.transform.translatedBy( + x: bounds.origin.x, + y: bounds.origin.y + ) + size = bounds.size } func updateState( @@ -477,7 +1045,73 @@ extension DisplayList.ViewUpdater.Platform { newSize: CGSize, newState: UnsafePointer ) -> Bool { - _openSwiftUIUnimplementedFailure() + guard oldState.pointee.properties == newState.pointee.properties else { + return false + } + layer.update( + DisplayList.ViewUpdater.OpacityLayer.self, + from: oldState.pointee.opacity, + to: newState.pointee.opacity + ) + guard oldState.pointee.versions.blend == newState.pointee.versions.blend else { + return false + } + if oldState.pointee.versions.filters != newState.pointee.versions.filters { + var oldFilters = oldState.pointee.filters + var newFilters = newState.pointee.filters + if layer.kind == .drawing { + let oldColor = oldFilters.popColorMultiply(drawable: layer.layer.delegate as? PlatformDrawable) + let newColor = newFilters.popColorMultiply(drawable: layer.layer.delegate as? PlatformDrawable) + layer.update( + DisplayList.ViewUpdater.ContentsMultiplyColor.self, + from: oldColor, + to: newColor + ) + } + guard GraphicsFilter.updateAsync( + layer: &layer, + oldFilters: oldFilters, + newFilters: newFilters + ) else { + return false + } + } + if oldState.pointee.versions.clips != newState.pointee.versions.clips || + oldState.pointee.versions.transform != newState.pointee.versions.transform { + guard updateClipShapesAsync( + asyncLayer: &layer, + oldState: oldState, + newState: newState + ) else { + return false + } + } + guard let boundsChanged = updateGeometryAsync( + asyncLayer: &layer, + oldItem: oldItem, + oldSize: oldSize, + oldState: oldState, + newItem: newItem, + newSize: newSize, + newState: newState + ) else { + return false + } + if boundsChanged || + oldState.pointee.versions.shadow != newState.pointee.versions.shadow || + oldItem.version != newItem.version { + guard updateShadowAsync( + asyncLayer: &layer, + oldState: oldState, + oldItem: oldItem, + newState: newState, + newItem: newItem, + boundsChanged: boundsChanged + ) else { + return false + } + } + return true } func _makeItemView( @@ -560,8 +1194,10 @@ extension DisplayList.ViewUpdater.Platform { container: view, state: .init(kind: .platformLayer) ) - case .text: - let view = definition.makeDrawingView(options: .init(base: .init())) as AnyObject + case let .text(text, _): + var options = RasterizationOptions() + options.isAccelerated = text.needsDrawingGroup + let view = definition.makeDrawingView(options: .init(base: options)) as AnyObject return DisplayList.ViewUpdater.ViewInfo( view: view, layer: viewLayer(view), @@ -587,8 +1223,8 @@ extension DisplayList.ViewUpdater.Platform { let info = DisplayList.ViewUpdater.ViewInfo(platform: self, kind: .compositing) #if canImport(QuartzCore) let layer = info.layer - info.layer.allowsGroupOpacity = true - info.layer.allowsGroupBlending = true + layer.allowsGroupOpacity = true + layer.allowsGroupBlending = true #else _openSwiftUIPlatformUnimplementedWarning() #endif @@ -769,7 +1405,61 @@ extension DisplayList.ViewUpdater.Platform { newStyle: FillStyle, contentsChanged: Bool ) -> Bool { - _openSwiftUIUnimplementedFailure() + let currentLayerType = type(of: layer.layer) + let oldOriginalSize = oldSize + let newOriginalSize = newSize + let oldContentsScale = oldState.globals.pointee.environment.contentsScale + let newContentsScale = newState.globals.pointee.environment.contentsScale + let oldBounds = ShapeLayerHelper.makeLayerBounds( + size: oldOriginalSize, + path: oldPath, + layerType: currentLayerType, + contentsScale: oldContentsScale + ) + let newBounds = ShapeLayerHelper.makeLayerBounds( + size: newOriginalSize, + path: newPath, + layerType: currentLayerType, + contentsScale: newContentsScale + ) + if contentsChanged { + var oldHelper = ShapeLayerHelper( + layer: layer.layer, + layerType: currentLayerType, + path: oldPath, + origin: oldBounds.origin, + paint: oldPaint, + paintBounds: CGRect( + origin: CGPoint(x: -oldBounds.origin.x, y: -oldBounds.origin.y), + size: oldOriginalSize + ), + style: oldStyle, + contentsScale: oldContentsScale, + mayClip: !oldState.hasDODEffects + ) + var newHelper = ShapeLayerHelper( + layer: layer.layer, + layerType: currentLayerType, + path: newPath, + origin: newBounds.origin, + paint: newPaint, + paintBounds: CGRect( + origin: CGPoint(x: -newBounds.origin.x, y: -newBounds.origin.y), + size: newOriginalSize + ), + style: newStyle, + contentsScale: newContentsScale, + mayClip: !newState.hasDODEffects + ) + guard ShapeLayerHelper.updateAsync(layer: &layer, old: &oldHelper, new: &newHelper) else { + return false + } + } + oldState.transform = oldState.transform.translatedBy(x: oldBounds.origin.x, y: oldBounds.origin.y) + newState.transform = newState.transform.translatedBy(x: newBounds.origin.x, y: newBounds.origin.y) + oldSize = oldBounds.size + newSize = newBounds.size + return true } private func updateDrawingViewAsync( @@ -1075,7 +1765,48 @@ extension DisplayList.ViewUpdater.Platform { oldState: UnsafePointer, newState: UnsafePointer ) -> Bool { - _openSwiftUIUnimplementedFailure() + #if canImport(QuartzCore) + guard !oldState.pointee.clips.isEmpty || !newState.pointee.clips.isEmpty else { + return true + } + if asyncLayer.isClipRectEnabled { + guard let oldClipRect = oldState.pointee.clipRect(), + let newClipRect = newState.pointee.clipRect(), + oldClipRect.style == newClipRect.style + else { + return false + } + asyncLayer.update( + DisplayList.ViewUpdater.CornerRadiusLayer.self, + from: oldClipRect.clampedCornerRadius, + to: newClipRect.clampedCornerRadius + ) + return true + } else { + guard newState.pointee.clipRect() == nil, + let maskLayer = asyncLayer.layer.mask else { + return false + } + var maskAsyncLayer = DisplayList.ViewUpdater.AsyncLayer( + layer: maskLayer, + cache: asyncLayer.cache, + kind: asyncLayer.kind, + flags: asyncLayer.flags, + nextUpdate: asyncLayer.nextUpdate, + isInvalid: asyncLayer.isInvalid + ) + return MaskLayer.updateClipsAsync( + layer: &maskAsyncLayer, + oldClips: oldState.pointee.clips, + newClips: newState.pointee.clips, + oldTransform: oldState.pointee.transform.inverted(), + newTransform: newState.pointee.transform.inverted() + ) + } + #else + _openSwiftUIUnimplementedWarning() + return false + #endif } private func updateGeometryAsync( @@ -1087,7 +1818,80 @@ extension DisplayList.ViewUpdater.Platform { newSize: CGSize, newState: UnsafePointer ) -> Bool? { - _openSwiftUIUnimplementedFailure() + #if canImport(QuartzCore) + var oldBounds = CGRect(origin: .zero, size: oldSize) + var newBounds = CGRect(origin: .zero, size: newSize) + var oldPosition = CGPoint( + x: oldState.pointee.transform.tx, + y: oldState.pointee.transform.ty + ) + var newPosition = CGPoint( + x: newState.pointee.transform.tx, + y: newState.pointee.transform.ty + ) + + if asyncLayer.isClipRectEnabled, + let oldClipRect = oldState.pointee.clipRect(), + let newClipRect = newState.pointee.clipRect() { + oldBounds = oldClipRect.rect + newBounds = newClipRect.rect + oldPosition.x += oldBounds.origin.x + oldPosition.y += oldBounds.origin.y + newPosition.x += newBounds.origin.x + newPosition.y += newBounds.origin.y + } + + let boundsChanged = oldBounds != newBounds + if boundsChanged { + switch asyncLayer.kind { + case .platformView, .platformGroup, .platformLayer: + return nil + default: + break + } + asyncLayer.setValue( + DisplayList.ViewUpdater.BoundsLayer.self, + to: newBounds + ) + if asyncLayer.kind == .mask { + var maskLayer = DisplayList.ViewUpdater.AsyncLayer( + layer: asyncLayer.layer.mask!, + cache: asyncLayer.cache, + kind: asyncLayer.kind, + flags: asyncLayer.flags, + nextUpdate: asyncLayer.nextUpdate, + isInvalid: asyncLayer.isInvalid + ) + maskLayer.setValue( + DisplayList.ViewUpdater.BoundsLayer.self, + to: newBounds + ) + } + } + guard !asyncLayer.isProjectionGeometryEnabled else { + return boundsChanged + } + asyncLayer.update( + DisplayList.ViewUpdater.PositionLayer.self, + from: oldPosition, + to: newPosition + ) + var oldTransform = oldState.pointee.transform + oldTransform.tx = 0 + oldTransform.ty = 0 + var newTransform = newState.pointee.transform + newTransform.tx = 0 + newTransform.ty = 0 + asyncLayer.update( + DisplayList.ViewUpdater.AffineTransformLayer.self, + from: oldTransform, + to: newTransform + ) + return boundsChanged + #else + _openSwiftUIUnimplementedWarning() + return false + #endif } private func updateShadowAsync( @@ -1098,7 +1902,84 @@ extension DisplayList.ViewUpdater.Platform { newItem: DisplayList.Item, boundsChanged: Bool ) -> Bool { - _openSwiftUIUnimplementedFailure() + #if canImport(QuartzCore) + let oldShadow = oldState.pointee.shadow?.value + let newShadow = newState.pointee.shadow?.value + switch (oldShadow, newShadow) { + case (nil, nil): + return true + case (nil, _?), (_?, nil): + return false + case let (oldShadow?, newShadow?): + guard boundsChanged || oldShadow != newShadow else { + return true + } + guard asyncLayer.kind != .inherited, + case let .content(oldContent) = oldItem.value, + case let .content(newContent) = newItem.value + else { + return asyncLayer.updateShadowStyle( + oldShadow: oldShadow, + newShadow: newShadow + ) + } + switch (oldContent.value, newContent.value) { + case let (.color(oldColor), .color(newColor)): + return _updateShadowAsync( + layer: &asyncLayer, + oldShadow: oldShadow, + newShadow: newShadow, + oldPaintOpacity: oldColor.opacity, + newPaintOpacity: newColor.opacity + ) + case let (.shape(oldPath, oldPaint, _), .shape(newPath, newPaint, _)): + let layerType = type(of: asyncLayer.layer) + let oldBounds = ShapeLayerHelper.makeLayerBounds( + size: oldItem.size, + path: oldPath, + layerType: layerType, + contentsScale: oldState.pointee.globals.pointee.environment.contentsScale + ) + let newBounds = ShapeLayerHelper.makeLayerBounds( + size: newItem.size, + path: newPath, + layerType: layerType, + contentsScale: newState.pointee.globals.pointee.environment.contentsScale + ) + var oldHelper = ShapeLayerShadowHelper( + platform: self, + layer: asyncLayer.layer, + path: oldPath, + offset: oldBounds.origin, + shadow: oldShadow, + updateShape: false + ) + var newHelper = ShapeLayerShadowHelper( + platform: self, + layer: asyncLayer.layer, + path: newPath, + offset: newBounds.origin, + shadow: newShadow, + updateShape: false + ) + return ShapeLayerShadowHelper.updateAsync( + layer: &asyncLayer, + old: &oldHelper, + new: &newHelper, + oldPaint: oldPaint, + newPaint: newPaint + ) + default: + return asyncLayer.updateShadowStyle( + oldShadow: oldShadow, + newShadow: newShadow + ) + } + } + #else + _openSwiftUIUnimplementedWarning() + return false + #endif } func updateDrawingView( @@ -1120,129 +2001,10 @@ extension DisplayList.ViewUpdater.Platform { return drawable } - // TBA - @inline(__always) - private func updateContentView( - _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, - content: DisplayList.Content, - item: DisplayList.Item, - state: UnsafePointer - ) { - switch content.value { - case let .color(resolved): - #if canImport(QuartzCore) - let cgColor = resolved.cgColor - viewLayer(viewInfo.view).backgroundColor = cgColor - #else - _openSwiftUIPlatformUnimplementedWarning() - #endif - case .backdrop, .chameleonColor, .image, .shadow, .view, .placeholder: - break - case let .shape(path, _, _): - definition.setPath(path, shapeView: viewInfo.view) - case let .platformView(factory): - var view = viewInfo.view - factory.updatePlatformView(&view) - updateViewReference(&viewInfo, view: view, kind: .platformView) - case let .platformLayer(factory): - #if canImport(QuartzCore) - if let layer = viewInfo.view as? CALayer { - factory.updatePlatformLayer(layer) - } - #endif - case .text, .flattened, .drawing: - updateDrawingContent(&viewInfo, content: content, item: item, state: state) - } - } - - // TBA - @inline(__always) - private func updateEffectView( - _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, - effect: DisplayList.Effect, - item: DisplayList.Item, - state: UnsafePointer - ) { - switch effect { - case let .transform(transform): - if case let .projection(projectionTransform) = transform { - definition.setProjectionTransform( - projectionTransform, - projectionView: viewInfo.view - ) - } - case let .platformGroup(factory): - var view = viewInfo.view - factory.updatePlatformGroup(&view) - updateViewReference(&viewInfo, view: view, kind: .platformGroup) - viewInfo.container = factory.platformGroupContainer(viewInfo.view) - case .identity, .geometryGroup, .compositingGroup, .backdropGroup, .archive, - .properties, .opacity, .blendMode, .clip, .mask, .filter, .animation, - .contentTransition, .view, .accessibility, .platform, .state, - .interpolatorRoot, .interpolatorLayer, .interpolatorAnimation: - break - } - } - - // TBA - @inline(__always) - private func updateDrawingContent( - _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, - content: DisplayList.Content, - item: DisplayList.Item, - state: UnsafePointer - ) { - _openSwiftUIUnimplementedFailure() -// let contentsScale = state.pointee.globals.pointee.environment.contentsScale -// let options = drawingOptions(for: content, state: state) -// var view = viewInfo.view -// let drawable = updateDrawingView( -// &view, -// options: options, -// contentsScale: contentsScale -// ) -// updateViewReference(&viewInfo, view: view, kind: .drawing) -// var drawableContent = PlatformDrawableContent() -// switch content.value { -// case let .flattened(list, offset, _): -// drawableContent.storage = .displayList( -// list, -// offset, -// state.pointee.globals.pointee.time -// ) -// case let .drawing(contents, offset, _): -// drawableContent.storage = .rbDisplayList(contents, offset) -// case .text: -// drawableContent.storage = .empty -// default: -// return -// } -// _ = drawable.update( -// content: drawableContent, -// required: item.features.contains(.required) -// ) - } - - // TBA - @inline(__always) - private func updateViewReference( - _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, - view: AnyObject, - kind: PlatformViewDefinition.ViewKind - ) { - guard viewInfo.view !== view else { - return - } - viewInfo.view = view - viewInfo.container = view - viewInfo.reset(platform: self) - } - func forEachChild( of viewInfo: DisplayList.ViewUpdater.ViewInfo, do body: (AnyObject) -> Void ) { - #if canImport(Darwin) let kind = viewInfo.state.kind if kind.isContainer { for subview in subviews(viewInfo.container) { @@ -1252,10 +2014,9 @@ extension DisplayList.ViewUpdater.Platform { if kind == .mask, let maskView = maskView(viewInfo.view) { for subview in subviews(maskView) { - body(subview as AnyObject) + body(subview) } } - #endif } } @@ -1283,7 +2044,7 @@ extension DisplayList.GraphicsRenderer { } } -// MARK: - GraphicsFilter + Platform Filters +// MARK: - GraphicsFilter + Platform Filters [TODO] extension [GraphicsFilter] { fileprivate mutating func popColorMultiply( @@ -1313,3 +2074,14 @@ extension [GraphicsFilter] { // _openSwiftUIUnimplementedFailure() // } //} + +extension GraphicsFilter { + static func updateAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldFilters: [GraphicsFilter], + newFilters: [GraphicsFilter] + ) -> Bool { + _openSwiftUIUnimplementedWarning() + return false + } +} diff --git a/Sources/OpenSwiftUICore/Shape/MaskLayer.swift b/Sources/OpenSwiftUICore/Shape/MaskLayer.swift index fbafcbc53..3347d25f3 100644 --- a/Sources/OpenSwiftUICore/Shape/MaskLayer.swift +++ b/Sources/OpenSwiftUICore/Shape/MaskLayer.swift @@ -112,6 +112,17 @@ final class MaskLayer: CAShapeLayer { finalTransform.ty += position.x * finalTransform.b + position.y * finalTransform.d - position.y layer.setAffineTransform(finalTransform) } + + static func updateClipsAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldClips: [DisplayList.ViewUpdater.Model.Clip], + newClips: [DisplayList.ViewUpdater.Model.Clip], + oldTransform: CGAffineTransform, + newTransform: CGAffineTransform + ) -> Bool { + _openSwiftUIUnimplementedWarning() + return false + } } #endif diff --git a/Sources/OpenSwiftUICore/Shape/ShapeLayer.swift b/Sources/OpenSwiftUICore/Shape/ShapeLayer.swift index 30a962bbf..a7c318833 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeLayer.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeLayer.swift @@ -46,6 +46,24 @@ struct ShapeLayerHelper: ResolvedPaintVisitor { ) -> CGRect { _openSwiftUIUnimplementedFailure() } + + static func updateAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + old: UnsafeMutablePointer, + new: UnsafeMutablePointer + ) -> Bool { + guard old.pointee.style.isEOFilled == new.pointee.style.isEOFilled, + old.pointee.style.isAntialiased == new.pointee.style.isAntialiased, + old.pointee.mayClip == new.pointee.mayClip + else { + return false + } + return withUnsafeMutablePointer(to: &layer) { layer in + var helper = ShapeLayerAsyncHelper(layer: layer, old: old, new: new, result: false) + old.pointee.paint.visit(&helper) + return helper.result + } + } } // MARK: - ShapeLayerShadowHelper [WIP] @@ -61,6 +79,47 @@ struct ShapeLayerShadowHelper: ResolvedPaintVisitor { mutating func visitPaint(_ paint: P) where P: ResolvedPaint { _openSwiftUIUnimplementedFailure() } + + @inline(__always) + static func updateAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + old: UnsafeMutablePointer, + new: UnsafeMutablePointer, + oldPaint: AnyResolvedPaint, + newPaint: AnyResolvedPaint + ) -> Bool { + return withUnsafeMutablePointer(to: &layer) { layer in + var helper = ShapeLayerAsyncShadowHelper( + layer: layer, + old: old, + new: new, + newPaint: newPaint, + result: false + ) + oldPaint.visit(&helper) + return helper.result + } + } +} + +func _updateShadowAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldShadow: ResolvedShadowStyle?, + newShadow: ResolvedShadowStyle?, + oldPaintOpacity: Float, + newPaintOpacity: Float +) -> Bool { + var oldShadow = oldShadow + var newShadow = newShadow + if var shadow = oldShadow { + shadow.color = shadow.color.multiplyingOpacity(by: oldPaintOpacity) + oldShadow = shadow + } + if var shadow = newShadow { + shadow.color = shadow.color.multiplyingOpacity(by: newPaintOpacity) + newShadow = shadow + } + return layer.updateShadowStyle(oldShadow: oldShadow, newShadow: newShadow) } // MARK: - Async Shape Helpers @@ -88,7 +147,7 @@ private struct ShapeLayerAsyncShadowHelper: ResolvedPaintVisitor { } } -// FIXME: ShapeLayerShadowHelper & ShapeLayerAsyncShadowHelper +// MARK: - AsyncLayer + shadow extension DisplayList.ViewUpdater.AsyncLayer { @discardableResult @@ -99,77 +158,48 @@ extension DisplayList.ViewUpdater.AsyncLayer { switch (oldShadow, newShadow) { case (nil, nil): return true - case let (oldShadow?, newShadow?): - guard oldShadow.kind == newShadow.kind else { - return false - } - update(ShadowOffsetProperty.self, from: oldShadow.offset, to: newShadow.offset) - update(ShadowRadiusProperty.self, from: oldShadow.radius, to: newShadow.radius) - update(ShadowColorProperty.self, from: oldShadow.color, to: newShadow.color) - return !isInvalid + case let (oldShadow?, newShadow?) where oldShadow.kind == newShadow.kind: + update(DisplayList.ViewUpdater.ShadowOffsetProperty.self, from: oldShadow.offset, to: newShadow.offset) + update(DisplayList.ViewUpdater.ShadowRadiusProperty.self, from: oldShadow.radius, to: newShadow.radius) + update(DisplayList.ViewUpdater.ShadowColorProperty.self, from: oldShadow.color, to: newShadow.color) + return true default: return false } } +} - private mutating func update( - _ property: P.Type, - from oldValue: P.Value, - to newValue: P.Value - ) where P: Property, P.Value: Equatable { - guard oldValue != newValue else { - return - } - setValue(newValue, for: property) - } +extension DisplayList.ViewUpdater { + struct ShadowOffsetProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowOffset" - private mutating func setValue( - _ value: P.Value, - for property: P.Type - ) where P: Property { - guard !isInvalid else { - return + static func boxValue(_ value: CGSize) -> NSObject { + #if canImport(Darwin) + NSValue(size: value) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif } - cache.pointee.setAsyncValue( - P.boxValue(value), - for: P.keyPath, - in: layer, - usingPresentationModifier: P.supportsPresentationModifier - ) } -} - -private struct ShadowColorProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowColor" + + struct ShadowRadiusProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowRadius" - static func boxValue(_ value: Color.Resolved) -> NSObject { - #if canImport(Darwin) - return value.cgColor as! NSObject - #else - return NSObject() - #endif + static func boxValue(_ value: Double) -> NSObject { + NSNumber(value: value) + } } -} - -private struct ShadowRadiusProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowRadius" - static func boxValue(_ value: CGFloat) -> NSObject { - NSNumber(value: Double(value)) - } -} + struct ShadowColorProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowColor" -private struct ShadowOffsetProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowOffset" - - static func boxValue(_ value: CGSize) -> NSObject { - #if canImport(Darwin) -// return NSValue(size: value) - // FIXME - return NSObject() - #else - return NSObject() - #endif + static func boxValue(_ value: Color.Resolved) -> NSObject { + #if canImport(Darwin) + unsafeDowncast(value.cgColor, to: NSObject.self) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } } } diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift index fa340c2c1..1ab76ba3b 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift @@ -444,10 +444,14 @@ final package class _ShapeStyle_InterpolatorGroup: DisplayList.InterpolatorGroup _openSwiftUIUnimplementedWarning() return .interpolatorData(group: .init(), serial: 0) } -} -extension DisplayList { - struct InterpolatorLayer {} + override func nextUpdate(after time: Time) -> Time { + var nextUpdate = Time.infinity + for layer in layers { + nextUpdate = min(nextUpdate, layer.state.nextUpdate(after: time)) + } + return nextUpdate + } } diff --git a/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift b/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift index 4257d5cba..a2c4cc19b 100644 --- a/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift +++ b/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift @@ -132,15 +132,16 @@ final package class ImageLayer: CALayer { add(animation, forKey: nil) } -// func updateAsync( -// layer: DisplayList.ViewUpdater.AsyncLayer, -// oldImage: GraphicsImage, -// oldSize: CGSize, -// newImage: GraphicsImage, -// newSize: CGSize -// ) -> Bool { -// _openSwiftUIUnimplementedFailure() -// } + static func updateAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldImage: GraphicsImage, + oldSize: CGSize, + newImage: GraphicsImage, + newSize: CGSize + ) -> Bool { + _openSwiftUIUnimplementedWarning() + return false + } } // MARK: - GraphicsImage + LayerStretch diff --git a/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift b/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift index abe3ab104..daa834ac6 100644 --- a/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift +++ b/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift @@ -657,7 +657,15 @@ package class ResolvedStyledText: CustomStringConvertible { equivalentDate date: Date, reduceFrequency: Bool = false ) -> Time { - _openSwiftUIUnimplementedFailure() + let nextDate = schedule?.nextEntry( + after: date, + mode: reduceFrequency ? .lowFrequency : .normal, + limit: .minimumTimelineScheduleLimit + ) + guard let nextDate else { + return .infinity + } + return time + nextDate.timeIntervalSince(date) } final package var updatesAsynchronously: Bool { diff --git a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h index 95326c8cf..8e153392a 100644 --- a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h +++ b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h @@ -6,9 +6,15 @@ #if __has_include() +#import #import @interface CABackdropLayer : CALayer + +@property (nonatomic) CGFloat scale; +@property (nonatomic) BOOL allowsInPlaceFiltering; +@property (nonatomic, copy, nullable) NSString *groupName; + @end #endif /* __has_include() */
( + _ property: P.Type, + from oldValue: P.Value, + to newValue: P.Value + ) where P: Property, P.Value: Equatable { + guard oldValue != newValue else { + return + } + setValue(P.self, to: newValue) + } + + @inline(__always) + mutating func setValue
( + _ property: P.Type, + to value: P.Value + ) where P: Property { + cache.pointee.setAsyncValue( + P.boxValue(value), + for: P.keyPath, + in: layer, + usingPresentationModifier: P.supportsPresentationModifier + ) + } } + + // MARK: - AsyncLayer.Property + + struct BackgroundColor: AsyncLayer.Property { + static let keyPath = "backgroundColor" + + static func boxValue(_ value: Color.Resolved) -> NSObject { + #if canImport(Darwin) + unsafeDowncast(value.cgColor, to: NSObject.self) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + + struct PositionLayer: AsyncLayer.Property { + static let keyPath = "position" + + static func boxValue(_ value: CGPoint) -> NSObject { + #if canImport(QuartzCore) + NSValue(point: value) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + + struct BoundsLayer: AsyncLayer.Property { + static let keyPath = "bounds" + + static func boxValue(_ value: CGRect) -> NSObject { + #if canImport(QuartzCore) + NSValue(rect: value) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + + struct AffineTransformLayer: AsyncLayer.Property { + static let keyPath = "transform" + + static func boxValue(_ value: CGAffineTransform) -> NSObject { + #if canImport(QuartzCore) + NSValue(caTransform3D: CATransform3DMakeAffineTransform(value)) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + + struct LayerProjectionTransform: AsyncLayer.Property { + static let keyPath = "transform" + + static func boxValue(_ value: ProjectionTransform) -> NSObject { + #if canImport(QuartzCore) + NSValue(caTransform3D: CATransform3D(value)) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + + struct OpacityLayer: AsyncLayer.Property { + static let keyPath = "opacity" + + static func boxValue(_ value: Float) -> NSObject { + NSNumber(value: value) + } + } + + struct CornerRadiusLayer: AsyncLayer.Property { + static let keyPath = "cornerRadius" + + static func boxValue(_ value: CGFloat) -> NSObject { + NSNumber(value: Double(value)) + } + } + + struct ContentsMultiplyColor: AsyncLayer.Property { + static let keyPath = "contentsMultiplyColor" + + static func boxValue(_ value: Color.Resolved?) -> NSObject { + #if canImport(Darwin) + guard let value else { + return NSNull() + } + return unsafeDowncast(value.cgColor, to: NSObject.self) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + } diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift index 20bb40b06..99aa232b0 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift @@ -3,7 +3,7 @@ // OpenSwiftUICore // // Audited for 6.5.4 -// Status: Blocked by GraphicsContext and Platform +// Status: Blocked by GraphicsContext // ID: 8BBC66CBE42B8A65F8A2F3799C81A349 (SwiftUICore) public import OpenQuartzCoreShims @@ -187,7 +187,7 @@ extension DisplayList.ViewUpdater { } } -// MARK: - DisplayList.ViewUpdater.Platform API [WIP] +// MARK: - DisplayList.ViewUpdater.Platform API extension DisplayList.ViewUpdater.Platform { package init(definition: PlatformViewDefinition.Type) { @@ -381,9 +381,312 @@ extension DisplayList.ViewUpdater.Platform { item: DisplayList.Item, state: UnsafePointer ) { - _openSwiftUIUnimplementedFailure() + var item = item + switch item.value { + case let .content(content): + guard viewInfo.seeds.content != content.seed else { + updateSizeDependentContent(&viewInfo, item: item, state: state) + return + } + var localState = state.pointee + var size = item.size + viewInfo.isInvalid = false + viewInfo.state.isContentGeometryEnabled = false + switch content.value { + case let .backdrop(effect): + if viewInfo.state.kind != .backdrop { + viewInfo = _makeItemView(item: item, state: state) + } + #if canImport(QuartzCore) + let layer = viewInfo.layer as! CABackdropLayer + let hasZeroScale = effect.scale == 0 + layer.scale = hasZeroScale ? 1.0 : CGFloat(effect.scale) + layer.allowsInPlaceFiltering = hasZeroScale + layer.backgroundColor = effect.color.cgColor + let groupID = state.pointee.backdropGroupID + layer.groupName = groupID == 0 ? nil : "OpenSwiftUI-\(groupID)" + #else + _openSwiftUIPlatformUnimplementedWarning() + #endif + case let .color(color): + if viewInfo.state.kind != .color { + viewInfo = _makeItemView(item: item, state: state) + } + #if canImport(QuartzCore) + viewInfo.layer.backgroundColor = color.cgColor + #else + _openSwiftUIPlatformUnimplementedWarning() + #endif + case .chameleonColor: + if viewInfo.state.kind != .chameleonColor { + viewInfo = _makeItemView(item: item, state: state) + } + case let .image(image): + if viewInfo.state.kind != .image { + viewInfo = _makeItemView(item: item, state: state) + } + #if canImport(QuartzCore) + let layer = viewInfo.layer as! ImageLayer + layer.update(image: image, size: size) + #else + _openSwiftUIPlatformUnimplementedWarning() + #endif + adjustImageContentGeometry( + image: image, + state: &localState, + size: &size + ) + viewInfo.state.isContentGeometryEnabled = true + case let .shape(path, paint, style): + if viewInfo.state.kind != .shape { + viewInfo = _makeItemView(item: item, state: state) + } + updateShapeView( + &viewInfo, + state: &localState, + size: &size, + path: path, + paint: paint, + style: style, + contentsChanged: true + ) + case let .shadow(path, shadow): + if viewInfo.state.kind != .shadow { + viewInfo = _makeItemView(item: item, state: state) + } + updateShadowView( + &viewInfo, + path: path, + shadow: shadow, + size: size + ) + case let .platformView(factory): + if viewInfo.state.kind != .platformView { + viewInfo = _makeItemView(item: item, state: state) + } + let oldView = viewInfo.view + factory.updatePlatformView(&viewInfo.view) + let newView = viewInfo.view + if oldView !== newView { + definition.makePlatformView(view: newView, kind: .platformView) + viewInfo.reset(platform: self) + } + case let .platformLayer(factory): + if viewInfo.state.kind != .platformLayer { + viewInfo = _makeItemView(item: item, state: state) + } + let layer = viewInfo.layer + #if canImport(QuartzCore) + layer.contentsScale = state.pointee.globals.pointee.environment.contentsScale + #endif + factory.updatePlatformLayer(layer) + case let .text(text, textSize): + if viewInfo.state.kind != .drawing { + viewInfo = _makeItemView(item: item, state: state) + } + var options = RasterizationOptions() + options.isAccelerated = text.needsDrawingGroup + updateDrawingView( + &viewInfo, + options: options, + contentsScale: state.pointee.globals.pointee.environment.contentsScale, + content: .platformCallback { size in + text.text.draw( + in: CGRect(origin: .zero, size: size), + with: textSize, + applyingMarginOffsets: true, + containsResolvable: text.text.isDynamic, + context: .shared, + renderer: text.renderer + ) + }, + sizeChanged: viewInfo.state.size != item.size + ) + viewInfo.nextUpdate = min( + viewInfo.nextUpdate, + text.text.nextUpdate( + after: state.pointee.globals.pointee.time, + equivalentDate: .now, + reduceFrequency: false + ) + ) + case let .flattened(list, offset, options): + if viewInfo.state.kind != .drawing { + viewInfo = _makeItemView(item: item, state: state) + } + let time = state.pointee.globals.pointee.time + updateDrawingView( + &viewInfo, + options: options, + contentsScale: state.pointee.globals.pointee.environment.contentsScale, + content: .displayList( + list, + offset, + time + ), + sizeChanged: viewInfo.state.size != item.size + ) + viewInfo.nextUpdate = min(viewInfo.nextUpdate, list.nextUpdate(after: time)) + case let .drawing(contents, offset, options): + if viewInfo.state.kind != .drawing { + viewInfo = _makeItemView(item: item, state: state) + } + updateDrawingView( + &viewInfo, + options: options, + contentsScale: state.pointee.globals.pointee.environment.contentsScale, + content: .rbDisplayList(contents, offset), + sizeChanged: viewInfo.state.size != item.size + ) + case .view, .placeholder: + _openSwiftUIUnreachableCode() + } + if viewInfo.state.isContentGeometryEnabled { + localState.versions.transform.combine(with: item.version) + } + if !viewInfo.isInvalid, viewInfo.nextUpdate == .infinity { + viewInfo.seeds.content = content.seed + } + withUnsafePointer(to: localState) { statePtr in + updateState( + &viewInfo, + item: item, + size: size, + state: statePtr + ) + } + case let .effect(effect, _): + let contentChanged = viewInfo.seeds.content != item.version.seed + var changed = contentChanged + if !changed { + let transformChanged: Bool + if case let .transform(transform) = effect, + transform.projectionTransform != nil, + viewInfo.seeds.transform != state.pointee.versions.transform.seed { + transformChanged = true + } else { + transformChanged = false + } + changed = changed || transformChanged + } + guard changed else { + updateSizeDependentContent(&viewInfo, item: item, state: state) + return + } + viewInfo.seeds.content = item.version.seed + switch effect { + case .geometryGroup: + if viewInfo.state.kind != .geometry { + viewInfo = _makeItemView(item: item, state: state) + } + case .compositingGroup: + if viewInfo.state.kind != .compositing { + viewInfo = _makeItemView(item: item, state: state) + } + case let .platformGroup(factory): + if viewInfo.state.kind != .platformGroup { + viewInfo = _makeItemView(item: item, state: state) + } + let oldView = viewInfo.view + factory.updatePlatformGroup(&viewInfo.view) + let newView = viewInfo.view + if oldView !== newView { + definition.makePlatformView(view: newView, kind: .platformGroup) + viewInfo.reset(platform: self) + } + viewInfo.container = factory.platformGroupContainer(newView) + case .mask: + if viewInfo.state.kind != .mask { + viewInfo = _makeItemView(item: item, state: state) + } + case let .transform(transform): + if let projectionTransform = transform.projectionTransform { + if viewInfo.state.kind != .projection { + viewInfo = _makeItemView(item: item, state: state) + } + definition.setProjectionTransform( + projectionTransform.concatenating(ProjectionTransform(state.pointee.transform)), + projectionView: viewInfo.view + ) + } + case .platform: + if viewInfo.state.kind != .platformEffect { + viewInfo = _makeItemView(item: item, state: state) + } + default: + _openSwiftUIUnreachableCode() + } + updateState( + &viewInfo, + item: item, + size: item.size, + state: state + ) + case .empty, .states: + _openSwiftUIUnreachableCode() + } } - + + @inline(__always) + private func updateSizeDependentContent( + _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, + item: DisplayList.Item, + state: UnsafePointer + ) { + guard viewInfo.state.isContentGeometryEnabled else { + if viewInfo.state.kind == .drawing && viewInfo.state.size != item.size { + let drawable = viewInfo.view as! PlatformDrawable + viewInfo.isInvalid = !drawable.update(content: nil, required: true) + } + updateState( + &viewInfo, + item: item, + size: item.size, + state: state + ) + return + } + guard case let .content(content) = item.value else { + _openSwiftUIUnreachableCode() + } + var size = item.size + var localState = state.pointee + switch content.value { + case let .image(image): + adjustImageContentGeometry( + image: image, + state: &localState, + size: &size + ) + case let .shape(path, paint, style): + updateShapeView( + &viewInfo, + state: &localState, + size: &size, + path: path, + paint: paint, + style: style, + contentsChanged: false + ) + default: + viewInfo.seeds.content = .init() + Log.internalError( + "Invalid size-dependent display list content: %s, %s", + content.value.caseName, + "\(viewInfo.state.kind)" + ) + } + localState.versions.transform.combine(with: item.version) + withUnsafePointer(to: localState) { statePtr in + updateState( + &viewInfo, + item: item, + size: size, + state: statePtr + ) + } + } + func updateItemViewAsync( layer: inout DisplayList.ViewUpdater.AsyncLayer, index: DisplayList.Index, @@ -392,7 +695,272 @@ extension DisplayList.ViewUpdater.Platform { newItem: DisplayList.Item, newState: UnsafePointer ) -> Bool { - _openSwiftUIUnimplementedFailure() + switch (oldItem.value, newItem.value) { + case let (.content(oldContent), .content(newContent)): + guard oldContent.seed != newContent.seed else { + return updateSizeDependentContentAsync( + layer: &layer, + oldItem: oldItem, + oldState: oldState, + newItem: newItem, + newState: newState + ) + } + var oldLocalState = oldState.pointee + var newLocalState = newState.pointee + var oldSize = oldItem.size + var newSize = newItem.size + layer.isInvalid = false + switch (oldContent.value, newContent.value) { + case let (.color(oldColor), .color(newColor)): + layer.update( + DisplayList.ViewUpdater.BackgroundColor.self, + from: oldColor, + to: newColor + ) + case let (.image(oldImage), .image(newImage)): + #if canImport(QuartzCore) + guard ImageLayer.updateAsync( + layer: &layer, + oldImage: oldImage, + oldSize: oldSize, + newImage: newImage, + newSize: newSize + ) else { + return false + } + #else + _openSwiftUIPlatformUnimplementedWarning() + return false + #endif + adjustImageContentGeometry( + image: oldImage, + state: &oldLocalState, + size: &oldSize + ) + adjustImageContentGeometry( + image: newImage, + state: &newLocalState, + size: &newSize + ) + case let (.shape(oldPath, oldPaint, oldStyle), .shape(newPath, newPaint, newStyle)): + guard updateShapeViewAsync( + layer: &layer, + oldState: &oldLocalState, + oldSize: &oldSize, + oldPath: oldPath, + oldPaint: oldPaint, + oldStyle: oldStyle, + newState: &newLocalState, + newSize: &newSize, + newPath: newPath, + newPaint: newPaint, + newStyle: newStyle, + contentsChanged: true + ) else { + return false + } + case let (.flattened(_, _, oldOptions), .flattened(newList, newOffset, newOptions)): + let time = newState.pointee.globals.pointee.time + guard updateDrawingViewAsync( + &layer, + oldOptions: oldOptions, + newOptions: newOptions, + content: .displayList(newList, newOffset, time), + sizeChanged: oldItem.size != newItem.size, + newSize: newItem.size, + newState: newState + ) else { + return false + } + layer.nextUpdate = min(layer.nextUpdate, newList.nextUpdate(after: time)) + case let (.drawing(_, _, oldOptions), .drawing(newContents, newOffset, newOptions)): + guard updateDrawingViewAsync( + &layer, + oldOptions: oldOptions, + newOptions: newOptions, + content: .rbDisplayList(newContents, newOffset), + sizeChanged: oldItem.size != newItem.size, + newSize: newItem.size, + newState: newState + ) else { + return false + } + default: + return false + } + return withUnsafePointer(to: oldLocalState) { oldStatePtr in + withUnsafePointer(to: newLocalState) { newStatePtr in + updateStateAsync( + layer: &layer, + oldItem: oldItem, + oldSize: oldSize, + oldState: oldStatePtr, + newItem: newItem, + newSize: newSize, + newState: newStatePtr + ) + } + } + case let (.effect(oldEffect, _), .effect(newEffect, _)): + let contentChanged = oldItem.version != newItem.version + var changed = contentChanged + if !changed { + let transformChanged: Bool + if case let .transform(oldTransform) = oldEffect, + oldTransform.projectionTransform != nil, + oldState.pointee.versions.transform != newState.pointee.versions.transform { + transformChanged = true + } else { + transformChanged = false + } + changed = changed || transformChanged + } + guard changed else { + return updateSizeDependentContentAsync( + layer: &layer, + oldItem: oldItem, + oldState: oldState, + newItem: newItem, + newState: newState + ) + } + switch (oldEffect, newEffect) { + case let (.platformGroup(oldFactory), .platformGroup(newFactory)): + guard !oldFactory.needsUpdateFor(newValue: newFactory) else { + return false + } + case let (.transform(oldTransform), .transform(newTransform)): + if let oldProjectionTransform = oldTransform.projectionTransform, + let newProjectionTransform = newTransform.projectionTransform { + layer.update( + DisplayList.ViewUpdater.LayerProjectionTransform.self, + from: oldProjectionTransform.concatenating(ProjectionTransform(oldState.pointee.transform)), + to: newProjectionTransform.concatenating(ProjectionTransform(newState.pointee.transform)) + ) + } + default: + break + } + return updateStateAsync( + layer: &layer, + oldItem: oldItem, + oldSize: oldItem.size, + oldState: oldState, + newItem: newItem, + newSize: newItem.size, + newState: newState + ) + default: + return false + } + } + + @inline(__always) + private func updateSizeDependentContentAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldItem: DisplayList.Item, + oldState: UnsafePointer, + newItem: DisplayList.Item, + newState: UnsafePointer + ) -> Bool { + guard layer.isContentGeometryEnabled else { + return updateStateAsync( + layer: &layer, + oldItem: oldItem, + oldSize: oldItem.size, + oldState: oldState, + newItem: newItem, + newSize: newItem.size, + newState: newState + ) + } + guard case let .content(oldContent) = oldItem.value, + case let .content(newContent) = newItem.value + else { + return false + } + var oldLocalState = oldState.pointee + var newLocalState = newState.pointee + var oldSize = oldItem.size + var newSize = newItem.size + switch (oldContent.value, newContent.value) { + case let (.image(oldImage), .image(newImage)): + adjustImageContentGeometry( + image: oldImage, + state: &oldLocalState, + size: &oldSize + ) + adjustImageContentGeometry( + image: newImage, + state: &newLocalState, + size: &newSize + ) + case let (.shape(oldPath, _, _), .shape(newPath, _, _)): + adjustShapeContentGeometry( + layer: layer, + state: &oldLocalState, + size: &oldSize, + path: oldPath + ) + adjustShapeContentGeometry( + layer: layer, + state: &newLocalState, + size: &newSize, + path: newPath + ) + default: + return false + } + return withUnsafePointer(to: oldLocalState) { oldStatePtr in + withUnsafePointer(to: newLocalState) { newStatePtr in + updateStateAsync( + layer: &layer, + oldItem: oldItem, + oldSize: oldSize, + oldState: oldStatePtr, + newItem: newItem, + newSize: newSize, + newState: newStatePtr + ) + } + } + } + + @inline(__always) + private func adjustImageContentGeometry( + image: GraphicsImage, + state: inout DisplayList.ViewUpdater.Model.State, + size: inout CGSize + ) { + let orientation = image.bitmapOrientation + if orientation != .up { + state.transform = CGAffineTransform( + orientation: orientation, + in: size + ).concatenating(state.transform) + size = size.apply(orientation) + } + } + + @inline(__always) + private func adjustShapeContentGeometry( + layer: DisplayList.ViewUpdater.AsyncLayer, + state: inout DisplayList.ViewUpdater.Model.State, + size: inout CGSize, + path: Path + ) { + let bounds = ShapeLayerHelper.makeLayerBounds( + size: size, + path: path, + layerType: type(of: layer.layer), + contentsScale: state.globals.pointee.environment.contentsScale + ) + state.transform = state.transform.translatedBy( + x: bounds.origin.x, + y: bounds.origin.y + ) + size = bounds.size } func updateState( @@ -477,7 +1045,73 @@ extension DisplayList.ViewUpdater.Platform { newSize: CGSize, newState: UnsafePointer ) -> Bool { - _openSwiftUIUnimplementedFailure() + guard oldState.pointee.properties == newState.pointee.properties else { + return false + } + layer.update( + DisplayList.ViewUpdater.OpacityLayer.self, + from: oldState.pointee.opacity, + to: newState.pointee.opacity + ) + guard oldState.pointee.versions.blend == newState.pointee.versions.blend else { + return false + } + if oldState.pointee.versions.filters != newState.pointee.versions.filters { + var oldFilters = oldState.pointee.filters + var newFilters = newState.pointee.filters + if layer.kind == .drawing { + let oldColor = oldFilters.popColorMultiply(drawable: layer.layer.delegate as? PlatformDrawable) + let newColor = newFilters.popColorMultiply(drawable: layer.layer.delegate as? PlatformDrawable) + layer.update( + DisplayList.ViewUpdater.ContentsMultiplyColor.self, + from: oldColor, + to: newColor + ) + } + guard GraphicsFilter.updateAsync( + layer: &layer, + oldFilters: oldFilters, + newFilters: newFilters + ) else { + return false + } + } + if oldState.pointee.versions.clips != newState.pointee.versions.clips || + oldState.pointee.versions.transform != newState.pointee.versions.transform { + guard updateClipShapesAsync( + asyncLayer: &layer, + oldState: oldState, + newState: newState + ) else { + return false + } + } + guard let boundsChanged = updateGeometryAsync( + asyncLayer: &layer, + oldItem: oldItem, + oldSize: oldSize, + oldState: oldState, + newItem: newItem, + newSize: newSize, + newState: newState + ) else { + return false + } + if boundsChanged || + oldState.pointee.versions.shadow != newState.pointee.versions.shadow || + oldItem.version != newItem.version { + guard updateShadowAsync( + asyncLayer: &layer, + oldState: oldState, + oldItem: oldItem, + newState: newState, + newItem: newItem, + boundsChanged: boundsChanged + ) else { + return false + } + } + return true } func _makeItemView( @@ -560,8 +1194,10 @@ extension DisplayList.ViewUpdater.Platform { container: view, state: .init(kind: .platformLayer) ) - case .text: - let view = definition.makeDrawingView(options: .init(base: .init())) as AnyObject + case let .text(text, _): + var options = RasterizationOptions() + options.isAccelerated = text.needsDrawingGroup + let view = definition.makeDrawingView(options: .init(base: options)) as AnyObject return DisplayList.ViewUpdater.ViewInfo( view: view, layer: viewLayer(view), @@ -587,8 +1223,8 @@ extension DisplayList.ViewUpdater.Platform { let info = DisplayList.ViewUpdater.ViewInfo(platform: self, kind: .compositing) #if canImport(QuartzCore) let layer = info.layer - info.layer.allowsGroupOpacity = true - info.layer.allowsGroupBlending = true + layer.allowsGroupOpacity = true + layer.allowsGroupBlending = true #else _openSwiftUIPlatformUnimplementedWarning() #endif @@ -769,7 +1405,61 @@ extension DisplayList.ViewUpdater.Platform { newStyle: FillStyle, contentsChanged: Bool ) -> Bool { - _openSwiftUIUnimplementedFailure() + let currentLayerType = type(of: layer.layer) + let oldOriginalSize = oldSize + let newOriginalSize = newSize + let oldContentsScale = oldState.globals.pointee.environment.contentsScale + let newContentsScale = newState.globals.pointee.environment.contentsScale + let oldBounds = ShapeLayerHelper.makeLayerBounds( + size: oldOriginalSize, + path: oldPath, + layerType: currentLayerType, + contentsScale: oldContentsScale + ) + let newBounds = ShapeLayerHelper.makeLayerBounds( + size: newOriginalSize, + path: newPath, + layerType: currentLayerType, + contentsScale: newContentsScale + ) + if contentsChanged { + var oldHelper = ShapeLayerHelper( + layer: layer.layer, + layerType: currentLayerType, + path: oldPath, + origin: oldBounds.origin, + paint: oldPaint, + paintBounds: CGRect( + origin: CGPoint(x: -oldBounds.origin.x, y: -oldBounds.origin.y), + size: oldOriginalSize + ), + style: oldStyle, + contentsScale: oldContentsScale, + mayClip: !oldState.hasDODEffects + ) + var newHelper = ShapeLayerHelper( + layer: layer.layer, + layerType: currentLayerType, + path: newPath, + origin: newBounds.origin, + paint: newPaint, + paintBounds: CGRect( + origin: CGPoint(x: -newBounds.origin.x, y: -newBounds.origin.y), + size: newOriginalSize + ), + style: newStyle, + contentsScale: newContentsScale, + mayClip: !newState.hasDODEffects + ) + guard ShapeLayerHelper.updateAsync(layer: &layer, old: &oldHelper, new: &newHelper) else { + return false + } + } + oldState.transform = oldState.transform.translatedBy(x: oldBounds.origin.x, y: oldBounds.origin.y) + newState.transform = newState.transform.translatedBy(x: newBounds.origin.x, y: newBounds.origin.y) + oldSize = oldBounds.size + newSize = newBounds.size + return true } private func updateDrawingViewAsync( @@ -1075,7 +1765,48 @@ extension DisplayList.ViewUpdater.Platform { oldState: UnsafePointer, newState: UnsafePointer ) -> Bool { - _openSwiftUIUnimplementedFailure() + #if canImport(QuartzCore) + guard !oldState.pointee.clips.isEmpty || !newState.pointee.clips.isEmpty else { + return true + } + if asyncLayer.isClipRectEnabled { + guard let oldClipRect = oldState.pointee.clipRect(), + let newClipRect = newState.pointee.clipRect(), + oldClipRect.style == newClipRect.style + else { + return false + } + asyncLayer.update( + DisplayList.ViewUpdater.CornerRadiusLayer.self, + from: oldClipRect.clampedCornerRadius, + to: newClipRect.clampedCornerRadius + ) + return true + } else { + guard newState.pointee.clipRect() == nil, + let maskLayer = asyncLayer.layer.mask else { + return false + } + var maskAsyncLayer = DisplayList.ViewUpdater.AsyncLayer( + layer: maskLayer, + cache: asyncLayer.cache, + kind: asyncLayer.kind, + flags: asyncLayer.flags, + nextUpdate: asyncLayer.nextUpdate, + isInvalid: asyncLayer.isInvalid + ) + return MaskLayer.updateClipsAsync( + layer: &maskAsyncLayer, + oldClips: oldState.pointee.clips, + newClips: newState.pointee.clips, + oldTransform: oldState.pointee.transform.inverted(), + newTransform: newState.pointee.transform.inverted() + ) + } + #else + _openSwiftUIUnimplementedWarning() + return false + #endif } private func updateGeometryAsync( @@ -1087,7 +1818,80 @@ extension DisplayList.ViewUpdater.Platform { newSize: CGSize, newState: UnsafePointer ) -> Bool? { - _openSwiftUIUnimplementedFailure() + #if canImport(QuartzCore) + var oldBounds = CGRect(origin: .zero, size: oldSize) + var newBounds = CGRect(origin: .zero, size: newSize) + var oldPosition = CGPoint( + x: oldState.pointee.transform.tx, + y: oldState.pointee.transform.ty + ) + var newPosition = CGPoint( + x: newState.pointee.transform.tx, + y: newState.pointee.transform.ty + ) + + if asyncLayer.isClipRectEnabled, + let oldClipRect = oldState.pointee.clipRect(), + let newClipRect = newState.pointee.clipRect() { + oldBounds = oldClipRect.rect + newBounds = newClipRect.rect + oldPosition.x += oldBounds.origin.x + oldPosition.y += oldBounds.origin.y + newPosition.x += newBounds.origin.x + newPosition.y += newBounds.origin.y + } + + let boundsChanged = oldBounds != newBounds + if boundsChanged { + switch asyncLayer.kind { + case .platformView, .platformGroup, .platformLayer: + return nil + default: + break + } + asyncLayer.setValue( + DisplayList.ViewUpdater.BoundsLayer.self, + to: newBounds + ) + if asyncLayer.kind == .mask { + var maskLayer = DisplayList.ViewUpdater.AsyncLayer( + layer: asyncLayer.layer.mask!, + cache: asyncLayer.cache, + kind: asyncLayer.kind, + flags: asyncLayer.flags, + nextUpdate: asyncLayer.nextUpdate, + isInvalid: asyncLayer.isInvalid + ) + maskLayer.setValue( + DisplayList.ViewUpdater.BoundsLayer.self, + to: newBounds + ) + } + } + guard !asyncLayer.isProjectionGeometryEnabled else { + return boundsChanged + } + asyncLayer.update( + DisplayList.ViewUpdater.PositionLayer.self, + from: oldPosition, + to: newPosition + ) + var oldTransform = oldState.pointee.transform + oldTransform.tx = 0 + oldTransform.ty = 0 + var newTransform = newState.pointee.transform + newTransform.tx = 0 + newTransform.ty = 0 + asyncLayer.update( + DisplayList.ViewUpdater.AffineTransformLayer.self, + from: oldTransform, + to: newTransform + ) + return boundsChanged + #else + _openSwiftUIUnimplementedWarning() + return false + #endif } private func updateShadowAsync( @@ -1098,7 +1902,84 @@ extension DisplayList.ViewUpdater.Platform { newItem: DisplayList.Item, boundsChanged: Bool ) -> Bool { - _openSwiftUIUnimplementedFailure() + #if canImport(QuartzCore) + let oldShadow = oldState.pointee.shadow?.value + let newShadow = newState.pointee.shadow?.value + switch (oldShadow, newShadow) { + case (nil, nil): + return true + case (nil, _?), (_?, nil): + return false + case let (oldShadow?, newShadow?): + guard boundsChanged || oldShadow != newShadow else { + return true + } + guard asyncLayer.kind != .inherited, + case let .content(oldContent) = oldItem.value, + case let .content(newContent) = newItem.value + else { + return asyncLayer.updateShadowStyle( + oldShadow: oldShadow, + newShadow: newShadow + ) + } + switch (oldContent.value, newContent.value) { + case let (.color(oldColor), .color(newColor)): + return _updateShadowAsync( + layer: &asyncLayer, + oldShadow: oldShadow, + newShadow: newShadow, + oldPaintOpacity: oldColor.opacity, + newPaintOpacity: newColor.opacity + ) + case let (.shape(oldPath, oldPaint, _), .shape(newPath, newPaint, _)): + let layerType = type(of: asyncLayer.layer) + let oldBounds = ShapeLayerHelper.makeLayerBounds( + size: oldItem.size, + path: oldPath, + layerType: layerType, + contentsScale: oldState.pointee.globals.pointee.environment.contentsScale + ) + let newBounds = ShapeLayerHelper.makeLayerBounds( + size: newItem.size, + path: newPath, + layerType: layerType, + contentsScale: newState.pointee.globals.pointee.environment.contentsScale + ) + var oldHelper = ShapeLayerShadowHelper( + platform: self, + layer: asyncLayer.layer, + path: oldPath, + offset: oldBounds.origin, + shadow: oldShadow, + updateShape: false + ) + var newHelper = ShapeLayerShadowHelper( + platform: self, + layer: asyncLayer.layer, + path: newPath, + offset: newBounds.origin, + shadow: newShadow, + updateShape: false + ) + return ShapeLayerShadowHelper.updateAsync( + layer: &asyncLayer, + old: &oldHelper, + new: &newHelper, + oldPaint: oldPaint, + newPaint: newPaint + ) + default: + return asyncLayer.updateShadowStyle( + oldShadow: oldShadow, + newShadow: newShadow + ) + } + } + #else + _openSwiftUIUnimplementedWarning() + return false + #endif } func updateDrawingView( @@ -1120,129 +2001,10 @@ extension DisplayList.ViewUpdater.Platform { return drawable } - // TBA - @inline(__always) - private func updateContentView( - _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, - content: DisplayList.Content, - item: DisplayList.Item, - state: UnsafePointer - ) { - switch content.value { - case let .color(resolved): - #if canImport(QuartzCore) - let cgColor = resolved.cgColor - viewLayer(viewInfo.view).backgroundColor = cgColor - #else - _openSwiftUIPlatformUnimplementedWarning() - #endif - case .backdrop, .chameleonColor, .image, .shadow, .view, .placeholder: - break - case let .shape(path, _, _): - definition.setPath(path, shapeView: viewInfo.view) - case let .platformView(factory): - var view = viewInfo.view - factory.updatePlatformView(&view) - updateViewReference(&viewInfo, view: view, kind: .platformView) - case let .platformLayer(factory): - #if canImport(QuartzCore) - if let layer = viewInfo.view as? CALayer { - factory.updatePlatformLayer(layer) - } - #endif - case .text, .flattened, .drawing: - updateDrawingContent(&viewInfo, content: content, item: item, state: state) - } - } - - // TBA - @inline(__always) - private func updateEffectView( - _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, - effect: DisplayList.Effect, - item: DisplayList.Item, - state: UnsafePointer - ) { - switch effect { - case let .transform(transform): - if case let .projection(projectionTransform) = transform { - definition.setProjectionTransform( - projectionTransform, - projectionView: viewInfo.view - ) - } - case let .platformGroup(factory): - var view = viewInfo.view - factory.updatePlatformGroup(&view) - updateViewReference(&viewInfo, view: view, kind: .platformGroup) - viewInfo.container = factory.platformGroupContainer(viewInfo.view) - case .identity, .geometryGroup, .compositingGroup, .backdropGroup, .archive, - .properties, .opacity, .blendMode, .clip, .mask, .filter, .animation, - .contentTransition, .view, .accessibility, .platform, .state, - .interpolatorRoot, .interpolatorLayer, .interpolatorAnimation: - break - } - } - - // TBA - @inline(__always) - private func updateDrawingContent( - _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, - content: DisplayList.Content, - item: DisplayList.Item, - state: UnsafePointer - ) { - _openSwiftUIUnimplementedFailure() -// let contentsScale = state.pointee.globals.pointee.environment.contentsScale -// let options = drawingOptions(for: content, state: state) -// var view = viewInfo.view -// let drawable = updateDrawingView( -// &view, -// options: options, -// contentsScale: contentsScale -// ) -// updateViewReference(&viewInfo, view: view, kind: .drawing) -// var drawableContent = PlatformDrawableContent() -// switch content.value { -// case let .flattened(list, offset, _): -// drawableContent.storage = .displayList( -// list, -// offset, -// state.pointee.globals.pointee.time -// ) -// case let .drawing(contents, offset, _): -// drawableContent.storage = .rbDisplayList(contents, offset) -// case .text: -// drawableContent.storage = .empty -// default: -// return -// } -// _ = drawable.update( -// content: drawableContent, -// required: item.features.contains(.required) -// ) - } - - // TBA - @inline(__always) - private func updateViewReference( - _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, - view: AnyObject, - kind: PlatformViewDefinition.ViewKind - ) { - guard viewInfo.view !== view else { - return - } - viewInfo.view = view - viewInfo.container = view - viewInfo.reset(platform: self) - } - func forEachChild( of viewInfo: DisplayList.ViewUpdater.ViewInfo, do body: (AnyObject) -> Void ) { - #if canImport(Darwin) let kind = viewInfo.state.kind if kind.isContainer { for subview in subviews(viewInfo.container) { @@ -1252,10 +2014,9 @@ extension DisplayList.ViewUpdater.Platform { if kind == .mask, let maskView = maskView(viewInfo.view) { for subview in subviews(maskView) { - body(subview as AnyObject) + body(subview) } } - #endif } } @@ -1283,7 +2044,7 @@ extension DisplayList.GraphicsRenderer { } } -// MARK: - GraphicsFilter + Platform Filters +// MARK: - GraphicsFilter + Platform Filters [TODO] extension [GraphicsFilter] { fileprivate mutating func popColorMultiply( @@ -1313,3 +2074,14 @@ extension [GraphicsFilter] { // _openSwiftUIUnimplementedFailure() // } //} + +extension GraphicsFilter { + static func updateAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldFilters: [GraphicsFilter], + newFilters: [GraphicsFilter] + ) -> Bool { + _openSwiftUIUnimplementedWarning() + return false + } +} diff --git a/Sources/OpenSwiftUICore/Shape/MaskLayer.swift b/Sources/OpenSwiftUICore/Shape/MaskLayer.swift index fbafcbc53..3347d25f3 100644 --- a/Sources/OpenSwiftUICore/Shape/MaskLayer.swift +++ b/Sources/OpenSwiftUICore/Shape/MaskLayer.swift @@ -112,6 +112,17 @@ final class MaskLayer: CAShapeLayer { finalTransform.ty += position.x * finalTransform.b + position.y * finalTransform.d - position.y layer.setAffineTransform(finalTransform) } + + static func updateClipsAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldClips: [DisplayList.ViewUpdater.Model.Clip], + newClips: [DisplayList.ViewUpdater.Model.Clip], + oldTransform: CGAffineTransform, + newTransform: CGAffineTransform + ) -> Bool { + _openSwiftUIUnimplementedWarning() + return false + } } #endif diff --git a/Sources/OpenSwiftUICore/Shape/ShapeLayer.swift b/Sources/OpenSwiftUICore/Shape/ShapeLayer.swift index 30a962bbf..a7c318833 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeLayer.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeLayer.swift @@ -46,6 +46,24 @@ struct ShapeLayerHelper: ResolvedPaintVisitor { ) -> CGRect { _openSwiftUIUnimplementedFailure() } + + static func updateAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + old: UnsafeMutablePointer, + new: UnsafeMutablePointer + ) -> Bool { + guard old.pointee.style.isEOFilled == new.pointee.style.isEOFilled, + old.pointee.style.isAntialiased == new.pointee.style.isAntialiased, + old.pointee.mayClip == new.pointee.mayClip + else { + return false + } + return withUnsafeMutablePointer(to: &layer) { layer in + var helper = ShapeLayerAsyncHelper(layer: layer, old: old, new: new, result: false) + old.pointee.paint.visit(&helper) + return helper.result + } + } } // MARK: - ShapeLayerShadowHelper [WIP] @@ -61,6 +79,47 @@ struct ShapeLayerShadowHelper: ResolvedPaintVisitor { mutating func visitPaint(_ paint: P) where P: ResolvedPaint { _openSwiftUIUnimplementedFailure() } + + @inline(__always) + static func updateAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + old: UnsafeMutablePointer, + new: UnsafeMutablePointer, + oldPaint: AnyResolvedPaint, + newPaint: AnyResolvedPaint + ) -> Bool { + return withUnsafeMutablePointer(to: &layer) { layer in + var helper = ShapeLayerAsyncShadowHelper( + layer: layer, + old: old, + new: new, + newPaint: newPaint, + result: false + ) + oldPaint.visit(&helper) + return helper.result + } + } +} + +func _updateShadowAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldShadow: ResolvedShadowStyle?, + newShadow: ResolvedShadowStyle?, + oldPaintOpacity: Float, + newPaintOpacity: Float +) -> Bool { + var oldShadow = oldShadow + var newShadow = newShadow + if var shadow = oldShadow { + shadow.color = shadow.color.multiplyingOpacity(by: oldPaintOpacity) + oldShadow = shadow + } + if var shadow = newShadow { + shadow.color = shadow.color.multiplyingOpacity(by: newPaintOpacity) + newShadow = shadow + } + return layer.updateShadowStyle(oldShadow: oldShadow, newShadow: newShadow) } // MARK: - Async Shape Helpers @@ -88,7 +147,7 @@ private struct ShapeLayerAsyncShadowHelper: ResolvedPaintVisitor { } } -// FIXME: ShapeLayerShadowHelper & ShapeLayerAsyncShadowHelper +// MARK: - AsyncLayer + shadow extension DisplayList.ViewUpdater.AsyncLayer { @discardableResult @@ -99,77 +158,48 @@ extension DisplayList.ViewUpdater.AsyncLayer { switch (oldShadow, newShadow) { case (nil, nil): return true - case let (oldShadow?, newShadow?): - guard oldShadow.kind == newShadow.kind else { - return false - } - update(ShadowOffsetProperty.self, from: oldShadow.offset, to: newShadow.offset) - update(ShadowRadiusProperty.self, from: oldShadow.radius, to: newShadow.radius) - update(ShadowColorProperty.self, from: oldShadow.color, to: newShadow.color) - return !isInvalid + case let (oldShadow?, newShadow?) where oldShadow.kind == newShadow.kind: + update(DisplayList.ViewUpdater.ShadowOffsetProperty.self, from: oldShadow.offset, to: newShadow.offset) + update(DisplayList.ViewUpdater.ShadowRadiusProperty.self, from: oldShadow.radius, to: newShadow.radius) + update(DisplayList.ViewUpdater.ShadowColorProperty.self, from: oldShadow.color, to: newShadow.color) + return true default: return false } } +} - private mutating func update( - _ property: P.Type, - from oldValue: P.Value, - to newValue: P.Value - ) where P: Property, P.Value: Equatable { - guard oldValue != newValue else { - return - } - setValue(newValue, for: property) - } +extension DisplayList.ViewUpdater { + struct ShadowOffsetProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowOffset" - private mutating func setValue( - _ value: P.Value, - for property: P.Type - ) where P: Property { - guard !isInvalid else { - return + static func boxValue(_ value: CGSize) -> NSObject { + #if canImport(Darwin) + NSValue(size: value) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif } - cache.pointee.setAsyncValue( - P.boxValue(value), - for: P.keyPath, - in: layer, - usingPresentationModifier: P.supportsPresentationModifier - ) } -} - -private struct ShadowColorProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowColor" + + struct ShadowRadiusProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowRadius" - static func boxValue(_ value: Color.Resolved) -> NSObject { - #if canImport(Darwin) - return value.cgColor as! NSObject - #else - return NSObject() - #endif + static func boxValue(_ value: Double) -> NSObject { + NSNumber(value: value) + } } -} - -private struct ShadowRadiusProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowRadius" - static func boxValue(_ value: CGFloat) -> NSObject { - NSNumber(value: Double(value)) - } -} + struct ShadowColorProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowColor" -private struct ShadowOffsetProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowOffset" - - static func boxValue(_ value: CGSize) -> NSObject { - #if canImport(Darwin) -// return NSValue(size: value) - // FIXME - return NSObject() - #else - return NSObject() - #endif + static func boxValue(_ value: Color.Resolved) -> NSObject { + #if canImport(Darwin) + unsafeDowncast(value.cgColor, to: NSObject.self) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } } } diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift index fa340c2c1..1ab76ba3b 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift @@ -444,10 +444,14 @@ final package class _ShapeStyle_InterpolatorGroup: DisplayList.InterpolatorGroup _openSwiftUIUnimplementedWarning() return .interpolatorData(group: .init(), serial: 0) } -} -extension DisplayList { - struct InterpolatorLayer {} + override func nextUpdate(after time: Time) -> Time { + var nextUpdate = Time.infinity + for layer in layers { + nextUpdate = min(nextUpdate, layer.state.nextUpdate(after: time)) + } + return nextUpdate + } } diff --git a/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift b/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift index 4257d5cba..a2c4cc19b 100644 --- a/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift +++ b/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift @@ -132,15 +132,16 @@ final package class ImageLayer: CALayer { add(animation, forKey: nil) } -// func updateAsync( -// layer: DisplayList.ViewUpdater.AsyncLayer, -// oldImage: GraphicsImage, -// oldSize: CGSize, -// newImage: GraphicsImage, -// newSize: CGSize -// ) -> Bool { -// _openSwiftUIUnimplementedFailure() -// } + static func updateAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldImage: GraphicsImage, + oldSize: CGSize, + newImage: GraphicsImage, + newSize: CGSize + ) -> Bool { + _openSwiftUIUnimplementedWarning() + return false + } } // MARK: - GraphicsImage + LayerStretch diff --git a/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift b/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift index abe3ab104..daa834ac6 100644 --- a/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift +++ b/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift @@ -657,7 +657,15 @@ package class ResolvedStyledText: CustomStringConvertible { equivalentDate date: Date, reduceFrequency: Bool = false ) -> Time { - _openSwiftUIUnimplementedFailure() + let nextDate = schedule?.nextEntry( + after: date, + mode: reduceFrequency ? .lowFrequency : .normal, + limit: .minimumTimelineScheduleLimit + ) + guard let nextDate else { + return .infinity + } + return time + nextDate.timeIntervalSince(date) } final package var updatesAsynchronously: Bool { diff --git a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h index 95326c8cf..8e153392a 100644 --- a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h +++ b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h @@ -6,9 +6,15 @@ #if __has_include() +#import #import @interface CABackdropLayer : CALayer + +@property (nonatomic) CGFloat scale; +@property (nonatomic) BOOL allowsInPlaceFiltering; +@property (nonatomic, copy, nullable) NSString *groupName; + @end #endif /* __has_include() */
(_ paint: P) where P: ResolvedPaint { _openSwiftUIUnimplementedFailure() } + + @inline(__always) + static func updateAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + old: UnsafeMutablePointer, + new: UnsafeMutablePointer, + oldPaint: AnyResolvedPaint, + newPaint: AnyResolvedPaint + ) -> Bool { + return withUnsafeMutablePointer(to: &layer) { layer in + var helper = ShapeLayerAsyncShadowHelper( + layer: layer, + old: old, + new: new, + newPaint: newPaint, + result: false + ) + oldPaint.visit(&helper) + return helper.result + } + } +} + +func _updateShadowAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldShadow: ResolvedShadowStyle?, + newShadow: ResolvedShadowStyle?, + oldPaintOpacity: Float, + newPaintOpacity: Float +) -> Bool { + var oldShadow = oldShadow + var newShadow = newShadow + if var shadow = oldShadow { + shadow.color = shadow.color.multiplyingOpacity(by: oldPaintOpacity) + oldShadow = shadow + } + if var shadow = newShadow { + shadow.color = shadow.color.multiplyingOpacity(by: newPaintOpacity) + newShadow = shadow + } + return layer.updateShadowStyle(oldShadow: oldShadow, newShadow: newShadow) } // MARK: - Async Shape Helpers @@ -88,7 +147,7 @@ private struct ShapeLayerAsyncShadowHelper: ResolvedPaintVisitor { } } -// FIXME: ShapeLayerShadowHelper & ShapeLayerAsyncShadowHelper +// MARK: - AsyncLayer + shadow extension DisplayList.ViewUpdater.AsyncLayer { @discardableResult @@ -99,77 +158,48 @@ extension DisplayList.ViewUpdater.AsyncLayer { switch (oldShadow, newShadow) { case (nil, nil): return true - case let (oldShadow?, newShadow?): - guard oldShadow.kind == newShadow.kind else { - return false - } - update(ShadowOffsetProperty.self, from: oldShadow.offset, to: newShadow.offset) - update(ShadowRadiusProperty.self, from: oldShadow.radius, to: newShadow.radius) - update(ShadowColorProperty.self, from: oldShadow.color, to: newShadow.color) - return !isInvalid + case let (oldShadow?, newShadow?) where oldShadow.kind == newShadow.kind: + update(DisplayList.ViewUpdater.ShadowOffsetProperty.self, from: oldShadow.offset, to: newShadow.offset) + update(DisplayList.ViewUpdater.ShadowRadiusProperty.self, from: oldShadow.radius, to: newShadow.radius) + update(DisplayList.ViewUpdater.ShadowColorProperty.self, from: oldShadow.color, to: newShadow.color) + return true default: return false } } +} - private mutating func update( - _ property: P.Type, - from oldValue: P.Value, - to newValue: P.Value - ) where P: Property, P.Value: Equatable { - guard oldValue != newValue else { - return - } - setValue(newValue, for: property) - } +extension DisplayList.ViewUpdater { + struct ShadowOffsetProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowOffset" - private mutating func setValue( - _ value: P.Value, - for property: P.Type - ) where P: Property { - guard !isInvalid else { - return + static func boxValue(_ value: CGSize) -> NSObject { + #if canImport(Darwin) + NSValue(size: value) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif } - cache.pointee.setAsyncValue( - P.boxValue(value), - for: P.keyPath, - in: layer, - usingPresentationModifier: P.supportsPresentationModifier - ) } -} - -private struct ShadowColorProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowColor" + + struct ShadowRadiusProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowRadius" - static func boxValue(_ value: Color.Resolved) -> NSObject { - #if canImport(Darwin) - return value.cgColor as! NSObject - #else - return NSObject() - #endif + static func boxValue(_ value: Double) -> NSObject { + NSNumber(value: value) + } } -} - -private struct ShadowRadiusProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowRadius" - static func boxValue(_ value: CGFloat) -> NSObject { - NSNumber(value: Double(value)) - } -} + struct ShadowColorProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowColor" -private struct ShadowOffsetProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowOffset" - - static func boxValue(_ value: CGSize) -> NSObject { - #if canImport(Darwin) -// return NSValue(size: value) - // FIXME - return NSObject() - #else - return NSObject() - #endif + static func boxValue(_ value: Color.Resolved) -> NSObject { + #if canImport(Darwin) + unsafeDowncast(value.cgColor, to: NSObject.self) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } } } diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift index fa340c2c1..1ab76ba3b 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift @@ -444,10 +444,14 @@ final package class _ShapeStyle_InterpolatorGroup: DisplayList.InterpolatorGroup _openSwiftUIUnimplementedWarning() return .interpolatorData(group: .init(), serial: 0) } -} -extension DisplayList { - struct InterpolatorLayer {} + override func nextUpdate(after time: Time) -> Time { + var nextUpdate = Time.infinity + for layer in layers { + nextUpdate = min(nextUpdate, layer.state.nextUpdate(after: time)) + } + return nextUpdate + } } diff --git a/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift b/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift index 4257d5cba..a2c4cc19b 100644 --- a/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift +++ b/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift @@ -132,15 +132,16 @@ final package class ImageLayer: CALayer { add(animation, forKey: nil) } -// func updateAsync( -// layer: DisplayList.ViewUpdater.AsyncLayer, -// oldImage: GraphicsImage, -// oldSize: CGSize, -// newImage: GraphicsImage, -// newSize: CGSize -// ) -> Bool { -// _openSwiftUIUnimplementedFailure() -// } + static func updateAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldImage: GraphicsImage, + oldSize: CGSize, + newImage: GraphicsImage, + newSize: CGSize + ) -> Bool { + _openSwiftUIUnimplementedWarning() + return false + } } // MARK: - GraphicsImage + LayerStretch diff --git a/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift b/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift index abe3ab104..daa834ac6 100644 --- a/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift +++ b/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift @@ -657,7 +657,15 @@ package class ResolvedStyledText: CustomStringConvertible { equivalentDate date: Date, reduceFrequency: Bool = false ) -> Time { - _openSwiftUIUnimplementedFailure() + let nextDate = schedule?.nextEntry( + after: date, + mode: reduceFrequency ? .lowFrequency : .normal, + limit: .minimumTimelineScheduleLimit + ) + guard let nextDate else { + return .infinity + } + return time + nextDate.timeIntervalSince(date) } final package var updatesAsynchronously: Bool { diff --git a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h index 95326c8cf..8e153392a 100644 --- a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h +++ b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h @@ -6,9 +6,15 @@ #if __has_include() +#import #import @interface CABackdropLayer : CALayer + +@property (nonatomic) CGFloat scale; +@property (nonatomic) BOOL allowsInPlaceFiltering; +@property (nonatomic, copy, nullable) NSString *groupName; + @end #endif /* __has_include() */
( - _ property: P.Type, - from oldValue: P.Value, - to newValue: P.Value - ) where P: Property, P.Value: Equatable { - guard oldValue != newValue else { - return - } - setValue(newValue, for: property) - } +extension DisplayList.ViewUpdater { + struct ShadowOffsetProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowOffset" - private mutating func setValue
( - _ value: P.Value, - for property: P.Type - ) where P: Property { - guard !isInvalid else { - return + static func boxValue(_ value: CGSize) -> NSObject { + #if canImport(Darwin) + NSValue(size: value) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif } - cache.pointee.setAsyncValue( - P.boxValue(value), - for: P.keyPath, - in: layer, - usingPresentationModifier: P.supportsPresentationModifier - ) } -} - -private struct ShadowColorProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowColor" + + struct ShadowRadiusProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowRadius" - static func boxValue(_ value: Color.Resolved) -> NSObject { - #if canImport(Darwin) - return value.cgColor as! NSObject - #else - return NSObject() - #endif + static func boxValue(_ value: Double) -> NSObject { + NSNumber(value: value) + } } -} - -private struct ShadowRadiusProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowRadius" - static func boxValue(_ value: CGFloat) -> NSObject { - NSNumber(value: Double(value)) - } -} + struct ShadowColorProperty: DisplayList.ViewUpdater.AsyncLayer.Property { + static let keyPath = "shadowColor" -private struct ShadowOffsetProperty: DisplayList.ViewUpdater.AsyncLayer.Property { - static let keyPath = "shadowOffset" - - static func boxValue(_ value: CGSize) -> NSObject { - #if canImport(Darwin) -// return NSValue(size: value) - // FIXME - return NSObject() - #else - return NSObject() - #endif + static func boxValue(_ value: Color.Resolved) -> NSObject { + #if canImport(Darwin) + unsafeDowncast(value.cgColor, to: NSObject.self) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } } } diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift index fa340c2c1..1ab76ba3b 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift @@ -444,10 +444,14 @@ final package class _ShapeStyle_InterpolatorGroup: DisplayList.InterpolatorGroup _openSwiftUIUnimplementedWarning() return .interpolatorData(group: .init(), serial: 0) } -} -extension DisplayList { - struct InterpolatorLayer {} + override func nextUpdate(after time: Time) -> Time { + var nextUpdate = Time.infinity + for layer in layers { + nextUpdate = min(nextUpdate, layer.state.nextUpdate(after: time)) + } + return nextUpdate + } } diff --git a/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift b/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift index 4257d5cba..a2c4cc19b 100644 --- a/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift +++ b/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift @@ -132,15 +132,16 @@ final package class ImageLayer: CALayer { add(animation, forKey: nil) } -// func updateAsync( -// layer: DisplayList.ViewUpdater.AsyncLayer, -// oldImage: GraphicsImage, -// oldSize: CGSize, -// newImage: GraphicsImage, -// newSize: CGSize -// ) -> Bool { -// _openSwiftUIUnimplementedFailure() -// } + static func updateAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldImage: GraphicsImage, + oldSize: CGSize, + newImage: GraphicsImage, + newSize: CGSize + ) -> Bool { + _openSwiftUIUnimplementedWarning() + return false + } } // MARK: - GraphicsImage + LayerStretch diff --git a/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift b/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift index abe3ab104..daa834ac6 100644 --- a/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift +++ b/Sources/OpenSwiftUICore/View/Text/Text/Text+View.swift @@ -657,7 +657,15 @@ package class ResolvedStyledText: CustomStringConvertible { equivalentDate date: Date, reduceFrequency: Bool = false ) -> Time { - _openSwiftUIUnimplementedFailure() + let nextDate = schedule?.nextEntry( + after: date, + mode: reduceFrequency ? .lowFrequency : .normal, + limit: .minimumTimelineScheduleLimit + ) + guard let nextDate else { + return .infinity + } + return time + nextDate.timeIntervalSince(date) } final package var updatesAsynchronously: Bool { diff --git a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h index 95326c8cf..8e153392a 100644 --- a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h +++ b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h @@ -6,9 +6,15 @@ #if __has_include() +#import #import @interface CABackdropLayer : CALayer + +@property (nonatomic) CGFloat scale; +@property (nonatomic) BOOL allowsInPlaceFiltering; +@property (nonatomic, copy, nullable) NSString *groupName; + @end #endif /* __has_include() */