Skip to content

Commit

Permalink
Improve ShapeStyles to match iOS 15+ (#417)
Browse files Browse the repository at this point in the history
  • Loading branch information
carson-katri authored Jul 7, 2021
1 parent 79a9a66 commit 54146b8
Show file tree
Hide file tree
Showing 14 changed files with 445 additions and 20 deletions.
8 changes: 7 additions & 1 deletion NativeDemo/TokamakDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
207C05702610E16E00BBBE54 /* DatePickerDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207C056F2610E16E00BBBE54 /* DatePickerDemo.swift */; };
207C05712610E16E00BBBE54 /* DatePickerDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 207C056F2610E16E00BBBE54 /* DatePickerDemo.swift */; };
262DA7B32695D99500CABEAE /* ShapeStyleDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 262DA7B22695D99500CABEAE /* ShapeStyleDemo.swift */; };
262DA7B42695D99500CABEAE /* ShapeStyleDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 262DA7B22695D99500CABEAE /* ShapeStyleDemo.swift */; };
3DCDE44424CA6AD400910F17 /* SidebarDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCDE44324CA6AD400910F17 /* SidebarDemo.swift */; };
3DCDE44524CA6AD400910F17 /* SidebarDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCDE44324CA6AD400910F17 /* SidebarDemo.swift */; };
4550BD5225B642B80088F4EA /* ShadowDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4550BD5125B642B80088F4EA /* ShadowDemo.swift */; };
Expand Down Expand Up @@ -97,6 +99,7 @@

/* Begin PBXFileReference section */
207C056F2610E16E00BBBE54 /* DatePickerDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatePickerDemo.swift; sourceTree = "<group>"; };
262DA7B22695D99500CABEAE /* ShapeStyleDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShapeStyleDemo.swift; sourceTree = "<group>"; };
3DCDE44324CA6AD400910F17 /* SidebarDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarDemo.swift; sourceTree = "<group>"; };
4550BD5125B642B80088F4EA /* ShadowDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShadowDemo.swift; sourceTree = "<group>"; };
8500293E24D2FF3E001A2E84 /* SliderDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SliderDemo.swift; sourceTree = "<group>"; };
Expand All @@ -111,7 +114,7 @@
85ED189A24AD425E0085DFA0 /* SpacerDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpacerDemo.swift; sourceTree = "<group>"; };
85ED189B24AD425E0085DFA0 /* TextDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextDemo.swift; sourceTree = "<group>"; };
85ED189C24AD425E0085DFA0 /* ForEachDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForEachDemo.swift; sourceTree = "<group>"; };
85ED189D24AD425E0085DFA0 /* TokamakDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokamakDemo.swift; sourceTree = "<group>"; };
85ED189D24AD425E0085DFA0 /* TokamakDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = TokamakDemo.swift; sourceTree = "<group>"; tabWidth = 2; };
85ED189E24AD425E0085DFA0 /* Counter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Counter.swift; sourceTree = "<group>"; };
85ED189F24AD425E0085DFA0 /* TextFieldDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldDemo.swift; sourceTree = "<group>"; };
85ED18A024AD425E0085DFA0 /* EnvironmentDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentDemo.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -191,6 +194,7 @@
85ED189924AD425E0085DFA0 /* TokamakDemo */ = {
isa = PBXGroup;
children = (
262DA7B22695D99500CABEAE /* ShapeStyleDemo.swift */,
D120FDDA257E7145008FFBAD /* TextEditorDemo.swift */,
D1D6B62224D817350041E1D9 /* GeometryReaderDemo.swift */,
D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */,
Expand Down Expand Up @@ -382,6 +386,7 @@
B5C76E4A24C73ED5003EABB2 /* AppStorageDemo.swift in Sources */,
3DCDE44424CA6AD400910F17 /* SidebarDemo.swift in Sources */,
85ED18AD24AD425E0085DFA0 /* TextFieldDemo.swift in Sources */,
262DA7B32695D99500CABEAE /* ShapeStyleDemo.swift in Sources */,
85ED18A724AD425E0085DFA0 /* ForEachDemo.swift in Sources */,
D1C726F324CB63C6003B576D /* ButtonStyleDemo.swift in Sources */,
854A1A9124B3E3630027BC32 /* ToggleDemo.swift in Sources */,
Expand Down Expand Up @@ -415,6 +420,7 @@
B5C76E4B24C73ED5003EABB2 /* AppStorageDemo.swift in Sources */,
3DCDE44524CA6AD400910F17 /* SidebarDemo.swift in Sources */,
85ED18AC24AD425E0085DFA0 /* Counter.swift in Sources */,
262DA7B42695D99500CABEAE /* ShapeStyleDemo.swift in Sources */,
85ED18A824AD425E0085DFA0 /* ForEachDemo.swift in Sources */,
D1C726F424CB63C6003B576D /* ButtonStyleDemo.swift in Sources */,
854A1A9324B3F28F0027BC32 /* ToggleDemo.swift in Sources */,
Expand Down
27 changes: 27 additions & 0 deletions Sources/TokamakCore/Modifiers/StyleModifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,33 @@ public extension View {
) -> some View where Background: View {
modifier(_BackgroundModifier(background: background, alignment: alignment))
}

@inlinable
func background<V>(
alignment: Alignment = .center,
@ViewBuilder content: () -> V
) -> some View where V: View {
background(content(), alignment: alignment)
}

@inlinable
func background<S, T>(
_ style: S,
in shape: T,
fillStyle: FillStyle = FillStyle()
) -> some View where S: ShapeStyle, T: Shape {
background {
shape.fill(style, style: fillStyle)
}
}

@inlinable
func background<S>(
in shape: S,
fillStyle: FillStyle = FillStyle()
) -> some View where S: Shape {
background(ForegroundStyle(), in: shape, fillStyle: fillStyle)
}
}

public struct _OverlayModifier<Overlay>: ViewModifier, EnvironmentReader
Expand Down
2 changes: 2 additions & 0 deletions Sources/TokamakCore/Shapes/ModifiedShapes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public struct _StrokedShape<S>: Shape, DynamicProperty where S: Shape {
.path(in: rect)
.strokedPath(style)
}

public static var role: ShapeRole { .stroke }
}

public struct _TrimmedShape<S>: Shape where S: Shape {
Expand Down
18 changes: 12 additions & 6 deletions Sources/TokamakCore/Shapes/Shape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Rectangle, Self> {
var body: some View {
Expand All @@ -34,11 +44,7 @@ public protocol InsettableShape: Shape {
func inset(by amount: CGFloat) -> InsetShape
}

public struct ForegroundStyle: ShapeStyle {
public init() {}
}

public struct FillStyle: Equatable, ShapeStyle {
public struct FillStyle: Equatable {
public var isEOFilled: Bool
public var isAntialiased: Bool

Expand Down
30 changes: 30 additions & 0 deletions Sources/TokamakCore/Shapes/ShapeStyles/ForegroundStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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 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) {}
}
201 changes: 201 additions & 0 deletions Sources/TokamakCore/Shapes/ShapeStyles/ShapeStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// 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 ShapeStyle {
func _apply(to shape: inout _ShapeStyle_Shape)
static func _apply(to type: inout _ShapeStyle_ShapeType)
}

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: &copy)
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
result = .none
self.environment = environment
bounds = nil
self.role = role
inRecursiveStyle = false
}

public enum Operation {
case prepare(Text, level: Int)
case resolveStyle(levels: Range<Int>)
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

public 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: &copy)
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<S>(_ style: S) -> some View
where S: ShapeStyle
{
foregroundStyle(style, style, style)
}

@inlinable
func foregroundStyle<S1, S2>(_ primary: S1, _ secondary: S2) -> some View
where S1: ShapeStyle, S2: ShapeStyle
{
foregroundStyle(primary, secondary, secondary)
}

@inlinable
func foregroundStyle<S1, S2, S3>(_ 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)
}
}
2 changes: 1 addition & 1 deletion Sources/TokamakCore/Tokens/AnyTokenBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading

0 comments on commit 54146b8

Please sign in to comment.