diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index e5483f05f..998067246 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -39,6 +39,13 @@ remoteGlobalIDString = 275751E22DEE1441003E467C; remoteInfo = TestingHost; }; + 27DC9A772F2017EA00F8F371 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 27CD0B412AFC8D37003665EB /* Project object */; + proxyType = 1; + remoteGlobalIDString = 27D49DF72BA604FB00F6E2E2; + remoteInfo = HostingExample; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -233,6 +240,7 @@ ); dependencies = ( 279080A42E8E600A0082B5B6 /* PBXTargetDependency */, + 27DC9A782F2017EA00F8F371 /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( 275751F62DEE1456003E467C /* OpenSwiftUIUITests */, @@ -310,7 +318,7 @@ }; 279283B82DFF11CE00234D64 = { CreatedOnToolsVersion = 16.3; - TestTargetID = 275751E22DEE1441003E467C; + TestTargetID = 27D49DF72BA604FB00F6E2E2; }; 27CD0B482AFC8D37003665EB = { CreatedOnToolsVersion = 15.0; @@ -414,6 +422,11 @@ target = 275751E22DEE1441003E467C /* TestingHost */; targetProxy = 279080A32E8E600A0082B5B6 /* PBXContainerItemProxy */; }; + 27DC9A782F2017EA00F8F371 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 27D49DF72BA604FB00F6E2E2 /* HostingExample */; + targetProxy = 27DC9A772F2017EA00F8F371 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -1103,7 +1116,7 @@ SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = NO; TARGETED_DEVICE_FAMILY = "1,2,7"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestingHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TestingHost"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HostingExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/HostingExample"; }; name = SwiftUIDebug; }; @@ -1125,7 +1138,7 @@ SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = NO; TARGETED_DEVICE_FAMILY = "1,2,7"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestingHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TestingHost"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HostingExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/HostingExample"; }; name = SwiftUIRelease; }; @@ -1147,7 +1160,7 @@ SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = NO; TARGETED_DEVICE_FAMILY = "1,2,7"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestingHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TestingHost"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HostingExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/HostingExample"; }; name = OpenSwiftUIDebug; }; @@ -1169,7 +1182,7 @@ SUPPORTS_MACCATALYST = NO; SWIFT_EMIT_LOC_STRINGS = NO; TARGETED_DEVICE_FAMILY = "1,2,7"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestingHost.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/TestingHost"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HostingExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/HostingExample"; }; name = OpenSwiftUIRelease; }; diff --git a/Example/OpenSwiftUIUITests/Render/RendererEffect/BlurEffectUITests.swift b/Example/OpenSwiftUIUITests/Render/RendererEffect/BlurEffectUITests.swift new file mode 100644 index 000000000..a6c2ac0e5 --- /dev/null +++ b/Example/OpenSwiftUIUITests/Render/RendererEffect/BlurEffectUITests.swift @@ -0,0 +1,25 @@ +// +// BlurEffectUITests.swift +// OpenSwiftUIUITests + +import Testing +import SnapshotTesting + +@MainActor +@Suite(.snapshots(record: .never, diffTool: diffTool)) +struct BlurEffectUITests { + @Test + func blurColor() { + struct ContentView: View { + var body: some View { + Color.blue + .frame(width: 100, height: 100) + .blur(radius: 10) + .background(Color.red) + } + } + openSwiftUIAssertSnapshot(of: ContentView(), drawHierarchyInKeyWindow: true) + } + + // TODO: blur image test when named image is supported +} diff --git a/Example/OpenSwiftUIUITests/UITests/SnapshotTesting+Testing.swift b/Example/OpenSwiftUIUITests/UITests/SnapshotTesting+Testing.swift index 744566979..d657ab2a7 100644 --- a/Example/OpenSwiftUIUITests/UITests/SnapshotTesting+Testing.swift +++ b/Example/OpenSwiftUIUITests/UITests/SnapshotTesting+Testing.swift @@ -38,6 +38,7 @@ let defaultSize = CGSize(width: 200, height: 200) func openSwiftUIAssertSnapshot( of value: @autoclosure () -> V, + drawHierarchyInKeyWindow: Bool = false, perceptualPrecision: Float = 1, size: CGSize = defaultSize, named name: String? = nil, @@ -51,7 +52,7 @@ func openSwiftUIAssertSnapshot( ) { openSwiftUIAssertSnapshot( of: PlatformHostingController(rootView: value()), - as: .image(perceptualPrecision: perceptualPrecision, size: size), + as: .image(drawHierarchyInKeyWindow: drawHierarchyInKeyWindow, perceptualPrecision: perceptualPrecision, size: size), named: (name.map { ".\($0)" } ?? "") + "\(Int(size.width))x\(Int(size.height))", record: recording, timeout: timeout, diff --git a/Example/TestingHost/TestingHostApp.swift b/Example/TestingHost/TestingHostApp.swift index 881df04aa..0b619b24e 100644 --- a/Example/TestingHost/TestingHostApp.swift +++ b/Example/TestingHost/TestingHostApp.swift @@ -2,6 +2,9 @@ // TestingHostApp.swift // TestingHost +// FIXME: OpenSwiftUI does not set up key window correctly +// -> use HostingExample as OpenSwiftUIUITests's host temporary to add drawHierarchyInKeyWindow support + #if OPENSWIFTUI import OpenSwiftUI #else @@ -16,6 +19,7 @@ import UIKit @main struct TestingHostApp: App { + // FIXME: OpenSwiftUI does not support ApplicationDelegateAdaptor yet #if !OPENSWIFTUI #if canImport(AppKit) @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate diff --git a/Sources/OpenSwiftUICore/Render/RendererEffect/BlurEffect.swift b/Sources/OpenSwiftUICore/Render/RendererEffect/BlurEffect.swift new file mode 100644 index 000000000..fe8c8e896 --- /dev/null +++ b/Sources/OpenSwiftUICore/Render/RendererEffect/BlurEffect.swift @@ -0,0 +1,87 @@ +// +// BlurEffect.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +public import Foundation + +// MARK: - _BlurEffect + +/// A blur effect applied to a view. +@available(OpenSwiftUI_v1_0, *) +@frozen +public struct _BlurEffect: RendererEffect, Equatable { + + public var radius: CGFloat + + public var isOpaque: Bool + + @inlinable + public init(radius: CGFloat, opaque: Bool) { + self.radius = radius + self.isOpaque = opaque + } + + public var animatableData: CGFloat { + get { radius } + set { radius = newValue } + } + + package var descriptionAttributes: [(name: String, value: String)] { + var attributes: [(name: String, value: String)] = [] + attributes.append(("radius", radius.description)) + attributes.append(("isOpaque", isOpaque ? "true" : "false")) + return attributes + } + + package func effectValue(size: CGSize) -> DisplayList.Effect { + .filter(.blur(BlurStyle(radius: radius, isOpaque: isOpaque))) + } + + nonisolated public static func == (a: _BlurEffect, b: _BlurEffect) -> Swift.Bool { + a.radius == b.radius && a.isOpaque == b.isOpaque + } +} + +// MARK: - View + blur + +@available(OpenSwiftUI_v1_0, *) +extension View { + + /// Applies a Gaussian blur to this view. + /// + /// Use `blur(radius:opaque:)` to apply a gaussian blur effect to the + /// rendering of this view. + /// + /// The example below shows two ``Text`` views, the first with no blur + /// effects, the second with `blur(radius:opaque:)` applied with the + /// `radius` set to `2`. The larger the radius, the more diffuse the + /// effect. + /// + /// struct Blur: View { + /// var body: some View { + /// VStack { + /// Text("This is some text.") + /// .padding() + /// Text("This is some blurry text.") + /// .blur(radius: 2.0) + /// } + /// } + /// } + /// + /// ![A screenshot showing the effect of applying gaussian blur effect to + /// the rendering of a view.](OpenSwiftUI-View-blurRadius.png) + /// + /// - Parameters: + /// - radius: The radial size of the blur. A blur is more diffuse when its + /// radius is large. + /// - opaque: A Boolean value that indicates whether the blur renderer + /// permits transparency in the blur output. Set to `true` to create an + /// opaque blur, or set to `false` to permit transparency. + @inlinable + nonisolated public func blur(radius: CGFloat, opaque: Bool = false) -> some View { + return modifier(_BlurEffect(radius: radius, opaque: opaque)) + } +}