Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/actions/uitests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,13 @@ runs:
test_arguments=(-workspace Example.xcworkspace)
if [[ "${{ inputs.test-suite }}" == "InteractionTests" ]]; then
test_arguments+=(-parallel-testing-enabled NO)
elif [[ "${{ inputs.platform }}" == "macos" &&
"$UITEST_ONLY_TESTING" =~ ^[^/]+/[^/]+$ ]]; then
# FIXME:
# Xcode 26.6 can dispatch a macOS suite filter twice. Affected recording
# runs can terminate with SIGTRAP and leave xcresult without Info.plist.
# Swift Testing's .serialized trait does not prevent duplicate dispatch.
test_arguments+=(-parallel-testing-enabled NO)
fi
if [[ -n "$UITEST_ONLY_TESTING" ]]; then
test_arguments+=("-only-testing:$UITEST_ONLY_TESTING")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import Testing
@testable import TestingHost

@MainActor
@Suite(
.snapshots(record: .never, diffTool: diffTool),
.disabled("#817")
)
@Suite(.snapshots(record: .never, diffTool: diffTool))
struct NamedImageUITests {
@Test("Test named image of logo with resizable")
func decorativeLogo() {
Expand Down
2 changes: 1 addition & 1 deletion Example/Tuist/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -1095,7 +1095,7 @@ extension DisplayList {
contentOffset: CGSize,
version: DisplayList.Version
) -> Bool {
_openSwiftUIEmptyStub()
_openSwiftUIUnimplementedWarning()
return false
}
}
Expand Down
42 changes: 36 additions & 6 deletions Sources/OpenSwiftUICore/Render/InterpolatableContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,41 @@ private struct InterpolatedDisplayList<Content>: StatefulRule, AsyncAttribute wh
_openSwiftUIUnimplementedWarning()
}
// FIXME
let result: [DisplayList.Item] = list?.items.map {
var item = $0
item.frame.origin = .init(position - containerPosition)
return item
} ?? []
value = DisplayList(result)
let offset = animatedPosition - containerPosition
let contentOrigin: CGPoint
let contentOffset: CGSize
if animatesSize {
contentOrigin = CGPoint(offset)
contentOffset = .zero
} else {
let position = position.roundedToNearestOrUp(toMultipleOf: pixelLength)
contentOrigin = CGPoint(position - containerPosition)
contentOffset = contentOrigin - CGPoint(offset)
}
var result = list ?? DisplayList()
if defersRender {
let effect = DisplayList.Effect.interpolatorRoot(
group,
contentOrigin: contentOrigin,
contentOffset: contentOffset
)
let item = DisplayList.Item(
.effect(effect, result),
frame: .zero,
identity: .none,
version: version
)
result = DisplayList(item)
} else {
_ = group.rewriteDisplayList(
&result,
time: $time,
contentOrigin: contentOrigin,
contentOffset: contentOffset,
version: version
)
}
result.translate(by: offset, version: version)
value = result
}
}
164 changes: 154 additions & 10 deletions Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,17 +265,161 @@ package struct _ShapeStyle_RenderedShape {
styles: ShapeStyle.Pack,
layers: inout ShapeStyle.RenderedLayers
) {
// TODO: RBSymbolAnimator/RBSymbolUpdate
// We use a plain implementation for now
_openSwiftUIUnimplementedWarning()
let style = styles[name, 0]
layers.beginLayer(
id: .styled(name, 0),
style: style,
shape: &self
#if canImport(Darwin)
func level(for style: ORBSymbolStyle) -> Int? {
switch style {
case .primary: 0
case .secondary: 1
case .tertiary: 2
case .quaternary: 3
case .quinary: 4
default: nil
}
}

let slice = styles[name]
var flags: ORBSymbolUpdateFlags
switch orientation {
case .up: flags = []
case .upMirrored: flags = [.flipX]
case .down: flags = [.flipX, .flipY]
case .downMirrored: flags = [.flipY]
case .left: flags = [.swapAxes]
case .leftMirrored: flags = [.swapAxes, .flipX]
case .right: flags = [.swapAxes, .flipX, .flipY]
case .rightMirrored: flags = [.swapAxes, .flipY]
}
flags.insert(.mergeLayers)
if !glyph.preservesVectorRepresentation {
flags.insert(.applyVerticalOffset)
}
var position = frame.origin
var size = frame.size
let update = glyph.animator.beginUpdate(
with: .unspecified,
position: &position,
size: &size,
flags: flags
)
render(style: style)
layers.endLayer(shape: &self)
defer { glyph.animator.end(update) }
let nextTime = update.nextTime
var imageScale = 1.0
var imageOpacity: Float = 1.0
if !glyph.preservesVectorRepresentation,
!slice.isEmpty,
let image = update.templateImage(scale: &imageScale, opacity: &imageOpacity) {
shape = .image(GraphicsImage(
contents: .cgImage(image),
scale: imageScale,
unrotatedPixelSize: CGSize(width: image.width, height: image.height),
orientation: orientation,
isTemplate: true
))
let style = slice[0].applyingOpacity(imageOpacity)
layers.beginLayer(id: .styled(name, 0), style: style, shape: &self)
render(style: style)
layers.endLayer(shape: &self)
return
}

let groupCount = update.groupCount
// Style-mask bit 12 disables the single-layer path. Its meaning is not known.
let isSingleLayer = update.styleMask & (1 << 12) == 0
&& groupCount == 1 && update.groupSize(group: 0) == 1
if !isSingleLayer, slice.allColors {
let list = ORBDisplayList()
update.draw(in: list.state) { symbolStyle, colorName in
let color: Color.Resolved?
if let level = level(for: symbolStyle), !slice.isEmpty {
color = slice[min(level, slice.count - 1)].color
} else if symbolStyle == .namedColor {
color = colorName.flatMap { styles[$0] }
} else {
color = nil
}
guard let color else { return .null }
return ORBColor(red: color.red, green: color.green, blue: color.blue, alpha: color.opacity)
}
layers.beginLayer(id: .unstyled, style: nil, shape: &self)
item = makeDrawingItem(
from: list.moveContents(),
offset: CGSize(item.frame.origin),
flags: []
)
if nextTime.isFinite {
layerNeedes.insert(.drawingGroup)
}
layers.endLayer(shape: &self)
return
}

var drawingList: ORBDisplayList?
for group in 0 ..< groupCount {
for index in 0 ..< update.groupSize(group: group) {
let layer = ORBSymbolUpdateLayer(update: update, group: group, index: index)
let opacity = layer.drawingOpacity
guard opacity > 0 else { continue }
let style: ShapeStyle.Pack.Style
let id: ShapeStyle.LayerID
if let level = level(for: layer.style), !slice.isEmpty {
style = slice[min(level, slice.count - 1)].applyingOpacity(opacity)
id = .styled(name, UInt16(level))
} else if layer.style == .namedColor, let colorName = layer.colorName {
let color: Color.Resolved
if let resolved = styles[colorName] {
color = resolved
} else if let proposed = layer.defaultColor {
color = Color.Resolved(failableCGColor: proposed) ?? .clear
} else {
continue
}
style = ShapeStyle.Pack.Style(.color(color)).applyingOpacity(opacity)
id = .named(colorName)
} else {
continue
}
layers.beginLayer(id: id, style: style, shape: &self)
var transform = CGAffineTransform()
let path = glyph.preservesVectorRepresentation
? ORBPath.null
: layer.path(transform: &transform, flags: [.includeClips])
if !path.isNull {
// Layer paths belong to the update; the shape takes an owned copy.
shape = .path(
Path(box: Path.PathBox(takingPath: path.copy(transform: &transform))),
FillStyle()
)
} else {
let list: ORBDisplayList
if let drawingList {
list = drawingList
} else {
list = ORBDisplayList()
drawingList = list
}
layer.draw(
in: list.state,
color: .white,
opacity: 1,
blendMode: .init(rawValue: 0),
flags: [.includeClips]
)
shape = .alphaMask(makeDrawingItem(
from: list.moveContents(),
offset: .zero,
flags: [.alphaOnly]
))
if nextTime.isFinite {
layerNeedes.insert(.drawingGroup)
}
}
render(style: style)
layers.endLayer(shape: &self)
}
}
#else
_openSwiftUIPlatformUnimplementedFailure()
#endif
}

private mutating func renderUnstyledImage(
Expand Down
6 changes: 3 additions & 3 deletions Sources/OpenSwiftUICore/View/Image/GraphicsImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ package struct ResolvedVectorGlyph: Equatable {
#endif
animator.variableValue = variableValue
animator.flipsRightToLeft = flipsRightToLeft
animator.renderingMode = context.effectiveSymbolRenderingMode?.rbRenderingMode ?? 255
animator.renderingMode = context.effectiveSymbolRenderingMode?.rbRenderingMode ?? .unspecified
let direction = context.environment.layoutDirection
let version = animator.version
let options = context.options
Expand Down Expand Up @@ -237,9 +237,9 @@ package struct ResolvedVectorGlyph: Equatable {
}

package var resolvedRenderingMode: SymbolRenderingMode.Storage? {
#if OPENSWIFTUI_LINK_COREUI && OPENSWIFTUI_RENDERBOX
#if OPENSWIFTUI_LINK_COREUI
let rbMode = animator.renderingMode
guard rbMode == 0 else {
guard rbMode == .preferred else {
return SymbolRenderingMode(rbRenderingMode: rbMode)?.storage
}
guard let glyph = animator.glyph else {
Expand Down
38 changes: 20 additions & 18 deletions Sources/OpenSwiftUICore/View/Image/SymbolRenderingMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
// Status: Complete
// ID: FE3FF33C1D9A704A22DF5519034B23F2 (SwiftUICore)

package import OpenRenderBoxShims

// MARK: - SymbolRenderingMode

/// A symbol rendering mode.
Expand Down Expand Up @@ -101,29 +103,29 @@ public struct SymbolRenderingMode: Sendable {
// MARK: - SymbolRenderingMode + RB Rendering Mode

extension SymbolRenderingMode {
package var rbRenderingMode: UInt32 {
package var rbRenderingMode: ORBSymbolRenderingMode {
switch storage {
case .monochrome: 1
case .multicolor: 2
case .hierarchical: 3
case .palette: 4
case .preferred: 0
case .hierarchicalUnlessSlashed: 128
case .hierarchicalSlashBadge: 129
case .paletteSlashBadge: 130
case .monochrome: .monochrome
case .multicolor: .multicolor
case .hierarchical: .hierarchical
case .palette: .palette
case .preferred: .preferred
case .hierarchicalUnlessSlashed: .hierarchicalUnlessSlashed
case .hierarchicalSlashBadge: .hierarchicalSlashBadge
case .paletteSlashBadge: .paletteSlashBadge
}
}

package init?(rbRenderingMode mode: UInt32) {
package init?(rbRenderingMode mode: ORBSymbolRenderingMode) {
switch mode {
case 0: self = .preferred
case 1: self = .monochrome
case 2: self = .multicolor
case 3: self = .hierarchical
case 4: self = .palette
case 128: self = .hierarchicalUnlessSlashed
case 129: self = .hierarchicalSlashBadge
case 130: self = .paletteSlashBadge
case .preferred: self = .preferred
case .monochrome: self = .monochrome
case .multicolor: self = .multicolor
case .hierarchical: self = .hierarchical
case .palette: self = .palette
case .hierarchicalUnlessSlashed: self = .hierarchicalUnlessSlashed
case .hierarchicalSlashBadge: self = .hierarchicalSlashBadge
case .paletteSlashBadge: self = .paletteSlashBadge
default: return nil
}
}
Expand Down
Loading