diff --git a/Sources/TokamakCore/Shapes/ShapeStyles/BackgroundStyle.swift b/Sources/TokamakCore/Shapes/ShapeStyles/BackgroundStyle.swift index c2dd4fe7a..becf50ac1 100644 --- a/Sources/TokamakCore/Shapes/ShapeStyles/BackgroundStyle.swift +++ b/Sources/TokamakCore/Shapes/ShapeStyles/BackgroundStyle.swift @@ -74,6 +74,9 @@ public extension View { } public func modifyEnvironment(_ values: inout EnvironmentValues) { - values._backgroundStyle = .init(styles: [style, style, style], environment: values) + values._backgroundStyle = .init( + styles: (primary: style, secondary: style, tertiary: style), + environment: values + ) } } diff --git a/Sources/TokamakCore/Shapes/ShapeStyles/ContentStyles.swift b/Sources/TokamakCore/Shapes/ShapeStyles/ContentStyles.swift new file mode 100644 index 000000000..c8a2ca589 --- /dev/null +++ b/Sources/TokamakCore/Shapes/ShapeStyles/ContentStyles.swift @@ -0,0 +1,104 @@ +// Copyright 2020-2021 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Created by Carson Katri on 7/7/21. +// + +@frozen public struct PrimaryContentStyle { + @inlinable + public init() {} +} + +extension PrimaryContentStyle: ShapeStyle { + public func _apply(to shape: inout _ShapeStyle_Shape) { + if !shape.inRecursiveStyle, + let foregroundStyle = shape.environment._foregroundStyle + { + if foregroundStyle.styles.primary is Self { + shape.inRecursiveStyle = true + } + foregroundStyle.styles.primary._apply(to: &shape) + } else { + shape.result = .color(shape.environment.foregroundColor ?? .primary) + } + } + + public static func _apply(to shape: inout _ShapeStyle_ShapeType) {} +} + +@frozen public struct SecondaryContentStyle { + @inlinable + public init() {} +} + +extension SecondaryContentStyle: ShapeStyle { + public func _apply(to shape: inout _ShapeStyle_Shape) { + if !shape.inRecursiveStyle, + let foregroundStyle = shape.environment._foregroundStyle + { + if foregroundStyle.styles.secondary is Self { + shape.inRecursiveStyle = true + } + foregroundStyle.styles.secondary._apply(to: &shape) + } else { + shape.result = .color((shape.environment.foregroundColor ?? .primary).opacity(0.5)) + } + } + + public static func _apply(to shape: inout _ShapeStyle_ShapeType) {} +} + +@frozen public struct TertiaryContentStyle { + @inlinable + public init() {} +} + +extension TertiaryContentStyle: ShapeStyle { + public func _apply(to shape: inout _ShapeStyle_Shape) { + if !shape.inRecursiveStyle, + let foregroundStyle = shape.environment._foregroundStyle + { + if foregroundStyle.styles.tertiary is Self { + shape.inRecursiveStyle = true + } + foregroundStyle.styles.tertiary._apply(to: &shape) + } else { + shape.result = .color((shape.environment.foregroundColor ?? .primary).opacity(0.3)) + } + } + + public static func _apply(to shape: inout _ShapeStyle_ShapeType) {} +} + +@frozen public struct QuaternaryContentStyle { + @inlinable + public init() {} +} + +extension QuaternaryContentStyle: ShapeStyle { + public func _apply(to shape: inout _ShapeStyle_Shape) { + if !shape.inRecursiveStyle, + let foregroundStyle = shape.environment._foregroundStyle + { + if foregroundStyle.styles.tertiary is Self { + shape.inRecursiveStyle = true + } + foregroundStyle.styles.tertiary._apply(to: &shape) + } else { + shape.result = .color((shape.environment.foregroundColor ?? .primary).opacity(0.2)) + } + } + + public static func _apply(to shape: inout _ShapeStyle_ShapeType) {} +} diff --git a/Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift b/Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift index 892489bb8..c3ef99c0a 100644 --- a/Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift +++ b/Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift @@ -60,24 +60,36 @@ public extension View { } @inlinable - func foregroundStyle(_ primary: S1, _ secondary: S2, - _ tertiary: S3) -> some View + func foregroundStyle( + _ primary: S1, + _ secondary: S2, + _ tertiary: S3 + ) -> some View where S1: ShapeStyle, S2: ShapeStyle, S3: ShapeStyle { - modifier(_ForegroundStyleModifier(styles: [primary, secondary, tertiary])) + modifier(_ForegroundStyleModifier(primary: primary, secondary: secondary, tertiary: tertiary)) } } -@frozen public struct _ForegroundStyleModifier: ViewModifier, EnvironmentModifier { - public var styles: [ShapeStyle] +@frozen public struct _ForegroundStyleModifier: ViewModifier, + EnvironmentModifier + where Primary: ShapeStyle, Secondary: ShapeStyle, Tertiary: ShapeStyle +{ + public var primary: Primary + public var secondary: Secondary + public var tertiary: Tertiary @inlinable - public init(styles: [ShapeStyle]) { - self.styles = styles + public init( + primary: Primary, + secondary: Secondary, + tertiary: Tertiary + ) { + (self.primary, self.secondary, self.tertiary) = (primary, secondary, tertiary) } public typealias Body = Never public func modifyEnvironment(_ values: inout EnvironmentValues) { - values._foregroundStyle = .init(styles: styles, environment: values) + values._foregroundStyle = .init(styles: (primary, secondary, tertiary), environment: values) } } diff --git a/Sources/TokamakCore/Shapes/ShapeStyles/ShapeStyle.swift b/Sources/TokamakCore/Shapes/ShapeStyles/ShapeStyle.swift index 43e49e743..8412392e5 100644 --- a/Sources/TokamakCore/Shapes/ShapeStyles/ShapeStyle.swift +++ b/Sources/TokamakCore/Shapes/ShapeStyles/ShapeStyle.swift @@ -23,23 +23,23 @@ public protocol ShapeStyle { } public struct AnyShapeStyle: ShapeStyle { - let styles: [ShapeStyle] + let styles: (primary: ShapeStyle, secondary: ShapeStyle, tertiary: ShapeStyle) + var stylesArray: [ShapeStyle] { + [styles.primary, styles.secondary, styles.tertiary] + } + let environment: EnvironmentValues public func _apply(to shape: inout _ShapeStyle_Shape) { shape.environment = environment - if styles.count > 1 { - let results = styles.map { style -> _ShapeStyle_Shape.Result in - var copy = shape - style._apply(to: ©) - return copy.result - } - shape - .result = - .resolved(.array(results.compactMap { $0.resolvedStyle(on: shape, in: environment) })) - } else if let first = styles.first { - first._apply(to: &shape) + let results = stylesArray.map { style -> _ShapeStyle_Shape.Result in + var copy = shape + style._apply(to: ©) + return copy.result } + shape + .result = + .resolved(.array(results.compactMap { $0.resolvedStyle(on: shape, in: environment) })) switch shape.operation { case let .prepare(text, level): @@ -50,7 +50,9 @@ public struct AnyShapeStyle: ShapeStyle { shape.result = .prepared(Text(storage: text.storage, modifiers: modifiers)) case let .resolveStyle(levels): if case let .resolved(resolved) = shape.result { - if case let .array(children) = resolved { + if case let .array(children) = resolved, + children.count >= levels.upperBound + { shape.result = .resolved(.array(.init(children[levels]))) } } else if let resolved = shape.result.resolvedStyle(on: shape, in: environment) { diff --git a/Sources/TokamakDOM/Core.swift b/Sources/TokamakDOM/Core.swift index d061a3c32..2b904a887 100644 --- a/Sources/TokamakDOM/Core.swift +++ b/Sources/TokamakDOM/Core.swift @@ -83,7 +83,16 @@ public typealias Rectangle = TokamakCore.Rectangle public typealias RoundedRectangle = TokamakCore.RoundedRectangle public typealias ContainerRelativeShape = TokamakCore.ContainerRelativeShape +// MARK: Shape Styles + +public typealias PrimaryContentStyle = TokamakCore.PrimaryContentStyle +public typealias SecondaryContentStyle = TokamakCore.SecondaryContentStyle +public typealias TertiaryContentStyle = TokamakCore.TertiaryContentStyle +public typealias QuaternaryContentStyle = TokamakCore.QuaternaryContentStyle + public typealias ForegroundStyle = TokamakCore.ForegroundStyle +public typealias BackgroundStyle = TokamakCore.BackgroundStyle + public typealias Material = TokamakCore.Material // MARK: Primitive values diff --git a/Sources/TokamakDemo/ShapeStyleDemo.swift b/Sources/TokamakDemo/ShapeStyleDemo.swift index af4ac7a2d..3cd6c068e 100644 --- a/Sources/TokamakDemo/ShapeStyleDemo.swift +++ b/Sources/TokamakDemo/ShapeStyleDemo.swift @@ -89,6 +89,18 @@ struct ShapeStyleDemo: View { } } } + HStack { + VStack { + Text("Primary") + .foregroundStyle(PrimaryContentStyle()) + Text("Secondary") + .foregroundStyle(SecondaryContentStyle()) + Text("Tertiary") + .foregroundStyle(TertiaryContentStyle()) + Text("Quaternary") + .foregroundStyle(QuaternaryContentStyle()) + } + } #endif } } diff --git a/Sources/TokamakStaticHTML/Core.swift b/Sources/TokamakStaticHTML/Core.swift index 1b79cf635..ce1b488f5 100644 --- a/Sources/TokamakStaticHTML/Core.swift +++ b/Sources/TokamakStaticHTML/Core.swift @@ -44,7 +44,16 @@ public typealias Rectangle = TokamakCore.Rectangle public typealias RoundedRectangle = TokamakCore.RoundedRectangle public typealias ContainerRelativeShape = TokamakCore.ContainerRelativeShape +// MARK: Shape Styles + +public typealias PrimaryContentStyle = TokamakCore.PrimaryContentStyle +public typealias SecondaryContentStyle = TokamakCore.SecondaryContentStyle +public typealias TertiaryContentStyle = TokamakCore.TertiaryContentStyle +public typealias QuaternaryContentStyle = TokamakCore.QuaternaryContentStyle + public typealias ForegroundStyle = TokamakCore.ForegroundStyle +public typealias BackgroundStyle = TokamakCore.BackgroundStyle + public typealias Material = TokamakCore.Material // MARK: Primitive values diff --git a/Tests/TokamakStaticHTMLTests/RenderingTests.swift b/Tests/TokamakStaticHTMLTests/RenderingTests.swift index 018940b76..c1a9150ec 100644 --- a/Tests/TokamakStaticHTMLTests/RenderingTests.swift +++ b/Tests/TokamakStaticHTMLTests/RenderingTests.swift @@ -200,6 +200,28 @@ final class RenderingTests: XCTestCase { ) } + func testContentStyles() { + assertSnapshot( + matching: HStack { + Rectangle() + .frame(width: 50, height: 50) + .foregroundStyle(PrimaryContentStyle()) + Rectangle() + .frame(width: 50, height: 50) + .foregroundStyle(SecondaryContentStyle()) + Rectangle() + .frame(width: 50, height: 50) + .foregroundStyle(TertiaryContentStyle()) + Rectangle() + .frame(width: 50, height: 50) + .foregroundStyle(QuaternaryContentStyle()) + } + .foregroundColor(.blue), + as: .image(size: .init(width: 275, height: 100)), + timeout: defaultSnapshotTimeout + ) + } + func testMaterial() { assertSnapshot( matching: ZStack { diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testContentStyles.1.png b/Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testContentStyles.1.png new file mode 100644 index 000000000..592014eea Binary files /dev/null and b/Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testContentStyles.1.png differ