From dd038a73a046507d2c934248864effcf467e208e Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 1 Nov 2026 21:48:36 +0800 Subject: [PATCH 01/19] feat: add container background kind environment value --- .../ContainerBackgroundKind.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 Sources/OpenSwiftUICore/Data/EnvironmentKeys/ContainerBackgroundKind.swift diff --git a/Sources/OpenSwiftUICore/Data/EnvironmentKeys/ContainerBackgroundKind.swift b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/ContainerBackgroundKind.swift new file mode 100644 index 000000000..2eda96f3f --- /dev/null +++ b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/ContainerBackgroundKind.swift @@ -0,0 +1,32 @@ +// +// ContainerBackgroundKind.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +// MARK: - ContainerBackgroundKind + +package enum ContainerBackgroundKind: Hashable { + package enum Builtin: Hashable, CaseIterable { + case navigation + case navigationSplitView + case tabView + case presentation + case window + } + + case builtin(Builtin) + case custom + + struct Key: EnvironmentKey { + static let defaultValue: ContainerBackgroundKind? = nil + } +} + +extension EnvironmentValues { + package var containerBackgroundKind: ContainerBackgroundKind? { + get { self[ContainerBackgroundKind.Key.self] } + set { self[ContainerBackgroundKind.Key.self] = newValue } + } +} From d76857baee503f5934cf7e7e1740dd3c32ae40b6 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 1 Nov 2026 21:53:21 +0800 Subject: [PATCH 02/19] feat: add background prominence environment API --- .../BackgroundProminence.swift | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 Sources/OpenSwiftUICore/Data/EnvironmentKeys/BackgroundProminence.swift diff --git a/Sources/OpenSwiftUICore/Data/EnvironmentKeys/BackgroundProminence.swift b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/BackgroundProminence.swift new file mode 100644 index 000000000..e3063b8e2 --- /dev/null +++ b/Sources/OpenSwiftUICore/Data/EnvironmentKeys/BackgroundProminence.swift @@ -0,0 +1,153 @@ +// +// BackgroundProminence.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete + +// MARK: - BackgroundProminence + +/// The prominence of backgrounds underneath other views. +/// +/// Background prominence should influence foreground styling to maintain +/// sufficient contrast against the background. For example, selected rows in +/// a `List` and `Table` can have increased prominence backgrounds with +/// accent color fills when focused; the foreground content above the background +/// should be adjusted to reflect that level of prominence. +/// +/// This can be read and written for views with the +/// `EnvironmentValues.backgroundProminence` property. +@available(OpenSwiftUI_v5_0, *) +public struct BackgroundProminence: Hashable, Sendable { + enum Guts { + case standard + case increased + } + + var guts: Guts + + /// The standard prominence of a background + /// + /// This is the default level of prominence and doesn't require any + /// adjustment to achieve satisfactory contrast with the background. + public static let standard = BackgroundProminence(guts: .standard) + + /// A more prominent background that likely requires some changes to the + /// views above it. + /// + /// This is the level of prominence for more highly saturated and full + /// color backgrounds, such as focused/emphasized selected list rows. + /// Typically foreground content should take on monochrome styling to + /// have greater contrast against the background. + public static let increased = BackgroundProminence(guts: .increased) + + struct Key: EnvironmentKey { + static let defaultValue = BackgroundProminence.standard + } +} + +@available(OpenSwiftUI_v5_0, *) +extension EnvironmentValues { + /// The prominence of the background underneath views associated with this + /// environment. + /// + /// Foreground elements above an increased prominence background are + /// typically adjusted to have higher contrast against a potentially vivid + /// color, such as taking on a higher opacity monochrome appearance + /// according to the `colorScheme`. System styles like `primary`, + /// `secondary`, etc will automatically resolve to an appropriate style in + /// this context. The property can be read and used for custom styled + /// elements. + /// + /// In the example below, a custom star rating element is in a list row + /// alongside some text. When the row is selected and has an increased + /// prominence appearance, the text and star rating will update their + /// appearance, the star rating replacing its use of yellow with the + /// standard `secondary` style. + /// + /// struct RecipeList: View { + /// var recipes: [Recipe] + /// @Binding var selectedRecipe: Recipe.ID? + /// + /// var body: some View { + /// List(recipes, selection: $selectedRecipe) { + /// RecipeListRow(recipe: $0) + /// } + /// } + /// } + /// + /// struct RecipeListRow: View { + /// var recipe: Recipe + /// var body: some View { + /// VStack(alignment: .leading) { + /// HStack(alignment: .firstTextBaseline) { + /// Text(recipe.name) + /// Spacer() + /// StarRating(rating: recipe.rating) + /// } + /// Text(recipe.description) + /// .foregroundStyle(.secondary) + /// .lineLimit(2, reservesSpace: true) + /// } + /// } + /// } + /// + /// private struct StarRating: View { + /// var rating: Int + /// + /// @Environment(\.backgroundProminence) + /// private var backgroundProminence + /// + /// var body: some View { + /// HStack(spacing: 1) { + /// ForEach(0.. some ShapeStyle { + /// switch env.backgroundProminence { + /// case .increased: return AnyShapeStyle(.secondary) + /// default: return AnyShapeStyle(.yellow) + /// } + /// } + /// } + /// } + /// + /// Views like `List` and `Table` as well as standard shape styles like + /// `ShapeStyle.selection` will automatically update the background + /// prominence of foreground views. For custom backgrounds, this environment + /// property can be explicitly set on views above custom backgrounds. + public var backgroundProminence: BackgroundProminence { + get { self[BackgroundProminence.Key.self] } + set { self[BackgroundProminence.Key.self] = newValue } + } +} From 3888e1986863e6af69c1b900dda3c58c10a6603f Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 1 Nov 2026 23:13:37 +0800 Subject: [PATCH 03/19] build: add watchOS v5 availability macro --- Package.resolved | 2 +- Package.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Package.resolved b/Package.resolved index 2b56331a3..603aa4830 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "baa9d73a9a30e9cf8367ffea2c8151f6ed08ebfb5fe1056d2f5c10994c2e31fc", + "originHash" : "1ba667fa351927216c6d6c5e588e2a276a6d0f6ca06f71d13d84407d81598c28", "pins" : [ { "identity" : "darwinprivateframeworks", diff --git a/Package.swift b/Package.swift index e2e650214..3487a155d 100644 --- a/Package.swift +++ b/Package.swift @@ -557,6 +557,7 @@ extension [SwiftSetting] { .enableExperimentalFeature("AvailabilityMacro=OpenSwiftUI_v4_1:\(ignoreAvailability ? minimumVersion : "iOS 16.1, macOS 13.0, tvOS 16.1, watchOS 9.1")"), .enableExperimentalFeature("AvailabilityMacro=OpenSwiftUI_v4_4:\(ignoreAvailability ? minimumVersion : "iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4")"), .enableExperimentalFeature("AvailabilityMacro=OpenSwiftUI_v5_0:\(ignoreAvailability ? minimumVersion : "iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0")"), + .enableExperimentalFeature("AvailabilityMacro=OpenSwiftUI_watchOS_v5_0:\(ignoreAvailability ? minimumVersion : "watchOS 10.0")"), .enableExperimentalFeature("AvailabilityMacro=OpenSwiftUI_v5_1:\(ignoreAvailability ? minimumVersion : "iOS 17.1, macOS 14.1, tvOS 17.1, watchOS 10.1, visionOS 1.0")"), .enableExperimentalFeature("AvailabilityMacro=OpenSwiftUI_v5_2:\(ignoreAvailability ? minimumVersion : "iOS 17.2, macOS 14.2, tvOS 17.2, watchOS 10.2, visionOS 1.0")"), .enableExperimentalFeature("AvailabilityMacro=OpenSwiftUI_v5_4:\(ignoreAvailability ? minimumVersion : "iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, visionOS 1.1")"), From d172354414b2f523997ff1e8defe5c6d5ec2e6a5 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sun, 1 Nov 2026 23:13:54 +0800 Subject: [PATCH 04/19] feat: add material APIs --- .../Shape/ShapeStyle/Material.swift | 236 ------ .../Shape/ShapeStyle/Material/Material.swift | 784 ++++++++++++++++++ 2 files changed, 784 insertions(+), 236 deletions(-) delete mode 100644 Sources/OpenSwiftUICore/Shape/ShapeStyle/Material.swift create mode 100644 Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material.swift deleted file mode 100644 index eb3889a68..000000000 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material.swift +++ /dev/null @@ -1,236 +0,0 @@ -// -// Material.swift -// OpenSwiftUICore -// -// Audited for 6.0.87 -// Status: WIP -// ID: B2CCB444DA7C00CFB13A219298A4122C (SwiftUICore) - -package import Foundation - -// MARK: Material - -/// A background material type. -/// -/// You can apply a blur effect to a view that appears behind another view by -/// adding a material with the ``View/background(_:ignoresSafeAreaEdges:)`` -/// modifier: -/// -/// ZStack { -/// Color.teal -/// Label("Flag", systemImage: "flag.fill") -/// .padding() -/// .background(.regularMaterial) -/// } -/// -/// In the example above, the ``ZStack`` layers a ``Label`` on top of the color -/// ``ShapeStyle/teal``. The background modifier inserts the -/// regular material below the label, blurring the part of -/// the background that the label --- including its padding --- covers: -/// -/// ![A screenshot of a label on a teal background, where the area behind -/// the label appears blurred.](Material-1) -/// -/// A material isn't a view, but adding a material is like inserting a -/// translucent layer between the modified view and its background: -/// -/// ![An illustration that shows a background layer below a material layer, -/// which in turn appears below a foreground layer.](Material-2) -/// -/// The blurring effect provided by the material isn't simple opacity. Instead, -/// it uses a platform-specific blending that produces an effect that resembles -/// heavily frosted glass. You can see this more easily with a complex -/// background, like an image: -/// -/// ZStack { -/// Image("chili_peppers") -/// .resizable() -/// .aspectRatio(contentMode: .fit) -/// Label("Flag", systemImage: "flag.fill") -/// .padding() -/// .background(.regularMaterial) -/// } -/// -/// ![A screenshot of a label on an image background, where the area behind -/// the label appears blurred.](Material-3) -/// -/// For physical materials, the degree to which the background colors pass -/// through depends on the thickness. The effect also varies with light and -/// dark appearance: -/// -/// ![An array of labels on a teal background. The first column, labeled light -/// appearance, shows a succession of labels on blurred backgrounds where the -/// blur increases from top to bottom, resulting in lighter and lighter blur. -/// The second column, labeled dark appearance, shows a similar succession of -/// labels, except that the blur gets darker from top to bottom. The rows are -/// labeled, from top to bottom: no material, ultra thin, thin, regular, thick, -/// and ultra thick.](Material-4) -/// -/// If you need a material to have a particular shape, you can use the -/// ``View/background(_:in:fillStyle:)`` modifier. For example, you can -/// create a material with rounded corners: -/// -/// ZStack { -/// Color.teal -/// Label("Flag", systemImage: "flag.fill") -/// .padding() -/// .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8)) -/// } -/// -/// ![A screenshot of a label on a teal background, where the area behind -/// the label appears blurred. The blurred area has rounded corners.](Material-5) -/// -/// When you add a material, foreground elements exhibit vibrancy, -/// a context-specific blend of the foreground and background colors -/// that improves contrast. However using ``View/foregroundStyle(_:)`` -/// to set a custom foreground style --- excluding the hierarchical styles, -/// like ``ShapeStyle/secondary-swift.type.property`` --- disables vibrancy. -/// -/// > Note: A material blurs a background that's part of your app, but not -/// what appears behind your app on the screen. -/// For example, the content on the Home Screen doesn't affect the appearance -/// of a widget. -public struct Material: Sendable { - package enum ID: Hashable { - case ultraThin - case thin - case regular - case thick - case ultraThick - case systemBars - case intelligenceLightSource_Unreactive - case intelligenceLightSource_AudioReactive - indirect case coreMaterial(light: String, dark: String, bundle: Bundle?) - } - - package struct ResolvedMaterial: Hashable { - package struct Flags: OptionSet, Hashable { - package let rawValue: UInt32 - - package init(rawValue: UInt32) { self.rawValue = rawValue } - - package static let darkColorScheme = Flags(rawValue: 1 << 0) - - package static let reduceTransparency = Flags(rawValue: 1 << 1) - - package static let increasedContrast = Flags(rawValue: 1 << 2) - - package static let disableChameleon = Flags(rawValue: 1 << 3) - - package static let isActive = Flags(rawValue: 1 << 4) - - package static let noBlur = Flags(rawValue: 1 << 5) - - package static let isEmphasized = Flags(rawValue: 1 << 6) - - package static let filtersInPlace = Flags(rawValue: 1 << 7) - - package static let noTransparentBlur = Flags(rawValue: 1 << 8) - - package static let isVisionEnabled = Flags(rawValue: 1 << 9) - - package static let noNormalizedEdgesBlur = Flags(rawValue: 1 << 10) - - package static let applyHardEdgesBlur = Flags(rawValue: 1 << 11) - - package init(environment: EnvironmentValues) { - var flags = Flags() - if environment.colorScheme == .dark { - flags.insert(.darkColorScheme) - } - if environment.accessibilityReduceTransparency { - flags.insert(.reduceTransparency) - } - if environment.colorSchemeContrast == .increased { - flags.insert(.increasedContrast) - } - self = flags - } - } - - package var id: Material.ID - - package var flags: Material.ResolvedMaterial.Flags - - package var colorScheme: ColorScheme { - flags.contains(.darkColorScheme) ? .dark : .light - } - - package var colorSchemeContrast: ColorSchemeContrast { - flags.contains(.increasedContrast) ? .increased : .standard - } - - package var isEmphasized: Swift.Bool { - flags.contains(.isEmphasized) - } - } - - package var id: Material.ID - - package var flags: Material.ResolvedMaterial.Flags - - package init(id: Material.ID) { - self.id = id - self.flags = [] - } - - package func resolve(in env: EnvironmentValues, role: ShapeRole? = nil) -> Material.ResolvedMaterial { - ResolvedMaterial( - id: id, - flags: Material.ResolvedMaterial.Flags(environment: env).union(flags) - ) - } -} - -// TODO - -extension Material: ShapeStyle { - // FIXME - - package static let ultraThin = Material(id: .ultraThin) - - package static let thin = Material(id: .thin) - - package static let regular = Material(id: .regular) - - package static let thick = Material(id: .thick) -} - -package struct ForegroundMaterialStyle: ShapeStyle, PrimitiveShapeStyle { - package var material: Material - - package init(material: Material) { - self.material = material - } - - package func _apply(to shape: inout _ShapeStyle_Shape) { - // TODO: CoreMaterial - _openSwiftUIUnimplementedWarning() - SystemColorsStyle()._apply(to: &shape) - } - - package typealias Resolved = Swift.Never -} - -private struct BackgroundMaterialKey: EnvironmentKey { - static var defaultValue: Material? { nil } -} - -extension EnvironmentValues { - /// The material underneath the current view. - /// - /// This value is `nil` if the current background isn't one of the standard - /// materials. If you set a material, the standard content styles enable - /// their vibrant rendering modes. - /// - /// You set this value by calling one of the background modifiers that takes - /// a ``ShapeStyle``, like ``View/background(_:ignoresSafeAreaEdges:)`` - /// or ``View/background(_:in:fillStyle:)``, and passing in a - /// ``Material``. You can also set the value manually, using - /// `nil` to disable vibrant rendering, or a ``Material`` instance to - /// enable the vibrancy style associated with the specified material. - public var backgroundMaterial: Material? { - get { self[BackgroundMaterialKey.self] } - set { self[BackgroundMaterialKey.self] = newValue } - } -} diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift new file mode 100644 index 000000000..656ad8310 --- /dev/null +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift @@ -0,0 +1,784 @@ +// +// Material.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete +// ID: B2CCB444DA7C00CFB13A219298A4122C (SwiftUICore) + +public import Foundation +import OpenAttributeGraphShims + +// MARK: Material + +/// A background material type. +/// +/// You can apply a blur effect to a view that appears behind another view by +/// adding a material with the ``View/background(_:ignoresSafeAreaEdges:)`` +/// modifier: +/// +/// ZStack { +/// Color.teal +/// Label("Flag", systemImage: "flag.fill") +/// .padding() +/// .background(.regularMaterial) +/// } +/// +/// In the example above, the ``ZStack`` layers a ``Label`` on top of the color +/// ``ShapeStyle/teal``. The background modifier inserts the +/// regular material below the label, blurring the part of +/// the background that the label --- including its padding --- covers: +/// +/// ![A screenshot of a label on a teal background, where the area behind +/// the label appears blurred.](Material-1) +/// +/// A material isn't a view, but adding a material is like inserting a +/// translucent layer between the modified view and its background: +/// +/// ![An illustration that shows a background layer below a material layer, +/// which in turn appears below a foreground layer.](Material-2) +/// +/// The blurring effect provided by the material isn't simple opacity. Instead, +/// it uses a platform-specific blending that produces an effect that resembles +/// heavily frosted glass. You can see this more easily with a complex +/// background, like an image: +/// +/// ZStack { +/// Image("chili_peppers") +/// .resizable() +/// .aspectRatio(contentMode: .fit) +/// Label("Flag", systemImage: "flag.fill") +/// .padding() +/// .background(.regularMaterial) +/// } +/// +/// ![A screenshot of a label on an image background, where the area behind +/// the label appears blurred.](Material-3) +/// +/// For physical materials, the degree to which the background colors pass +/// through depends on the thickness. The effect also varies with light and +/// dark appearance: +/// +/// ![An array of labels on a teal background. The first column, labeled light +/// appearance, shows a succession of labels on blurred backgrounds where the +/// blur increases from top to bottom, resulting in lighter and lighter blur. +/// The second column, labeled dark appearance, shows a similar succession of +/// labels, except that the blur gets darker from top to bottom. The rows are +/// labeled, from top to bottom: no material, ultra thin, thin, regular, thick, +/// and ultra thick.](Material-4) +/// +/// If you need a material to have a particular shape, you can use the +/// ``View/background(_:in:fillStyle:)`` modifier. For example, you can +/// create a material with rounded corners: +/// +/// ZStack { +/// Color.teal +/// Label("Flag", systemImage: "flag.fill") +/// .padding() +/// .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8)) +/// } +/// +/// ![A screenshot of a label on a teal background, where the area behind +/// the label appears blurred. The blurred area has rounded corners.](Material-5) +/// +/// When you add a material, foreground elements exhibit vibrancy, +/// a context-specific blend of the foreground and background colors +/// that improves contrast. However using ``View/foregroundStyle(_:)`` +/// to set a custom foreground style --- excluding the hierarchical styles, +/// like ``ShapeStyle/secondary-swift.type.property`` --- disables vibrancy. +/// +/// > Note: A material blurs a background that's part of your app, but not +/// what appears behind your app on the screen. +/// For example, the content on the Home Screen doesn't affect the appearance +/// of a widget. +@available(OpenSwiftUI_v3_0, *) +public struct Material: Sendable { + package enum ID: Hashable { + case ultraThin + case thin + case regular + case thick + case ultraThick + case systemBars + #if os(macOS) + case contentFill + case windowFill + case menu + case secondaryBackground + case tertiaryBackground + case quaternaryBackground + #elseif os(watchOS) + case modal + case toolbarButton + #elseif os(iOS) || os(visionOS) + case intelligenceLightSource_Unreactive + case intelligenceLightSource_AudioReactive + #endif + indirect case coreMaterial(light: String, dark: String, bundle: Bundle?) + #if os(macOS) || os(iOS) + // case provider(MaterialProviderBoxBase) + // case layers(Layers) + #endif + } + + package struct ResolvedMaterial: Hashable { + package struct Flags: OptionSet, Hashable { + package let rawValue: UInt32 + + package init(rawValue: UInt32) { self.rawValue = rawValue } + + package static let darkColorScheme = Flags(rawValue: 1 << 0) + + package static let reduceTransparency = Flags(rawValue: 1 << 1) + + package static let increasedContrast = Flags(rawValue: 1 << 2) + + package static let disableChameleon = Flags(rawValue: 1 << 3) + + package static let isActive = Flags(rawValue: 1 << 4) + + package static let noBlur = Flags(rawValue: 1 << 5) + + package static let isEmphasized = Flags(rawValue: 1 << 6) + + package static let filtersInPlace = Flags(rawValue: 1 << 7) + + package static let noTransparentBlur = Flags(rawValue: 1 << 8) + + package static let isVisionEnabled = Flags(rawValue: 1 << 9) + + package static let noNormalizedEdgesBlur = Flags(rawValue: 1 << 10) + + package static let applyHardEdgesBlur = Flags(rawValue: 1 << 11) + + package init(environment: EnvironmentValues) { + var flags = Flags() + if environment.colorScheme == .dark { + flags.insert(.darkColorScheme) + } + if environment.accessibilityReduceTransparency { + flags.insert(.reduceTransparency) + } + if environment.colorSchemeContrast == .increased { + flags.insert(.increasedContrast) + } + #if os(macOS) + if environment.reduceDesktopTinting { + flags.insert(.disableChameleon) + } + if isAppKitBased(), environment.backgroundProminence == .increased { + flags.insert(.isEmphasized) + } + #endif + self = flags + } + } + + package var id: Material.ID + + package var flags: Material.ResolvedMaterial.Flags + + package var colorScheme: ColorScheme { + flags.contains(.darkColorScheme) ? .dark : .light + } + + package var colorSchemeContrast: ColorSchemeContrast { + flags.contains(.increasedContrast) ? .increased : .standard + } + + package var isEmphasized: Bool { + flags.contains(.isEmphasized) + } + } + + package var id: Material.ID + + package var flags: Material.ResolvedMaterial.Flags = [] + + #if os(macOS) + package var activeAppearance: MaterialActiveAppearance = .active + #endif + + package init(_ id: Material.ID) { + self.id = id + } + + package func resolve(in env: EnvironmentValues, role: ShapeRole? = nil) -> Material.ResolvedMaterial { + var flags = ResolvedMaterial.Flags(environment: env).union(flags) + #if os(macOS) + let appearance = activeAppearance == .automatic ? env.materialActiveAppearance : activeAppearance + if appearance.resolve(material: id, in: env) { + flags.insert(.isActive) + } + #endif + return ResolvedMaterial(id: id, flags: flags) + } +} + +// MARK: - Material options + +@available(OpenSwiftUI_v4_0, *) +extension Material { + @_spi(_) + public func disablingBlur(_ flag: Bool = true) -> Material { + var result = self + result.flags.setValue(flag, for: .noBlur) + return result + } + + @_spi(_) + public func applyingFiltersInPlace(_ flag: Bool = true) -> Material { + var result = self + result.flags.setValue(flag, for: .filtersInPlace) + return result + } +} + +// MARK: - Material presets + +@available(OpenSwiftUI_v3_0, *) +@available(OpenSwiftUI_watchOS_v5_0, *) +extension ShapeStyle where Self == Material { + /// A material that's somewhat translucent. + @_alwaysEmitIntoClient + public static var regularMaterial: Material { .regular } + + /// A material that's more opaque than translucent. + @_alwaysEmitIntoClient + public static var thickMaterial: Material { .thick } + + /// A material that's more translucent than opaque. + @_alwaysEmitIntoClient + public static var thinMaterial: Material { .thin } + + /// A mostly translucent material. + @_alwaysEmitIntoClient + public static var ultraThinMaterial: Material { .ultraThin } + + /// A mostly opaque material. + @_alwaysEmitIntoClient + public static var ultraThickMaterial: Material { .ultraThick } +} + +@available(OpenSwiftUI_v3_0, *) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +extension ShapeStyle where Self == Material { + /// A material matching the style of system toolbars. + @_alwaysEmitIntoClient + public static var bar: Material { .bar } +} + +@available(OpenSwiftUI_v3_0, *) +@available(OpenSwiftUI_watchOS_v5_0, *) +extension Material { + /// A material that's somewhat translucent. + public static let regular = Material(.regular) + + /// A material that's more opaque than translucent. + public static let thick = Material(.thick) + + /// A material that's more translucent than opaque. + public static let thin = Material(.thin) + + /// A mostly translucent material. + public static let ultraThin = Material(.ultraThin) + + /// A mostly opaque material. + public static let ultraThick = Material(.ultraThick) +} + +@available(OpenSwiftUI_v3_0, *) +@available(OpenSwiftUI_watchOS_v5_0, *) +extension Material { + @_spi(_) + public static var pinched: Material { Material(.regular) } + + @_spi(_) + public static var selected: Material { .pinched } + + @_spi(_) + public static var disabled: Material { .pinched } + + @_spi(_) + public static func coreMaterial(bundle: Bundle? = nil, provider: @escaping (ColorScheme) -> String) -> Material { + Material(.coreMaterial(light: provider(.light), dark: provider(.dark), bundle: bundle)) + } +} + +@available(OpenSwiftUI_v3_0, *) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +extension Material { + /// A material matching the style of system toolbars. + public static let bar = Material(.systemBars) +} + +@_spi(Private) +@available(OpenSwiftUI_v3_0, *) +@available(macOS, unavailable) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +extension Material { + @available(*, deprecated, renamed: "bar") + public static let chrome = Material(.systemBars) +} + +@_spi(Private) +@available(OpenSwiftUI_v3_0, *) +@available(iOS, unavailable) +@available(macOS, unavailable) +@available(tvOS, unavailable) +extension Material { + public static var modal: Material { + #if os(watchOS) + Material(.modal) + #else + _openSwiftUIUnreachableCode() + #endif + } +} + +@_spi(Private) +@available(OpenSwiftUI_v5_0, *) +@available(iOS, unavailable) +@available(macOS, unavailable) +@available(tvOS, unavailable) +extension Material { + public static var toolbarButton: Material { + #if os(watchOS) + Material(.toolbarButton) + #else + _openSwiftUIUnreachableCode() + #endif + } +} + +#if os(macOS) +extension Material { + package static let contentFill = Material(.contentFill) + + @_spi(Private) + @available(OpenSwiftUI_v4_0, *) + @available(macCatalyst 17.0, *) + @available(iOS, unavailable) + @available(tvOS, unavailable) + @available(watchOS, unavailable) + public static let windowFill = Material(.windowFill) + + package static let menu = Material(.menu) + + package static let secondaryBackground = Material(.secondaryBackground) + + package static let tertiaryBackground = Material(.tertiaryBackground) + + package static let quaternaryBackground = Material(.quaternaryBackground) +} +#endif + + +@_spi(Private) +@available(OpenSwiftUI_v6_0, *) +@available(macOS, unavailable) +@available(macCatalyst, unavailable) +@available(tvOS, unavailable) +@available(watchOS, unavailable) +@available(visionOS, unavailable) +extension Material { + public static func _intelligenceLightSource(prefersAudioReactivity: Bool = false) -> Material { + #if os(macOS) || os(watchOS) + _openSwiftUIUnreachableCode() + #else + Material(prefersAudioReactivity ? .intelligenceLightSource_AudioReactive : .intelligenceLightSource_Unreactive) + #endif + } +} + +// MARK: - View + Material + +@_spi(_) +@available(OpenSwiftUI_v3_0, *) +extension View { + @available(*, deprecated, renamed: "background(_:)") + @_disfavoredOverload + nonisolated public func backgroundMaterial(_ material: Material) -> some View { + modifier(MaterialView(material: material, shape: Rectangle(), ignoredSafeAreaEdges: .all)) + } + + @available(*, deprecated, renamed: "background(_:in:)") + @_disfavoredOverload + nonisolated public func backgroundMaterial(_ material: Material, in shape: S) -> some View where S: Shape { + modifier(MaterialView(material: material, shape: shape)) + } + + @available(*, deprecated, renamed: "background(_:)") + @_alwaysEmitIntoClient + nonisolated public func backgroundMaterial(_v2: Void = (), _ material: Material) -> some View { + background(material) + } + + @available(*, deprecated, renamed: "background(_:in:)") + @_alwaysEmitIntoClient + nonisolated public func backgroundMaterial(_v2: Void = (), _ material: Material, in shape: S) -> some View where S: Shape { + background(material, in: shape) + } +} + +// MARK: - EnvironmentValues + backgroundMaterial + +private struct BackgroundMaterialKey: EnvironmentKey { + static var defaultValue: Material? { nil } +} + +@available(OpenSwiftUI_v3_0, *) +extension EnvironmentValues { + /// The material underneath the current view. + /// + /// This value is `nil` if the current background isn't one of the standard + /// materials. If you set a material, the standard content styles enable + /// their vibrant rendering modes. + /// + /// You set this value by calling one of the background modifiers that takes + /// a ``ShapeStyle``, like ``View/background(_:ignoresSafeAreaEdges:)`` + /// or ``View/background(_:in:fillStyle:)``, and passing in a + /// ``Material``. You can also set the value manually, using + /// `nil` to disable vibrant rendering, or a ``Material`` instance to + /// enable the vibrancy style associated with the specified material. + public var backgroundMaterial: Material? { + get { self[BackgroundMaterialKey.self] } + set { self[BackgroundMaterialKey.self] = newValue } + } +} + +// MARK: - MaterialView + +package struct MaterialView where S: Shape { + package var material: Material + + package var shape: S + + package var ignoredSafeAreaEdges: Edge.Set + + package init(material: Material, shape: S, ignoredSafeAreaEdges: Edge.Set = []) { + self.material = material + self.shape = shape + self.ignoredSafeAreaEdges = ignoredSafeAreaEdges + } + + private struct Background: Rule, AsyncAttribute { + @Attribute var view: MaterialView + + var value: some View { + view.shape.fill(view.material) + .ignoresSafeArea(.container, edges: view.ignoredSafeAreaEdges) + } + } + + private struct ChildEnvironment: Rule { + @Attribute var material: Material + @Attribute var environment: EnvironmentValues + + var value: EnvironmentValues { + var shape = _ShapeStyle_Shape(operation: .modifyBackground(level: 0), environment: environment) + material._apply(to: &shape) + return shape.environment + } + } +} + +extension MaterialView: PrimitiveView, MultiView { + nonisolated package static func _makeView(view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs { + let background = Attribute(Background(view: view.value)) + return Background.Value.makeDebuggableView(view: .init(background), inputs: inputs) + } +} + +extension MaterialView: PrimitiveViewModifier, MultiViewModifier { + nonisolated package static func _makeView( + modifier: _GraphValue, + inputs: _ViewInputs, + body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs + ) -> _ViewOutputs { + let background = Attribute(Background(view: modifier.value)) + var inputs = inputs + inputs.environment = Attribute(ChildEnvironment( + material: modifier[offset: { .of(&$0.material) }].value, + environment: inputs.environment + )) + inputs.needsGeometry = true + return makeSecondaryLayerView( + secondaryLayer: background, + alignment: nil, + inputs: inputs, + body: body, + flipOrder: true + ) + } +} + +// MARK: - MaterialLayer + +package protocol MaterialLayer { + var content: DisplayList.Content.Value { get } + + var opacity: Float { get } + + var blendMode: GraphicsBlendMode { get } +} + +extension MaterialLayer { + package var opacity: Float { + 1 + } + + package var blendMode: GraphicsBlendMode { + .normal + } +} + +package struct AnyMaterialLayer: MaterialLayer { + package var content: DisplayList.Content.Value + + package var opacity: Float + + package var blendMode: GraphicsBlendMode + + package init( + content: DisplayList.Content.Value, + opacity: Float = 1, + blendMode: GraphicsBlendMode = .normal + ) { + self.content = content + self.opacity = opacity + self.blendMode = blendMode + } +} + +package protocol MaterialLayerVisitor { + mutating func visit(layer: L) where L: MaterialLayer +} + +// MARK: - Material + ShapeStyle + +@available(OpenSwiftUI_v3_0, *) +extension Material: ShapeStyle, PrimitiveShapeStyle { + nonisolated public static func _makeView( + view: _GraphValue<_ShapeView>, + inputs: _ViewInputs + ) -> _ViewOutputs where S: Shape { + legacyMakeShapeView(view: view, inputs: inputs) + } + + public func _apply(to shape: inout _ShapeStyle_Shape) { + switch shape.operation { + case .prepareText: + shape.result = .preparedText(.foregroundKeyColor) + case let .resolveStyle(name, levels): + guard let level = levels.first else { return } + let material = resolve(in: shape.environment, role: shape.role) + shape.stylePack[name, level] = .init(.backgroundMaterial(material)) + case .fallbackColor: + shape.result = .color(Color._background) + case .modifyBackground: + #if os(macOS) + switch id { + case .contentFill, .windowFill, .secondaryBackground, .tertiaryBackground, .quaternaryBackground: + shape.environment.backgroundMaterial = nil + default: + shape.environment.backgroundMaterial = self + } + #else + shape.environment.backgroundMaterial = self + #endif + case .copyStyle, .multiLevel, .primaryStyle: + break + } + } + + public static func _apply(to type: inout _ShapeStyle_ShapeType) { + type.result = .bool(true) + } +} + +// MARK: - ForegroundMaterialStyle + +package struct ForegroundMaterialStyle: ShapeStyle, PrimitiveShapeStyle { + package var material: Material + + package init(material: Material) { + self.material = material + } + + package func _apply(to shape: inout _ShapeStyle_Shape) { + switch shape.operation { + case .prepareText: + shape.result = .preparedText(.foregroundKeyColor) + case let .resolveStyle(name, levels): + guard let level = levels.first else { return } + let id = ContentStyle.ID(truncatingLevel: level) + shape.stylePack[name, level] = shape.resolveStyle(id: id, material: material) + case .fallbackColor: + SystemColorsStyle()._apply(to: &shape) + default: + break + } + } +} + +extension _ShapeStyle_Shape { + fileprivate func resolveStyle(id: ContentStyle.ID, material: Material) -> _ShapeStyle_Pack.Style { + let style = ContentStyle.MaterialStyle( + material: material.resolve(in: environment, role: nil), + base: .init(id: id, primitive: .init(role)) + ) + return .init(.foregroundMaterial(style.resolveColor(in: environment), style)) + } +} + +// MARK: - MaterialActiveAppearance + +/// The behavior for how materials appear active and inactive. +/// +/// On macOS, materials have active and inactive appearances that can reinforce +/// the active appearance of the window they are in: +/// - Materials used as a `window` container background and `bar` materials +/// will appear inactive when their containing window is inactive. +/// - All other materials will always appear active by default. +/// +/// An explicit active appearance can be set to override a material's default +/// behavior. For example, materials used as the `window` container background +/// can be made to always appear active by setting the active appearance +/// behavior to be always active: +/// +/// Text("Hello, World!") +/// .containerBackground( +/// Material.regular.materialActiveAppearance(.active), +/// for: .window) +/// +@available(OpenSwiftUI_v6_0, *) +public struct MaterialActiveAppearance: Sendable, Equatable { + private enum Storage { + case automatic + case active + case inactive + case matchWindow + } + + private var storage: Storage + + /// Materials will automatically appear active or inactive based on context + /// and platform convention. + /// + /// Examples of automatic behavior on macOS: + /// - Materials used as a `window` container background will appear inactive + /// when the containing window is inactive. + /// - `Material.bar` materials will appear inactive when the containing + /// window is inactive. + /// - All other materials will appear as active. + /// + /// Materials always appear as active on all other platforms. + public static let automatic = MaterialActiveAppearance(storage: .automatic) + + /// Materials will always appear active. + public static let active = MaterialActiveAppearance(storage: .active) + + /// Materials will always appear inactive. + @available(iOS, unavailable) + @available(tvOS, unavailable) + @available(watchOS, unavailable) + @available(visionOS, unavailable) + public static let inactive = MaterialActiveAppearance(storage: .inactive) + + /// Materials will have an active or inactive appearance based on + /// the active appearance of their window. + /// + /// On non-macOS platforms, materials will always appear as active. + public static let matchWindow = MaterialActiveAppearance(storage: .matchWindow) + + #if os(macOS) + fileprivate func resolve(material: Material.ID, in environment: EnvironmentValues) -> Bool { + let appearsActive = environment.appearsActive + let container = environment.containerBackgroundKind + switch storage { + case .automatic: + return appearsActive || (material != .systemBars && container != .builtin(.window)) + case .active: + return true + case .inactive: + return false + case .matchWindow: + return appearsActive + } + } + #endif + + fileprivate struct Key: EnvironmentKey { + static let defaultValue = MaterialActiveAppearance.automatic + } +} + +@available(OpenSwiftUI_v6_0, *) +extension EnvironmentValues { + /// The behavior materials should use for their active state, defaulting to + /// `automatic`. + public var materialActiveAppearance: MaterialActiveAppearance { + get { self[MaterialActiveAppearance.Key.self] } + set { self[MaterialActiveAppearance.Key.self] = newValue } + } +} + +@available(OpenSwiftUI_v6_0, *) +extension Material { + /// Sets an explicit active appearance for this material. + /// + /// Materials used as the `window` container background on macOS will + /// automatically appear inactive when their window appears inactive, but + /// can be made to always appear active by setting the active appearance + /// behavior to be always active: + /// + /// Text("Hello, World!") + /// .containerBackground( + /// Material.regular.materialActiveAppearance(.active), + /// for: .window) + /// + public func materialActiveAppearance(_ appearance: MaterialActiveAppearance) -> Material { + #if os(macOS) + var result = self + result.activeAppearance = appearance + return result + #else + return self + #endif + } +} + +@available(OpenSwiftUI_v6_0, *) +extension ShapeStyle { + /// Sets an explicit active appearance for materials created by this style. + public func materialActiveAppearance(_ appearance: MaterialActiveAppearance) -> some ShapeStyle { + MaterialActiveAppearanceShapeStyle(base: self, appearance: appearance) + } +} + +@available(OpenSwiftUI_v6_0, *) +extension View { + /// Sets an explicit active appearance for materials in this view. + nonisolated public func materialActiveAppearance(_ appearance: MaterialActiveAppearance) -> some View { + environment(\.materialActiveAppearance, appearance) + } +} + +private struct MaterialActiveAppearanceShapeStyle: ShapeStyle, PrimitiveShapeStyle where Base: ShapeStyle { + var base: Base + + var appearance: MaterialActiveAppearance + + func _apply(to shape: inout _ShapeStyle_Shape) { + shape.environment.materialActiveAppearance = appearance + base._apply(to: &shape) + } + + static func _apply(to type: inout _ShapeStyle_ShapeType) { + Base._apply(to: &type) + } +} From e421d065e78a24e272cefb8b7e315b3d54355b31 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 2 Nov 2026 22:57:30 +0800 Subject: [PATCH 05/19] build(deps): add CoreMaterial and update DPF --- Package.resolved | 4 ++-- Package.swift | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Package.resolved b/Package.resolved index 603aa4830..d181d4c5b 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "1ba667fa351927216c6d6c5e588e2a276a6d0f6ca06f71d13d84407d81598c28", + "originHash" : "cf469bde21164ae1f51cdb8c68e8b3b73f160f09f00bb744d8cdae370bb9acbc", "pins" : [ { "identity" : "darwinprivateframeworks", @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", "state" : { "branch" : "main", - "revision" : "bc86728a45d2d6dc04bea9ad248971b24c36f019" + "revision" : "5a8c0cb54ae37cccad0e222b51f4d7bee928b709" } }, { diff --git a/Package.swift b/Package.swift index 3487a155d..0392cf488 100644 --- a/Package.swift +++ b/Package.swift @@ -153,6 +153,7 @@ let attributeGraphCondition = envBoolValue("OPENATTRIBUTESHIMS_ATTRIBUTEGRAPH", let renderBoxCondition = envBoolValue("RENDERBOX", default: buildForDarwinPlatform) let linkCoreUI = envBoolValue("LINK_COREUI", default: buildForDarwinPlatform) +let linkCoreMaterial = envBoolValue("LINK_COREMATERIAL", default: buildForDarwinPlatform) let linkCoreSVG = envBoolValue("LINK_CORESVG", default: buildForDarwinPlatform) let linkSFSymbols = envBoolValue("LINK_SFSYMBOLS", default: buildForDarwinPlatform) let linkSymbols = envBoolValue("LINK_SYMBOLS", default: buildForDarwinPlatform) @@ -258,6 +259,12 @@ if linkCoreUI { sharedSwiftSettings.append(.define("OPENSWIFTUI_LINK_COREUI")) } +if linkCoreMaterial { + sharedSwiftSettings.append( + .define("OPENSWIFTUI_LINK_COREMATERIAL", .when(platforms: [.iOS, .macOS, .visionOS])) + ) +} + if linkCoreSVG { sharedSwiftSettings.append( .define("OPENSWIFTUI_LINK_CORESVG", .when(platforms: [.iOS, .macOS, .visionOS])) @@ -418,6 +425,16 @@ extension Target { dependencies.append(.product(name: "CoreUI", package: "DarwinPrivateFrameworks")) } + func addCoreMaterialSettings() { + dependencies.append( + .product( + name: "CoreMaterial", + package: "DarwinPrivateFrameworks", + condition: .when(platforms: [.iOS, .macOS, .visionOS]) + ) + ) + } + func addCoreSVGSettings() { // FIXME: Weird SwiftPM behavior for test Target. Otherwize we'll get the following error message // "could not determine executable path for bundle 'CoreSVG.framework'" @@ -965,6 +982,10 @@ if linkCoreUI { openSwiftUISPITarget.addCoreUISettings() } +if linkCoreMaterial { + openSwiftUICoreTarget.addCoreMaterialSettings() +} + if linkCoreSVG { openSwiftUICoreTarget.addCoreSVGSettings() openSwiftUISPITarget.addCoreSVGSettings() @@ -996,7 +1017,7 @@ if useLocalDeps { .package(path: "../OpenRenderBox"), .package(path: "../OpenObservation"), ] - if attributeGraphCondition || renderBoxCondition || linkCoreUI || linkCoreSVG || linkSFSymbols || linkSymbols || linkFeatureFlags || linkBacklightServices || linkGestures { + if attributeGraphCondition || renderBoxCondition || linkCoreUI || linkCoreMaterial || linkCoreSVG || linkSFSymbols || linkSymbols || linkFeatureFlags || linkBacklightServices || linkGestures { dependencies.append(.package(path: "../DarwinPrivateFrameworks")) } package.dependencies += dependencies @@ -1008,7 +1029,7 @@ if useLocalDeps { .package(url: "https://github.com/OpenSwiftUIProject/OpenRenderBox", branch: "main"), .package(url: "https://github.com/OpenSwiftUIProject/OpenObservation", branch: "main"), ] - if attributeGraphCondition || renderBoxCondition || linkCoreUI || linkCoreSVG || linkSFSymbols || linkSymbols || linkFeatureFlags || linkGestures { + if attributeGraphCondition || renderBoxCondition || linkCoreUI || linkCoreMaterial || linkCoreSVG || linkSFSymbols || linkSymbols || linkFeatureFlags || linkGestures { dependencies.append(.package(url: "https://github.com/OpenSwiftUIProject/DarwinPrivateFrameworks.git", branch: "main")) } package.dependencies += dependencies From 1c9692a6fa32962e76cdcc41b7c0352f482047b6 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 3 Nov 2026 01:03:27 +0800 Subject: [PATCH 06/19] feat: implement CoreMaterial definitions --- .../Material/CoreMaterialDefinitions.swift | 410 ++++++++++++++++++ 1 file changed, 410 insertions(+) create mode 100644 Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/CoreMaterialDefinitions.swift diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/CoreMaterialDefinitions.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/CoreMaterialDefinitions.swift new file mode 100644 index 000000000..a79fb984b --- /dev/null +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/CoreMaterialDefinitions.swift @@ -0,0 +1,410 @@ +// +// CoreMaterialDefinitions.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete +// ID: 1136DDC987AEEDFF1217B91AA2732172 (SwiftUICore) + +import Foundation +#if canImport(CoreGraphics) +import CoreGraphics +#endif +#if OPENSWIFTUI_LINK_COREMATERIAL +import CoreMaterial +#endif +#if os(visionOS) && canImport(QuartzCore) +import QuartzCore_Private +#endif + +// MARK: - ContentStyle.MaterialStyle + +extension ContentStyle.MaterialStyle { + package func resolveCoreMaterialColor(in environment: EnvironmentValues) -> Color.Resolved { + #if os(visionOS) + if environment.isVisionEnabled { + return (environment.materialBaseColor ?? .white).resolve(in: environment) + } + #endif + return material.colorScheme == .dark ? .white : .black + } + + func resolveCoreMaterialEffects() -> [DisplayList.Effect] { + CoreMaterialCache.$shared.access { $0[self] } + } + + fileprivate var coreMaterialDescription: [String: Any] { + #if OPENSWIFTUI_LINK_COREMATERIAL + let category: MTCoreMaterialVisualStyleCategory + let style: MTCoreMaterialVisualStyle + switch base.primitive { + case .separator: + category = .fill + style = .separator + default: + switch base.primitive { + case .fill: category = .fill + #if os(visionOS) + case .overlay: category = .overlay + #endif + default: category = .stroke + } + switch base.id { + case .primary: style = .primary + case .secondary: style = .secondary + case .tertiary: style = .tertiary + case .quaternary, .quinary: + #if os(visionOS) + style = base.primitive == .fill && !material.flags.contains(.isVisionEnabled) + ? .tertiary : .quaternary + #else + style = base.primitive == .fill ? .tertiary : .quaternary + #endif + } + } + var description = MTVisualStylingCreateDictionaryRepresentation( + material.coreMaterialRecipe, + category, + style, + material.coreMaterialOptions + ) ?? [:] + #if os(visionOS) + if base.primitive == .destructive, + var filter = description["compositingFilter"] as? [String: Any], + filter["inputAmount"] is Double { + filter["inputAmount"] = 0.65 + description["compositingFilter"] = filter + } + #endif + return description + #else + _openSwiftUIPlatformUnimplementedWarning() + return [:] + #endif + } +} + +// MARK: - Material.ResolvedMaterial + +extension Material.ResolvedMaterial { + package func visitCoreMaterialLayers(_ visitor: inout V) { + let layers = CoreMaterialCache.$shared.access { $0[self] } + for layer in layers { + visitor.visit(layer: layer) + } + } + + fileprivate var coreMaterialDescription: [String: Any] { + #if OPENSWIFTUI_LINK_COREMATERIAL + return MTMaterialCreateDictionaryRepresentation(coreMaterialRecipe, coreMaterialOptions)! + #else + _openSwiftUIPlatformUnimplementedWarning() + return [:] + #endif + } + + #if OPENSWIFTUI_LINK_COREMATERIAL + fileprivate var coreMaterialRecipe: MTCoreMaterialRecipe { + let isDark = colorScheme == .dark + switch id { + case .ultraThin: + return isDark ? .platformContentUltraThinDark : .platformContentUltraThinLight + case .thin: + return isDark ? .platformContentThinDark : .platformContentThinLight + case .regular: + return isDark ? .platformContentDark : .platformContentLight + case .thick, .ultraThick: + return isDark ? .platformContentThickDark : .platformContentThickLight + case .systemBars: + return isDark ? .platformChromeDark : .platformChromeLight + #if os(visionOS) + case .pinched: return MTCoreMaterialRecipe(rawValue: "platformPinched") + case .selected: return MTCoreMaterialRecipe(rawValue: "platformSelected") + case .disabled: return MTCoreMaterialRecipe(rawValue: "platformDisabled") + case .vibrantGlassContent: return .platformContentGlass + case .darkerGlass: return .platformContentGlassDarker + case .lighterGlass: return .platformContentGlassLighter + case .ultraDarkerGlass: return .platformContentGlassUltraDarker + #endif + case let .coreMaterial(light, dark, _): + return MTCoreMaterialRecipe(rawValue: isDark ? dark : light) + default: + preconditionFailure("unexpected material ID type") + } + } + + fileprivate var coreMaterialOptions: [String: Any] { + var options: [String: Any] = [ + kMTDictionaryRepresentationOptionsIncludeOptimizationsKey: true, + kMTDictionaryRepresentationOptionsReduceTransparencyKey: flags.contains(.reduceTransparency), + ] + if case let .coreMaterial(_, _, bundle) = id { + options[kMTDictionaryRepresentationOptionsBundleKey] = bundle + } + #if os(visionOS) + options[kMTDictionaryRepresentationOptionsFiltersRepresentationV2Key] = true + #endif + return options + } + #endif +} + +// MARK: - CoreMaterialCache + +private struct CoreMaterialCache { + var materials: [Material.ResolvedMaterial: [AnyMaterialLayer]] = [:] + var styles: [ContentStyle.MaterialStyle: [DisplayList.Effect]] = [:] + + @AtomicBox + static var shared = CoreMaterialCache() + + subscript(material: Material.ResolvedMaterial) -> [AnyMaterialLayer] { + mutating get { + if let layers = materials[material] { + return layers + } else { + let layers = load(material: material) + materials[material] = layers + return layers + } + } + } + + subscript(style: ContentStyle.MaterialStyle) -> [DisplayList.Effect] { + mutating get { + if let effects = styles[style] { + return effects + } else { + let effects = loadEffects(style: style) + styles[style] = effects + return effects + } + } + } + + func load(material: Material.ResolvedMaterial) -> [AnyMaterialLayer] { + let description = material.coreMaterialDescription + guard let definitions = description["filters"] as? [[String: Any]] else { + return [] + } + let scale: Float + if material.flags.contains(.filtersInPlace) { + scale = 0 + } else if material.flags.contains(.noBlur) { + scale = 1 + } else { + scale = Float(description["scale"] as? Double ?? 0.25) + } + var filters: [GraphicsFilter] = [] + for definition in definitions { + switch definition["type"] as? String { + case "colorSaturate": + if let amount = definition["inputAmount"] as? Double, amount != 1 { + filters.append(.saturation(amount)) + } + case "colorBrightness": + if let amount = definition["inputAmount"] as? Double, amount != 0 { + filters.append(.brightness(amount)) + } + case "gaussianBlur": + guard !material.flags.contains(.noBlur), + let radius = definition["inputRadius"] as? Double else { + continue + } + var style = BlurStyle( + radius: CGFloat(radius), + isOpaque: definition["inputNormalizeEdges"] as? Bool ?? false, + dither: definition["inputDither"] as? Bool ?? false + ) + #if os(visionOS) + style.isOpaque = style.isOpaque && !material.flags.contains(.noNormalizedEdgesBlur) + style.hardEdges = (definition["inputHardEdges"] as? Bool ?? false) + || material.flags.contains(.applyHardEdgesBlur) + style.isVisionEnabled = material.flags.contains(.isVisionEnabled) + && !material.flags.contains(.noTransparentBlur) + #endif + if !style.isIdentity { + filters.append(.blur(style)) + } + #if canImport(CoreGraphics) + case "variableBlur": + guard !material.flags.contains(.noBlur), + let radius = definition["inputRadius"] as? Double, + let image = definition["inputMaskImage"] as! CGImage? else { + continue + } + let mask = GraphicsImage( + contents: .cgImage(image), + scale: 1, + unrotatedPixelSize: CGSize(width: image.width, height: image.height), + orientation: .up, + isTemplate: false + ) + var style = VariableBlurStyle( + isOpaque: definition["inputNormalizeEdges"] as? Bool ?? false, + dither: definition["inputDither"] as? Bool ?? false, + mask: .image(mask) + ) + style.caFilterRadius = CGFloat(radius) + if !style.isIdentity { + filters.append(.variableBlur(style)) + } + #endif + case "averageColor": + if !material.flags.contains(.noBlur) { + filters.append(.averageColor) + } + case "luminanceCurveMap": + if let amount = definition["inputAmount"] as? Double { + filters.append(.luminanceCurve(.init( + curve: Self.parseCurve(definition["inputValues"]), + amount: Float(amount) + ))) + } + case "curves": + filters.append(.colorCurves(.init( + redCurve: Self.parseCurve(definition["inputRedValues"]), + greenCurve: Self.parseCurve(definition["inputGreenValues"]), + blueCurve: Self.parseCurve(definition["inputBlueValues"]), + opacityCurve: Self.parseCurve(definition["inputAlphaValues"]) + ))) + #if canImport(ObjectiveC) + case "colorMatrix": + if let value = definition["inputColorMatrix"] as? NSValue { + var matrix = _ColorMatrix() + value.getValue(&matrix) + if !matrix.isIdentity { + filters.append(.colorMatrix(matrix, premultiplied: false)) + } + } + #endif + default: + break + } + } + guard !filters.isEmpty else { return [] } + let backdrop = BackdropEffect(scale: scale, color: .clear, filters: filters) + let layer = AnyMaterialLayer(content: .backdrop(backdrop)) + #if os(visionOS) && canImport(QuartzCore) + if material.flags.contains(.isVisionEnabled), + !material.flags.contains(.noTransparentBlur), + !material.flags.contains(.noBlur) { + var capture = backdrop + capture.captureOnly = true + return [ + AnyMaterialLayer(content: .backdrop(capture)), + AnyMaterialLayer(content: .color(.black), blendMode: .destOutPassthrough), + layer, + ] + } + #endif + return [layer] + } + + private static func parseCurve(_ value: Any?) -> GraphicsFilter.Curve { + guard let values = value as? [Double] else { + return .init((0, 0, 1, 1)) + } + return .init((Float(values[0]), Float(values[1]), Float(values[2]), Float(values[3]))) + } + + func loadEffects(style: ContentStyle.MaterialStyle) -> [DisplayList.Effect] { + let description = style.coreMaterialDescription + var effects: [DisplayList.Effect] = [] + #if os(visionOS) && canImport(QuartzCore) + if let effect = loadBlendModeEffect(description) { + effects.append(effect) + } + if let effect = loadTintAlphaEffect(description) { + effects.append(effect) + } + #endif + + var colorMatrix: _ColorMatrix? + #if canImport(ObjectiveC) + if let filters = description["filters"] as? [[String: Any]] { + for filter in filters { + guard filter["type"] as? String == "vibrantColorMatrix", + let value = filter["inputColorMatrix"] as? NSValue else { + continue + } + var matrix = _ColorMatrix() + value.getValue(&matrix) + colorMatrix = colorMatrix.map { matrix * $0 } ?? matrix + } + } + #endif + + if let colorMatrix { + effects.append(.filter(.vibrantColorMatrix(colorMatrix))) + } else if let tint = Self.parseColor(description["tintColor"]) { + if tint.linearRed == 1, tint.linearGreen == 1, tint.linearBlue == 1 { + if tint.opacity != 1 { + effects.append(.opacity(tint.opacity)) + } + } else { + effects.append(.filter(.colorMultiply(tint))) + } + } + return effects + } + + static func parseColor(_ value: Any?) -> Color.Resolved? { + guard let color = value as? [String: Any] else { + return nil + } + guard let alpha = color["alpha"] as? Double else { + return .white + } + if let white = color["white"] as? Double { + return Color.Resolved(white: Float(white), opacity: Float(alpha)) + } + if let red = color["red"] as? Double, + let green = color["green"] as? Double, + let blue = color["blue"] as? Double { + return Color.Resolved( + colorSpace: .sRGB, + red: Float(red), + green: Float(green), + blue: Float(blue), + opacity: Float(alpha) + ) + } + return Color.Resolved(linearWhite: 1, opacity: Float(alpha)) + } + + #if os(visionOS) && canImport(QuartzCore) + private static let subtractSIgnoreAlpha = GraphicsBlendMode.caFilter(CAFilter(type: "subtractSIgnoreAlpha")) + private static let plusDIgnoreAlpha = GraphicsBlendMode.caFilter(CAFilter(type: "plusDIgnoreAlpha")) + private static let plusLIgnoreAlpha = GraphicsBlendMode.caFilter(CAFilter(type: "plusLIgnoreAlpha")) + + func loadBlendModeEffect(_ description: [String: Any]) -> DisplayList.Effect? { + let filter = description["compositingFilter"] as? [String: Any] + let type = description["compositingFilter"] as? String ?? filter?["type"] as? String + switch type { + case "subtractSIgnoreAlpha": + return .blendMode(Self.subtractSIgnoreAlpha) + case "plusDIgnoreAlpha": + return .blendMode(Self.plusDIgnoreAlpha) + case "plusLIgnoreAlpha": + return .blendMode(Self.plusLIgnoreAlpha) + case "plusLIgnoreAlphaSourceOverBlend": + guard let amount = filter?["inputAmount"] as? Double else { + return nil + } + let filter = CAFilter(type: "plusLIgnoreAlphaSourceOverBlend") + filter.setInput(value: amount, key: .amount) + return .blendMode(.caFilter(filter)) + default: + return nil + } + } + + func loadTintAlphaEffect(_ description: [String: Any]) -> DisplayList.Effect? { + guard let alpha = description["tintAlpha"] as? Float, alpha != 1 else { + return nil + } + return .opacity(alpha) + } + #endif +} From 869bfcd803ae8647383b84e955081882287ea1e9 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 3 Nov 2026 01:11:00 +0800 Subject: [PATCH 07/19] feat: add visionOS material state --- .../Graphic/BackdropEffect.swift | 7 + .../Graphic/BlurStyle/BlurStyle.swift | 16 + .../Graphic/Color/ColorMatrix.swift | 17 + .../OpaqueBackgroundModifier.swift | 22 + .../Shape/ShapeStyle/ContentStyle.swift | 4 + .../Material/CoreMaterialDefinitions.swift | 14 +- .../Shape/ShapeStyle/Material/Material.swift | 69 +++- .../Material/MaterialDefinitions.swift | 382 ++++++++++++++++++ .../Shims/QuartzCore/CAFilterPrivate.h | 2 + 9 files changed, 520 insertions(+), 13 deletions(-) create mode 100644 Sources/OpenSwiftUICore/Modifier/ViewModifier/OpaqueBackgroundModifier.swift create mode 100644 Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift diff --git a/Sources/OpenSwiftUICore/Graphic/BackdropEffect.swift b/Sources/OpenSwiftUICore/Graphic/BackdropEffect.swift index 310db2f39..345de156b 100644 --- a/Sources/OpenSwiftUICore/Graphic/BackdropEffect.swift +++ b/Sources/OpenSwiftUICore/Graphic/BackdropEffect.swift @@ -12,6 +12,10 @@ package struct BackdropEffect { package var filters: [GraphicsFilter] + #if os(visionOS) + package var captureOnly: Bool + #endif + package init( scale: Float = 1, color: Color.Resolved = .black, @@ -21,5 +25,8 @@ package struct BackdropEffect { self.scale = scale self.color = color self.filters = filters + #if os(visionOS) + self.captureOnly = captureOnly + #endif } } diff --git a/Sources/OpenSwiftUICore/Graphic/BlurStyle/BlurStyle.swift b/Sources/OpenSwiftUICore/Graphic/BlurStyle/BlurStyle.swift index 1c85420a1..48fe9961a 100644 --- a/Sources/OpenSwiftUICore/Graphic/BlurStyle/BlurStyle.swift +++ b/Sources/OpenSwiftUICore/Graphic/BlurStyle/BlurStyle.swift @@ -11,6 +11,10 @@ package struct BlurStyle: Equatable { package var radius: CGFloat package var isOpaque: Bool package var dither: Bool + #if os(visionOS) + package var hardEdges: Bool + package var isVisionEnabled: Bool + #endif package init( radius: CGFloat = 0, @@ -21,6 +25,10 @@ package struct BlurStyle: Equatable { self.radius = radius self.isOpaque = isOpaque self.dither = dither + #if os(visionOS) + self.hardEdges = hardEdges + self.isVisionEnabled = false + #endif } package var isIdentity: Bool { @@ -40,6 +48,10 @@ extension BlurStyle: ProtobufMessage { encoder.cgFloatField(1, radius) encoder.boolField(2, isOpaque) encoder.boolField(3, dither) + #if os(visionOS) + encoder.boolField(4, hardEdges) + encoder.boolField(5, isVisionEnabled) + #endif } package init(from decoder: inout ProtobufDecoder) throws { @@ -49,6 +61,10 @@ extension BlurStyle: ProtobufMessage { case 1: style.radius = try decoder.cgFloatField(field) case 2: style.isOpaque = try decoder.boolField(field) case 3: style.dither = try decoder.boolField(field) + #if os(visionOS) + case 4: style.hardEdges = try decoder.boolField(field) + case 5: style.isVisionEnabled = try decoder.boolField(field) + #endif default: try decoder.skipField(field) } } diff --git a/Sources/OpenSwiftUICore/Graphic/Color/ColorMatrix.swift b/Sources/OpenSwiftUICore/Graphic/Color/ColorMatrix.swift index 33dc960d2..61987a552 100644 --- a/Sources/OpenSwiftUICore/Graphic/Color/ColorMatrix.swift +++ b/Sources/OpenSwiftUICore/Graphic/Color/ColorMatrix.swift @@ -357,6 +357,23 @@ extension _ColorMatrix { } } +// MARK: - Color.Resolved + _ColorMatrix + +extension Color.Resolved { + package func applying(_ matrix: _ColorMatrix) -> Color.Resolved { + let red = red + let green = green + let blue = blue + return Color.Resolved( + colorSpace: .sRGB, + red: matrix.m15 + (red * matrix.m11 + green * matrix.m12 + blue * matrix.m13 + opacity * matrix.m14), + green: matrix.m25 + (red * matrix.m21 + green * matrix.m22 + blue * matrix.m23 + opacity * matrix.m24), + blue: matrix.m35 + (red * matrix.m31 + green * matrix.m32 + blue * matrix.m33 + opacity * matrix.m34), + opacity: matrix.m45 + (red * matrix.m41 + green * matrix.m42 + blue * matrix.m43 + opacity * matrix.m44) + ) + } +} + // MARK: - _ColorMatrix + ShapeStyle @_spi(Private) diff --git a/Sources/OpenSwiftUICore/Modifier/ViewModifier/OpaqueBackgroundModifier.swift b/Sources/OpenSwiftUICore/Modifier/ViewModifier/OpaqueBackgroundModifier.swift new file mode 100644 index 000000000..2ff0cb35c --- /dev/null +++ b/Sources/OpenSwiftUICore/Modifier/ViewModifier/OpaqueBackgroundModifier.swift @@ -0,0 +1,22 @@ +// +// OpaqueBackgroundModifier.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete +// ID: 79BADF4A1DAAA1CD480AFBF6598504A8 (SwiftUICore) + +#if os(visionOS) +// MARK: - EnvironmentValues + isBackgroundOpaque + +extension EnvironmentValues { + package var isBackgroundOpaque: Bool { + get { !isVisionEnabled || self[OpaqueBackgroundKey.self] } + set { self[OpaqueBackgroundKey.self] = newValue } + } +} + +private struct OpaqueBackgroundKey: EnvironmentKey { + static let defaultValue: Bool = false +} +#endif diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ContentStyle.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ContentStyle.swift index 3781f6f18..392c0a5d7 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ContentStyle.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ContentStyle.swift @@ -90,6 +90,10 @@ package enum ContentStyle { case fill case stroke case separator + #if os(visionOS) + case overlay + case destructive + #endif package init(_ role: ShapeRole) { switch role { diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/CoreMaterialDefinitions.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/CoreMaterialDefinitions.swift index a79fb984b..2560b1475 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/CoreMaterialDefinitions.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/CoreMaterialDefinitions.swift @@ -374,27 +374,21 @@ private struct CoreMaterialCache { } #if os(visionOS) && canImport(QuartzCore) - private static let subtractSIgnoreAlpha = GraphicsBlendMode.caFilter(CAFilter(type: "subtractSIgnoreAlpha")) - private static let plusDIgnoreAlpha = GraphicsBlendMode.caFilter(CAFilter(type: "plusDIgnoreAlpha")) - private static let plusLIgnoreAlpha = GraphicsBlendMode.caFilter(CAFilter(type: "plusLIgnoreAlpha")) - func loadBlendModeEffect(_ description: [String: Any]) -> DisplayList.Effect? { let filter = description["compositingFilter"] as? [String: Any] let type = description["compositingFilter"] as? String ?? filter?["type"] as? String switch type { case "subtractSIgnoreAlpha": - return .blendMode(Self.subtractSIgnoreAlpha) + return .blendMode(.subtractSIgnoreAlpha) case "plusDIgnoreAlpha": - return .blendMode(Self.plusDIgnoreAlpha) + return .blendMode(.plusDIgnoreAlpha) case "plusLIgnoreAlpha": - return .blendMode(Self.plusLIgnoreAlpha) + return .blendMode(.plusLIgnoreAlpha) case "plusLIgnoreAlphaSourceOverBlend": guard let amount = filter?["inputAmount"] as? Double else { return nil } - let filter = CAFilter(type: "plusLIgnoreAlphaSourceOverBlend") - filter.setInput(value: amount, key: .amount) - return .blendMode(.caFilter(filter)) + return .blendMode(.plusLIgnoreAlphaSourceOver(amount)) default: return nil } diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift index 656ad8310..d41e318e8 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift @@ -114,6 +114,15 @@ public struct Material: Sendable { case intelligenceLightSource_Unreactive case intelligenceLightSource_AudioReactive #endif + #if os(visionOS) + case pinched + case selected + case disabled + case vibrantGlassContent + case darkerGlass + case lighterGlass + case ultraDarkerGlass + #endif indirect case coreMaterial(light: String, dark: String, bundle: Bundle?) #if os(macOS) || os(iOS) // case provider(MaterialProviderBoxBase) @@ -156,9 +165,18 @@ public struct Material: Sendable { if environment.colorScheme == .dark { flags.insert(.darkColorScheme) } + #if os(visionOS) + if environment.isVisionEnabled { + flags.insert(.isVisionEnabled) + } + if environment.isBackgroundOpaque { + flags.insert(.noTransparentBlur) + } + #else if environment.accessibilityReduceTransparency { flags.insert(.reduceTransparency) } + #endif if environment.colorSchemeContrast == .increased { flags.insert(.increasedContrast) } @@ -178,6 +196,10 @@ public struct Material: Sendable { package var flags: Material.ResolvedMaterial.Flags + #if os(visionOS) + package var styleID: ContentStyle.ID? = nil + #endif + package var colorScheme: ColorScheme { flags.contains(.darkColorScheme) ? .dark : .light } @@ -211,7 +233,19 @@ public struct Material: Sendable { flags.insert(.isActive) } #endif + #if os(visionOS) + let styleID: ContentStyle.ID? + switch id { + case .ultraThin, .thin, .regular, .thick, .ultraThick, + .pinched, .selected, .disabled, .darkerGlass, .lighterGlass, .ultraDarkerGlass: + styleID = env.isVisionEnabled ? env.backgroundContentStyleID : nil + default: + styleID = nil + } + return ResolvedMaterial(id: id, flags: flags, styleID: styleID) + #else return ResolvedMaterial(id: id, flags: flags) + #endif } } @@ -292,13 +326,31 @@ extension Material { @available(OpenSwiftUI_watchOS_v5_0, *) extension Material { @_spi(_) - public static var pinched: Material { Material(.regular) } + public static var pinched: Material { + #if os(visionOS) + Material(.pinched) + #else + Material(.regular) + #endif + } @_spi(_) - public static var selected: Material { .pinched } + public static var selected: Material { + #if os(visionOS) + Material(.selected) + #else + .pinched + #endif + } @_spi(_) - public static var disabled: Material { .pinched } + public static var disabled: Material { + #if os(visionOS) + Material(.disabled) + #else + .pinched + #endif + } @_spi(_) public static func coreMaterial(bundle: Bundle? = nil, provider: @escaping (ColorScheme) -> String) -> Material { @@ -432,6 +484,17 @@ private struct BackgroundMaterialKey: EnvironmentKey { @available(OpenSwiftUI_v3_0, *) extension EnvironmentValues { + #if os(visionOS) + package var backgroundContentStyleID: ContentStyle.ID? { + switch backgroundMaterial?.id { + case .ultraThin, .thin, .lighterGlass: .secondary + case .regular, .darkerGlass: .tertiary + case .thick, .ultraThick, .ultraDarkerGlass: .quaternary + default: .primary + } + } + #endif + /// The material underneath the current view. /// /// This value is `nil` if the current background isn't one of the standard diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift new file mode 100644 index 000000000..dc8f6d86c --- /dev/null +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift @@ -0,0 +1,382 @@ +// +// MaterialDefinitions.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete +// ID: F296DEBC927E1390A362D58BE2242E68 (SwiftUICore) + +import Foundation +#if os(macOS) && OPENSWIFTUI_LINK_COREUI +import CoreUI +#endif +#if os(visionOS) +import QuartzCore_Private +#endif + +// MARK: - ContentStyle.MaterialStyle + +extension ContentStyle.MaterialStyle { + package func resolveColor(in environment: EnvironmentValues) -> Color.Resolved { + #if os(macOS) + if case .coreMaterial = material.id { + return resolveCoreMaterialColor(in: environment) + } + let usesSystemColor: Bool + switch material.id { + case .contentFill, .windowFill, .secondaryBackground, .tertiaryBackground, .quaternaryBackground: + usesSystemColor = true + default: + usesSystemColor = material.isEmphasized + } + if usesSystemColor { + #if OPENSWIFTUI_LINK_COREUI + return CoreUIDefaultSystemColorDefinition.value(for: SystemColorType(base.id), environment: environment) + #else + return base.id.resolve(in: environment) + #endif + } + let name: CoreUISystemCatalogColorName + switch base.id { + case .primary: name = .label + case .secondary: name = .secondaryLabel + case .tertiary: name = .tertiaryLabel + case .quaternary: name = .quaternaryLabel + case .quinary: name = .quinaryLabel + } + return environment.appearance(allowsVibrantBlending: true).asset(for: name)?.0 ?? .clear + #else + return resolveCoreMaterialColor(in: environment) + #endif + } + + package func resolveEffects() -> [DisplayList.Effect] { + #if os(macOS) + if case .coreMaterial = material.id { + return resolveCoreMaterialEffects() + } + guard !material.isEmphasized else { return [] } + switch material.id { + case .contentFill, .windowFill, .secondaryBackground, .tertiaryBackground, .quaternaryBackground: + return [] + default: + guard base.id != .primary, material.colorSchemeContrast != .increased else { + return [] + } + return [.blendMode(.blendMode(material.colorScheme == .dark ? .plusLighter : .plusDarker))] + } + #else + return resolveCoreMaterialEffects() + #endif + } +} + +#if os(macOS) +// MARK: - Material.ResolvedMaterial + +extension Material.ResolvedMaterial { + @AtomicBox + private static var materialCache: MaterialLayerCache = { + let definitions = MaterialLayerCache.materialsData().flatMap { + (try? JSONSerialization.jsonObject(with: $0)) as? NSDictionary + } + return MaterialLayerCache(definitions: definitions) + }() + + package func visitLayers(_ visitor: inout V) { + switch id { + case .coreMaterial: + visitCoreMaterialLayers(&visitor) + case .menu: + var material = self + material.id = colorScheme == .dark && colorSchemeContrast == .increased ? .regular : .thick + material.visitLayers(&visitor) + default: + let layers = Self.$materialCache.access { $0[self] } + for layer in layers { + visitor.visit(layer: layer) + } + } + } +} + +// MARK: - MaterialLayerCache + +private struct MaterialLayerCache { + var definitions: NSDictionary? + var cache: [Material.ResolvedMaterial: [AnyMaterialLayer]] = [:] + + subscript(material: Material.ResolvedMaterial) -> [AnyMaterialLayer] { + mutating get { + if let layers = cache[material] { + return layers + } else { + let layers: [AnyMaterialLayer] + switch material.id { + case .secondaryBackground: + layers = createSystemBackground(material: material, level: 1) + case .tertiaryBackground: + layers = createSystemBackground(material: material, level: 2) + case .quaternaryBackground: + layers = createSystemBackground(material: material, level: 3) + default: + layers = load(material: material) + } + cache[material] = layers + return layers + } + } + } + + static func materialsData() -> Data? { + guard let url = Bundle(identifier: "com.apple.SwiftUI")? + .url(forResource: "mac-materials", withExtension: "json") else { + return nil + } + return try? Data(contentsOf: url) + } + + func load(material: Material.ResolvedMaterial) -> [AnyMaterialLayer] { + let category: String + if material.flags.contains(.increasedContrast) { + category = "increased-contrast" + } else if material.flags.contains(.reduceTransparency) { + category = "reduced-transparency" + } else if material.flags.contains(.disableChameleon) { + category = "non-chameleon" + } else { + category = "normal" + } + guard let definitions = definitions?.value(forKey: category) as? NSDictionary else { + return [] + } + var name: String + switch material.id { + case .ultraThin: name = "ultrathin" + case .thin: name = "thin" + case .regular: name = "medium" + case .thick: name = "thick" + case .ultraThick: name = "ultrathick" + case .systemBars: name = "Header" + case .contentFill: name = "ContentAreaFill" + case .windowFill: name = "WindowFill" + default: return [] + } + name += material.colorScheme == .dark ? " dark" : " light" + if !material.flags.contains(.isActive) { + name += ", inactive" + } + guard let definitions = definitions.value(forKey: name) as? NSArray else { + return [] + } + + func decodeFloatArray(_ array: NSArray) -> [Float]? { + let values = array.compactMap { $0 as? NSNumber }.map { $0.floatValue } + return values.count == array.count ? values : nil + } + + var layers: [AnyMaterialLayer] = [] + for case let definition as NSDictionary in definitions { + guard let type = definition.value(forKey: "type") as? String else { + continue + } + var filters: [GraphicsFilter] = [] + if let definitions = definition.value(forKey: "filters") as? NSArray { + for case let definition as NSDictionary in definitions { + switch definition.value(forKey: "type") as? String { + case "colorSaturate": + if let amount = definition.value(forKey: "amount") as? Double, amount != 1 { + filters.append(.saturation(amount)) + } + case "colorBrightness": + if let amount = definition.value(forKey: "amount") as? Double, amount != 0 { + filters.append(.brightness(amount)) + } + case "gaussianBlur": + if !material.flags.contains(.noBlur), + let radius = definition.value(forKey: "radius") as? Double, + radius > 0 { + filters.append(.blur(BlurStyle(radius: radius, isOpaque: true, dither: true))) + } + case "colorTint", "colorOffset": + if let array = definition.value(forKey: "matrix") as? NSArray, + let values = decodeFloatArray(array) { + let matrix = _ColorMatrix(floatArray: values) + if !matrix.isIdentity { + filters.append(.colorMatrix(matrix, premultiplied: false)) + } + } + default: + break + } + } + } + + let content: DisplayList.Content.Value + switch type { + case "layer": + guard var color = Self.parseColor(definition["bgColor"]) else { + Log.internalWarning("invalid bgColor for layer \(definition)") + continue + } + for filter in filters { + if let matrix = _ColorMatrix(filter) { + color = color.applying(matrix) + } + } + content = .color(color) + case "backdrop": + content = .backdrop(BackdropEffect( + scale: material.flags.contains(.noBlur) ? 1 : 0.25, + color: Self.parseColor(definition["bgColor"]) ?? .clear, + filters: filters + )) + case "chameleon": + content = .chameleonColor(fallback: .gray_75, filters: filters) + default: + continue + } + let blendMode: BlendMode + switch definition.value(forKey: "blend") as? String { + case "lighten": blendMode = .lighten + case "darken": blendMode = .darken + case "plusL": blendMode = .plusLighter + case "plusD": blendMode = .plusDarker + default: blendMode = .normal + } + layers.append(AnyMaterialLayer( + content: content, + opacity: Float(definition.value(forKey: "opacity") as? Double ?? 1), + blendMode: GraphicsBlendMode(blendMode) + )) + } + return layers + } + + static func parseColor(_ value: Any?) -> Color.Resolved? { + guard let values = value as? NSArray, values.count == 4, + let red = values[0] as? Double, + let green = values[1] as? Double, + let blue = values[2] as? Double, + let opacity = values[3] as? Double else { + return nil + } + return Color.Resolved( + colorSpace: .sRGB, + red: Float(red), + green: Float(green), + blue: Float(blue), + opacity: Float(opacity) + ) + } + + mutating func createSystemBackground(material: Material.ResolvedMaterial, level: Int) -> [AnyMaterialLayer] { + var contentMaterial = material + contentMaterial.id = .contentFill + var layers = self[contentMaterial] + #if OPENSWIFTUI_LINK_COREUI + var environment = EnvironmentValues() + CoreGlue2.shared.configureDefaultEnvironment(&environment) + environment.colorScheme = material.colorScheme + environment.colorSchemeContrast = material.colorSchemeContrast + let name = CUIColorName(rawValue: material.colorSchemeContrast == .increased ? 25 : 24)! + let entry = CUIDesignLibraryCacheKey(name: name, in: environment).fetch() + let layer = AnyMaterialLayer(content: .color(entry.color), blendMode: GraphicsBlendMode(entry.blendMode)) + if material.colorScheme == .dark { + for _ in 0.. some View { + environment(\.materialBaseColor, color) + } +} + +// MARK: - GraphicsBlendMode + +extension GraphicsBlendMode { + package static let plusLIgnoreAlpha = GraphicsBlendMode.caFilter(CAFilter(type: "plusLIgnoreAlpha")) + + package static let subtractSIgnoreAlpha = GraphicsBlendMode.caFilter(CAFilter(type: "subtractSIgnoreAlpha")) + + package static let destOutPassthrough = GraphicsBlendMode.caFilter(CAFilter(type: "destOutPassthrough")) + + package static let plusDIgnoreAlpha = GraphicsBlendMode.caFilter(CAFilter(type: "plusDIgnoreAlpha")) + + package static func plusLIgnoreAlphaSourceOver(_ inputAmount: Double) -> GraphicsBlendMode { + let filter = CAFilter(type: "plusLIgnoreAlphaSourceOverBlend") + filter.setInput(value: inputAmount, key: .amount) + return .caFilter(filter) + } + + package init?(_ type: CAFilterType) { + switch type { + case .plusLIgnoreAlpha: self = .plusLIgnoreAlpha + case .subtractSIgnoreAlpha: self = .subtractSIgnoreAlpha + case .destOutPassthrough: self = .destOutPassthrough + case .plusDIgnoreAlpha: self = .plusDIgnoreAlpha + } + } + + package enum CAFilterType: UInt, Hashable, ProtobufEnum { + case plusLIgnoreAlpha = 1 + case subtractSIgnoreAlpha + case destOutPassthrough + case plusDIgnoreAlpha + + package init?(_ filter: CAFilter) { + switch filter.type { + case "plusLIgnoreAlpha": self = .plusLIgnoreAlpha + case "subtractSIgnoreAlpha": self = .subtractSIgnoreAlpha + case "destOutPassthrough": self = .destOutPassthrough + case "plusDIgnoreAlpha": self = .plusDIgnoreAlpha + default: return nil + } + } + + package func hash(into hasher: inout Hasher) { + hasher.combine(rawValue) + } + } +} +#endif diff --git a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CAFilterPrivate.h b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CAFilterPrivate.h index c580a64d8..a4f367814 100644 --- a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CAFilterPrivate.h +++ b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CAFilterPrivate.h @@ -16,6 +16,8 @@ OPENSWIFTUI_ASSUME_NONNULL_BEGIN @interface CAFilter : NSObject +@property (nullable, readonly) NSString *type; + + (NSArray *)filterTypes; + (instancetype)filterWithName:(NSString *)name; + (instancetype)filterWithType:(NSString *)type; From 865894e2333e8d2efbe3cc4ec98ba151d381fb8e Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 12:00:18 +0800 Subject: [PATCH 08/19] feat: add backdrop encoding and rendering support --- .../Graphic/BackdropEffect.swift | 49 +++- .../Graphic/Color/ConstantColor.swift | 10 +- .../Graphic/GraphicsConversions.swift | 7 + .../Graphic/GraphicsFilter.swift | 237 +++++++++++++++++- .../Graphic/Shader/Shader.swift | 34 ++- .../DisplayList/DisplayListViewPlatform.swift | 3 + .../Shape/ShapeStyle/ShapeStyleShape.swift | 2 + .../Shims/QuartzCore/CABackdropLayer.h | 4 + 8 files changed, 333 insertions(+), 13 deletions(-) diff --git a/Sources/OpenSwiftUICore/Graphic/BackdropEffect.swift b/Sources/OpenSwiftUICore/Graphic/BackdropEffect.swift index 345de156b..638ee26aa 100644 --- a/Sources/OpenSwiftUICore/Graphic/BackdropEffect.swift +++ b/Sources/OpenSwiftUICore/Graphic/BackdropEffect.swift @@ -3,9 +3,11 @@ // OpenSwiftUICore // // Audited for 6.5.4 -// Status: WIP +// Status: Complete -package struct BackdropEffect { +// MARK: - BackdropEffect + +package struct BackdropEffect: Equatable { package var scale: Float package var color: Color.Resolved @@ -30,3 +32,46 @@ package struct BackdropEffect { #endif } } + +extension BackdropEffect: ProtobufMessage { + package func encode(to encoder: inout ProtobufEncoder) throws { + encoder.floatField(1, scale) + try encoder.messageField(2, color) + for filter in filters { + try encoder.messageField(3, filter) + } + #if os(visionOS) + encoder.boolField(4, captureOnly) + #endif + } + + package init(from decoder: inout ProtobufDecoder) throws { + self.init() + while let field = try decoder.nextField() { + switch field.tag { + case 1: scale = try decoder.floatField(field) + case 2: color = try decoder.messageField(field) + case 3: filters.append(try decoder.messageField(field)) + #if os(visionOS) + case 4: captureOnly = try decoder.boolField(field) + #endif + default: try decoder.skipField(field) + } + } + } +} + +// MARK: - BackdropView + +struct BackdropView: RendererLeafView { + nonisolated static func _makeView( + view: _GraphValue, + inputs: _ViewInputs + ) -> _ViewOutputs { + makeLeafView(view: view, inputs: inputs) + } + + func content() -> DisplayList.Content.Value { + .backdrop(BackdropEffect(color: .clear)) + } +} diff --git a/Sources/OpenSwiftUICore/Graphic/Color/ConstantColor.swift b/Sources/OpenSwiftUICore/Graphic/Color/ConstantColor.swift index 6d2f3c286..eae5771e1 100644 --- a/Sources/OpenSwiftUICore/Graphic/Color/ConstantColor.swift +++ b/Sources/OpenSwiftUICore/Graphic/Color/ConstantColor.swift @@ -366,10 +366,10 @@ extension Color.Resolved: Codable { extension Color.Resolved: ProtobufMessage { package func encode(to encoder: inout ProtobufEncoder) { - encoder.floatField(1, red) - encoder.floatField(2, green) - encoder.floatField(3, blue) - encoder.floatField(4, opacity) + encoder.floatField(1, linearRed) + encoder.floatField(2, linearGreen) + encoder.floatField(3, linearBlue) + encoder.floatField(4, opacity, defaultValue: 1) } package init(from decoder: inout ProtobufDecoder) throws { @@ -386,7 +386,7 @@ extension Color.Resolved: ProtobufMessage { default: try decoder.skipField(field) } } - self.init(red: red, green: green, blue: blue, opacity: opacity) + self.init(linearRed: red, linearGreen: green, linearBlue: blue, opacity: opacity) } } diff --git a/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift b/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift index 2997c900e..65650a9e7 100644 --- a/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift +++ b/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift @@ -83,6 +83,13 @@ extension GraphicsFilter { filter.setInput(value: NSNumber(value: style.radius), key: .radius) filter.setInput(value: NSNumber(value: style.isOpaque), key: .normalizeEdges) filter.setInput(value: NSNumber(value: style.dither), key: .dither) + #if os(visionOS) + filter.setInput(value: NSNumber(value: style.hardEdges), key: .hardEdges) + if style.isVisionEnabled && style.isOpaque { + filter.setInput(value: NSNumber(value: false), key: .normalizeEdges) + filter.setInput(value: NSNumber(value: true), key: .normalizeEdgesTransparent) + } + #endif return filter case let .variableBlur(style): let filter = CAFilter(key: .variableBlur) diff --git a/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift b/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift index 5e35b556a..8e9ba531a 100644 --- a/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift +++ b/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift @@ -11,7 +11,7 @@ import OpenQuartzCoreShims import QuartzCore_Private #endif -package enum GraphicsFilter { +package enum GraphicsFilter: Equatable { case blur(BlurStyle) case variableBlur(VariableBlurStyle) case averageColor @@ -80,7 +80,7 @@ package enum GraphicsFilter { } } - package struct ShaderFilter { + package struct ShaderFilter: Equatable { package var shader: Shader.ResolvedShader package var size: CGSize @@ -195,4 +195,235 @@ extension GraphicsFilter { } } -// TODO: Extension API +// MARK: - GraphicsFilter + ProtobufMessage + +extension GraphicsFilter: ProtobufMessage { + package func encode(to encoder: inout ProtobufEncoder) throws { + switch self { + case let .blur(style): + try encoder.messageField(1, style) + case let .variableBlur(style): + try encoder.messageField(18, style) + case .averageColor: + encoder.emptyField(16) + case let .shadow(style): + try encoder.messageField(2, style) + case let .projection(transform): + try encoder.messageField(3, transform) + case let .colorMatrix(matrix, premultiplied): + try encoder.messageField(4, matrix) + encoder.boolField(19, premultiplied) + case let .colorMultiply(color): + try encoder.messageField(5, color) + case let .hueRotation(angle): + encoder.doubleField(6, angle.radians) + case let .saturation(amount): + encoder.doubleField(7, amount) + case let .brightness(amount): + encoder.doubleField(8, amount) + case let .contrast(amount): + encoder.doubleField(9, amount) + case .luminanceToAlpha: + encoder.emptyField(10) + case .colorInvert: + encoder.emptyField(11) + case let .grayscale(amount): + encoder.doubleField(12, amount) + case let .colorMonochrome(monochrome): + try encoder.messageField(13, monochrome) + case let .vibrantColorMatrix(matrix): + try encoder.messageField(14, matrix) + case let .luminanceCurve(curve): + try encoder.messageField(15, curve) + case let .colorCurves(curves): + try encoder.messageField(20, curves) + case let .shader(shader): + try encoder.messageField(17, shader) + case let .alphaThreshold(threshold): + try encoder.messageField(21, threshold) + } + } + + package init(from decoder: inout ProtobufDecoder) throws { + self = .colorMultiply(.white) + var premultiplied = false + while let field = try decoder.nextField() { + switch field.tag { + case 1: self = .blur(try decoder.messageField(field)) + case 2: self = .shadow(try decoder.messageField(field)) + case 3: self = .projection(try decoder.messageField(field)) + case 4: self = .colorMatrix(try decoder.messageField(field), premultiplied: false) + case 5: self = .colorMultiply(try decoder.messageField(field)) + case 6: self = .hueRotation(.radians(try decoder.doubleField(field))) + case 7: self = .saturation(try decoder.doubleField(field)) + case 8: self = .brightness(try decoder.doubleField(field)) + case 9: self = .contrast(try decoder.doubleField(field)) + case 10: + try decoder.skipField(field) + self = .luminanceToAlpha + case 11: + try decoder.skipField(field) + self = .colorInvert + case 12: self = .grayscale(try decoder.doubleField(field)) + case 13: self = .colorMonochrome(try decoder.messageField(field)) + case 14: self = .vibrantColorMatrix(try decoder.messageField(field)) + case 15: self = .luminanceCurve(try decoder.messageField(field)) + case 16: + try decoder.skipField(field) + self = .averageColor + case 17: self = .shader(try decoder.messageField(field)) + case 18: self = .variableBlur(try decoder.messageField(field)) + case 19: premultiplied = try decoder.boolField(field) + case 20: self = .colorCurves(try decoder.messageField(field)) + case 21: self = .alphaThreshold(try decoder.messageField(field)) + default: try decoder.skipField(field) + } + if premultiplied, case let .colorMatrix(matrix, _) = self { + self = .colorMatrix(matrix, premultiplied: true) + } + } + } +} + +// MARK: - GraphicsFilter.ColorMonochrome + ProtobufMessage + +extension GraphicsFilter.ColorMonochrome: ProtobufMessage { + package func encode(to encoder: inout ProtobufEncoder) throws { + try encoder.messageField(1, color) + encoder.floatField(2, amount, defaultValue: 1) + encoder.floatField(3, bias) + } + + package init(from decoder: inout ProtobufDecoder) throws { + self.init(color: .clear, amount: 1, bias: 0) + while let field = try decoder.nextField() { + switch field.tag { + case 1: color = try decoder.messageField(field) + case 2: amount = try decoder.floatField(field) + case 3: bias = try decoder.floatField(field) + default: try decoder.skipField(field) + } + } + } +} + +// MARK: - GraphicsFilter.LuminanceCurve + ProtobufMessage + +extension GraphicsFilter.LuminanceCurve: ProtobufMessage { + package func encode(to encoder: inout ProtobufEncoder) throws { + curve.encode(to: &encoder, tag: 1) + encoder.floatField(2, amount) + } + + package init(from decoder: inout ProtobufDecoder) throws { + self.init(curve: .init((0, 0, 0, 0)), amount: 0) + var index = 0 + while let field = try decoder.nextField() { + switch field.tag { + case 1: try curve.decode(from: &decoder, field: field, index: &index) + case 2: amount = try decoder.floatField(field) + default: try decoder.skipField(field) + } + } + } +} + +// MARK: - GraphicsFilter.ColorCurves + ProtobufMessage + +extension GraphicsFilter.ColorCurves: ProtobufMessage { + package func encode(to encoder: inout ProtobufEncoder) throws { + redCurve.encode(to: &encoder, tag: 1) + greenCurve.encode(to: &encoder, tag: 2) + blueCurve.encode(to: &encoder, tag: 3) + opacityCurve.encode(to: &encoder, tag: 4) + } + + package init(from decoder: inout ProtobufDecoder) throws { + let zero = GraphicsFilter.Curve((0, 0, 0, 0)) + self.init(redCurve: zero, greenCurve: zero, blueCurve: zero, opacityCurve: zero) + var indices = (0, 0, 0, 0) + while let field = try decoder.nextField() { + switch field.tag { + case 1: try redCurve.decode(from: &decoder, field: field, index: &indices.0) + case 2: try greenCurve.decode(from: &decoder, field: field, index: &indices.1) + case 3: try blueCurve.decode(from: &decoder, field: field, index: &indices.2) + case 4: try opacityCurve.decode(from: &decoder, field: field, index: &indices.3) + default: try decoder.skipField(field) + } + } + } +} + +extension GraphicsFilter.Curve { + fileprivate func encode(to encoder: inout ProtobufEncoder, tag: UInt) { + encoder.packedField(tag) { encoder in + encoder.encodeFloat(values.0) + encoder.encodeFloat(values.1) + encoder.encodeFloat(values.2) + encoder.encodeFloat(values.3) + } + } + + fileprivate mutating func decode( + from decoder: inout ProtobufDecoder, + field: ProtobufDecoder.Field, + index: inout Int + ) throws { + let value = try decoder.floatField(field) + switch index { + case 0: values.0 = value + case 1: values.1 = value + case 2: values.2 = value + case 3: values.3 = value + default: return + } + index += 1 + } +} + +// MARK: - GraphicsFilter.ShaderFilter + ProtobufMessage + +extension GraphicsFilter.ShaderFilter: ProtobufMessage { + package func encode(to encoder: inout ProtobufEncoder) throws { + try encoder.messageField(1, shader) + try encoder.messageField(2, size, defaultValue: .zero) + } + + package init(from decoder: inout ProtobufDecoder) throws { + var shader: Shader.ResolvedShader? + var size: CGSize = .zero + while let field = try decoder.nextField() { + switch field.tag { + case 1: shader = try decoder.messageField(field) + case 2: size = try decoder.messageField(field) + default: try decoder.skipField(field) + } + } + guard let shader else { + throw ProtobufDecoder.DecodingError.failed + } + self.init(shader: shader, size: size) + } +} + +// MARK: - GraphicsFilter.AlphaThreshold + ProtobufMessage + +extension GraphicsFilter.AlphaThreshold: ProtobufMessage { + package func encode(to encoder: inout ProtobufEncoder) throws { + try encoder.messageField(1, color) + encoder.floatField(2, amount, defaultValue: 0.5) + } + + package init(from decoder: inout ProtobufDecoder) throws { + self.init(color: .black, amount: 0.5) + while let field = try decoder.nextField() { + switch field.tag { + case 1: color = try decoder.messageField(field) + case 2: amount = try decoder.floatField(field) + default: try decoder.skipField(field) + } + } + } +} + +// TODO: Remaining extension API diff --git a/Sources/OpenSwiftUICore/Graphic/Shader/Shader.swift b/Sources/OpenSwiftUICore/Graphic/Shader/Shader.swift index 6c324de17..4bb11c4df 100644 --- a/Sources/OpenSwiftUICore/Graphic/Shader/Shader.swift +++ b/Sources/OpenSwiftUICore/Graphic/Shader/Shader.swift @@ -3,8 +3,9 @@ // OpenSwiftUICore // // Audited for 6.5.4 -// Status: Empty +// Status: WIP +package import Foundation package import OpenCoreGraphicsShims // package import OpenRenderBoxShims @@ -32,7 +33,7 @@ public struct Shader { // MARK: - Shader.ResolvedShader - package struct ResolvedShader { + package struct ResolvedShader: Equatable { package var rbShader: ORBShader? package var maxSampleOffset: CGSize @@ -51,5 +52,32 @@ public struct Shader { } } +// MARK: - Shader.ResolvedShader + ProtobufMessage [TBA] + +extension Shader.ResolvedShader: ProtobufMessage { + package func encode(to encoder: inout ProtobufEncoder) throws { + if rbShader != nil { + // TODO: Encode the RenderBox shader through its archive delegate. + _openSwiftUIUnimplementedFailure() + } + try encoder.messageField(2, maxSampleOffset, defaultValue: .zero) + encoder.uintField(3, UInt(options.rawValue)) + } + + package init(from decoder: inout ProtobufDecoder) throws { + self.init(maxSampleOffset: .zero, options: []) + while let field = try decoder.nextField() { + switch field.tag { + case 1: + // TODO: Decode the RenderBox shader through its archive delegate. + _openSwiftUIUnimplementedFailure() + case 2: maxSampleOffset = try decoder.messageField(field) + case 3: options = .init(rawValue: try decoder.uint32Field(field)) + default: try decoder.skipField(field) + } + } + } +} + // FIXME: RB -package class ORBShader {} +package class ORBShader: NSObject {} diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift index eed8c6d6e..1de9c6b66 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift @@ -405,6 +405,9 @@ extension DisplayList.ViewUpdater.Platform { layer.backgroundColor = effect.color.cgColor let groupID = state.pointee.backdropGroupID layer.groupName = groupID == 0 ? nil : "OpenSwiftUI-\(groupID)" + #if os(visionOS) + layer.captureOnly = effect.captureOnly + #endif #else _openSwiftUIPlatformUnimplementedWarning() #endif diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleShape.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleShape.swift index c214e10c7..123ee801f 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleShape.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleShape.swift @@ -71,6 +71,8 @@ public struct _ShapeStyle_Shape { package static let foreground: RecursiveStyles = .init(rawValue: 1 << 1) package static let background: RecursiveStyles = .init(rawValue: 1 << 2) + + package static let materialProvider: RecursiveStyles = .init(rawValue: 1 << 3) } package var activeRecursiveStyles: RecursiveStyles = [] diff --git a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h index 8e153392a..c16718a12 100644 --- a/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h +++ b/Sources/OpenSwiftUI_SPI/Shims/QuartzCore/CABackdropLayer.h @@ -8,12 +8,16 @@ #import #import +#import @interface CABackdropLayer : CALayer @property (nonatomic) CGFloat scale; @property (nonatomic) BOOL allowsInPlaceFiltering; @property (nonatomic, copy, nullable) NSString *groupName; +#if TARGET_OS_VISION +@property (nonatomic) BOOL captureOnly; +#endif @end From c33b9f112555837b79c0f2c161e546651211b2f9 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 12:27:39 +0800 Subject: [PATCH 09/19] feat: add intelligence light source material layer Normalize audit header order in the light source, modified font, and recursive description files. --- .../Material/IntelligenceLightSource.swift | 54 +++++++++++++++++++ .../CustomRecursiveStringConvertible.swift | 2 +- .../View/Text/Font/ModifiedFont.swift | 2 +- 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/IntelligenceLightSource.swift diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/IntelligenceLightSource.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/IntelligenceLightSource.swift new file mode 100644 index 000000000..79dee4e14 --- /dev/null +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/IntelligenceLightSource.swift @@ -0,0 +1,54 @@ +// +// IntelligenceLightSource.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: Complete +// ID: 92AD8414A5A88FE2AC36477864AB44D8 (SwiftUICore) + +#if os(iOS) || os(visionOS) +import OpenCoreGraphicsShims +import OpenSwiftUI_SPI + +extension Material.ResolvedMaterial { + package func visitIntelligenceLightSourceLayers(_ visitor: inout V) { + visitor.visit(layer: IntelligenceLightSourceLayer( + colorScheme: colorScheme, + prefersAudioReactivity: id == .intelligenceLightSource_AudioReactive + )) + } +} + +private struct IntelligenceLightSourceLayer: MaterialLayer, PlatformViewFactory { + var colorScheme: ColorScheme + var prefersAudioReactivity: Bool + + var content: DisplayList.Content.Value { + .platformView(self) + } + + var viewType: any Any.Type { + EmptyView.self + } + + func makePlatformView() -> AnyObject? { + CoreViewMakeIntelligenceLightSourceView(reactive: prefersAudioReactivity) as AnyObject? + } + + func updatePlatformView(_ view: inout AnyObject) { + CoreViewUpdateIntelligenceLightSourceView(view: view, reactive: prefersAudioReactivity) + } + + func renderPlatformView( + in ctx: GraphicsContext, + size: CGSize, + renderer: DisplayList.GraphicsRenderer + ) { + ctx.renderMissingPlatformView(size: size) + } + + var features: DisplayList.Features { + [] + } +} +#endif diff --git a/Sources/OpenSwiftUICore/Util/CustomRecursiveStringConvertible.swift b/Sources/OpenSwiftUICore/Util/CustomRecursiveStringConvertible.swift index 2592c849e..9d13dd3f7 100644 --- a/Sources/OpenSwiftUICore/Util/CustomRecursiveStringConvertible.swift +++ b/Sources/OpenSwiftUICore/Util/CustomRecursiveStringConvertible.swift @@ -2,8 +2,8 @@ // CustomRecursiveStringConvertible.swift // OpenSwiftUICore // -// Status: Complete // Audited for 6.5.4 +// Status: Complete // ID: 2DFA09903A864CB0F038E089ECDB7AF8 (SwiftUICore) import Foundation diff --git a/Sources/OpenSwiftUICore/View/Text/Font/ModifiedFont.swift b/Sources/OpenSwiftUICore/View/Text/Font/ModifiedFont.swift index 61f30e1e0..4013358f1 100644 --- a/Sources/OpenSwiftUICore/View/Text/Font/ModifiedFont.swift +++ b/Sources/OpenSwiftUICore/View/Text/Font/ModifiedFont.swift @@ -2,8 +2,8 @@ // ModifiedFont.swift // OpenSwiftUICore // -// Status: Complete // Audited for 6.5.4 +// Status: Complete // ID: 25811D44B7BE5E768C1CBA33158F398B (SwiftUICore) public import Foundation From 5c2e0cc540d6ccfba05d87c6cfea2fd9a3733931 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 13:02:03 +0800 Subject: [PATCH 10/19] feat: render material and vibrant shape fills --- .../Graphic/GraphicsContext.swift | 32 ++- .../DisplayList/DisplayListViewPlatform.swift | 2 +- .../ShapeStyle/ShapeStyleRendering.swift | 206 +++++++++++++++++- 3 files changed, 231 insertions(+), 9 deletions(-) diff --git a/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift b/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift index 6ffb58dbe..3e648e44b 100644 --- a/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift +++ b/Sources/OpenSwiftUICore/Graphic/GraphicsContext.swift @@ -100,6 +100,26 @@ public struct GraphicsContext { } var storage: Storage + + /// The opacity of drawing operations in the context. + /// + /// Set this value to affect the opacity of content that you subsequently + /// draw into the context. Changing this value has no impact on the + /// content you previously drew into the context. + public var opacity: Double { + get { _openSwiftUIUnimplementedFailure() } + set { _openSwiftUIUnimplementedFailure() } + } + + /// The blend mode used by drawing operations in the context. + /// + /// Set this value to affect how any content that you subsequently draw + /// into the context blends with content that's already in the context. + /// Use one of the ``GraphicsContext/BlendMode-swift.struct`` values. + public var blendMode: BlendMode { + get { _openSwiftUIUnimplementedFailure() } + set { _openSwiftUIUnimplementedFailure() } + } /// The ways that a graphics context combines new content with background /// content. @@ -746,6 +766,16 @@ extension GraphicsContext { _openSwiftUIUnimplementedWarning() } + // MARK: - Drawing State [WIP] + + package func draw(_ path: Path, with shading: Shading, style: PathDrawingStyle) { + _openSwiftUIUnimplementedFailure() + } + + package mutating func addFilter(_ filter: GraphicsFilter, in bounds: CGRect) { + _openSwiftUIUnimplementedFailure() + } + // FIXME struct ORBLayerFlags: OptionSet { let rawValue: UInt32 @@ -758,7 +788,7 @@ extension GraphicsContext { func drawLayer( flags: ORBLayerFlags, content: (inout GraphicsContext) throws -> Void - ) throws { + ) rethrows { _openSwiftUIUnimplementedFailure() } diff --git a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift index 1de9c6b66..8f818ba2f 100644 --- a/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift +++ b/Sources/OpenSwiftUICore/Render/DisplayList/DisplayListViewPlatform.swift @@ -2088,7 +2088,7 @@ extension DisplayList.GraphicsRenderer { layer.bounds = CGRect(origin: .zero, size: size) layer.layoutIfNeeded() } - try? ctx.drawLayer(flags: []) { _ in + ctx.drawLayer(flags: []) { _ in // TODO: Blocked by GraphicsContext _openSwiftUIUnimplementedFailure() } diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift index 738cb46bd..2e241c76c 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/ShapeStyleRendering.swift @@ -40,7 +40,7 @@ package struct _ShapeStyle_RenderedShape { @Attribute var environment: EnvironmentValues - var blendMode: GraphicsBlendMode = .normal + var blendMode: GraphicsBlendMode var opacity: Float @@ -48,9 +48,23 @@ package struct _ShapeStyle_RenderedShape { let rawValue: UInt8 static let drawingGroup: LayerNeeds = .init(rawValue: 1 << 0) + + static let backdrop: LayerNeeds = .init(rawValue: 1 << 1) } - private var layerNeedes: LayerNeeds + private var layerNeeds: LayerNeeds + + private var bounds: CGRect { + switch shape { + case let .path(path, _): + let bounds = path.boundingRect + return bounds.isNull ? .zero : bounds + case let .alphaMask(mask): + return CGRect(origin: .zero, size: mask.size) + default: + return CGRect(origin: .zero, size: item.size) + } + } package init( _ shape: ShapeStyle.RenderedShape.Shape, @@ -75,7 +89,7 @@ package struct _ShapeStyle_RenderedShape { self._environment = environment self.blendMode = .normal self.opacity = 1.0 - self.layerNeedes = [] + self.layerNeeds = [] } package mutating func renderItem( @@ -137,7 +151,7 @@ package struct _ShapeStyle_RenderedShape { defer { blendMode = .normal opacity = 1.0 - layerNeedes = [] + layerNeeds = [] item.value = .empty item.frame = frame } @@ -154,7 +168,7 @@ package struct _ShapeStyle_RenderedShape { addEffect(.interpolatorLayer(data.group, serial: data.serial)) interpolatorData = nil } - if layerNeedes.contains(.drawingGroup) { + if layerNeeds.contains(.drawingGroup) { item.addDrawingGroup(contentSeed: contentSeed) } if blendMode != .blendMode(.normal) { @@ -194,6 +208,29 @@ package struct _ShapeStyle_RenderedShape { render(color: resolved) case let .paint(paint): render(paint: paint) + case let .foregroundMaterial(color, material): + let effects = material.resolveEffects() + var color = color + if effects.contains(where: { + if case .filter(.vibrantColorMatrix) = $0 { + return true + } + return false + }) { + color = Color.Resolved(linearWhite: 1, opacity: color.opacity) + } + render(color: color) + for effect in effects { + render(effect: effect) + } + case let .backgroundMaterial(material): + render(material: material) + case let .vibrantColor(color): + render(color: .white) + render(effect: .filter(.vibrantColorMatrix(color.colorMatrix))) + case let .vibrantMatrix(matrix): + render(color: .white) + render(effect: .filter(.vibrantColorMatrix(matrix))) default: _openSwiftUIUnimplementedFailure() } @@ -287,6 +324,79 @@ package struct _ShapeStyle_RenderedShape { } } + private mutating func render(effect: DisplayList.Effect) { + switch effect { + case let .opacity(opacity): + self.opacity *= opacity + case let .blendMode(blendMode): + self.blendMode = blendMode + case .filter(.vibrantColorMatrix): + if layerNeeds.contains(.drawingGroup) { + item.addDrawingGroup(contentSeed: contentSeed) + layerNeeds.remove(.drawingGroup) + } + layerNeeds.insert(.backdrop) + addEffect(effect) + case .identity: + break + default: + addEffect(effect) + } + } + + private mutating func render(material: Material.ResolvedMaterial) { + let bounds = bounds + var layers = MaterialLayers( + size: bounds.size, + version: item.version, + contentSeed: contentSeed, + options: options + ) + #if os(macOS) + material.visitLayers(&layers) + #else + switch material.id { + #if os(iOS) + case let .layers(materialLayers): + materialLayers.visit(&layers) + #endif + #if os(iOS) || os(visionOS) + case .intelligenceLightSource_Unreactive, .intelligenceLightSource_AudioReactive: + material.visitIntelligenceLightSourceLayers(&layers) + #endif + default: + material.visitCoreMaterialLayers(&layers) + } + #endif + let value: DisplayList.Item.Value + switch layers.items.count { + case 0: value = .empty + case 1: value = layers.items[0].value + default: value = .effect(.identity, DisplayList(layers.items)) + } + let materialItem = DisplayList.Item( + value, + frame: bounds, + identity: .none, + version: item.version + ) + if case let .path(path, fillStyle) = shape, interpolatorData == nil { + item.value = .effect(.clip(path, fillStyle), DisplayList(materialItem)) + } else { + render(color: .white) + let mask = DisplayList.Item( + item.value, + frame: CGRect(origin: .zero, size: item.size), + identity: .none, + version: item.version + ) + item.value = .effect(.mask(DisplayList(mask)), DisplayList(materialItem)) + } + if layers.needsBackdrop { + layerNeeds.insert(.backdrop) + } + } + private mutating func renderVectorGlyph( _ glyph: ResolvedVectorGlyph, size: CGSize, @@ -377,7 +487,7 @@ package struct _ShapeStyle_RenderedShape { flags: [] ) if nextTime.isFinite { - layerNeedes.insert(.drawingGroup) + layerNeeds.insert(.drawingGroup) } layers.endLayer(shape: &self) return @@ -440,7 +550,7 @@ package struct _ShapeStyle_RenderedShape { flags: [.alphaOnly] )) if nextTime.isFinite { - layerNeedes.insert(.drawingGroup) + layerNeeds.insert(.drawingGroup) } } render(style: style) @@ -588,6 +698,88 @@ package struct _ShapeStyle_RenderedShape { } } +private struct MaterialLayers: MaterialLayerVisitor { + var size: CGSize + var version: DisplayList.Version + var contentSeed: DisplayList.Seed + var options: DisplayList.Options + var items: [DisplayList.Item] = [] + var needsBackdrop: Bool = false + + mutating func visit(layer: L) { + let content = layer.content + switch content { + case .backdrop, .chameleonColor: + needsBackdrop = true + default: + break + } + let frame = CGRect(origin: .zero, size: size) + var item = DisplayList.Item( + .content(DisplayList.Content(content, seed: contentSeed)), + frame: frame, + identity: .none, + version: version + ) + if layer.opacity != 1.0 { + item = DisplayList.Item( + .effect(.opacity(layer.opacity), DisplayList(item)), + frame: frame, + identity: .none, + version: version + ) + } + if layer.blendMode != .normal { + item = DisplayList.Item( + .effect(.blendMode(layer.blendMode), DisplayList(item)), + frame: frame, + identity: .none, + version: version + ) + needsBackdrop = true + } + item.canonicalize(options: options) + items.append(item) + } +} + +private struct MaterialRenderer: MaterialLayerVisitor { + var ctx: GraphicsContext + var bounds: CGRect + + mutating func visit(layer: L) { + ctx.drawLayer(flags: []) { ctx in + if layer.opacity != 1.0 { + ctx.opacity = Double(layer.opacity) + } + if layer.blendMode != .normal, + case let .blendMode(blendMode) = layer.blendMode { + ctx.blendMode = blendMode + } + switch layer.content { + case let .backdrop(backdrop): + for filter in backdrop.filters.reversed() { + ctx.addFilter(filter, in: bounds) + } + ctx.draw( + Path(bounds), + with: GraphicsContext.Shading(storage: .backdrop(backdrop.color)), + style: .fill(FillStyle()) + ) + case let .color(color): + ctx.draw(Path(bounds), with: .color(color), style: .fill(FillStyle())) + case let .chameleonColor(color, filters): + for filter in filters.reversed() { + ctx.addFilter(filter, in: bounds) + } + ctx.draw(Path(bounds), with: .color(color), style: .fill(FillStyle())) + default: + break + } + } + } +} + package struct _ShapeStyle_RenderedLayers { var group: ShapeStyle.InterpolatorGroup? From 73703d655c649b7908903a9b776e14fecc1b98d4 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 13:37:53 +0800 Subject: [PATCH 11/19] feat: add custom material providers --- .../Shape/ShapeStyle/Material/Material.swift | 17 +- .../Material/MaterialDefinitions.swift | 10 + .../Material/MaterialProvider.swift | 544 ++++++++++++++++++ 3 files changed, 569 insertions(+), 2 deletions(-) create mode 100644 Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialProvider.swift diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift index d41e318e8..d61b82906 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift @@ -125,8 +125,8 @@ public struct Material: Sendable { #endif indirect case coreMaterial(light: String, dark: String, bundle: Bundle?) #if os(macOS) || os(iOS) - // case provider(MaterialProviderBoxBase) - // case layers(Layers) + case provider(MaterialProviderBoxBase) + case layers(Layers) #endif } @@ -226,6 +226,13 @@ public struct Material: Sendable { } package func resolve(in env: EnvironmentValues, role: ShapeRole? = nil) -> Material.ResolvedMaterial { + #if os(macOS) || os(iOS) + if case let .provider(provider) = id { + var context = Context(environment: env) + context.role = role + return ResolvedMaterial(id: .layers(provider.resolveLayers(in: context)), flags: flags) + } + #endif var flags = ResolvedMaterial.Flags(environment: env).union(flags) #if os(macOS) let appearance = activeAppearance == .automatic ? env.materialActiveAppearance : activeAppearance @@ -673,6 +680,12 @@ package struct ForegroundMaterialStyle: ShapeStyle, PrimitiveShapeStyle { } package func _apply(to shape: inout _ShapeStyle_Shape) { + #if os(macOS) || os(iOS) + if case let .provider(provider) = material.id { + provider.applyForegroundStyle(to: &shape) + return + } + #endif switch shape.operation { case .prepareText: shape.result = .preparedText(.foregroundKeyColor) diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift index dc8f6d86c..49e1031cb 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift @@ -66,6 +66,14 @@ extension ContentStyle.MaterialStyle { return [.blendMode(.blendMode(material.colorScheme == .dark ? .plusLighter : .plusDarker))] } #else + #if os(iOS) || os(visionOS) + switch material.id { + case .intelligenceLightSource_Unreactive, .intelligenceLightSource_AudioReactive: + return [] + default: + break + } + #endif return resolveCoreMaterialEffects() #endif } @@ -87,6 +95,8 @@ extension Material.ResolvedMaterial { switch id { case .coreMaterial: visitCoreMaterialLayers(&visitor) + case let .layers(layers): + layers.visit(&visitor) case .menu: var material = self material.id = colorScheme == .dark && colorSchemeContrast == .increased ? .regular : .thick diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialProvider.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialProvider.swift new file mode 100644 index 000000000..6cb90922e --- /dev/null +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialProvider.swift @@ -0,0 +1,544 @@ +// +// MaterialProvider.swift +// OpenSwiftUICore +// +// Audited for 6.5.4 +// Status: WIP +// ID: 4075E3A4E56336DD739D990E781CBB12 (SwiftUICore) + +package import OpenCoreGraphicsShims + +#if os(macOS) || os(iOS) + +// MARK: - MaterialProvider + +package protocol MaterialProvider: Hashable { + func resolveLayers(in context: Material.Context) -> [Material.Layer] + + func resolveForegroundStyle(level: Int, in context: Material.Context) -> Material.ForegroundStyle? +} + +extension MaterialProvider { + package func resolveForegroundStyle(level: Int, in context: Material.Context) -> Material.ForegroundStyle? { + nil + } +} + +// MARK: - Material.Context + +extension Material { + package struct Context { + package var environment: EnvironmentValues + + package var role: ShapeRole? + + package init(environment: EnvironmentValues) { + self.environment = environment + role = nil + } + } + + package init(provider: P) { + self.init(.provider(MaterialProviderBox(provider))) + } +} + +// MARK: - Material.Layer + +extension Material { + package struct Layer: Equatable { + fileprivate var storage: Storage + + private var _opacity: Float = 1 + + private var _blendMode: GraphicsBlendMode = .normal + + fileprivate enum Storage: Equatable { + case color(Color.Resolved) + case backdrop(BackdropEffect) + #if os(macOS) + case chameleon(Chameleon) + #endif + } + + #if os(macOS) + fileprivate struct Chameleon: Equatable { + var fallbackColor: Color.Resolved + var filters: [GraphicsFilter] + } + #endif + + package static func color(_ color: Color.Resolved) -> Layer { + Layer(storage: .color(color)) + } + + package static func backdrop(scale: CGFloat, color: Color.Resolved, filters: [Filter]) -> Layer { + Layer(storage: .backdrop(BackdropEffect( + scale: Float(scale), + color: color, + filters: filters.map(\.base) + ))) + } + + #if os(macOS) + package static func chameleon(fallbackColor: Color.Resolved, filters: [Filter]) -> Layer { + Layer(storage: .chameleon(Chameleon( + fallbackColor: fallbackColor, + filters: filters.map(\.base) + ))) + } + #endif + + package func opacity(_ opacity: Double) -> Layer { + var result = self + result._opacity = Float(opacity) + return result + } + + package func blendMode(_ blendMode: BlendMode) -> Layer { + var result = self + result._blendMode = GraphicsBlendMode(blendMode) + return result + } + + package var opacity: Double { + Double(_opacity) + } + + package var blendMode: GraphicsContext.BlendMode? { + guard case let .blendMode(mode) = _blendMode else { return nil } + return mode + } + + package var compositingFilter: Any? { + _blendMode.filter(compositingGroup: false) + } + + package var contents: Contents { + switch storage { + case let .color(color): .color(color) + case let .backdrop(backdrop): .backdrop(.init(backdrop: backdrop)) + #if os(macOS) + case let .chameleon(chameleon): .chameleon(.init(chameleon: chameleon)) + #endif + } + } + + fileprivate struct Wrapper: MaterialLayer { + var layer: Layer + + var content: DisplayList.Content.Value { + switch layer.storage { + case let .color(color): .color(color) + case let .backdrop(backdrop): .backdrop(backdrop) + #if os(macOS) + case let .chameleon(chameleon): + .chameleonColor(fallback: chameleon.fallbackColor, filters: chameleon.filters) + #endif + } + } + + var opacity: Float { + layer._opacity + } + + var blendMode: GraphicsBlendMode { + layer._blendMode + } + } + } +} + +// MARK: - Material.Layer.Filter + +extension Material.Layer { + package struct Filter: Equatable { + fileprivate var base: GraphicsFilter + + package static var averageColor: Filter { + Filter(base: .averageColor) + } + + package static func brightness(amount: Double) -> Filter { + Filter(base: .brightness(amount)) + } + + package static func colorCurves( + red: SIMD4, + green: SIMD4, + blue: SIMD4, + alpha: SIMD4 + ) -> Filter { + Filter(base: .colorCurves(.init( + redCurve: .init((red.x, red.y, red.z, red.w)), + greenCurve: .init((green.x, green.y, green.z, green.w)), + blueCurve: .init((blue.x, blue.y, blue.z, blue.w)), + opacityCurve: .init((alpha.x, alpha.y, alpha.z, alpha.w)) + ))) + } + + package static func saturation(amount: Double) -> Filter { + Filter(base: .saturation(amount)) + } + + package static func colorMatrix(_ matrix: _ColorMatrix, isPremultiplied: Bool) -> Filter { + Filter(base: .colorMatrix(matrix, premultiplied: isPremultiplied)) + } + + package static func gaussianBlur(radius: CGFloat, isOpaque: Bool, isDithered: Bool) -> Filter { + Filter(base: .blur(.init(radius: radius, isOpaque: isOpaque, dither: isDithered))) + } + + package static func luminanceCurve(_ curve: SIMD4, amount: Double) -> Filter { + Filter(base: .luminanceCurve(.init( + curve: .init((curve.x, curve.y, curve.z, curve.w)), + amount: Float(amount) + ))) + } + + package static func variableBlur( + radius: CGFloat, + isOpaque: Bool, + isDithered: Bool, + mask: CGImage + ) -> Filter { + let image = GraphicsImage( + contents: .cgImage(mask), + scale: 1, + unrotatedPixelSize: CGSize(width: mask.width, height: mask.height), + orientation: .up, + isTemplate: false + ) + return Filter(base: .variableBlur(.init( + radius: radius, + isOpaque: isOpaque, + dither: isDithered, + mask: .image(image) + ))) + } + + package var contents: Contents { + switch base { + case .averageColor: .averageColor(.init()) + case let .brightness(amount): .brightness(.init(amount: amount)) + case let .colorCurves(curves): .colorCurves(.init(base: curves)) + case let .saturation(amount): .saturation(.init(amount: amount)) + case let .colorMatrix(matrix, premultiplied): + .colorMatrix(.init(matrix: matrix, isPremultiplied: premultiplied)) + case let .blur(blur): .gaussianBlur(.init(base: blur)) + case let .luminanceCurve(curve): .luminanceCurve(.init(base: curve)) + case let .variableBlur(blur): .variableBlur(.init(base: blur)) + default: _openSwiftUIUnreachableCode() + } + } + } +} + +// MARK: - Material.ForegroundStyle + +extension Material { + package struct ForegroundStyle: Equatable { + fileprivate var storage: Storage + + fileprivate enum Storage: Equatable { + case color(Color.Resolved) + case colorBlend(Color.Resolved, BlendMode) + case colorMatrix(_ColorMatrix) + } + + package static func color(_ color: Color.Resolved) -> ForegroundStyle { + ForegroundStyle(storage: .color(color)) + } + + package static func color(_ color: Color.Resolved, blendMode: BlendMode) -> ForegroundStyle { + ForegroundStyle(storage: .colorBlend(color, blendMode)) + } + + package static func colorMatrix(_ matrix: _ColorMatrix) -> ForegroundStyle { + ForegroundStyle(storage: .colorMatrix(matrix)) + } + + package var contents: Contents { + Contents(storage) + } + } +} + +// MARK: - Material.Layers + +extension Material { + package struct Layers: Hashable { + package var layers: [Layer] + + package init(_ layers: [Layer]) { + self.layers = layers + } + + package func hash(into hasher: inout Hasher) { + _openSwiftUIEmptyStub() + } + + package func visit(_ visitor: inout V) { + for layer in layers { + visitor.visit(layer: Layer.Wrapper(layer: layer)) + } + } + } +} + +// MARK: - MaterialProviderBoxBase + +package class MaterialProviderBoxBase: Hashable { + package func resolveLayers(in context: Material.Context) -> Material.Layers { + _openSwiftUIBaseClassAbstractMethod() + } + + package func applyForegroundStyle(to shape: inout _ShapeStyle_Shape) { + _openSwiftUIBaseClassAbstractMethod() + } + + package func isEqual(to other: MaterialProviderBoxBase) -> Bool { + _openSwiftUIBaseClassAbstractMethod() + } + + package func hash(into hasher: inout Hasher) { + _openSwiftUIBaseClassAbstractMethod() + } + + package static func == (lhs: MaterialProviderBoxBase, rhs: MaterialProviderBoxBase) -> Bool { + lhs.isEqual(to: rhs) + } +} + +// MARK: - MaterialProviderBox + +private final class MaterialProviderBox: MaterialProviderBoxBase { + let provider: P + + init(_ provider: P) { + self.provider = provider + } + + override func resolveLayers(in context: Material.Context) -> Material.Layers { + Material.Layers(provider.resolveLayers(in: context)) + } + + override func applyForegroundStyle(to shape: inout _ShapeStyle_Shape) { + guard !shape.activeRecursiveStyles.contains(.materialProvider) else { + SystemColorsStyle()._apply(to: &shape) + return + } + let activeStyles = shape.activeRecursiveStyles + shape.activeRecursiveStyles.insert(.materialProvider) + defer { shape.activeRecursiveStyles = activeStyles } + var context = Material.Context(environment: shape.environment) + context.role = shape.role + switch shape.operation { + case let .prepareText(level): + guard let style = provider.resolveForegroundStyle(level: level, in: context) else { + SystemColorsStyle()._apply(to: &shape) + return + } + switch style.storage { + case let .color(color): + shape.result = .preparedText(.foregroundColor(Color(color))) + case .colorBlend, .colorMatrix: + shape.result = .preparedText(.foregroundKeyColor) + } + case let .fallbackColor(level): + guard let style = provider.resolveForegroundStyle(level: level, in: context) else { + SystemColorsStyle()._apply(to: &shape) + return + } + switch style.storage { + case let .color(color), let .colorBlend(color, _): + shape.result = .color(Color(color)) + case .colorMatrix: + break + } + case let .resolveStyle(name, levels): + guard name == .foreground else { + SystemColorsStyle()._apply(to: &shape) + return + } + for level in levels { + guard let foreground = provider.resolveForegroundStyle(level: level, in: context) else { + shape.operation = .resolveStyle(name: name, levels: level ..< level + 1) + SystemColorsStyle()._apply(to: &shape) + continue + } + var style = shape.stylePack[name, level] + switch foreground.storage { + case let .color(color): + style.fill = .color(color) + case let .colorBlend(color, blendMode): + style.fill = .color(color) + style.applyBlend(GraphicsBlendMode(blendMode)) + case let .colorMatrix(matrix): + style.fill = .vibrantMatrix(matrix) + } + shape.stylePack[name, level] = style + } + default: + break + } + } + + override func isEqual(to other: MaterialProviderBoxBase) -> Bool { + guard let other = other as? MaterialProviderBox

else { return false } + return provider == other.provider + } + + override func hash(into hasher: inout Hasher) { + provider.hash(into: &hasher) + } +} + +// MARK: - Material.Layer.Contents + +extension Material.Layer { + package enum Contents { + case color(Color.Resolved) + case backdrop(Backdrop) + #if os(macOS) + case chameleon(Chameleon) + #endif + + package struct Backdrop { + fileprivate var backdrop: BackdropEffect + + package var scale: CGFloat { CGFloat(backdrop.scale) } + + package var color: Color.Resolved { backdrop.color } + + package var filters: [Filter] { backdrop.filters.map { Filter(base: $0) } } + } + + #if os(macOS) + package struct Chameleon { + fileprivate var chameleon: Material.Layer.Chameleon + } + #endif + } +} + +// MARK: - Material.Layer.Filter.Contents + +extension Material.Layer.Filter { + package enum Contents { + case averageColor(AverageColor) + case brightness(Brightness) + case colorCurves(ColorCurves) + case saturation(Saturation) + case colorMatrix(ColorMatrix) + case gaussianBlur(GaussianBlur) + case luminanceCurve(LuminanceCurve) + case variableBlur(VariableBlur) + + package struct AverageColor {} + + package struct Brightness { + package var amount: Double + } + + package struct ColorCurves { + fileprivate var base: GraphicsFilter.ColorCurves + + package var redCurve: SIMD4 { + let values = base.redCurve.values + return SIMD4(values.0, values.1, values.2, values.3) + } + + package var greenCurve: SIMD4 { + let values = base.greenCurve.values + return SIMD4(values.0, values.1, values.2, values.3) + } + + package var blueCurve: SIMD4 { + let values = base.blueCurve.values + return SIMD4(values.0, values.1, values.2, values.3) + } + + package var opacityCurve: SIMD4 { + let values = base.opacityCurve.values + return SIMD4(values.0, values.1, values.2, values.3) + } + } + + package struct Saturation { + package var amount: Double + } + + package struct ColorMatrix { + package var matrix: _ColorMatrix + + package var isPremultiplied: Bool + } + + package struct GaussianBlur { + fileprivate var base: BlurStyle + + package var radius: CGFloat { base.radius } + + package var isOpaque: Bool { base.isOpaque } + + package var isDithered: Bool { base.dither } + } + + package struct LuminanceCurve { + fileprivate var base: GraphicsFilter.LuminanceCurve + + package var curve: SIMD4 { + let values = base.curve.values + return SIMD4(values.0, values.1, values.2, values.3) + } + + package var amount: Double { Double(base.amount) } + } + + package struct VariableBlur { + fileprivate var base: VariableBlurStyle + + package var radius: CGFloat { base.radius } + + package var isOpaque: Bool { base.isOpaque } + + package var isDithered: Bool { base.dither } + + package var mask: Mask? { + guard case let .image(image) = base.mask, + case let .cgImage(image) = image.contents else { return nil } + return .cgImage(image) + } + + package enum Mask { + case cgImage(CGImage) + } + } + } +} + +// MARK: - Material.ForegroundStyle.Contents + +extension Material.ForegroundStyle { + package enum Contents { + case color(Color.Resolved, GraphicsContext.BlendMode) + case colorMatrix(_ColorMatrix) + + fileprivate init(_ storage: Material.ForegroundStyle.Storage) { + switch storage { + case let .color(color): + self = .color(color, .normal) + case let .colorBlend(color, blend): + guard case let .blendMode(mode) = GraphicsBlendMode(blend) else { + preconditionFailure() + } + self = .color(color, mode) + case let .colorMatrix(matrix): + self = .colorMatrix(matrix) + } + } + } +} + +#endif From 08f43122b327229a0378d656712b2e60b7d6cfc2 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 14:07:14 +0800 Subject: [PATCH 12/19] feat: add graphics context blend mode conversion --- .../Graphic/GraphicsConversions.swift | 30 +++++++++++++++++++ .../Graphic/GraphicsFilter.swift | 25 +--------------- .../Material/MaterialProvider.swift | 5 +--- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift b/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift index 65650a9e7..c206b7e3c 100644 --- a/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift +++ b/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift @@ -54,6 +54,36 @@ extension BlendMode { } #endif +// MARK: - GraphicsContext.BlendMode + BlendMode + +extension GraphicsContext.BlendMode { + init(_ blendMode: OpenSwiftUICore.BlendMode) { + self = switch blendMode { + case .normal: .normal + case .multiply: .multiply + case .screen: .screen + case .overlay: .overlay + case .darken: .darken + case .lighten: .lighten + case .colorDodge: .colorDodge + case .colorBurn: .colorBurn + case .softLight: .softLight + case .hardLight: .hardLight + case .difference: .difference + case .exclusion: .exclusion + case .hue: .hue + case .saturation: .saturation + case .color: .color + case .luminosity: .luminosity + case .sourceAtop: .sourceAtop + case .destinationOver: .destinationOver + case .destinationOut: .destinationOut + case .plusDarker: .plusDarker + case .plusLighter: .plusLighter + } + } +} + // MARK: - [GraphicsFilter] + caFilters extension [GraphicsFilter] { diff --git a/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift b/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift index 8e9ba531a..9e3e708da 100644 --- a/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift +++ b/Sources/OpenSwiftUICore/Graphic/GraphicsFilter.swift @@ -107,30 +107,7 @@ package enum GraphicsBlendMode: Equatable { case caFilter(AnyObject) package init(_ mode: BlendMode) { - let result: GraphicsContext.BlendMode = switch mode { - case .normal: .normal - case .multiply: .multiply - case .screen: .screen - case .overlay: .overlay - case .darken: .darken - case .lighten: .lighten - case .colorDodge: .colorDodge - case .colorBurn: .colorBurn - case .softLight: .softLight - case .hardLight: .hardLight - case .difference: .difference - case .exclusion: .exclusion - case .hue: .hue - case .saturation: .saturation - case .color: .color - case .luminosity: .luminosity - case .sourceAtop: .sourceAtop - case .destinationOver: .destinationOver - case .destinationOut: .destinationOut - case .plusDarker: .plusDarker - case .plusLighter: .plusLighter - } - self = .blendMode(result) + self = .blendMode(.init(mode)) } package static let normal: GraphicsBlendMode = .blendMode(.normal) diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialProvider.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialProvider.swift index 6cb90922e..7523782b5 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialProvider.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialProvider.swift @@ -530,10 +530,7 @@ extension Material.ForegroundStyle { case let .color(color): self = .color(color, .normal) case let .colorBlend(color, blend): - guard case let .blendMode(mode) = GraphicsBlendMode(blend) else { - preconditionFailure() - } - self = .color(color, mode) + self = .color(color, .init(blend)) case let .colorMatrix(matrix): self = .colorMatrix(matrix) } From fc97f7c617d5b9e517606fd72c871f9cd978195b Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 14:44:23 +0800 Subject: [PATCH 13/19] test: add material rendering examples and snapshots --- .../Graphics/Material/MaterialExample.swift | 62 +++++++++++++++++++ .../Shape/ShapeStyle/MaterialUITests.swift | 44 +++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 Example/Shared/Graphics/Material/MaterialExample.swift create mode 100644 Example/Tests/OpenSwiftUIUITests/Shape/ShapeStyle/MaterialUITests.swift diff --git a/Example/Shared/Graphics/Material/MaterialExample.swift b/Example/Shared/Graphics/Material/MaterialExample.swift new file mode 100644 index 000000000..746eb6037 --- /dev/null +++ b/Example/Shared/Graphics/Material/MaterialExample.swift @@ -0,0 +1,62 @@ +// +// MaterialExample.swift +// Shared + +#if OPENSWIFTUI +import OpenSwiftUI +#else +import SwiftUI +#endif + +// Examples from the Material documentation. + +struct MaterialExample: View { + var body: some View { + ZStack { + Color.teal + Label("Flag", systemImage: "flag.fill") + .padding() + .background(.regularMaterial) + } + } +} + +struct MaterialImageBackgroundExample: View { + var body: some View { + ZStack { + // Use the bundled logo in place of the documentation's chili_peppers image. + Image("logo") + .resizable() + .aspectRatio(contentMode: .fit) + Label("Flag", systemImage: "flag.fill") + .padding() + .background(.regularMaterial) + } + } +} + +struct MaterialShapeBackgroundExample: View { + var body: some View { + ZStack { + Color.teal + Label("Flag", systemImage: "flag.fill") + .padding() + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8)) + } + } +} + +#Preview("Material: Color Background") { + MaterialExample() + .frame(width: 320, height: 240) +} + +#Preview("Material: Image Background") { + MaterialImageBackgroundExample() + .frame(width: 320, height: 240) +} + +#Preview("Material: Shape Background") { + MaterialShapeBackgroundExample() + .frame(width: 320, height: 240) +} diff --git a/Example/Tests/OpenSwiftUIUITests/Shape/ShapeStyle/MaterialUITests.swift b/Example/Tests/OpenSwiftUIUITests/Shape/ShapeStyle/MaterialUITests.swift new file mode 100644 index 000000000..3c9e6892e --- /dev/null +++ b/Example/Tests/OpenSwiftUIUITests/Shape/ShapeStyle/MaterialUITests.swift @@ -0,0 +1,44 @@ +// +// MaterialUITests.swift +// OpenSwiftUIUITests + +import SnapshotTesting +import Testing +@testable import TestingHost + +@MainActor +@Suite(.snapshots(record: .never, diffTool: diffTool)) +struct MaterialUITests { + @Test(arguments: [ColorScheme.light, .dark]) + func colorBackground(colorScheme: ColorScheme) { + openSwiftUIAssertSnapshot( + of: MaterialExample() + .environment(\.colorScheme, colorScheme), + drawHierarchyInKeyWindow: true, + size: CGSize(width: 320, height: 240), + named: colorScheme == .dark ? "dark" : "light" + ) + } + + @Test(arguments: [ColorScheme.light, .dark]) + func imageBackground(colorScheme: ColorScheme) { + openSwiftUIAssertSnapshot( + of: MaterialImageBackgroundExample() + .environment(\.colorScheme, colorScheme), + drawHierarchyInKeyWindow: true, + size: CGSize(width: 320, height: 240), + named: colorScheme == .dark ? "dark" : "light" + ) + } + + @Test(arguments: [ColorScheme.light, .dark]) + func shapeBackground(colorScheme: ColorScheme) { + openSwiftUIAssertSnapshot( + of: MaterialShapeBackgroundExample() + .environment(\.colorScheme, colorScheme), + drawHierarchyInKeyWindow: true, + size: CGSize(width: 320, height: 240), + named: colorScheme == .dark ? "dark" : "light" + ) + } +} From 0cbbdf35e5000b64b9350c2ac3b5611d47400727 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 14:56:45 +0800 Subject: [PATCH 14/19] fix: restore material backgrounds in timeline examples --- .../Shared/Animation/Timeline/AnimatedColorTimelineView.swift | 4 ---- Example/Shared/Animation/Timeline/ColorCodedClockView.swift | 4 ---- 2 files changed, 8 deletions(-) diff --git a/Example/Shared/Animation/Timeline/AnimatedColorTimelineView.swift b/Example/Shared/Animation/Timeline/AnimatedColorTimelineView.swift index e2b7dc866..a90b05473 100644 --- a/Example/Shared/Animation/Timeline/AnimatedColorTimelineView.swift +++ b/Example/Shared/Animation/Timeline/AnimatedColorTimelineView.swift @@ -70,11 +70,7 @@ struct AnimatedColorTimelineView: View { .font(.headline) .foregroundColor(.white) .padding() - #if OPENSWIFTUI // FIXME: Material is not supported. - .background(Color.black.opacity(0.3)) - #else .background(.ultraThinMaterial) - #endif .cornerRadius(10) } } diff --git a/Example/Shared/Animation/Timeline/ColorCodedClockView.swift b/Example/Shared/Animation/Timeline/ColorCodedClockView.swift index 298931afc..9dff1bf54 100644 --- a/Example/Shared/Animation/Timeline/ColorCodedClockView.swift +++ b/Example/Shared/Animation/Timeline/ColorCodedClockView.swift @@ -67,11 +67,7 @@ struct ColorCodedClockView: View { LegendItem(color: .blue, label: "Seconds") } .padding() - #if OPENSWIFTUI // FIXME: Material is not supported. - .background(Color.gray.opacity(0.2)) - #else .background(.ultraThinMaterial) - #endif .cornerRadius(15) // Time progress bars From 2826c11f40622296797b59445930fd17e45dd34f Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 15:08:39 +0800 Subject: [PATCH 15/19] fix: correct QuartzCore import visibility on visionOS --- .../Shape/ShapeStyle/Material/MaterialDefinitions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift index 49e1031cb..0f5b0ffab 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift @@ -11,7 +11,7 @@ import Foundation import CoreUI #endif #if os(visionOS) -import QuartzCore_Private +package import QuartzCore_Private #endif // MARK: - ContentStyle.MaterialStyle From 543f26d8c070651128db533429e8ffd8fc243e53 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 16:39:36 +0800 Subject: [PATCH 16/19] fix: preserve Core Animation filter application order --- Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift b/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift index c206b7e3c..835fbd61d 100644 --- a/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift +++ b/Sources/OpenSwiftUICore/Graphic/GraphicsConversions.swift @@ -91,7 +91,9 @@ extension [GraphicsFilter] { package func caFilters() -> [Any]? { #if canImport(QuartzCore) let caFilters = CAFilterArrayCreate() - for filter in self { + // Display-list state stores filters in reverse application order. + // Core Animation applies them in array order. + for filter in reversed() { guard let caFilter = filter.makeCAFilter() else { continue } From c69f2069f158bcf3b58320412aaec623d42ab75b Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 16:39:53 +0800 Subject: [PATCH 17/19] fix: use writable contrast property for macOS materials --- .../Shape/ShapeStyle/Material/MaterialDefinitions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift index 0f5b0ffab..c327d2357 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/MaterialDefinitions.swift @@ -288,7 +288,7 @@ private struct MaterialLayerCache { var environment = EnvironmentValues() CoreGlue2.shared.configureDefaultEnvironment(&environment) environment.colorScheme = material.colorScheme - environment.colorSchemeContrast = material.colorSchemeContrast + environment._colorSchemeContrast = material.colorSchemeContrast let name = CUIColorName(rawValue: material.colorSchemeContrast == .increased ? 25 : 24)! let entry = CUIDesignLibraryCacheKey(name: name, in: environment).fetch() let layer = AnyMaterialLayer(content: .color(entry.color), blendMode: GraphicsBlendMode(entry.blendMode)) From eb6d6ebc68c0c27210d9e67089a51c35434a957f Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 17:20:38 +0800 Subject: [PATCH 18/19] fix(material): guard intelligence IDs on supported platforms --- .../Shape/ShapeStyle/Material/Material.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift index d61b82906..1ae66763d 100644 --- a/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift +++ b/Sources/OpenSwiftUICore/Shape/ShapeStyle/Material/Material.swift @@ -445,10 +445,10 @@ extension Material { @available(visionOS, unavailable) extension Material { public static func _intelligenceLightSource(prefersAudioReactivity: Bool = false) -> Material { - #if os(macOS) || os(watchOS) - _openSwiftUIUnreachableCode() - #else + #if os(iOS) || os(visionOS) Material(prefersAudioReactivity ? .intelligenceLightSource_AudioReactive : .intelligenceLightSource_Unreactive) + #else + _openSwiftUIUnreachableCode() #endif } } From 77020df2db914301539efa0ab677d075bb5fd7d2 Mon Sep 17 00:00:00 2001 From: Kyle Date: Sat, 7 Nov 2026 17:20:48 +0800 Subject: [PATCH 19/19] test: update opaque color protobuf expectations --- .../Graphics/Color/PaintTests.swift | 2 +- .../OpenSwiftUICoreTests/Graphics/GradientTests.swift | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/OpenSwiftUICoreTests/Graphics/Color/PaintTests.swift b/Tests/OpenSwiftUICoreTests/Graphics/Color/PaintTests.swift index a48b05559..03b9c3f5a 100644 --- a/Tests/OpenSwiftUICoreTests/Graphics/Color/PaintTests.swift +++ b/Tests/OpenSwiftUICoreTests/Graphics/Color/PaintTests.swift @@ -98,7 +98,7 @@ struct PaintTests { let data = try ProtobufEncoder.encoding { encoder in try codablePaint.encode(to: &encoder) } - #expect(data.hexString == "0a0a0d0000803f250000803f") + #expect(data.hexString == "0a050d0000803f") } @Test diff --git a/Tests/OpenSwiftUICoreTests/Graphics/GradientTests.swift b/Tests/OpenSwiftUICoreTests/Graphics/GradientTests.swift index b1d460816..7f95bc270 100644 --- a/Tests/OpenSwiftUICoreTests/Graphics/GradientTests.swift +++ b/Tests/OpenSwiftUICoreTests/Graphics/GradientTests.swift @@ -417,7 +417,7 @@ struct ResolvedGradientStopTests { ] as [(ResolvedGradient.Interpolation?, String)]) func protobufEncodesOptionalInterpolation(interpolation: ResolvedGradient.Interpolation?, suffix: String) throws { let value = stop(rgba(1, 0, 0), 0.5, interpolation) - let bytes = "0a0a0d0000803f250000803f150000003f" + suffix + let bytes = "0a050d0000803f150000003f" + suffix try value.testPBEncoding(hexString: bytes) let decoded = try bytes.decodePBHexString(ResolvedGradient.Stop.self) #expect(decoded == value) @@ -448,7 +448,7 @@ struct ResolvedGradientStopTests { @Test func protobufPreservesWideLocations() throws { let value = stop(rgba(1, 0, 0), 65_536) - let bytes = "0a0a0d0000803f250000803f11000000000000f040" + let bytes = "0a050d0000803f11000000000000f040" try value.testPBEncoding(hexString: bytes) let decoded = try bytes.decodePBHexString(ResolvedGradient.Stop.self) #expect(decoded.location == 65_536) @@ -663,9 +663,9 @@ struct ResolvedGradientProtobufTests { let gradient = ResolvedGradient(stops: [ stop(rgba(1, 0, 0), 0.5), stop(rgba(0, 0, 1), 0.25), stop(rgba(1, 0, 0), 0.5), ], colorSpace: .linear) - let bytes = "0a110a0a0d0000803f250000803f150000003f" - + "0a110a0a1d0000803f250000803f150000803e" - + "0a110a0a0d0000803f250000803f150000003f1001" + let bytes = "0a0c0a050d0000803f150000003f" + + "0a0c0a051d0000803f150000803e" + + "0a0c0a050d0000803f150000003f1001" try gradient.testPBEncoding(hexString: bytes) let decoded = try bytes.decodePBHexString(ResolvedGradient.self) #expect(decoded == gradient)