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
33 changes: 33 additions & 0 deletions Example/Shared/Render/RendererEffect/BlendModeExample.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// BlendModeExample.swift
// Shared

#if OPENSWIFTUI
import OpenSwiftUI
#else
import SwiftUI
#endif

struct BlendModeExample: View {
var body: some View {
BlendModeRectanglesExample(blendMode: .colorBurn)
}
}

struct BlendModeRectanglesExample: View {
var blendMode: BlendMode

var body: some View {
HStack {
Color.yellow.frame(width: 50, height: 50, alignment: .center)
Color.red.frame(width: 50, height: 50, alignment: .center)
.rotationEffect(.degrees(45))
.padding(-20)
.blendMode(blendMode)
}
}
}

#Preview {
BlendModeExample()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// BlendModeEffectUITests.swift
// OpenSwiftUIUITests

import SnapshotTesting
import Testing
@testable import TestingHost

@MainActor
@Suite(.snapshots(record: .never, diffTool: diffTool))
struct BlendModeEffectUITests {
@Test(
arguments: [
(.normal, "normal"),
(.multiply, "multiply"),
(.screen, "screen"),
(.overlay, "overlay"),
(.darken, "darken"),
(.lighten, "lighten"),
(.colorDodge, "colorDodge"),
(.colorBurn, "colorBurn"),
(.softLight, "softLight"),
(.hardLight, "hardLight"),
(.difference, "difference"),
(.exclusion, "exclusion"),
(.hue, "hue"),
(.saturation, "saturation"),
(.color, "color"),
(.luminosity, "luminosity"),
(.sourceAtop, "sourceAtop"),
(.destinationOver, "destinationOver"),
(.destinationOut, "destinationOut"),
(.plusDarker, "plusDarker"),
(.plusLighter, "plusLighter"),
] as [(BlendMode, String)]
)
func blendMode(_ blendMode: BlendMode, name: String) {
openSwiftUIAssertSnapshot(
of: BlendModeRectanglesExample(blendMode: blendMode),
drawHierarchyInKeyWindow: true,
testName: "blend_mode_\(name)"
)
}
}
4 changes: 2 additions & 2 deletions Package.resolved

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

14 changes: 11 additions & 3 deletions Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

package import OpenCoreGraphicsShims
import OpenQuartzCoreShims
#if canImport(QuartzCore)
import QuartzCore_Private
#endif

package enum GraphicsFilter {
case blur(BlurStyle)
Expand Down Expand Up @@ -141,14 +144,19 @@ package enum GraphicsBlendMode: Equatable {
}

@inline(__always)
var filter: Any? {
func filter(compositingGroup: Bool) -> Any? {
switch self {
case let .caFilter(filter):
return filter
case let .blendMode(mode):
// TODO: _ORBBlendModeGetCompositingFilter & ORBBlendMode
_openSwiftUIUnimplementedWarning()
#if canImport(QuartzCore)
return ORBBlendModeGetCompositingFilter(
.init(rawValue: mode.rawValue),
compositingGroup: compositingGroup
)
#else
return nil
#endif
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1036,7 +1036,7 @@ extension DisplayList.ViewUpdater.Platform {
CoreViewSetCompositingFilter(
system: viewSystem,
view: viewInfo.view,
filter: state.pointee.blend.filter
filter: state.pointee.blend.filter(compositingGroup: state.pointee.compositingGroup)
)
viewInfo.seeds.blend = state.pointee.versions.blend.seed
}
Expand Down
118 changes: 118 additions & 0 deletions Sources/OpenSwiftUICore/Render/RendererEffect/BlendModeEffect.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//
// BlendModeEffect.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete

package import Foundation

// MARK: - _BlendModeEffect

@available(OpenSwiftUI_v1_0, *)
@frozen
@MainActor
@preconcurrency
public struct _BlendModeEffect: RendererEffect, Equatable {
public var blendMode: BlendMode

@inlinable
nonisolated public init(blendMode: BlendMode) {
self.blendMode = blendMode
}

package func effectValue(size: CGSize) -> DisplayList.Effect {
.blendMode(GraphicsBlendMode(blendMode))
}
}

@available(*, unavailable)
extension _BlendModeEffect: Sendable {}

// MARK: - _ExtendedBlendModeEffect

@_spi(Private)
@available(OpenSwiftUI_v3_0, *)
@frozen
@MainActor
@preconcurrency
public struct _ExtendedBlendModeEffect: RendererEffect, Equatable {
public var blendMode: GraphicsContext.BlendMode

@inlinable
public init(blendMode: GraphicsContext.BlendMode) {
self.blendMode = blendMode
}

package func effectValue(size: CGSize) -> DisplayList.Effect {
.blendMode(.blendMode(blendMode))
}
}

// MARK: - View + blendMode

@available(OpenSwiftUI_v1_0, *)
extension View {
/// Sets the blend mode for compositing this view with overlapping views.
///
/// Use `blendMode(_:)` to combine overlapping views and use a different
/// visual effect to produce the result. The ``BlendMode`` enumeration
/// defines many possible effects.
///
/// In the example below, the two overlapping rectangles have a
/// ``BlendMode/colorBurn`` effect applied, which effectively removes the
/// non-overlapping portion of the second image:
///
/// HStack {
/// Color.yellow.frame(width: 50, height: 50, alignment: .center)
///
/// Color.red.frame(width: 50, height: 50, alignment: .center)
/// .rotationEffect(.degrees(45))
/// .padding(-20)
/// .blendMode(.colorBurn)
/// }
///
/// ![Two overlapping rectangles showing the effect of the blend mode view
/// modifier applying the colorBurn effect.](OpenSwiftUI-blendMode)
///
/// - Parameter blendMode: The ``BlendMode`` for compositing this view.
///
/// - Returns: A view that applies `blendMode` to this view.
@inlinable
nonisolated public func blendMode(_ blendMode: BlendMode) -> some View {
modifier(_BlendModeEffect(blendMode: blendMode))
}
}

// MARK: - View + extendedBlendMode

@_spi(Private)
@available(OpenSwiftUI_v3_0, *)
extension View {
@inlinable
nonisolated public func extendedBlendMode(_ blendMode: GraphicsContext.BlendMode) -> some View {
modifier(_ExtendedBlendModeEffect(blendMode: blendMode))
}
}

// MARK: - View + graphicsBlendMode

extension View {
@MainActor
@preconcurrency
package func graphicsBlendMode(_ blendMode: GraphicsBlendMode) -> some View {
modifier(GraphicsBlendModeEffect(blendMode: blendMode))
}
}

// MARK: - GraphicsBlendModeEffect

@MainActor
@preconcurrency
struct GraphicsBlendModeEffect: RendererEffect {
var blendMode: GraphicsBlendMode

func effectValue(size: CGSize) -> DisplayList.Effect {
.blendMode(blendMode)
}
}
4 changes: 4 additions & 0 deletions Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CAFilterPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#if __has_include(<QuartzCore/CoreAnimation.h>)

#include <OpenRenderBoxShims/OpenRenderBoxShims.h>
#import <QuartzCore/CoreAnimation.h>

OPENSWIFTUI_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -66,6 +67,9 @@ OPENSWIFTUI_EXPORT id _Nullable _CAFilterGetInput(CAFilter *filter, CAFilterInpu
OPENSWIFTUI_EXPORT void _CAFilterSetInput(CAFilter * _Nullable filter, id _Nullable value, CAFilterInputKey key) OPENSWIFTUI_SWIFT_NAME(CAFilter.setInput(self:value:key:));
OPENSWIFTUI_EXPORT NSMutableArray<CAFilter *> *_CAFilterArrayCreate(void) OPENSWIFTUI_SWIFT_NAME(CAFilterArrayCreate());
OPENSWIFTUI_EXPORT void _CAFilterArrayAppend(NSMutableArray<CAFilter *> *array, CAFilter *filter) OPENSWIFTUI_SWIFT_NAME(CAFilterArrayAppend(_:_:));
OPENSWIFTUI_EXPORT id _Nullable _ORBBlendModeGetCompositingFilter(ORBBlendMode blendMode, bool compositingGroup) OPENSWIFTUI_SWIFT_NAME(ORBBlendModeGetCompositingFilter(_:compositingGroup:));
// Returns normal for nil and -1 for an unrecognized filter name.
OPENSWIFTUI_EXPORT ORBBlendMode _CACompositingFilterGetORBBlendMode(id _Nullable filter) OPENSWIFTUI_SWIFT_NAME(CACompositingFilterGetORBBlendMode(_:));

OPENSWIFTUI_ASSUME_NONNULL_END

Expand Down
Loading
Loading