Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
4 changes: 0 additions & 4 deletions Example/Shared/Animation/Timeline/ColorCodedClockView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
62 changes: 62 additions & 0 deletions Example/Shared/Graphics/Material/MaterialExample.swift
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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"
)
}
}
4 changes: 2 additions & 2 deletions Package.resolved

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

26 changes: 24 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]))
Expand Down Expand Up @@ -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'"
Expand Down Expand Up @@ -557,6 +574,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")"),
Expand Down Expand Up @@ -964,6 +982,10 @@ if linkCoreUI {
openSwiftUISPITarget.addCoreUISettings()
}

if linkCoreMaterial {
openSwiftUICoreTarget.addCoreMaterialSettings()
}

if linkCoreSVG {
openSwiftUICoreTarget.addCoreSVGSettings()
openSwiftUISPITarget.addCoreSVGSettings()
Expand Down Expand Up @@ -995,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
Expand All @@ -1007,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
Expand Down
Original file line number Diff line number Diff line change
@@ -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..<rating, id: \.self) { _ in
/// Image(systemName: "star.fill")
/// }
/// }
/// .foregroundStyle(backgroundProminence == .increased ?
/// AnyShapeStyle(.secondary) : AnyShapeStyle(.yellow))
/// .imageScale(.small)
/// }
/// }
///
/// Note that the use of `backgroundProminence` was used by a view that
/// was nested in additional stack containers within the row. This ensured
/// that the value correctly reflected the environment within the list row
/// itself, as opposed to the environment of the list as a whole. One way
/// to ensure correct resolution would be to prefer using this in a custom
/// ShapeStyle instead, for example:
///
/// private struct StarRating: View {
/// var rating: Int
///
/// var body: some View {
/// HStack(spacing: 1) {
/// ForEach(0..<rating, id: \.self) { _ in
/// Image(systemName: "star.fill")
/// }
/// }
/// .foregroundStyle(FillStyle())
/// .imageScale(.small)
/// }
/// }
///
/// extension StarRating {
/// struct FillStyle: ShapeStyle {
/// func resolve(in env: EnvironmentValues) -> 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 }
}
}
Loading
Loading