From 14015a4b7e69d1f279611c68676ee72cde940303 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Tue, 6 Jul 2021 17:04:22 -0400 Subject: [PATCH 01/18] Add ContainerRelativeShape --- .../Shapes/ContainerRelativeShape.swift | 97 +++++++++++++++++++ Sources/TokamakDOM/Core.swift | 3 + .../Shapes/ContainerRelativeShape.swift | 32 ++++++ 3 files changed, 132 insertions(+) create mode 100644 Sources/TokamakCore/Shapes/ContainerRelativeShape.swift create mode 100644 Sources/TokamakDOM/Shapes/ContainerRelativeShape.swift diff --git a/Sources/TokamakCore/Shapes/ContainerRelativeShape.swift b/Sources/TokamakCore/Shapes/ContainerRelativeShape.swift new file mode 100644 index 000000000..e65d61810 --- /dev/null +++ b/Sources/TokamakCore/Shapes/ContainerRelativeShape.swift @@ -0,0 +1,97 @@ +// 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/6/21. +// + +import Foundation + +public struct ContainerRelativeShape: Shape { + @Environment(\._containerShape) var containerShape + + public func path(in rect: CGRect) -> Path { + containerShape(rect, GeometryProxy(size: rect.size)) ?? Rectangle().path(in: rect) + } + + public init() {} +} + +extension ContainerRelativeShape: InsettableShape { + @inlinable public func inset(by amount: CGFloat) -> some InsettableShape { + return _Inset(amount: amount) + } + + @usableFromInline + @frozen internal struct _Inset: InsettableShape, DynamicProperty { + @usableFromInline + internal var amount: CGFloat + @inlinable internal init(amount: CGFloat) { + self.amount = amount + } + @usableFromInline + internal func path(in rect: CGRect) -> Path { + // FIXME: Inset the container shape. + Rectangle().path(in: rect) + } + @inlinable internal func inset(by amount: CGFloat) -> ContainerRelativeShape._Inset { + var copy = self + copy.amount += amount + return copy + } + } +} + +private extension EnvironmentValues { + enum ContainerShapeKey : EnvironmentKey { + static let defaultValue: (CGRect, GeometryProxy) -> Path? = { _, _ in nil } + } + + var _containerShape: (CGRect, GeometryProxy) -> Path? { + get { + self[ContainerShapeKey.self] + } + set { + self[ContainerShapeKey.self] = newValue + } + } +} + +@frozen public struct _ContainerShapeModifier: ViewModifier where Shape: InsettableShape { + public var shape: Shape + @inlinable public init(shape: Shape) { self.shape = shape } + + public func body(content: Content) -> some View { + _ContainerShapeView(content: content, shape: shape) + } + + public struct _ContainerShapeView: View { + public let content: Content + public let shape: Shape + + public var body: some View { + content + .environment(\._containerShape, { rect, proxy in + return shape + .inset(by: proxy.size.width) // TODO: Calculate the offset using content's geometry + .path(in: rect) + }) + } + } +} + +public extension View { + @inlinable func containerShape(_ shape: T) -> some View where T: InsettableShape { + modifier(_ContainerShapeModifier(shape: shape)) + } +} diff --git a/Sources/TokamakDOM/Core.swift b/Sources/TokamakDOM/Core.swift index 02332d5e0..09b505b8a 100644 --- a/Sources/TokamakDOM/Core.swift +++ b/Sources/TokamakDOM/Core.swift @@ -63,6 +63,8 @@ public typealias ButtonStyle = TokamakCore.ButtonStyle public typealias ButtonStyleConfiguration = TokamakCore.ButtonStyleConfiguration public typealias DefaultButtonStyle = TokamakCore.DefaultButtonStyle +public typealias TextFieldStyle = TokamakCore.TextFieldStyle + public typealias FillStyle = TokamakCore.FillStyle public typealias ShapeStyle = TokamakCore.ShapeStyle public typealias StrokeStyle = TokamakCore.StrokeStyle @@ -79,6 +81,7 @@ public typealias Ellipse = TokamakCore.Ellipse public typealias Path = TokamakCore.Path public typealias Rectangle = TokamakCore.Rectangle public typealias RoundedRectangle = TokamakCore.RoundedRectangle +public typealias ContainerRelativeShape = TokamakCore.ContainerRelativeShape // MARK: Primitive values diff --git a/Sources/TokamakDOM/Shapes/ContainerRelativeShape.swift b/Sources/TokamakDOM/Shapes/ContainerRelativeShape.swift new file mode 100644 index 000000000..a3930c581 --- /dev/null +++ b/Sources/TokamakDOM/Shapes/ContainerRelativeShape.swift @@ -0,0 +1,32 @@ +// 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. + +import Foundation +import JavaScriptKit +@_spi(TokamakCore) import TokamakCore +import TokamakStaticHTML + +//extension _ContainerShapeModifier._ContainerShapeView : _DOMPrimitive { +// public var renderedBody: AnyView { +// AnyView( +// content +// .environment(\._containerShape, { rect, proxy in +// guard let parentProxy = self.proxy else { return nil } +// return shape +// .inset(by: abs(proxy.size.width - parentProxy.size.width)) +// .path(in: rect) +// }) +// ) +// } +//} From 126397830ce7fe031ac66301bbc9b93e8f32cb84 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Tue, 6 Jul 2021 17:13:49 -0400 Subject: [PATCH 02/18] Add new background modifiers --- .../Modifiers/StyleModifiers.swift | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Sources/TokamakCore/Modifiers/StyleModifiers.swift b/Sources/TokamakCore/Modifiers/StyleModifiers.swift index b9d49758b..a4302f24e 100644 --- a/Sources/TokamakCore/Modifiers/StyleModifiers.swift +++ b/Sources/TokamakCore/Modifiers/StyleModifiers.swift @@ -58,6 +58,30 @@ public extension View { ) -> some View where Background: View { modifier(_BackgroundModifier(background: background, alignment: alignment)) } + + @inlinable func background( + alignment: Alignment = .center, + @ViewBuilder content: () -> V + ) -> some View where V: View { + background(content(), alignment: alignment) + } + + @inlinable func background( + _ style: S, + in shape: T, + fillStyle: FillStyle = FillStyle() + ) -> some View where S: ShapeStyle, T: Shape { + background { + shape.fill(style, style: fillStyle) + } + } + + @inlinable func background( + in shape: S, + fillStyle: FillStyle = FillStyle() + ) -> some View where S: Shape { + background(ForegroundStyle(), in: shape, fillStyle: fillStyle) + } } public struct _OverlayModifier: ViewModifier, EnvironmentReader From 5dfda2620711e49bcfb433347279bc5615bc4eb7 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Tue, 6 Jul 2021 23:32:41 -0400 Subject: [PATCH 03/18] Add new foregroundStyle modifiers and behaviour --- .../TokamakCore/Shapes/ModifiedShapes.swift | 2 + Sources/TokamakCore/Shapes/Shape.swift | 24 +- .../Shapes/ShapeStyles/ForegroundStyle.swift | 8 + .../Shapes/ShapeStyles/ShapeStyle.swift | 205 ++++++++++++++++++ Sources/TokamakCore/Tokens/AnyTokenBox.swift | 2 +- Sources/TokamakCore/Tokens/Color.swift | 49 ++++- Sources/TokamakCore/Views/Text/Text.swift | 13 +- Sources/TokamakDemo/ShapeStyleDemo.swift | 40 ++++ Sources/TokamakDemo/TokamakDemo.swift | 1 + .../TokamakStaticHTML/Shapes/_ShapeView.swift | 38 +++- 10 files changed, 373 insertions(+), 9 deletions(-) create mode 100644 Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift create mode 100644 Sources/TokamakCore/Shapes/ShapeStyles/ShapeStyle.swift create mode 100644 Sources/TokamakDemo/ShapeStyleDemo.swift diff --git a/Sources/TokamakCore/Shapes/ModifiedShapes.swift b/Sources/TokamakCore/Shapes/ModifiedShapes.swift index a40424959..4eff3696a 100644 --- a/Sources/TokamakCore/Shapes/ModifiedShapes.swift +++ b/Sources/TokamakCore/Shapes/ModifiedShapes.swift @@ -32,6 +32,8 @@ public struct _StrokedShape: Shape, DynamicProperty where S: Shape { .path(in: rect) .strokedPath(style) } + + public static var role: ShapeRole { .stroke } } public struct _TrimmedShape: Shape where S: Shape { diff --git a/Sources/TokamakCore/Shapes/Shape.swift b/Sources/TokamakCore/Shapes/Shape.swift index 2b3c055c3..c572ccf7d 100644 --- a/Sources/TokamakCore/Shapes/Shape.swift +++ b/Sources/TokamakCore/Shapes/Shape.swift @@ -19,9 +19,19 @@ import Foundation public protocol Shape: View { func path(in rect: CGRect) -> Path + + static var role: ShapeRole { get } } -public protocol ShapeStyle {} +public enum ShapeRole: Hashable { + case fill + case stroke + case separator +} + +public extension Shape { + static var role: ShapeRole { .fill } +} public extension ShapeStyle where Self: View, Self.Body == _ShapeView { var body: some View { @@ -36,9 +46,19 @@ public protocol InsettableShape: Shape { public struct ForegroundStyle: ShapeStyle { public init() {} + + public func _apply(to shape: inout _ShapeStyle_Shape) { + if let foregroundStyle = shape.environment._foregroundStyle { + foregroundStyle._apply(to: &shape) + } else { + shape.result = .color(shape.environment.foregroundColor ?? .primary) + } + } + + public static func _apply(to shape: inout _ShapeStyle_ShapeType) {} } -public struct FillStyle: Equatable, ShapeStyle { +public struct FillStyle: Equatable { public var isEOFilled: Bool public var isAntialiased: Bool diff --git a/Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift b/Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift new file mode 100644 index 000000000..44bb28845 --- /dev/null +++ b/Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift @@ -0,0 +1,8 @@ +// +// File.swift +// +// +// Created by Carson Katri on 7/6/21. +// + +import Foundation diff --git a/Sources/TokamakCore/Shapes/ShapeStyles/ShapeStyle.swift b/Sources/TokamakCore/Shapes/ShapeStyles/ShapeStyle.swift new file mode 100644 index 000000000..1a55d9ca4 --- /dev/null +++ b/Sources/TokamakCore/Shapes/ShapeStyles/ShapeStyle.swift @@ -0,0 +1,205 @@ +// 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/6/21. +// + +import Foundation + +//public protocol _PrimitiveShapeStyle { +// func _applyRendered(to shape: inout _ShapeStyle_Shape) +// static func _applyRendered(to type: inout _ShapeStyle_ShapeType) +//} + +public protocol ShapeStyle { + func _apply(to shape: inout _ShapeStyle_Shape) + static func _apply(to type: inout _ShapeStyle_ShapeType) +} + +class ShapeStyleBox: AnyTokenBox { + let styles: [ShapeStyle] + + init(styles: [ShapeStyle]) { + self.styles = styles + } + + func resolve(in environment: EnvironmentValues) -> AnyShapeStyle { + AnyShapeStyle(styles: styles, environment: environment) + } +} + +public struct AnyShapeStyle: ShapeStyle { + let styles: [ShapeStyle] + 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) + } + + switch shape.operation { + case let .prepare(text, level): + var modifiers = text.modifiers + if let color = shape.result.resolvedStyle(on: shape, in: environment)?.color(at: level) { + modifiers.insert(.color(color), at: 0) + } + 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 { + shape.result = .resolved(.array(.init(children[levels]))) + } + } else if let resolved = shape.result.resolvedStyle(on: shape, in: environment) { + shape.result = .resolved(resolved) + } + default: + // TODO: Handle other operations. + break + } + } + + public static func _apply(to type: inout _ShapeStyle_ShapeType) {} +} + +public struct _ShapeStyle_Shape { + public let operation: Operation + public var result: Result + public var environment: EnvironmentValues + public var bounds: CGRect? + public var role: ShapeRole + public var inRecursiveStyle: Bool + + public init( + for operation: Operation, + in environment: EnvironmentValues, + role: ShapeRole + ) { + self.operation = operation + self.result = .none + self.environment = environment + self.bounds = nil + self.role = role + self.inRecursiveStyle = false + } + + public enum Operation { + case prepare(Text, level: Int) + case resolveStyle(levels: Range) + case fallbackColor(level: Int) + case multiLevel + case copyForeground + case primaryStyle + case modifyBackground + } + + public enum Result { + case prepared(Text) + case resolved(_ResolvedStyle) + case style(AnyShapeStyle) + case color(Color) + case bool(Bool) + case none + + func resolvedStyle(on shape: _ShapeStyle_Shape, in environment: EnvironmentValues) -> _ResolvedStyle? { + switch self { + case let .resolved(resolved): return resolved + case let .style(anyStyle): + var copy = shape + anyStyle._apply(to: ©) + return copy.result.resolvedStyle(on: shape, in: environment) + case let .color(color): + return .color(color.provider.resolve(in: environment)) + default: + return nil + } + } + } +} +public struct _ShapeStyle_ShapeType {} + +public indirect enum _ResolvedStyle { + case color(AnyColorBox.ResolvedValue) +// case paint(AnyResolvedPaint) // I think is used for Image as a ShapeStyle (SwiftUI.ImagePaint). +// TODO: Material +// case foregroundMaterial(AnyColorBox.ResolvedValue, MaterialStyle) +// case backgroundMaterial(AnyColorBox.ResolvedValue) + case array([_ResolvedStyle]) + case opacity(Float, _ResolvedStyle) +// case multicolor(ResolvedMulticolorStyle) + + public func color(at level: Int) -> Color? { + switch self { + case let .color(resolved): + return Color(_ConcreteColorBox(resolved)) + case let .array(children): + return children[level].color(at: level) + case let .opacity(opacity, resolved): + guard let color = resolved.color(at: level) else { return nil } + return color.opacity(Double(opacity)) + } + } +} + +extension EnvironmentValues { + private struct ForegroundStyleKey: EnvironmentKey { + static let defaultValue: AnyShapeStyle? = nil + } + + public var _foregroundStyle: AnyShapeStyle? { + get { + self[ForegroundStyleKey.self] + } + set { + self[ForegroundStyleKey.self] = newValue + } + } +} + +public extension View { + @inlinable func foregroundStyle(_ style: S) -> some View + where S: ShapeStyle { + foregroundStyle(style, style, style) + } + + @inlinable func foregroundStyle(_ primary: S1, _ secondary: S2) -> some View + where S1: ShapeStyle, S2: ShapeStyle { + foregroundStyle(primary, secondary, secondary) + } + + @inlinable func foregroundStyle(_ primary: S1, _ secondary: S2, _ tertiary: S3) -> some View + where S1: ShapeStyle, S2: ShapeStyle, S3: ShapeStyle { + modifier(_ForegroundStyleModifier(styles: [primary, secondary, tertiary])) + } +} + +@frozen public struct _ForegroundStyleModifier: ViewModifier, EnvironmentModifier { + public var styles: [ShapeStyle] + + @inlinable public init(styles: [ShapeStyle]) { + self.styles = styles + } + + public typealias Body = Never + public func modifyEnvironment(_ values: inout EnvironmentValues) { + values._foregroundStyle = .init(styles: styles, environment: values) + } +} diff --git a/Sources/TokamakCore/Tokens/AnyTokenBox.swift b/Sources/TokamakCore/Tokens/AnyTokenBox.swift index 1c0cc44a2..820a9b536 100644 --- a/Sources/TokamakCore/Tokens/AnyTokenBox.swift +++ b/Sources/TokamakCore/Tokens/AnyTokenBox.swift @@ -16,7 +16,7 @@ // /// Allows "late-binding tokens" to be resolved in an environment by a `Renderer` (or `TokamakCore`) -public protocol AnyTokenBox: AnyObject, Hashable { +public protocol AnyTokenBox: AnyObject { associatedtype ResolvedValue func resolve(in environment: EnvironmentValues) -> ResolvedValue } diff --git a/Sources/TokamakCore/Tokens/Color.swift b/Sources/TokamakCore/Tokens/Color.swift index eb9227a51..111b089a4 100644 --- a/Sources/TokamakCore/Tokens/Color.swift +++ b/Sources/TokamakCore/Tokens/Color.swift @@ -39,7 +39,7 @@ public protocol AnyColorBoxDeferredToRenderer: AnyColorBox { func deferredResolve(in environment: EnvironmentValues) -> AnyColorBox.ResolvedValue } -public class AnyColorBox: AnyTokenBox, Equatable { +public class AnyColorBox: AnyTokenBox, Hashable { public struct _RGBA: Hashable, Equatable { public let red: Double public let green: Double @@ -123,6 +123,38 @@ public class _EnvironmentDependentColorBox: AnyColorBox { } } +public class _OpacityColorBox: AnyColorBox { + public let parent: AnyColorBox + public let opacity: Double + + override public func equals(_ other: AnyColorBox) -> Bool { + guard let other = other as? _OpacityColorBox + else { return false } + return parent.equals(other.parent) && opacity == other.opacity + } + + override public func hash(into hasher: inout Hasher) { + hasher.combine(parent) + hasher.combine(opacity) + } + + init(_ parent: AnyColorBox, opacity: Double) { + self.parent = parent + self.opacity = opacity + } + + override public func resolve(in environment: EnvironmentValues) -> ResolvedValue { + let resolved = parent.resolve(in: environment) + return .init( + red: resolved.red, + green: resolved.green, + blue: resolved.blue, + opacity: opacity, + space: resolved.space + ) + } +} + public class _SystemColorBox: AnyColorBox, CustomStringConvertible { public enum SystemColor: String, Equatable, Hashable { case clear @@ -209,7 +241,7 @@ public struct Color: Hashable, Equatable { let provider: AnyColorBox - private init(_ provider: AnyColorBox) { + internal init(_ provider: AnyColorBox) { self.provider = provider } @@ -249,6 +281,12 @@ public struct Color: Hashable, Equatable { } } +public extension Color { + func opacity(_ opacity: Double) -> Self { + Self.init(_OpacityColorBox(provider, opacity: opacity)) + } +} + public struct _ColorProxy { let subject: Color public init(_ subject: Color) { self.subject = subject } @@ -343,7 +381,12 @@ public extension Color { } } -extension Color: ShapeStyle {} +extension Color: ShapeStyle { + public func _apply(to shape: inout _ShapeStyle_Shape) { + shape.result = .color(self) + } + public static func _apply(to type: inout _ShapeStyle_ShapeType) {} +} extension Color: View { @_spi(TokamakCore) public var body: some View { diff --git a/Sources/TokamakCore/Views/Text/Text.swift b/Sources/TokamakCore/Views/Text/Text.swift index 6ec6b2ae7..f71f5db7d 100644 --- a/Sources/TokamakCore/Views/Text/Text.swift +++ b/Sources/TokamakCore/Views/Text/Text.swift @@ -92,7 +92,18 @@ public extension Text._Storage { public struct _TextProxy { public let subject: Text - public init(_ subject: Text) { self.subject = subject } + public init(_ subject: Text) { + // Resolve the foregroundStyle. + if let foregroundStyle = subject.environment._foregroundStyle { + var shape = _ShapeStyle_Shape(for: .prepare(subject, level: 0), in: subject.environment, role: .fill) + foregroundStyle._apply(to: &shape) + if case let .prepared(text) = shape.result { + self.subject = text + return + } + } + self.subject = subject + } public var storage: Text._Storage { subject.storage } public var rawText: String { diff --git a/Sources/TokamakDemo/ShapeStyleDemo.swift b/Sources/TokamakDemo/ShapeStyleDemo.swift new file mode 100644 index 000000000..9bdb6eb01 --- /dev/null +++ b/Sources/TokamakDemo/ShapeStyleDemo.swift @@ -0,0 +1,40 @@ +// Copyright 2020 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. + +import TokamakShim + +struct ShapeStyleDemo: View { + var body: some View { + HStack { + VStack { + Text("Red Style") + Rectangle() + .frame(width: 25, height: 25) + } + .foregroundStyle(Color.red) + VStack { + Text("Green Style") + Rectangle() + .frame(width: 25, height: 25) + } + .foregroundStyle(Color.green) + VStack { + Text("Blue Style") + Rectangle() + .frame(width: 25, height: 25) + } + .foregroundStyle(Color.blue) + } + } +} diff --git a/Sources/TokamakDemo/TokamakDemo.swift b/Sources/TokamakDemo/TokamakDemo.swift index 1da5e69bf..09788906b 100644 --- a/Sources/TokamakDemo/TokamakDemo.swift +++ b/Sources/TokamakDemo/TokamakDemo.swift @@ -141,6 +141,7 @@ struct TokamakDemoView: View { NavItem("Preferences", destination: PreferenceKeyDemo()) } NavItem("Color", destination: ColorDemo()) + NavItem("Shape Styles", destination: ShapeStyleDemo()) if #available(OSX 11.0, iOS 14.0, *) { NavItem("AppStorage", destination: AppStorageDemo()) } else { diff --git a/Sources/TokamakStaticHTML/Shapes/_ShapeView.swift b/Sources/TokamakStaticHTML/Shapes/_ShapeView.swift index b3a7c4241..f2280a1b0 100644 --- a/Sources/TokamakStaticHTML/Shapes/_ShapeView.swift +++ b/Sources/TokamakStaticHTML/Shapes/_ShapeView.swift @@ -21,9 +21,30 @@ protocol ShapeAttributes { func attributes(_ style: ShapeStyle) -> [HTMLAttribute: String] } +extension ShapeStyle { + func resolve( + for operation: _ShapeStyle_Shape.Operation, + in environment: EnvironmentValues, + role: ShapeRole + ) -> _ShapeStyle_Shape.Result { + var shape = _ShapeStyle_Shape( + for: operation, + in: environment, + role: role + ) + _apply(to: &shape) + return shape.result + } +} + extension _StrokedShape: ShapeAttributes { func attributes(_ style: ShapeStyle) -> [HTMLAttribute: String] { - if let color = style as? Color { + if case let .resolved(resolved) = style.resolve( + for: .resolveStyle(levels: 0..<1), + in: environment, + role: .stroke + ), + let color = resolved.color(at: 0) { return ["style": "stroke: \(color.cssValue(environment)); fill: none;"] } else { return ["style": "stroke: black; fill: none;"] @@ -39,7 +60,20 @@ extension _ShapeView: _HTMLPrimitive { if let shapeAttributes = shape as? ShapeAttributes { attributes = shapeAttributes.attributes(style) - } else if let color = style as? Color { + } else if case let .resolved(resolved) = style.resolve( + for: .resolveStyle(levels: 0..<1), + in: environment, + role: .stroke + ), + let color = resolved.color(at: 0) { + attributes = ["style": "fill: \(color.cssValue(environment));"] + } else if let foregroundStyle = environment._foregroundStyle, + case let .resolved(resolved) = foregroundStyle.resolve( + for: .resolveStyle(levels: 0..<1), + in: environment, + role: .stroke + ), + let color = resolved.color(at: 0) { attributes = ["style": "fill: \(color.cssValue(environment));"] } else if let foregroundColor = foregroundColor { attributes = ["style": "fill: \(foregroundColor.cssValue(environment));"] From b1423bdca2223c651b634bed3b09bb28ff5da06d Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Tue, 6 Jul 2021 23:45:41 -0400 Subject: [PATCH 04/18] Move ForegroundStyle to separate file --- Sources/TokamakCore/Shapes/Shape.swift | 14 ---------- .../Shapes/ShapeStyles/ForegroundStyle.swift | 28 +++++++++++++++++-- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/Sources/TokamakCore/Shapes/Shape.swift b/Sources/TokamakCore/Shapes/Shape.swift index c572ccf7d..db476538b 100644 --- a/Sources/TokamakCore/Shapes/Shape.swift +++ b/Sources/TokamakCore/Shapes/Shape.swift @@ -44,20 +44,6 @@ public protocol InsettableShape: Shape { func inset(by amount: CGFloat) -> InsetShape } -public struct ForegroundStyle: ShapeStyle { - public init() {} - - public func _apply(to shape: inout _ShapeStyle_Shape) { - if let foregroundStyle = shape.environment._foregroundStyle { - foregroundStyle._apply(to: &shape) - } else { - shape.result = .color(shape.environment.foregroundColor ?? .primary) - } - } - - public static func _apply(to shape: inout _ShapeStyle_ShapeType) {} -} - public struct FillStyle: Equatable { public var isEOFilled: Bool public var isAntialiased: Bool diff --git a/Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift b/Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift index 44bb28845..9b6f8ed14 100644 --- a/Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift +++ b/Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift @@ -1,8 +1,30 @@ +// Copyright 2020-2021 Tokamak contributors // -// File.swift -// +// 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/6/21. // -import Foundation +public struct ForegroundStyle: ShapeStyle { + public init() {} + + public func _apply(to shape: inout _ShapeStyle_Shape) { + if let foregroundStyle = shape.environment._foregroundStyle { + foregroundStyle._apply(to: &shape) + } else { + shape.result = .color(shape.environment.foregroundColor ?? .primary) + } + } + + public static func _apply(to shape: inout _ShapeStyle_ShapeType) {} +} From fc621b6012d5b170ef0dda6d60f115b41f321b0d Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Wed, 7 Jul 2021 08:17:00 -0400 Subject: [PATCH 05/18] Add Material --- .../Shapes/ShapeStyles/BackgroundStyle.swift | 73 ++++++++++++++++ .../Shapes/ShapeStyles/ForegroundStyle.swift | 45 ++++++++++ .../Shapes/ShapeStyles/Material.swift | 56 ++++++++++++ .../Shapes/ShapeStyles/ShapeStyle.swift | 50 +---------- Sources/TokamakDOM/Core.swift | 3 + Sources/TokamakDemo/ShapeStyleDemo.swift | 86 +++++++++++++++---- Sources/TokamakStaticHTML/Core.swift | 3 + .../Modifiers/ViewModifier.swift | 35 +++++++- 8 files changed, 285 insertions(+), 66 deletions(-) create mode 100644 Sources/TokamakCore/Shapes/ShapeStyles/BackgroundStyle.swift create mode 100644 Sources/TokamakCore/Shapes/ShapeStyles/Material.swift diff --git a/Sources/TokamakCore/Shapes/ShapeStyles/BackgroundStyle.swift b/Sources/TokamakCore/Shapes/ShapeStyles/BackgroundStyle.swift new file mode 100644 index 000000000..d10c3b68a --- /dev/null +++ b/Sources/TokamakCore/Shapes/ShapeStyles/BackgroundStyle.swift @@ -0,0 +1,73 @@ +// 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/6/21. +// + +public struct BackgroundStyle: ShapeStyle { + public init() {} + + public func _apply(to shape: inout _ShapeStyle_Shape) { + if let backgroundStyle = shape.environment._backgroundStyle { + backgroundStyle._apply(to: &shape) + } else { + shape.result = .none + } + } + + public static func _apply(to shape: inout _ShapeStyle_ShapeType) {} +} + +extension EnvironmentValues { + private struct BackgroundStyleKey: EnvironmentKey { + static let defaultValue: AnyShapeStyle? = nil + } + + public var _backgroundStyle: AnyShapeStyle? { + get { + self[BackgroundStyleKey.self] + } + set { + self[BackgroundStyleKey.self] = newValue + } + } +} + +public extension View { + @inlinable func background() -> some View { + modifier(_BackgroundStyleModifier(style: BackgroundStyle())) + } + + @inlinable func background(_ style: S) -> some View where S: ShapeStyle { + modifier(_BackgroundStyleModifier(style: style)) + } +} + +@frozen public struct _BackgroundStyleModifier