diff --git a/Example/README.md b/Example/README.md index dcfc6ffc3..dcc27d4f6 100644 --- a/Example/README.md +++ b/Example/README.md @@ -26,7 +26,19 @@ export OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_USE_BINARY=1 ## Generate Project +The recommended setup path is the local setup script: + +```shell +./setup.sh +``` + +The script trusts and installs the tools declared by `Example/mise.toml`, then runs Tuist through `mise exec` so the pinned Tuist version is used. + +To run the steps manually: + ```shell +mise trust mise.toml +mise install mise exec -- tuist install mise exec -- tuist generate --no-open ``` diff --git a/Example/Shared/ContentView.swift b/Example/Shared/ContentView.swift index 517cbf54f..99c257a25 100644 --- a/Example/Shared/ContentView.swift +++ b/Example/Shared/ContentView.swift @@ -10,6 +10,6 @@ import SwiftUI struct ContentView: View { var body: some View { - AsyncRenderExample() + ImageConversionsExample() } } diff --git a/Example/setup.sh b/Example/setup.sh new file mode 100755 index 000000000..56e8dd6e3 --- /dev/null +++ b/Example/setup.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" + +cd "$SCRIPT_DIR" +mise trust "$SCRIPT_DIR/mise.toml" +mise install +mise exec -- tuist install +mise exec -- tuist generate --no-open diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListAsyncLayer.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListAsyncLayer.swift index db32ca926..45d176fde 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListAsyncLayer.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListAsyncLayer.swift @@ -126,6 +126,18 @@ extension DisplayList.ViewUpdater { } } + struct ContentsCenter: AsyncLayer.Property { + static let keyPath = "contentsCenter" + + static func boxValue(_ value: CGRect) -> NSObject { + #if canImport(QuartzCore) + NSValue(cgRect: value) + #else + _openSwiftUIPlatformUnimplementedFailure() + #endif + } + } + struct AffineTransformLayer: AsyncLayer.Property { static let keyPath = "transform" @@ -166,6 +178,14 @@ extension DisplayList.ViewUpdater { } } + struct ContentsScale: AsyncLayer.Property { + static let keyPath = "contentsScale" + + static func boxValue(_ value: CGFloat) -> NSObject { + NSNumber(value: Double(value)) + } + } + struct ContentsMultiplyColor: AsyncLayer.Property { static let keyPath = "contentsMultiplyColor" diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift index 99aa232b0..85fcf087b 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift @@ -963,7 +963,62 @@ extension DisplayList.ViewUpdater.Platform { size = bounds.size } - func updateState( + @inline(__always) + func makeInheritedViewInfo( + item: DisplayList.Item, + size: CGSize, + state: UnsafePointer + ) -> DisplayList.ViewUpdater.ViewInfo { + var viewInfo = DisplayList.ViewUpdater.ViewInfo( + platform: self, + kind: .inherited + ) + updateState( + &viewInfo, + item: item, + size: size, + state: state + ) + return viewInfo + } + + @inline(__always) + func updateInheritedViewInfo( + _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, + item: DisplayList.Item, + size: CGSize, + state: UnsafePointer + ) { + updateState( + &viewInfo, + item: item, + size: size, + state: state + ) + } + + @inline(__always) + func updateInheritedLayerAsync( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldItem: DisplayList.Item, + oldSize: CGSize, + oldState: UnsafePointer, + newItem: DisplayList.Item, + newSize: CGSize, + newState: UnsafePointer + ) -> Bool { + updateStateAsync( + layer: &layer, + oldItem: oldItem, + oldSize: oldSize, + oldState: oldState, + newItem: newItem, + newSize: newSize, + newState: newState + ) + } + + private func updateState( _ viewInfo: inout DisplayList.ViewUpdater.ViewInfo, item: DisplayList.Item, size: CGSize, @@ -1036,7 +1091,7 @@ extension DisplayList.ViewUpdater.Platform { #endif } - func updateStateAsync( + private func updateStateAsync( layer: inout DisplayList.ViewUpdater.AsyncLayer, oldItem: DisplayList.Item, oldSize: CGSize, diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift index 8a2040ebf..a33054665 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewUpdater.swift @@ -293,18 +293,13 @@ extension DisplayList { tag: .inherited, in: container.id ) { [platform] _, item, state in - // TODO: Optimize the call here in Platform - var info = ViewInfo(platform: platform, kind: .inherited) - platform.updateState( - &info, + platform.makeInheritedViewInfo( item: item, size: item.size, state: state ) - return info } updateView: { [platform] info, _, item, state in - // TODO: Optimize the call here in Platform - platform.updateState( + platform.updateInheritedViewInfo( &info, item: item, size: item.size, @@ -399,7 +394,7 @@ extension DisplayList { newState: newStatePtr, tag: .inherited ) { layer, _, oldItem, oldState, newItem, newState in - platform.updateStateAsync( + platform.updateInheritedLayerAsync( layer: &layer, oldItem: oldItem, oldSize: oldItem.size, diff --git a/Sources/OpenSwiftUICore/View/Image/GraphicsImage.swift b/Sources/OpenSwiftUICore/View/Image/GraphicsImage.swift index 3a2973efa..cc461b8f8 100644 --- a/Sources/OpenSwiftUICore/View/Image/GraphicsImage.swift +++ b/Sources/OpenSwiftUICore/View/Image/GraphicsImage.swift @@ -280,7 +280,7 @@ package struct ResolvedVectorGlyph: Equatable { } } -// MARK: - GraphicsImage + Extension [WIP] +// MARK: - GraphicsImage + Extension extension GraphicsImage { package var bitmapOrientation: Image.Orientation { @@ -291,6 +291,60 @@ extension GraphicsImage { } package func render(at targetSize: CGSize, prefersMask: Bool = false) -> CGImage? { - _openSwiftUIUnimplementedFailure() + guard targetSize.width > 0, targetSize.height > 0 else { + return nil + } + switch contents { + case let .cgImage(cgImage): + return cgImage + case let .vectorGlyph(vectorGlyph): + #if OPENSWIFTUI_LINK_COREUI + let sizeAndScale = renderedSize(at: targetSize).map { ($0, scale) } + guard let glyph = vectorGlyph.glyph else { + return nil + } + return glyph.image( + at: sizeAndScale, + value: vectorGlyph.value + ) + #else + return nil + #endif + case let .vectorLayer(vectorLayer): + let imageSize: CGSize + if let renderedSize = renderedSize(at: targetSize) { + imageSize = renderedSize + } else if scale != 0 { + imageSize = unrotatedPixelSize * (1.0 / scale) + } else { + imageSize = .zero + } + return vectorLayer.image( + size: imageSize, + imageScale: scale, + prefersMask: prefersMask + ) + default: + return nil + } + } + + private func renderedSize(at targetSize: CGSize) -> CGSize? { + if let resizingInfo { + guard resizingInfo.capInsets.isEmpty, resizingInfo.mode == .stretch else { + return nil + } + } + let renderedSize = targetSize.apply(orientation) + let nativeSize: CGSize + if scale != 0 { + nativeSize = unrotatedPixelSize * (1.0 / scale) + } else { + nativeSize = .zero + } + guard renderedSize != nativeSize else { + return nil + } + return renderedSize } } diff --git a/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift b/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift index a2c4cc19b..ee6718b28 100644 --- a/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift +++ b/Sources/OpenSwiftUICore/View/Image/ImageLayer.swift @@ -3,7 +3,7 @@ // OpenSwiftUICore // // Audited for 6.5.4 -// Status: Complete - Blocked by AsyncUpdate +// Status: Complete // ID: 854C382F3D9A82BFCF900A549E57F233 (SwiftUICore) #if canImport(Darwin) @@ -139,8 +139,83 @@ final package class ImageLayer: CALayer { newImage: GraphicsImage, newSize: CGSize ) -> Bool { - _openSwiftUIUnimplementedWarning() - return false + guard (oldImage.maskColor == nil) == (newImage.maskColor == nil), + oldImage.interpolation == newImage.interpolation, + oldImage.isAntialiased == newImage.isAntialiased + else { + return false + } + switch (oldImage.contents, newImage.contents) { + case (.cgImage, .cgImage): + return updateAsyncLayerProperties( + layer: &layer, + oldImage: oldImage, + oldSize: oldSize, + newImage: newImage, + newSize: newSize + ) + case (.ioSurface, .ioSurface): + return updateAsyncLayerProperties( + layer: &layer, + oldImage: oldImage, + oldSize: oldSize, + newImage: newImage, + newSize: newSize + ) + case let (.vectorGlyph(oldContents), .vectorGlyph(newContents)) where oldContents == newContents: + return updateAsyncLayerProperties( + layer: &layer, + oldImage: oldImage, + oldSize: oldSize, + newImage: newImage, + newSize: newSize + ) + case let (.color(oldColor), .color(newColor)): + layer.update( + DisplayList.ViewUpdater.BackgroundColor.self, + from: oldColor, + to: newColor + ) + return true + default: + return false + } + } + + @inline(__always) + private static func updateAsyncLayerProperties( + layer: inout DisplayList.ViewUpdater.AsyncLayer, + oldImage: GraphicsImage, + oldSize: CGSize, + newImage: GraphicsImage, + newSize: CGSize + ) -> Bool { + guard oldSize == newSize else { + return false + } + let (oldCenter, oldTiled) = oldImage.layerStretchInPixels(size: oldSize) + let (newCenter, newTiled) = newImage.layerStretchInPixels(size: newSize) + guard oldTiled == newTiled else { + return false + } + layer.update( + DisplayList.ViewUpdater.ContentsCenter.self, + from: oldCenter, + to: newCenter + ) + layer.update( + DisplayList.ViewUpdater.ContentsScale.self, + from: oldImage.scale, + to: newImage.scale + ) + if let newMaskColor = newImage.maskColor { + layer.update( + DisplayList.ViewUpdater.ContentsMultiplyColor.self, + from: oldImage.maskColor, + to: newMaskColor + ) + } + return true } } diff --git a/Sources/OpenSwiftUICore/View/Image/VectorImageLayer.swift b/Sources/OpenSwiftUICore/View/Image/VectorImageLayer.swift index 77f30ddd2..382ed856a 100644 --- a/Sources/OpenSwiftUICore/View/Image/VectorImageLayer.swift +++ b/Sources/OpenSwiftUICore/View/Image/VectorImageLayer.swift @@ -129,6 +129,31 @@ extension VectorImageLayer: ProtobufMessage { } } +#if OPENSWIFTUI_LINK_COREUI +private let valueLock = NSLock() + +extension CUINamedVectorGlyph { + package func image(at sizeAndScale: (CGSize, CGFloat)?, value: Float?) -> CGImage? { + valueLock.lock() + defer { + valueLock.unlock() + } + let oldMinValue = variableMinValue + let oldMaxValue = variableMaxValue + defer { + setVariableMinValue(oldMinValue) + setVariableMaxValue(oldMaxValue) + } + setVariableMinValue(value == nil ? .infinity : .zero) + setVariableMaxValue(value.map { CGFloat($0) } ?? .infinity) + guard let (size, scale) = sizeAndScale else { + return image + } + return rasterizeImage(usingScaleFactor: scale, forTargetSize: size)?.takeRetainedValue() + } +} +#endif + // MARK: - VectorImageContents @_spi(ForOpenSwiftUIOnly)