From b3f63855bcb26794616d4395b1685eb8fe799719 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 20 Jan 2026 23:31:53 +0800 Subject: [PATCH 1/2] Add ClipEffect support --- Example/SharedExample/ContentView.swift | 7 +++- .../Graphic/GraphicsContext.swift | 28 +++++++++++++++- .../Render/DisplayList/DisplayList.swift | 11 ------- .../RendererEffect}/ClipEffect.swift | 32 ++++++++++++++----- Sources/OpenSwiftUICore/Shape/Shape.swift | 3 +- 5 files changed, 59 insertions(+), 22 deletions(-) rename Sources/OpenSwiftUICore/{Modifier/ViewModifier => Render/RendererEffect}/ClipEffect.swift (87%) diff --git a/Example/SharedExample/ContentView.swift b/Example/SharedExample/ContentView.swift index 239fe31f8..86bcd8c7d 100644 --- a/Example/SharedExample/ContentView.swift +++ b/Example/SharedExample/ContentView.swift @@ -14,6 +14,11 @@ import Foundation struct ContentView: View { var body: some View { - AsyncImageExample() + Color.blue + .frame(width: 80, height: 60) + .scaleEffect(0.5) + .background { Color.red } + .frame(width: 10, height: 10) + .clipped() } } diff --git a/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift b/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift index a10e1a9a1..4ccf2a050 100644 --- a/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift +++ b/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift @@ -121,6 +121,7 @@ package enum PathDrawingStyle { /// resolution and color scheme --- to resolve types like ``Image`` and /// ``Color`` that appear in the context. You can also access values stored /// in the environment for your own purposes. +@available(OpenSwiftUI_v3_0, *) @frozen public struct GraphicsContext { @usableFromInline @@ -484,7 +485,32 @@ public struct GraphicsContext { self.init(rawValue: CGBlendMode.plusLighter.rawValue) } } - + + // MARK: - GraphicsContext.ClipOptions + + /// Options that affect the use of clip shapes. + /// + /// Use these options to affect how OpenSwiftUI interprets a clip shape + /// when you call ``clip(to:style:options:)`` to add a path to the array of + /// clip shapes, or when you call ``clipToLayer(opacity:options:content:)`` + /// to add a clipping layer. + @frozen + public struct ClipOptions: OptionSet { + public let rawValue: UInt32 + + @inlinable + public init(rawValue: UInt32) { + self.rawValue = rawValue + } + + /// An option to invert the shape or layer alpha as the clip mask. + /// + /// When you use this option, OpenSwiftUI uses `1 - alpha` instead of + /// `alpha` for the given clip shape. + @inlinable + public static var inverse: ClipOptions { Self(rawValue: 1 << 0) } + } + // FIXME package enum ResolvedShading: Sendable { case backdrop(Color.Resolved) diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift index 7f38f358d..97dc7f8f5 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayList.swift @@ -550,17 +550,6 @@ extension DisplayList { package struct AccessibilityNodeAttachment {} -extension GraphicsContext { - @frozen - public struct ClipOptions: OptionSet { - public let rawValue: UInt32 - - public init(rawValue: UInt32) { - self.rawValue = rawValue - } - } -} - package protocol _DisplayList_AnyEffectAnimation {} package struct ResolvedShadowStyle {} diff --git a/Sources/OpenSwiftUICore/Modifier/ViewModifier/ClipEffect.swift b/Sources/OpenSwiftUICore/Render/RendererEffect/ClipEffect.swift similarity index 87% rename from Sources/OpenSwiftUICore/Modifier/ViewModifier/ClipEffect.swift rename to Sources/OpenSwiftUICore/Render/RendererEffect/ClipEffect.swift index b10c6f5cf..1b36d919f 100644 --- a/Sources/OpenSwiftUICore/Modifier/ViewModifier/ClipEffect.swift +++ b/Sources/OpenSwiftUICore/Render/RendererEffect/ClipEffect.swift @@ -1,10 +1,20 @@ -public import Foundation +// +// ClipEffect.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Blocked by HoverEffectContent and VisualEffect + +public import OpenCoreGraphicsShims // MARK: - ClipEffect +/// An effect to mask a view by a clip shape. +@available(OpenSwiftUI_v1_0, *) @frozen -public struct _ClipEffect where ClipShape: Shape { +public struct _ClipEffect: RendererEffect where ClipShape: Shape { public var shape: ClipShape + public var style: FillStyle @inlinable @@ -17,16 +27,18 @@ public struct _ClipEffect where ClipShape: Shape { get { shape.animatableData } set { shape.animatableData = newValue } } - - public typealias AnimatableData = ClipShape.AnimatableData - public typealias Body = Swift.Never -} -// FIXME -extension _ClipEffect: PrimitiveViewModifier {} + package func effectValue(size: CGSize) -> DisplayList.Effect { + .clip( + shape.effectivePath(in: CGRect(origin: .zero, size: size)), + style, + ) + } +} // MARK: - View Extension +@available(OpenSwiftUI_v1_0, *) extension View { /// Sets a clipping shape for this view. @@ -128,3 +140,7 @@ extension View { ) } } + +// TODO: HoverEffectContent + clipShape + +// TODO: VisualEffect + clipShape diff --git a/Sources/OpenSwiftUICore/Shape/Shape.swift b/Sources/OpenSwiftUICore/Shape/Shape.swift index 04e8a25c4..64f545033 100644 --- a/Sources/OpenSwiftUICore/Shape/Shape.swift +++ b/Sources/OpenSwiftUICore/Shape/Shape.swift @@ -105,7 +105,8 @@ extension Shape { package func effectivePath(in rect: CGRect) -> Path { // _threadGeometryProxyData - _openSwiftUIUnimplementedFailure() + _openSwiftUIUnimplementedWarning() + return Path(rect) } } From 5d1ee2bd0ea0dadd35083174615c2302cc90ebd3 Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 21 Jan 2026 00:11:06 +0800 Subject: [PATCH 2/2] Add UI test case --- .../RendererEffect/ClipEffectUITests.swift | 74 +++++++++++++++++++ Sources/OpenSwiftUICore/Shape/Shape.swift | 3 +- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 Example/OpenSwiftUIUITests/Render/RendererEffect/ClipEffectUITests.swift diff --git a/Example/OpenSwiftUIUITests/Render/RendererEffect/ClipEffectUITests.swift b/Example/OpenSwiftUIUITests/Render/RendererEffect/ClipEffectUITests.swift new file mode 100644 index 000000000..fabb43728 --- /dev/null +++ b/Example/OpenSwiftUIUITests/Render/RendererEffect/ClipEffectUITests.swift @@ -0,0 +1,74 @@ +// +// ClipEffectUITests.swift +// OpenSwiftUIUITests + +import Testing +import SnapshotTesting + +@MainActor +@Suite(.snapshots(record: .never, diffTool: diffTool)) +struct ClipEffectUITests { + @Test(.disabled("Shape is not implemented correctly")) + func clipShapeCircle() { + struct ContentView: View { + var body: some View { + Color.blue + .frame(width: 100, height: 100) + .clipShape(Circle()) + } + } + openSwiftUIAssertSnapshot(of: ContentView()) + } + + @Test(.disabled("Shape is not implemented correctly")) + func clipShapeRoundedRectangle() { + struct ContentView: View { + var body: some View { + Color.blue + .frame(width: 100, height: 60) + .clipShape(RoundedRectangle(cornerRadius: 15)) + } + } + openSwiftUIAssertSnapshot(of: ContentView()) + } + + @Test(.disabled("Shape is not implemented correctly")) + func clipShapeCapsule() { + struct ContentView: View { + var body: some View { + Color.blue + .frame(width: 100, height: 50) + .clipShape(Capsule()) + } + } + openSwiftUIAssertSnapshot(of: ContentView()) + } + + @Test + func clipped() { + struct ContentView: View { + var body: some View { + Color.blue + .frame(width: 150, height: 150) + .offset(x: 25, y: 25) + .frame(width: 100, height: 100) + .clipped() + .background { Color.red } + } + } + openSwiftUIAssertSnapshot(of: ContentView()) + } + + @Test(.disabled("Shape is not implemented correctly")) + func clipShapeEllipse() { + struct ContentView: View { + var body: some View { + Color.blue + .frame(width: 100, height: 60) + .clipShape(Ellipse()) + } + } + openSwiftUIAssertSnapshot(of: ContentView()) + } +} + diff --git a/Sources/OpenSwiftUICore/Shape/Shape.swift b/Sources/OpenSwiftUICore/Shape/Shape.swift index 64f545033..2d4c0b0ab 100644 --- a/Sources/OpenSwiftUICore/Shape/Shape.swift +++ b/Sources/OpenSwiftUICore/Shape/Shape.swift @@ -106,7 +106,8 @@ extension Shape { package func effectivePath(in rect: CGRect) -> Path { // _threadGeometryProxyData _openSwiftUIUnimplementedWarning() - return Path(rect) + let p = path(in: rect) + return p } }