From f14f14974e4cdd15ec243ac6bf6408078233958a Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 5 Aug 2020 20:12:36 +0100 Subject: [PATCH 01/27] Attempt `padding` modifier fusion --- Sources/TokamakCore/Modifiers/PaddingLayout.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sources/TokamakCore/Modifiers/PaddingLayout.swift b/Sources/TokamakCore/Modifiers/PaddingLayout.swift index 266d72662..e9059f45d 100644 --- a/Sources/TokamakCore/Modifiers/PaddingLayout.swift +++ b/Sources/TokamakCore/Modifiers/PaddingLayout.swift @@ -40,3 +40,15 @@ extension View { padding(.all, length) } } + +extension ModifiedContent where Modifier == _PaddingLayout, Content: View { + public func padding(_ length: CGFloat) -> some View { + var layout = modifier + layout.insets?.top += length + layout.insets?.leading += length + layout.insets?.bottom += length + layout.insets?.trailing += length + + return ModifiedContent(content: content, modifier: layout) + } +} From 0394e53821ad1b6b1dd91e7197716a42f794fb39 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sat, 7 Nov 2020 10:19:30 +0000 Subject: [PATCH 02/27] Fix linter warning --- Sources/TokamakStaticHTML/Views/Text/Text.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Sources/TokamakStaticHTML/Views/Text/Text.swift b/Sources/TokamakStaticHTML/Views/Text/Text.swift index d5be3c9c5..0083360ee 100644 --- a/Sources/TokamakStaticHTML/Views/Text/Text.swift +++ b/Sources/TokamakStaticHTML/Views/Text/Text.swift @@ -76,8 +76,8 @@ extension Font.Leading: CustomStringConvertible { } } -extension Font { - public func styles(in environment: EnvironmentValues) -> [String: String] { +public extension Font { + func styles(in environment: EnvironmentValues) -> [String: String] { let proxy = _FontProxy(self).resolve(in: environment) return [ "font-family": proxy._name == _FontNames.system.rawValue ? proxy._design.description : proxy @@ -170,8 +170,7 @@ extension Text { let hasStrikethrough = strikethrough?.0 ?? false let hasUnderline = underline?.0 ?? false - let textDecoration = !hasStrikethrough && !hasUnderline ? - "none" : + let textDecoration = !hasStrikethrough && !hasUnderline ? "none" : "\(hasStrikethrough ? "line-through" : "") \(hasUnderline ? "underline" : "")" let decorationColor = strikethrough?.1?.cssValue(environment) ?? underline?.1?.cssValue(environment) From c04615230415be54996e94b5c4bd586da968834f Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 16 Jun 2021 22:43:02 +0100 Subject: [PATCH 03/27] Add basic support for control snapshot tests --- Package.resolved | 9 ++++ Package.swift | 30 +++++++++-- Sources/TokamakDemo/TextDemo.swift | 4 +- Tests/TokamakStaticHTMLTests/HTMLTests.swift | 2 +- .../SnapshotTests.swift | 54 +++++++++++++++++++ 5 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 Tests/TokamakStaticHTMLTests/SnapshotTests.swift diff --git a/Package.resolved b/Package.resolved index f35c44869..8ecf3677d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -45,6 +45,15 @@ "revision": "8e0ef8bb7482ab97dcd2cd1d6855bd38921c345d", "version": "0.1.0" } + }, + { + "package": "SnapshotTesting", + "repositoryURL": "https://github.com/TokamakUI/swift-snapshot-testing.git", + "state": { + "branch": "swiftui-macos", + "revision": "58a8e1262a4ee54d45bf5cbbe33f2e4c911de6c6", + "version": null + } } ] }, diff --git a/Package.swift b/Package.swift index 2ea9dc3ac..45dabcf12 100644 --- a/Package.swift +++ b/Package.swift @@ -53,9 +53,24 @@ let package = Package( url: "https://github.com/swiftwasm/JavaScriptKit.git", .upToNextMinor(from: "0.10.0") ), - .package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.12.0"), - .package(url: "https://github.com/swiftwasm/OpenCombineJS.git", .upToNextMinor(from: "0.1.1")), - .package(name: "Benchmark", url: "https://github.com/google/swift-benchmark", from: "0.1.0"), + .package( + url: "https://github.com/OpenCombine/OpenCombine.git", + from: "0.12.0" + ), + .package( + url: "https://github.com/swiftwasm/OpenCombineJS.git", + .upToNextMinor(from: "0.1.1") + ), + .package( + name: "Benchmark", + url: "https://github.com/google/swift-benchmark", + from: "0.1.0" + ), + .package( + name: "SnapshotTesting", + url: "https://github.com/TokamakUI/swift-snapshot-testing.git", + .branch("swiftui-macos") + ), ], targets: [ // Targets are the basic building blocks of a package. A target can define @@ -180,7 +195,14 @@ let package = Package( ), .testTarget( name: "TokamakStaticHTMLTests", - dependencies: ["TokamakStaticHTML"] + dependencies: [ + "TokamakStaticHTML", + .product( + name: "SnapshotTesting", + package: "SnapshotTesting", + condition: .when(platforms: [.macOS]) + ), + ] ), ] ) diff --git a/Sources/TokamakDemo/TextDemo.swift b/Sources/TokamakDemo/TextDemo.swift index 2d1441371..cbbc513be 100644 --- a/Sources/TokamakDemo/TextDemo.swift +++ b/Sources/TokamakDemo/TextDemo.swift @@ -39,8 +39,8 @@ struct TextDemo: View { .heavy, .black, ], id: \.self) { weight in - Text("a") - .fontWeight(weight) + Text("a") + .fontWeight(weight) } } VStack { diff --git a/Tests/TokamakStaticHTMLTests/HTMLTests.swift b/Tests/TokamakStaticHTMLTests/HTMLTests.swift index 423d99a6b..3676cbadd 100644 --- a/Tests/TokamakStaticHTMLTests/HTMLTests.swift +++ b/Tests/TokamakStaticHTMLTests/HTMLTests.swift @@ -161,7 +161,7 @@ private let expectedHTML = """# -final class ReconcilerTests: XCTestCase { +final class HTMLTests: XCTestCase { struct Model { let text: Text } diff --git a/Tests/TokamakStaticHTMLTests/SnapshotTests.swift b/Tests/TokamakStaticHTMLTests/SnapshotTests.swift new file mode 100644 index 000000000..854336832 --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/SnapshotTests.swift @@ -0,0 +1,54 @@ +// Copyright 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 Max Desiatov on 13/06/2021. +// + +// SnapshotTesting with image snapshots are only supported on iOS. +#if !os(WASI) +import SnapshotTesting + +#if TOKAMAK_CONTROL_SNAPSHOTS +import SwiftUI +#else +import TokamakStaticHTML +#endif + +import XCTest + +struct HStackTest: View { + let spacing: CGFloat + var body: some View { + HStack(spacing: spacing) { + Rectangle() + .background(Color.red) + + Rectangle() + .background(Color.green) + + Rectangle() + .background(Color.blue) + }.frame(width: 300, height: 100) + } +} + +final class SnapshotTests: XCTestCase { + func testHStack() { + assertSnapshot(matching: HStackTest(spacing: 0), as: .image()) + assertSnapshot(matching: HStackTest(spacing: 10), as: .image()) + assertSnapshot(matching: HStackTest(spacing: 100), as: .image()) + } +} + +#endif From 39035cf2ae6af6250b22b83258ec72412283f4fa Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 16 Jun 2021 23:53:31 +0100 Subject: [PATCH 04/27] Use `CGFloat`, `CGPoint`, `CGRect` from Foundation --- .../Modifiers/Effects/ClipEffect.swift | 4 +- .../Modifiers/Effects/GeometryEffect.swift | 4 +- .../Modifiers/Effects/RotationEffect.swift | 6 +- .../Modifiers/FlexFrameLayout.swift | 4 +- .../TokamakCore/Modifiers/FrameLayout.swift | 4 +- .../TokamakCore/Modifiers/PaddingLayout.swift | 4 +- .../TokamakCore/Modifiers/ShadowLayout.swift | 16 ++++++ .../Modifiers/StyleModifiers.swift | 4 +- Sources/TokamakCore/PreviewProvider.swift | 4 +- Sources/TokamakCore/Shapes/Ellipse.swift | 4 +- .../TokamakCore/Shapes/FixedRoundedRect.swift | 4 +- .../TokamakCore/Shapes/ModifiedShapes.swift | 6 +- Sources/TokamakCore/Shapes/Path.swift | 11 +--- Sources/TokamakCore/Shapes/Rectangle.swift | 4 +- Sources/TokamakCore/Shapes/Shape.swift | 4 +- .../TokamakCore/Shapes/ShapeModifiers.swift | 4 +- Sources/TokamakCore/Shapes/StrokeStyle.swift | 4 +- Sources/TokamakCore/Shapes/TrimmedPath.swift | 4 +- Sources/TokamakCore/Stubs/CGStubs.swift | 57 ++----------------- Sources/TokamakCore/Tokens/Edge.swift | 4 +- Sources/TokamakCore/Tokens/Font.swift | 4 +- Sources/TokamakCore/Tokens/GridItem.swift | 4 +- Sources/TokamakCore/Tokens/UnitPoint.swift | 5 +- .../Views/Containers/Section.swift | 4 +- .../Views/Layout/GeometryReader.swift | 4 +- Sources/TokamakCore/Views/Layout/HStack.swift | 4 +- .../TokamakCore/Views/Layout/LazyHGrid.swift | 4 +- .../TokamakCore/Views/Layout/LazyVGrid.swift | 4 +- Sources/TokamakCore/Views/Layout/VStack.swift | 4 +- Sources/TokamakCore/Views/Layout/ZStack.swift | 4 +- .../TokamakCore/Views/Spacers/Spacer.swift | 4 +- Sources/TokamakCore/Views/Text/Text.swift | 4 +- Sources/TokamakDOM/Core.swift | 5 +- .../Views/Layout/GeometryReader.swift | 3 +- Sources/TokamakDemo/PathDemo.swift | 3 +- Sources/TokamakDemo/TextDemo.swift | 4 +- Sources/TokamakStaticHTML/Core.swift | 5 +- .../Modifiers/LayoutModifiers.swift | 3 +- Sources/TokamakStaticHTML/Shapes/Path.swift | 4 +- .../Views/Spacers/Spacer.swift | 3 +- .../TokamakStaticHTML/Views/Text/Text.swift | 3 +- 41 files changed, 130 insertions(+), 108 deletions(-) diff --git a/Sources/TokamakCore/Modifiers/Effects/ClipEffect.swift b/Sources/TokamakCore/Modifiers/Effects/ClipEffect.swift index 3e8848844..239a1ffbf 100644 --- a/Sources/TokamakCore/Modifiers/Effects/ClipEffect.swift +++ b/Sources/TokamakCore/Modifiers/Effects/ClipEffect.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 06/29/2020. // +import Foundation + public struct _ClipEffect: ViewModifier where ClipShape: Shape { public var shape: ClipShape public var style: FillStyle diff --git a/Sources/TokamakCore/Modifiers/Effects/GeometryEffect.swift b/Sources/TokamakCore/Modifiers/Effects/GeometryEffect.swift index f37893edc..bdd09731d 100644 --- a/Sources/TokamakCore/Modifiers/Effects/GeometryEffect.swift +++ b/Sources/TokamakCore/Modifiers/Effects/GeometryEffect.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 7/3/20. // +import Foundation + // FIXME: Make `Animatable` public protocol GeometryEffect: ViewModifier { func effectValue(size: CGSize) -> ProjectionTransform diff --git a/Sources/TokamakCore/Modifiers/Effects/RotationEffect.swift b/Sources/TokamakCore/Modifiers/Effects/RotationEffect.swift index 6bcfb317c..812b046b0 100644 --- a/Sources/TokamakCore/Modifiers/Effects/RotationEffect.swift +++ b/Sources/TokamakCore/Modifiers/Effects/RotationEffect.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 7/3/20. // +import Foundation + public struct _RotationEffect: GeometryEffect { public var angle: Angle public var anchor: UnitPoint @@ -25,7 +27,7 @@ public struct _RotationEffect: GeometryEffect { } public func effectValue(size: CGSize) -> ProjectionTransform { - .init(CGAffineTransform.identity.rotated(by: angle.radians)) + .init(CGAffineTransform.identity.rotated(by: CGFloat(angle.radians))) } public func body(content: Content) -> some View { diff --git a/Sources/TokamakCore/Modifiers/FlexFrameLayout.swift b/Sources/TokamakCore/Modifiers/FlexFrameLayout.swift index 24b6f62a8..7858e290c 100644 --- a/Sources/TokamakCore/Modifiers/FlexFrameLayout.swift +++ b/Sources/TokamakCore/Modifiers/FlexFrameLayout.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + public struct _FlexFrameLayout: ViewModifier { public let minWidth: CGFloat? public let idealWidth: CGFloat? diff --git a/Sources/TokamakCore/Modifiers/FrameLayout.swift b/Sources/TokamakCore/Modifiers/FrameLayout.swift index a786a2bf3..bbbc6ebb9 100644 --- a/Sources/TokamakCore/Modifiers/FrameLayout.swift +++ b/Sources/TokamakCore/Modifiers/FrameLayout.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + public struct _FrameLayout: ViewModifier { public let width: CGFloat? public let height: CGFloat? diff --git a/Sources/TokamakCore/Modifiers/PaddingLayout.swift b/Sources/TokamakCore/Modifiers/PaddingLayout.swift index 8bed5bdc3..3334d8c29 100644 --- a/Sources/TokamakCore/Modifiers/PaddingLayout.swift +++ b/Sources/TokamakCore/Modifiers/PaddingLayout.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + public struct _PaddingLayout: ViewModifier { public var edges: Edge.Set public var insets: EdgeInsets? diff --git a/Sources/TokamakCore/Modifiers/ShadowLayout.swift b/Sources/TokamakCore/Modifiers/ShadowLayout.swift index b1de3ea34..991da8ae8 100644 --- a/Sources/TokamakCore/Modifiers/ShadowLayout.swift +++ b/Sources/TokamakCore/Modifiers/ShadowLayout.swift @@ -1,3 +1,19 @@ +// Copyright 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 + public struct _ShadowLayout: ViewModifier, EnvironmentReader { public var color: Color public var radius: CGFloat diff --git a/Sources/TokamakCore/Modifiers/StyleModifiers.swift b/Sources/TokamakCore/Modifiers/StyleModifiers.swift index a7f734110..b9d49758b 100644 --- a/Sources/TokamakCore/Modifiers/StyleModifiers.swift +++ b/Sources/TokamakCore/Modifiers/StyleModifiers.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 6/29/20. // +import Foundation + public struct _BackgroundModifier: ViewModifier, EnvironmentReader where Background: View { diff --git a/Sources/TokamakCore/PreviewProvider.swift b/Sources/TokamakCore/PreviewProvider.swift index 758172282..171b4bb41 100644 --- a/Sources/TokamakCore/PreviewProvider.swift +++ b/Sources/TokamakCore/PreviewProvider.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + /// This protocol has no functionality currently, and is only provided for compatibility purposes. public protocol PreviewProvider { associatedtype Previews: View diff --git a/Sources/TokamakCore/Shapes/Ellipse.swift b/Sources/TokamakCore/Shapes/Ellipse.swift index ad61c20d7..4566b0816 100644 --- a/Sources/TokamakCore/Shapes/Ellipse.swift +++ b/Sources/TokamakCore/Shapes/Ellipse.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 6/29/20. // +import Foundation + public struct Ellipse: Shape { public func path(in rect: CGRect) -> Path { .init(storage: .ellipse(rect), sizing: .flexible) diff --git a/Sources/TokamakCore/Shapes/FixedRoundedRect.swift b/Sources/TokamakCore/Shapes/FixedRoundedRect.swift index 5e7b2c266..78f2f9d42 100644 --- a/Sources/TokamakCore/Shapes/FixedRoundedRect.swift +++ b/Sources/TokamakCore/Shapes/FixedRoundedRect.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 7/22/20. // +import Foundation + public struct FixedRoundedRect: Equatable { public let rect: CGRect public let cornerSize: CGSize? diff --git a/Sources/TokamakCore/Shapes/ModifiedShapes.swift b/Sources/TokamakCore/Shapes/ModifiedShapes.swift index 19317e987..485ac3283 100644 --- a/Sources/TokamakCore/Shapes/ModifiedShapes.swift +++ b/Sources/TokamakCore/Shapes/ModifiedShapes.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 06/28/2020. // +import Foundation + public struct _StrokedShape: Shape where S: Shape { @Environment(\.self) public var environment public var shape: S @@ -106,7 +108,7 @@ public struct RotatedShape: Shape where Content: Shape { public func path(in rect: CGRect) -> Path { shape .path(in: rect) - .applying(.init(rotationAngle: angle.radians)) + .applying(.init(rotationAngle: CGFloat(angle.radians))) } } diff --git a/Sources/TokamakCore/Shapes/Path.swift b/Sources/TokamakCore/Shapes/Path.swift index 20010b6e6..0a847af9e 100644 --- a/Sources/TokamakCore/Shapes/Path.swift +++ b/Sources/TokamakCore/Shapes/Path.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,13 +15,7 @@ // Created by Carson Katri on 06/28/2020. // -#if canImport(Darwin) -import Darwin -#elseif canImport(Glibc) -import Glibc -#elseif os(WASI) -import WASILibc -#endif +import Foundation /// The outline of a 2D shape. public struct Path: Equatable, LosslessStringConvertible { @@ -610,6 +604,7 @@ private func getArc( clockwise: clockwise ) } else { + let angle = CGFloat(angle) let endPoint = CGPoint( x: (radius * cos(angle)) + center.x, y: (radius * sin(angle)) + center.y diff --git a/Sources/TokamakCore/Shapes/Rectangle.swift b/Sources/TokamakCore/Shapes/Rectangle.swift index e849f929c..d7b74d168 100644 --- a/Sources/TokamakCore/Shapes/Rectangle.swift +++ b/Sources/TokamakCore/Shapes/Rectangle.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2020 Tokamak contributors +// Copyright 2018-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. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + public struct Rectangle: Shape { public func path(in rect: CGRect) -> Path { .init(storage: .rect(rect), sizing: .flexible) diff --git a/Sources/TokamakCore/Shapes/Shape.swift b/Sources/TokamakCore/Shapes/Shape.swift index 7b7bcb8e4..2b3c055c3 100644 --- a/Sources/TokamakCore/Shapes/Shape.swift +++ b/Sources/TokamakCore/Shapes/Shape.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 06/28/2020. // +import Foundation + public protocol Shape: View { func path(in rect: CGRect) -> Path } diff --git a/Sources/TokamakCore/Shapes/ShapeModifiers.swift b/Sources/TokamakCore/Shapes/ShapeModifiers.swift index 6a363a263..d5765e64a 100644 --- a/Sources/TokamakCore/Shapes/ShapeModifiers.swift +++ b/Sources/TokamakCore/Shapes/ShapeModifiers.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 6/29/20. // +import Foundation + public extension InsettableShape { func strokeBorder( _ content: S, diff --git a/Sources/TokamakCore/Shapes/StrokeStyle.swift b/Sources/TokamakCore/Shapes/StrokeStyle.swift index 236baeab6..d67a3c5e1 100644 --- a/Sources/TokamakCore/Shapes/StrokeStyle.swift +++ b/Sources/TokamakCore/Shapes/StrokeStyle.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 7/22/20. // +import Foundation + public struct StrokeStyle: Equatable { public var lineWidth: CGFloat public var lineCap: CGLineCap diff --git a/Sources/TokamakCore/Shapes/TrimmedPath.swift b/Sources/TokamakCore/Shapes/TrimmedPath.swift index fc74ffed7..1fcdb47c7 100644 --- a/Sources/TokamakCore/Shapes/TrimmedPath.swift +++ b/Sources/TokamakCore/Shapes/TrimmedPath.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 7/22/20. // +import Foundation + public struct TrimmedPath: Equatable { public let path: Path public let from: CGFloat diff --git a/Sources/TokamakCore/Stubs/CGStubs.swift b/Sources/TokamakCore/Stubs/CGStubs.swift index a67a28745..d84bb94a6 100644 --- a/Sources/TokamakCore/Stubs/CGStubs.swift +++ b/Sources/TokamakCore/Stubs/CGStubs.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,31 +15,12 @@ // Created by Max Desiatov on 08/04/2020. // -#if canImport(Glibc) -import Glibc -#elseif canImport(Darwin) -import Darwin -#elseif os(WASI) -import WASILibc -#endif - -public typealias CGFloat = Double -public struct CGPoint: Equatable { - public var x: CGFloat - public var y: CGFloat - - public init(x: CGFloat, y: CGFloat) { - self.x = x - self.y = y - } - - public static var zero: Self { - .init(x: 0, y: 0) - } +import Foundation +extension CGPoint { func rotate(_ angle: Angle, around origin: Self) -> Self { - let cosAngle = cos(angle.radians) - let sinAngle = sin(angle.radians) + let cosAngle = CGFloat(cos(angle.radians)) + let sinAngle = CGFloat(sin(angle.radians)) return .init( x: cosAngle * (x - origin.x) - sinAngle * (y - origin.y) + origin.x, y: sinAngle * (x - origin.x) + cosAngle * (y - origin.y) + origin.y @@ -54,34 +35,6 @@ public struct CGPoint: Equatable { } } -public struct CGSize: Equatable { - public var width: CGFloat - public var height: CGFloat - - public init(width: CGFloat, height: CGFloat) { - self.width = width - self.height = height - } - - public static var zero: Self { - .init(width: 0, height: 0) - } -} - -public struct CGRect: Equatable { - public var origin: CGPoint - public var size: CGSize - - public init(origin: CGPoint, size: CGSize) { - self.origin = origin - self.size = size - } - - public static var zero: Self { - .init(origin: .zero, size: .zero) - } -} - public enum CGLineCap { /// A line with a squared-off end. Extends to the endpoint of the Path. case butt diff --git a/Sources/TokamakCore/Tokens/Edge.swift b/Sources/TokamakCore/Tokens/Edge.swift index 9eb39ea64..09dadf73d 100644 --- a/Sources/TokamakCore/Tokens/Edge.swift +++ b/Sources/TokamakCore/Tokens/Edge.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2020 Tokamak contributors +// Copyright 2018-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. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + public enum Edge: Int8, CaseIterable { case top, leading, bottom, trailing diff --git a/Sources/TokamakCore/Tokens/Font.swift b/Sources/TokamakCore/Tokens/Font.swift index 3ea487f20..c0378b9be 100644 --- a/Sources/TokamakCore/Tokens/Font.swift +++ b/Sources/TokamakCore/Tokens/Font.swift @@ -1,4 +1,4 @@ -// Copyright 2018-2020 Tokamak contributors +// Copyright 2018-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. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + /// Override `TokamakCore`'s default `Font` resolvers with a Renderer-specific one. /// You can override a specific font box /// (such as `_SystemFontBox`, or all boxes with `AnyFontBox`). diff --git a/Sources/TokamakCore/Tokens/GridItem.swift b/Sources/TokamakCore/Tokens/GridItem.swift index 78b3440c8..51227d9d9 100644 --- a/Sources/TokamakCore/Tokens/GridItem.swift +++ b/Sources/TokamakCore/Tokens/GridItem.swift @@ -1,4 +1,4 @@ -// Copyright 2019-2020 Tokamak contributors +// Copyright 2019-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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 7/13/20. // +import Foundation + public struct GridItem { public enum Size { case fixed(CGFloat) diff --git a/Sources/TokamakCore/Tokens/UnitPoint.swift b/Sources/TokamakCore/Tokens/UnitPoint.swift index cbdc0f440..676390094 100644 --- a/Sources/TokamakCore/Tokens/UnitPoint.swift +++ b/Sources/TokamakCore/Tokens/UnitPoint.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -11,11 +11,12 @@ // 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 6/28/20. // +import Foundation + public struct UnitPoint: Hashable { public var x: CGFloat public var y: CGFloat diff --git a/Sources/TokamakCore/Views/Containers/Section.swift b/Sources/TokamakCore/Views/Containers/Section.swift index 4f5c2eba9..7d02dc620 100644 --- a/Sources/TokamakCore/Views/Containers/Section.swift +++ b/Sources/TokamakCore/Views/Containers/Section.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 7/5/20. // +import Foundation + protocol SectionView { func listRow(_ style: ListStyle) -> AnyView } diff --git a/Sources/TokamakCore/Views/Layout/GeometryReader.swift b/Sources/TokamakCore/Views/Layout/GeometryReader.swift index e3e04610c..35091bf38 100644 --- a/Sources/TokamakCore/Views/Layout/GeometryReader.swift +++ b/Sources/TokamakCore/Views/Layout/GeometryReader.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + public struct GeometryProxy { public let size: CGSize } diff --git a/Sources/TokamakCore/Views/Layout/HStack.swift b/Sources/TokamakCore/Views/Layout/HStack.swift index 2b68b0521..1978734fb 100644 --- a/Sources/TokamakCore/Views/Layout/HStack.swift +++ b/Sources/TokamakCore/Views/Layout/HStack.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Max Desiatov on 08/04/2020. // +import Foundation + /// An alignment position along the vertical axis. public enum VerticalAlignment: Equatable { case top diff --git a/Sources/TokamakCore/Views/Layout/LazyHGrid.swift b/Sources/TokamakCore/Views/Layout/LazyHGrid.swift index 72a7663f4..5291c05d5 100644 --- a/Sources/TokamakCore/Views/Layout/LazyHGrid.swift +++ b/Sources/TokamakCore/Views/Layout/LazyHGrid.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 7/13/20. // +import Foundation + public struct LazyHGrid: _PrimitiveView where Content: View { let rows: [GridItem] let alignment: VerticalAlignment diff --git a/Sources/TokamakCore/Views/Layout/LazyVGrid.swift b/Sources/TokamakCore/Views/Layout/LazyVGrid.swift index 0d286c6b4..2f9555af8 100644 --- a/Sources/TokamakCore/Views/Layout/LazyVGrid.swift +++ b/Sources/TokamakCore/Views/Layout/LazyVGrid.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 7/13/20. // +import Foundation + public struct LazyVGrid: _PrimitiveView where Content: View { let columns: [GridItem] let alignment: HorizontalAlignment diff --git a/Sources/TokamakCore/Views/Layout/VStack.swift b/Sources/TokamakCore/Views/Layout/VStack.swift index f26fcdaf3..ff61d3470 100644 --- a/Sources/TokamakCore/Views/Layout/VStack.swift +++ b/Sources/TokamakCore/Views/Layout/VStack.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + /// An alignment position along the horizontal axis. public enum HorizontalAlignment: Equatable { case leading diff --git a/Sources/TokamakCore/Views/Layout/ZStack.swift b/Sources/TokamakCore/Views/Layout/ZStack.swift index 4c63f134c..e12709fbc 100644 --- a/Sources/TokamakCore/Views/Layout/ZStack.swift +++ b/Sources/TokamakCore/Views/Layout/ZStack.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + /// An alignment in both axes. public struct Alignment: Equatable { public var horizontal: HorizontalAlignment diff --git a/Sources/TokamakCore/Views/Spacers/Spacer.swift b/Sources/TokamakCore/Views/Spacers/Spacer.swift index 1217ca245..2695d5d1c 100644 --- a/Sources/TokamakCore/Views/Spacers/Spacer.swift +++ b/Sources/TokamakCore/Views/Spacers/Spacer.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Carson Katri on 06/29/2020. // +import Foundation + /// A `View` that fills the major axis of its parent stack. /// /// HStack { diff --git a/Sources/TokamakCore/Views/Text/Text.swift b/Sources/TokamakCore/Views/Text/Text.swift index eb7ce8bbf..6ec6b2ae7 100644 --- a/Sources/TokamakCore/Views/Text/Text.swift +++ b/Sources/TokamakCore/Views/Text/Text.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -15,6 +15,8 @@ // Created by Max Desiatov on 08/04/2020. // +import Foundation + /// A view that displays one or more lines of read-only text. /// /// You can choose a font using the `font(_:)` view modifier. diff --git a/Sources/TokamakDOM/Core.swift b/Sources/TokamakDOM/Core.swift index 11f1682a3..6d24cfa3f 100644 --- a/Sources/TokamakDOM/Core.swift +++ b/Sources/TokamakDOM/Core.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -87,9 +87,6 @@ public typealias Font = TokamakCore.Font public typealias Angle = TokamakCore.Angle public typealias CGAffineTransform = TokamakCore.CGAffineTransform -public typealias CGPoint = TokamakCore.CGPoint -public typealias CGRect = TokamakCore.CGRect -public typealias CGSize = TokamakCore.CGSize public typealias UnitPoint = TokamakCore.UnitPoint public typealias Edge = TokamakCore.Edge diff --git a/Sources/TokamakDOM/Views/Layout/GeometryReader.swift b/Sources/TokamakDOM/Views/Layout/GeometryReader.swift index f78cf6f96..c6b9b1aa8 100644 --- a/Sources/TokamakDOM/Views/Layout/GeometryReader.swift +++ b/Sources/TokamakDOM/Views/Layout/GeometryReader.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation import JavaScriptKit @_spi(TokamakCore) import TokamakCore import TokamakStaticHTML diff --git a/Sources/TokamakDemo/PathDemo.swift b/Sources/TokamakDemo/PathDemo.swift index cc35f08fb..4e633aaa9 100644 --- a/Sources/TokamakDemo/PathDemo.swift +++ b/Sources/TokamakDemo/PathDemo.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation import TokamakShim struct Star: Shape { diff --git a/Sources/TokamakDemo/TextDemo.swift b/Sources/TokamakDemo/TextDemo.swift index 2d1441371..cbbc513be 100644 --- a/Sources/TokamakDemo/TextDemo.swift +++ b/Sources/TokamakDemo/TextDemo.swift @@ -39,8 +39,8 @@ struct TextDemo: View { .heavy, .black, ], id: \.self) { weight in - Text("a") - .fontWeight(weight) + Text("a") + .fontWeight(weight) } } VStack { diff --git a/Sources/TokamakStaticHTML/Core.swift b/Sources/TokamakStaticHTML/Core.swift index 7a90b2a9e..13eb99a96 100644 --- a/Sources/TokamakStaticHTML/Core.swift +++ b/Sources/TokamakStaticHTML/Core.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -49,9 +49,6 @@ public typealias Color = TokamakCore.Color public typealias Font = TokamakCore.Font public typealias CGAffineTransform = TokamakCore.CGAffineTransform -public typealias CGPoint = TokamakCore.CGPoint -public typealias CGRect = TokamakCore.CGRect -public typealias CGSize = TokamakCore.CGSize // MARK: Views diff --git a/Sources/TokamakStaticHTML/Modifiers/LayoutModifiers.swift b/Sources/TokamakStaticHTML/Modifiers/LayoutModifiers.swift index 527d24cf8..7e385c217 100644 --- a/Sources/TokamakStaticHTML/Modifiers/LayoutModifiers.swift +++ b/Sources/TokamakStaticHTML/Modifiers/LayoutModifiers.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation import TokamakCore private extension DOMViewModifier { diff --git a/Sources/TokamakStaticHTML/Shapes/Path.swift b/Sources/TokamakStaticHTML/Shapes/Path.swift index 8e69b0432..470ddffc7 100644 --- a/Sources/TokamakStaticHTML/Shapes/Path.swift +++ b/Sources/TokamakStaticHTML/Shapes/Path.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -14,6 +14,8 @@ // // Created by Carson Katri on 6/29/20. // + +import Foundation import TokamakCore extension StrokeStyle { diff --git a/Sources/TokamakStaticHTML/Views/Spacers/Spacer.swift b/Sources/TokamakStaticHTML/Views/Spacers/Spacer.swift index daeafa227..ce3f1d6aa 100644 --- a/Sources/TokamakStaticHTML/Views/Spacers/Spacer.swift +++ b/Sources/TokamakStaticHTML/Views/Spacers/Spacer.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation import TokamakCore public enum SpacerContainerAxis { diff --git a/Sources/TokamakStaticHTML/Views/Text/Text.swift b/Sources/TokamakStaticHTML/Views/Text/Text.swift index e0f186975..93283ec75 100644 --- a/Sources/TokamakStaticHTML/Views/Text/Text.swift +++ b/Sources/TokamakStaticHTML/Views/Text/Text.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Tokamak contributors +// 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. @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation import TokamakCore extension Font.Design: CustomStringConvertible { From 0163117a4d972d3217d2bcadde5a24b901415e93 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 17 Jun 2021 11:05:15 +0100 Subject: [PATCH 05/27] Fix GTK build --- Sources/TokamakCore/Stubs/CGStubs.swift | 21 ++++++++++------ Sources/TokamakGTK/Core.swift | 5 ++-- Sources/TokamakGTK/Shapes/Shape.swift | 33 +++++++++++++++++++------ Sources/TokamakGTK/Views/Stack.swift | 2 +- 4 files changed, 41 insertions(+), 20 deletions(-) diff --git a/Sources/TokamakCore/Stubs/CGStubs.swift b/Sources/TokamakCore/Stubs/CGStubs.swift index d84bb94a6..a62edf452 100644 --- a/Sources/TokamakCore/Stubs/CGStubs.swift +++ b/Sources/TokamakCore/Stubs/CGStubs.swift @@ -35,6 +35,17 @@ extension CGPoint { } } +public extension CGAffineTransform { + /// Transform the point into the transform's coordinate system. + func transform(point: CGPoint) -> CGPoint { + CGPoint( + x: (a * point.x) + (c * point.y) + tx, + y: (b * point.x) + (d * point.y) + ty + ) + } +} + +#if !canImport(CoreGraphics) public enum CGLineCap { /// A line with a squared-off end. Extends to the endpoint of the Path. case butt @@ -127,14 +138,6 @@ public struct CGAffineTransform: Equatable { ) } - /// Transform the point into the transform's coordinate system. - public func transform(point: CGPoint) -> CGPoint { - CGPoint( - x: (a * point.x) + (c * point.y) + tx, - y: (b * point.x) + (d * point.y) + ty - ) - } - /// Returns an affine transformation matrix constructed by combining two existing affine /// transforms. /// - Parameters: @@ -203,3 +206,5 @@ public struct CGAffineTransform: Equatable { self == Self.identity } } + +#endif diff --git a/Sources/TokamakGTK/Core.swift b/Sources/TokamakGTK/Core.swift index fba29544e..e1992bab5 100644 --- a/Sources/TokamakGTK/Core.swift +++ b/Sources/TokamakGTK/Core.swift @@ -78,10 +78,9 @@ public typealias RoundedRectangle = TokamakCore.RoundedRectangle public typealias Color = TokamakCore.Color public typealias Font = TokamakCore.Font +#if !canImport(CoreGraphics) public typealias CGAffineTransform = TokamakCore.CGAffineTransform -public typealias CGPoint = TokamakCore.CGPoint -public typealias CGRect = TokamakCore.CGRect -public typealias CGSize = TokamakCore.CGSize +#endif // MARK: Views diff --git a/Sources/TokamakGTK/Shapes/Shape.swift b/Sources/TokamakGTK/Shapes/Shape.swift index ae00a5bf4..4b8708047 100644 --- a/Sources/TokamakGTK/Shapes/Shape.swift +++ b/Sources/TokamakGTK/Shapes/Shape.swift @@ -17,6 +17,7 @@ import CGDK import CGTK +import Foundation import TokamakCore func createPath(from elements: [Path.Element], in cr: OpaquePointer) { @@ -25,12 +26,12 @@ func createPath(from elements: [Path.Element], in cr: OpaquePointer) { for element in elements { switch element { case let .move(to: p): - cairo_move_to(cr, p.x, p.y) + cairo_move_to(cr, Double(p.x), Double(p.y)) current = p start = p case let .line(to: p): - cairo_line_to(cr, p.x, p.y) + cairo_line_to(cr, Double(p.x), Double(p.y)) current = p case .closeSubpath: @@ -38,13 +39,29 @@ func createPath(from elements: [Path.Element], in cr: OpaquePointer) { current = start case let .curve(to: p, control1: c1, control2: c2): - cairo_curve_to(cr, c1.x, c1.y, c2.x, c2.y, p.x, p.y) + cairo_curve_to( + cr, + Double(c1.x), + Double(c1.y), + Double(c2.x), + Double(c2.y), + Double(p.x), + Double(p.y) + ) current = p case let .quadCurve(to: p, control: c): let c1 = CGPoint(x: (current.x + 2 * c.x) / 3, y: (current.y + 2 * c.y) / 3) let c2 = CGPoint(x: (p.x + 2 * c.x) / 3, y: (p.y + 2 * c.y) / 3) - cairo_curve_to(cr, c1.x, c1.y, c2.x, c2.y, p.x, p.y) + cairo_curve_to( + cr, + Double(c1.x), + Double(c1.y), + Double(c2.x), + Double(c2.y), + Double(p.x), + Double(p.y) + ) current = p } } @@ -87,12 +104,12 @@ extension _ShapeView: GTKPrimitive { stroke = true let style = strokedPath.style - cairo_set_line_width(cr, style.lineWidth) + cairo_set_line_width(cr, Double(style.lineWidth)) cairo_set_line_join(cr, style.lineJoin.cairo) cairo_set_line_cap(cr, style.lineCap.cairo) - cairo_set_miter_limit(cr, style.miterLimit) - cairo_set_dash(cr, style.dash, Int32(style.dash.count), style.dashPhase) - + cairo_set_miter_limit(cr, Double(style.miterLimit)) + let dash = style.dash.map(Double.init) + cairo_set_dash(cr, dash, Int32(dash.count), Double(style.dashPhase)) } else { elements = path.elements stroke = false diff --git a/Sources/TokamakGTK/Views/Stack.swift b/Sources/TokamakGTK/Views/Stack.swift index b9b3f9d4b..176c55b5a 100644 --- a/Sources/TokamakGTK/Views/Stack.swift +++ b/Sources/TokamakGTK/Views/Stack.swift @@ -26,7 +26,7 @@ protocol StackProtocol { struct Box: View, ParentView, AnyWidget, StackProtocol { let content: Content let orientation: GtkOrientation - let spacing: TokamakCore.CGFloat + let spacing: CGFloat let alignment: Alignment let expand = true From 6ad4edcca31ad77675c057171e93b201f764510f Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 17 Jun 2021 11:10:24 +0100 Subject: [PATCH 06/27] Fix macOS build --- Sources/TokamakDOM/Core.swift | 5 ++++- Sources/TokamakStaticHTML/Core.swift | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/TokamakDOM/Core.swift b/Sources/TokamakDOM/Core.swift index 6d24cfa3f..02332d5e0 100644 --- a/Sources/TokamakDOM/Core.swift +++ b/Sources/TokamakDOM/Core.swift @@ -85,8 +85,11 @@ public typealias RoundedRectangle = TokamakCore.RoundedRectangle public typealias Color = TokamakCore.Color public typealias Font = TokamakCore.Font -public typealias Angle = TokamakCore.Angle +#if !canImport(CoreGraphics) public typealias CGAffineTransform = TokamakCore.CGAffineTransform +#endif + +public typealias Angle = TokamakCore.Angle public typealias UnitPoint = TokamakCore.UnitPoint public typealias Edge = TokamakCore.Edge diff --git a/Sources/TokamakStaticHTML/Core.swift b/Sources/TokamakStaticHTML/Core.swift index 13eb99a96..2b8d56348 100644 --- a/Sources/TokamakStaticHTML/Core.swift +++ b/Sources/TokamakStaticHTML/Core.swift @@ -48,7 +48,9 @@ public typealias RoundedRectangle = TokamakCore.RoundedRectangle public typealias Color = TokamakCore.Color public typealias Font = TokamakCore.Font +#if !canImport(CoreGraphics) public typealias CGAffineTransform = TokamakCore.CGAffineTransform +#endif // MARK: Views From e7d7651d6f2a8e48190f011ce270a77c76c3bb44 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sat, 19 Jun 2021 13:13:23 +0100 Subject: [PATCH 07/27] Implement snapshot tests with headless MS Edge --- Package.resolved | 8 +- Package.swift | 4 +- .../SnapshotTests.swift | 73 +++++++++++++----- .../SnapshotTests/testVGrid.1.png | Bin 0 -> 15484 bytes 4 files changed, 58 insertions(+), 27 deletions(-) create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testVGrid.1.png diff --git a/Package.resolved b/Package.resolved index 8ecf3677d..e17176037 100644 --- a/Package.resolved +++ b/Package.resolved @@ -48,11 +48,11 @@ }, { "package": "SnapshotTesting", - "repositoryURL": "https://github.com/TokamakUI/swift-snapshot-testing.git", + "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing.git", "state": { - "branch": "swiftui-macos", - "revision": "58a8e1262a4ee54d45bf5cbbe33f2e4c911de6c6", - "version": null + "branch": null, + "revision": "f8a9c997c3c1dab4e216a8ec9014e23144cbab37", + "version": "1.9.0" } } ] diff --git a/Package.swift b/Package.swift index 45dabcf12..b3e257119 100644 --- a/Package.swift +++ b/Package.swift @@ -68,8 +68,8 @@ let package = Package( ), .package( name: "SnapshotTesting", - url: "https://github.com/TokamakUI/swift-snapshot-testing.git", - .branch("swiftui-macos") + url: "https://github.com/pointfreeco/swift-snapshot-testing.git", + from: "1.9.0" ), ], targets: [ diff --git a/Tests/TokamakStaticHTMLTests/SnapshotTests.swift b/Tests/TokamakStaticHTMLTests/SnapshotTests.swift index 854336832..ec96281ae 100644 --- a/Tests/TokamakStaticHTMLTests/SnapshotTests.swift +++ b/Tests/TokamakStaticHTMLTests/SnapshotTests.swift @@ -16,38 +16,69 @@ // // SnapshotTesting with image snapshots are only supported on iOS. -#if !os(WASI) +#if os(macOS) import SnapshotTesting - -#if TOKAMAK_CONTROL_SNAPSHOTS -import SwiftUI -#else import TokamakStaticHTML -#endif - import XCTest -struct HStackTest: View { - let spacing: CGFloat - var body: some View { - HStack(spacing: spacing) { - Rectangle() - .background(Color.red) +// Needed for `NSImage`, but would be great to make this truly cross-platform. +import class AppKit.NSImage + +public extension Snapshotting where Value: View, Format == NSImage { + static var image: Snapshotting { .image() } + + /// A snapshot strategy for comparing Tokamak Views based on pixel equality. + static func image(precision: Float = 1, size: CGSize? = nil) -> Snapshotting { + SimplySnapshotting.image(precision: precision).asyncPullback { view in + Async { callback in + let html = Data(StaticHTMLRenderer(view).render().utf8) + let cwd = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + let renderedPath = cwd.appendingPathComponent("rendered.html") - Rectangle() - .background(Color.green) + // swiftlint:disable:next force_try + try! html.write(to: renderedPath) + let browser = Process() + browser + .launchPath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" - Rectangle() - .background(Color.blue) + var arguments = ["--headless", "--disable-gpu", "--screenshot", renderedPath.path] + if let size = size { + arguments.append("--window-size=\(Int(size.width)),\(Int(size.height))") + } + + browser.arguments = arguments + browser.terminationHandler = { _ in + callback(NSImage( + contentsOfFile: cwd.appendingPathComponent("screenshot.png") + .path + )!) + } + browser.launch() + } + } + } +} + +struct HStackTest: View { + var body: some View { + VStack { + Text("Adaptive LazyVGrid") + LazyVGrid(columns: [ + GridItem(.adaptive(minimum: 50)), + ]) { + ForEach(0..<10) { + Text("\($0 + 1)") + .padding() + .background(Color.red) + } + } }.frame(width: 300, height: 100) } } final class SnapshotTests: XCTestCase { - func testHStack() { - assertSnapshot(matching: HStackTest(spacing: 0), as: .image()) - assertSnapshot(matching: HStackTest(spacing: 10), as: .image()) - assertSnapshot(matching: HStackTest(spacing: 100), as: .image()) + func testVGrid() { + assertSnapshot(matching: HStackTest(), as: .image(size: .init(width: 300, height: 100))) } } diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testVGrid.1.png b/Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testVGrid.1.png new file mode 100644 index 0000000000000000000000000000000000000000..43f933449853fdf3c71e0de7d5cdb3566b5d37df GIT binary patch literal 15484 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 Date: Sat, 19 Jun 2021 13:19:41 +0100 Subject: [PATCH 08/27] Increase snapshot tests timeout --- Tests/TokamakStaticHTMLTests/SnapshotTests.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/TokamakStaticHTMLTests/SnapshotTests.swift b/Tests/TokamakStaticHTMLTests/SnapshotTests.swift index ec96281ae..42fe84f78 100644 --- a/Tests/TokamakStaticHTMLTests/SnapshotTests.swift +++ b/Tests/TokamakStaticHTMLTests/SnapshotTests.swift @@ -78,7 +78,11 @@ struct HStackTest: View { final class SnapshotTests: XCTestCase { func testVGrid() { - assertSnapshot(matching: HStackTest(), as: .image(size: .init(width: 300, height: 100))) + assertSnapshot( + matching: HStackTest(), + as: .image(size: .init(width: 300, height: 100)), + timeout: 10 + ) } } From c8891922fc9aa82566551b2a904faf950baa299e Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sat, 19 Jun 2021 16:10:42 +0100 Subject: [PATCH 09/27] Force 1.0 resolution scale for headless Edge --- .../SnapshotTests.swift | 8 +++++++- .../SnapshotTests/testVGrid.1.png | Bin 15484 -> 7207 bytes 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Tests/TokamakStaticHTMLTests/SnapshotTests.swift b/Tests/TokamakStaticHTMLTests/SnapshotTests.swift index 42fe84f78..19fd6ab0e 100644 --- a/Tests/TokamakStaticHTMLTests/SnapshotTests.swift +++ b/Tests/TokamakStaticHTMLTests/SnapshotTests.swift @@ -41,7 +41,13 @@ public extension Snapshotting where Value: View, Format == NSImage { browser .launchPath = "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" - var arguments = ["--headless", "--disable-gpu", "--screenshot", renderedPath.path] + var arguments = [ + "--headless", + "--disable-gpu", + "--force-device-scale-factor=1.0", + "--screenshot", + renderedPath.path, + ] if let size = size { arguments.append("--window-size=\(Int(size.width)),\(Int(size.height))") } diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testVGrid.1.png b/Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testVGrid.1.png index 43f933449853fdf3c71e0de7d5cdb3566b5d37df..9ac4d7cfbc6ec8ef560e9d7eb59f68b070874767 100644 GIT binary patch literal 7207 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 literal 15484 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 Date: Sat, 19 Jun 2021 16:21:08 +0100 Subject: [PATCH 10/27] Avoid complex layouts in the snapshot test --- .../SnapshotTests.swift | 30 ++++++++---------- .../SnapshotTests/testPath.1.png | Bin 0 -> 2498 bytes .../SnapshotTests/testVGrid.1.png | Bin 7207 -> 0 bytes 3 files changed, 13 insertions(+), 17 deletions(-) create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testPath.1.png delete mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testVGrid.1.png diff --git a/Tests/TokamakStaticHTMLTests/SnapshotTests.swift b/Tests/TokamakStaticHTMLTests/SnapshotTests.swift index 19fd6ab0e..a3d00dfb7 100644 --- a/Tests/TokamakStaticHTMLTests/SnapshotTests.swift +++ b/Tests/TokamakStaticHTMLTests/SnapshotTests.swift @@ -65,28 +65,24 @@ public extension Snapshotting where Value: View, Format == NSImage { } } -struct HStackTest: View { - var body: some View { - VStack { - Text("Adaptive LazyVGrid") - LazyVGrid(columns: [ - GridItem(.adaptive(minimum: 50)), - ]) { - ForEach(0..<10) { - Text("\($0 + 1)") - .padding() - .background(Color.red) - } - } - }.frame(width: 300, height: 100) +struct Star: Shape { + func path(in rect: CGRect) -> Path { + Path { path in + path.move(to: .init(x: 40, y: 0)) + path.addLine(to: .init(x: 20, y: 76)) + path.addLine(to: .init(x: 80, y: 30.4)) + path.addLine(to: .init(x: 0, y: 30.4)) + path.addLine(to: .init(x: 64, y: 76)) + path.addLine(to: .init(x: 40, y: 0)) + } } } final class SnapshotTests: XCTestCase { - func testVGrid() { + func testPath() { assertSnapshot( - matching: HStackTest(), - as: .image(size: .init(width: 300, height: 100)), + matching: Star().fill(Color(red: 1, green: 0.75, blue: 0.1, opacity: 1)), + as: .image(size: .init(width: 100, height: 100)), timeout: 10 ) } diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testPath.1.png b/Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testPath.1.png new file mode 100644 index 0000000000000000000000000000000000000000..e8eb1fd5f337effd6c05d5d10a24e1cf77af82f7 GIT binary patch literal 2498 zcmV;z2|f0SP)4Tx02q~(kUdKSK@f&tgD7f>NFp{C2R2vuafz4@#3FZUP%J{C5bZ9P;|%6w zxvZ#l8h?bpz)DLIQd!to31VfJUJz{KyE){pb(YzA_T6E2c7PacAzHBn(C$dTQZlvL z$*DFy#1xW9AO+oadZJh^AAchY&QZl8e~|ThguMv2TNh5)H{x#RxXM_gFQJ4kmxmTytfPu^ zG+~nr?4p1?HW-UsoAfj?R0_!QWWa)n9QPddsN{K<#?F|TA>Avf9<;jR+HZPiQY&&w zuD0KC)>gEPkx8=w&OB%KY{JRXf%2XwSxihkOgKRLrvcT=HtN2tFV20EKSm5 zt_gfE@Vj?FatO@zsV=JPU)X9^4QN}^jQ{`uIAvH#W=%~1DgXcg2mk?xX#fNO00031 z000^Q000000-yo_1ONa40RR91WS|281ONa40RR91WB>pF01R{ddjJ3j=t)FDRCodH zoNH_pRTRh1ymq!N6eu8KfkIIb5aku+i^ga$8e>8<8cmF^L`~Ghh$hA;0Sl7g2jc?_ z21H|GV*Fx^@q>wv7>!TxiM)b&d$BJ%p}e3%-qMh z|6lK&nLX!>V&ew!CX52AFqXuI6cvbp2=HmB z2xd+^Lt-F;DIl0R@eGN9gs*@-cm(W$1K~H!p~Vp~LrC^VD5SpRAYb@hju0T*fuz0* zUmXrDjtC%DW+zyuj)9%)<{)9XT}ltc>fa4^Ay-s|q`wGP9S*HY4}@fXF3U#J-&_9VL>pm3_ZFXI=vf!GDGdiGW5ZbKn0_uZD%K&-w$z_tn%KmbVko2q@; zcBBSUdHryNw=%ns%Am6=-KX3@Dy|=n^0q~QWaWJ(krxOV0K4z7!=dVvq`t1&muW{@ zAf#s-oG+w|q<;mliA)^Iw#o}6d-nOGJetxMo8*D?od5jr@*?d^3&hI&UQt)i=8aRor-GfYJMJXg55Wi31g_K-A5($B9_w~;# zn`>7(AkMsdU$}fN;vl zM{br+d0e~F0C98IvY>Fv$l}_S21wBBhXX_w*RDK3T->#e*esv2xOR1x!O$`!^*LDm zl_58*HNz=q^&YCbq5RtzqY3X#cBFI3{=86O$?- z7Sbjz2J^BP0JJ1itL#8Z&C$^Rq224j&JQqkaG@4bR1J)o4}yO29lj*+4TQFC=d&=h zXBCi4^wZzh=r|Qt%~nXvTLWs#EZ-Ic0^;;iIQ}8z_rHhHgK|cAIO7QOQ)`o(ksNb+|-Lh{TF$fs}?+IPnSO_ie@$j(jA84BCk?7qEEC@Gy|8Y6`+m3J~Uy2HYAQuU28^BUc|pAf{rC{J#FIELcpK{5C=#} zPDO7GENswUEzRuW!HL%8}z&=S-V03VacuyD!5lY zB1qP*P(WC+YrTXGdR~;QU7>)mX4eK4-K(AmS-V03Va=}f5;o|05wdoL0pcgSHmKlU z^+d?p6$S`lcsqlapkbWnowX|v5P#XVVTkY621$9pw!^JnSjU6`VrBlSYfQ$ByP;|6 z`#^o(^=#Iuqxd5yzo0)MfCSC1rNS$T7Dz658H_3SgJQISIe!BrX5I%h*g5D%IKFH^ z2p~bTqv&i%F4_X>==oLJ({N66;p?C_jjuY+y2CSTR|p{fvuhQM{TFas;$#;iQ4Qt97h{_4bGOWpp9eC*Wi`4%N+>o^~0EQD%oRwjmm%xz%kcSPQ{%#M zFtWtxd=hUE*5WeQ>adFqu2oQ7bvv_y3B9d?%b}2Mb$CXe+Ax-K8H(`5rytG`%sco@gHLgrmEeB<&kWiO5V8C$=}TA&cD+w;3X~Ec~pCf$-DbwdWX!t1SEu#z6RK@7i+=#8no4 z2V)@ow0G?}2I49Uzk@Lle%ibC90PHch2Oy#2tVyzdyav)%EIs9|7Cx^LA4hMSO5S3 M07*qoM6N<$g2)k|5&!@I literal 0 HcmV?d00001 diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testVGrid.1.png b/Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testVGrid.1.png deleted file mode 100644 index 9ac4d7cfbc6ec8ef560e9d7eb59f68b070874767..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7207 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 From 8ea1695f5a7a3ab2a1913cff106f94a0c25991bc Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 17:21:45 +0100 Subject: [PATCH 11/27] Exclude dir from target sources, upload failures --- .github/workflows/ci.yml | 11 +++++++++-- Package.swift | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12ab1895d..4d02d55a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: shell: bash run: | set -ex - sudo xcode-select --switch /Applications/Xcode_12.3.app/Contents/Developer/ + sudo xcode-select --switch /Applications/Xcode_12.4.app/Contents/Developer/ # avoid building unrelated products for testing by specifying the test product explicitly swift build --product TokamakPackageTests `xcrun --find xctest` .build/debug/TokamakPackageTests.xctest @@ -55,6 +55,13 @@ jobs: ./benchmark.sh + - name: Upload failed snapshots + uses: actions/upload-artifact@v2 + if: ${{ failure() }} + with: + name: Failed snapshots + path: /var/folders/**/SnapshotTests + gtk_macos_build: runs-on: macos-latest @@ -64,7 +71,7 @@ jobs: shell: bash run: | set -ex - sudo xcode-select --switch /Applications/Xcode_12.3.app/Contents/Developer/ + sudo xcode-select --switch /Applications/Xcode_12.4.app/Contents/Developer/ brew install gtk+3 diff --git a/Package.swift b/Package.swift index b3e257119..bacee4972 100644 --- a/Package.swift +++ b/Package.swift @@ -202,7 +202,8 @@ let package = Package( package: "SnapshotTesting", condition: .when(platforms: [.macOS]) ), - ] + ], + exclude: ["__Snapshots__"] ), ] ) From 68d0aa5dd3dd200a0b1aa906399aa0707493c9aa Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 18:01:49 +0100 Subject: [PATCH 12/27] Add a test to verify that fusion works --- Package.resolved | 17 +- Package.swift | 14 +- .../TokamakCore/Modifiers/PaddingLayout.swift | 12 +- .../Reflection/Pointers/MetadataOffset.swift | 2 +- Tests/TokamakStaticHTMLTests/HTMLTests.swift | 153 ++---------------- .../HTMLTests/testOptional.1.txt | 139 ++++++++++++++++ .../HTMLTests/testPaddingFusion.1.txt | 136 ++++++++++++++++ 7 files changed, 318 insertions(+), 155 deletions(-) create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testOptional.1.txt create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt diff --git a/Package.resolved b/Package.resolved index f35c44869..cab685ee7 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/swiftwasm/JavaScriptKit.git", "state": { "branch": null, - "revision": "ebd9ca04215397f0e3cb72d6e96406a980a424e5", - "version": "0.10.0" + "revision": "b19e7c8b10a2750ed47753e31ed13613171f3294", + "version": "0.10.1" } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/apple/swift-argument-parser", "state": { "branch": null, - "revision": "9564d61b08a5335ae0a36f789a7d71493eacadfc", - "version": "0.3.2" + "revision": "986d191f94cec88f6350056da59c2e59e83d1229", + "version": "0.4.3" } }, { @@ -45,6 +45,15 @@ "revision": "8e0ef8bb7482ab97dcd2cd1d6855bd38921c345d", "version": "0.1.0" } + }, + { + "package": "SnapshotTesting", + "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing.git", + "state": { + "branch": null, + "revision": "f8a9c997c3c1dab4e216a8ec9014e23144cbab37", + "version": "1.9.0" + } } ] }, diff --git a/Package.swift b/Package.swift index 2ea9dc3ac..52508a129 100644 --- a/Package.swift +++ b/Package.swift @@ -56,6 +56,11 @@ let package = Package( .package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.12.0"), .package(url: "https://github.com/swiftwasm/OpenCombineJS.git", .upToNextMinor(from: "0.1.1")), .package(name: "Benchmark", url: "https://github.com/google/swift-benchmark", from: "0.1.0"), + .package( + name: "SnapshotTesting", + url: "https://github.com/pointfreeco/swift-snapshot-testing.git", + from: "1.9.0" + ), ], targets: [ // Targets are the basic building blocks of a package. A target can define @@ -180,7 +185,14 @@ let package = Package( ), .testTarget( name: "TokamakStaticHTMLTests", - dependencies: ["TokamakStaticHTML"] + dependencies: [ + "TokamakStaticHTML", + .product( + name: "SnapshotTesting", + package: "SnapshotTesting" + ), + ], + exclude: ["__Snapshots__"] ), ] ) diff --git a/Sources/TokamakCore/Modifiers/PaddingLayout.swift b/Sources/TokamakCore/Modifiers/PaddingLayout.swift index 351d09761..20788e606 100644 --- a/Sources/TokamakCore/Modifiers/PaddingLayout.swift +++ b/Sources/TokamakCore/Modifiers/PaddingLayout.swift @@ -29,22 +29,24 @@ public struct _PaddingLayout: ViewModifier { } public extension View { - func padding(_ insets: EdgeInsets) -> some View { + func padding(_ insets: EdgeInsets) -> ModifiedContent { modifier(_PaddingLayout(insets: insets)) } - func padding(_ edges: Edge.Set = .all, _ length: CGFloat? = nil) -> some View { + func padding(_ edges: Edge.Set = .all, + _ length: CGFloat? = nil) -> ModifiedContent + { let insets = length.map { EdgeInsets(_all: $0) } return modifier(_PaddingLayout(edges: edges, insets: insets)) } - func padding(_ length: CGFloat) -> some View { + func padding(_ length: CGFloat) -> ModifiedContent { padding(.all, length) } } -extension ModifiedContent where Modifier == _PaddingLayout, Content: View { - public func padding(_ length: CGFloat) -> some View { +public extension ModifiedContent where Modifier == _PaddingLayout, Content: View { + func padding(_ length: CGFloat) -> some View { var layout = modifier layout.insets?.top += length layout.insets?.leading += length diff --git a/Sources/TokamakCore/Reflection/Pointers/MetadataOffset.swift b/Sources/TokamakCore/Reflection/Pointers/MetadataOffset.swift index 4a05f3f19..83cec8ced 100644 --- a/Sources/TokamakCore/Reflection/Pointers/MetadataOffset.swift +++ b/Sources/TokamakCore/Reflection/Pointers/MetadataOffset.swift @@ -25,7 +25,7 @@ struct MetadataOffset { func apply(to ptr: UnsafeRawPointer) -> UnsafePointer { #if arch(wasm32) - return unsafeBitCast(offset, to: UnsafePointer.self) + return UnsafePointer(bitPattern: Int(offset))! #else return ptr.advanced(by: numericCast(offset)).assumingMemoryBound(to: Pointee.self) #endif diff --git a/Tests/TokamakStaticHTMLTests/HTMLTests.swift b/Tests/TokamakStaticHTMLTests/HTMLTests.swift index 423d99a6b..6e0b5c44a 100644 --- a/Tests/TokamakStaticHTMLTests/HTMLTests.swift +++ b/Tests/TokamakStaticHTMLTests/HTMLTests.swift @@ -15,152 +15,10 @@ // Created by Max Desiatov on 07/12/2018. // +import SnapshotTesting import TokamakStaticHTML import XCTest -private let expectedHTML = - #""" - - - - - -
text -
- - """# - final class ReconcilerTests: XCTestCase { struct Model { let text: Text @@ -184,6 +42,13 @@ final class ReconcilerTests: XCTestCase { let resultingHTML = StaticHTMLRenderer(OptionalBody(model: Model(text: Text("text")))) .render(shouldSortAttributes: true) - XCTAssertEqual(resultingHTML, expectedHTML) + assertSnapshot(matching: resultingHTML, as: .lines) + } + + func testPaddingFusion() { + let x = Text("text").padding(10) + let resultingHTML = StaticHTMLRenderer(x.padding(20)).render(shouldSortAttributes: true) + + assertSnapshot(matching: resultingHTML, as: .lines) } } diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testOptional.1.txt b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testOptional.1.txt new file mode 100644 index 000000000..d5c9189a9 --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testOptional.1.txt @@ -0,0 +1,139 @@ + + + + + +
text +
+ diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt new file mode 100644 index 000000000..8bc0f8810 --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt @@ -0,0 +1,136 @@ + + + + + +
text
+ From 216449ee48585681d9d1a8c3c90d1eeac36ce582 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 18:07:11 +0100 Subject: [PATCH 13/27] Enable fusion of modifiers nested three times --- .pre-commit-config.yaml | 21 +-- .../TokamakCore/Modifiers/PaddingLayout.swift | 2 +- Tests/TokamakStaticHTMLTests/HTMLTests.swift | 13 +- .../HTMLTests/testPaddingFusion.1.txt | 2 +- .../HTMLTests/testPaddingFusion.2.txt | 136 ++++++++++++++++++ 5 files changed, 159 insertions(+), 15 deletions(-) create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.2.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ad8e2246a..7147883a3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,17 +1,18 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks +exclude: __Snapshots__ repos: -- repo: https://github.com/pre-commit/pre-commit-hooks + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.5.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files - - id: detect-private-key - - id: check-merge-conflict -- repo: https://github.com/hodovani/pre-commit-swift + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: detect-private-key + - id: check-merge-conflict + - repo: https://github.com/hodovani/pre-commit-swift rev: master hooks: - - id: swift-lint - - id: swift-format + - id: swift-lint + - id: swift-format diff --git a/Sources/TokamakCore/Modifiers/PaddingLayout.swift b/Sources/TokamakCore/Modifiers/PaddingLayout.swift index 20788e606..70b5d928b 100644 --- a/Sources/TokamakCore/Modifiers/PaddingLayout.swift +++ b/Sources/TokamakCore/Modifiers/PaddingLayout.swift @@ -46,7 +46,7 @@ public extension View { } public extension ModifiedContent where Modifier == _PaddingLayout, Content: View { - func padding(_ length: CGFloat) -> some View { + func padding(_ length: CGFloat) -> ModifiedContent { var layout = modifier layout.insets?.top += length layout.insets?.leading += length diff --git a/Tests/TokamakStaticHTMLTests/HTMLTests.swift b/Tests/TokamakStaticHTMLTests/HTMLTests.swift index 6e0b5c44a..2acaa2871 100644 --- a/Tests/TokamakStaticHTMLTests/HTMLTests.swift +++ b/Tests/TokamakStaticHTMLTests/HTMLTests.swift @@ -46,9 +46,16 @@ final class ReconcilerTests: XCTestCase { } func testPaddingFusion() { - let x = Text("text").padding(10) - let resultingHTML = StaticHTMLRenderer(x.padding(20)).render(shouldSortAttributes: true) + let nestedTwice = StaticHTMLRenderer( + Text("text").padding(10).padding(20) + ).render(shouldSortAttributes: true) - assertSnapshot(matching: resultingHTML, as: .lines) + assertSnapshot(matching: nestedTwice, as: .lines) + + let nestedThrice = StaticHTMLRenderer( + Text("text").padding(20).padding(20).padding(20) + ).render(shouldSortAttributes: true) + + assertSnapshot(matching: nestedThrice, as: .lines) } } diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt index 8bc0f8810..37de2f8e5 100644 --- a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt @@ -133,4 +133,4 @@ vertical-align: baseline; text-decoration: none; text-decoration-color: inherit; text-align: left;">text - + \ No newline at end of file diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.2.txt b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.2.txt new file mode 100644 index 000000000..8293a1899 --- /dev/null +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.2.txt @@ -0,0 +1,136 @@ + + + + + +
text
+ \ No newline at end of file From 6ef292d45fad51bf912d9095e21eda610a86d83d Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 18:14:03 +0100 Subject: [PATCH 14/27] Filter out empty attributes --- Sources/TokamakStaticHTML/Views/HTML.swift | 5 ++++- .../__Snapshots__/HTMLTests/testPaddingFusion.1.txt | 2 +- .../__Snapshots__/HTMLTests/testPaddingFusion.2.txt | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/TokamakStaticHTML/Views/HTML.swift b/Sources/TokamakStaticHTML/Views/HTML.swift index 414884ade..a972272bf 100644 --- a/Sources/TokamakStaticHTML/Views/HTML.swift +++ b/Sources/TokamakStaticHTML/Views/HTML.swift @@ -58,7 +58,10 @@ public extension AnyHTML { if attributes.isEmpty { renderedAttributes = "" } else { - let mappedAttributes = attributes.map { #"\#($0)="\#($1)""# } + let mappedAttributes = attributes + // Exclude empty values to avoid waste of space with `class=""` + .filter { !$1.isEmpty } + .map { #"\#($0)="\#($1)""# } if shouldSortAttributes { renderedAttributes = mappedAttributes.sorted().joined(separator: " ") } else { diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt index 37de2f8e5..8df818bfd 100644 --- a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testPaddingFusion.1.txt @@ -115,7 +115,7 @@ width: 100%; height: 100%; justify-content: center; align-items: center; -overflow: hidden;">
Date: Sun, 20 Jun 2021 18:21:11 +0100 Subject: [PATCH 16/27] Fully exclude snapshot testing on WASI --- Package.swift | 2 +- Tests/TokamakStaticHTMLTests/HTMLTests.swift | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 0697992b8..12b01a5f0 100644 --- a/Package.swift +++ b/Package.swift @@ -190,7 +190,7 @@ let package = Package( .product( name: "SnapshotTesting", package: "SnapshotTesting", - condition: .when(platforms: [.macOS]) + condition: .when(platforms: [.macOS, .linux]) ), ], exclude: ["__Snapshots__"] diff --git a/Tests/TokamakStaticHTMLTests/HTMLTests.swift b/Tests/TokamakStaticHTMLTests/HTMLTests.swift index 2acaa2871..eb1c3f7e6 100644 --- a/Tests/TokamakStaticHTMLTests/HTMLTests.swift +++ b/Tests/TokamakStaticHTMLTests/HTMLTests.swift @@ -15,11 +15,13 @@ // Created by Max Desiatov on 07/12/2018. // +#if canImport(SnapshotTesting) + import SnapshotTesting import TokamakStaticHTML import XCTest -final class ReconcilerTests: XCTestCase { +final class HTMLTests: XCTestCase { struct Model { let text: Text } @@ -59,3 +61,5 @@ final class ReconcilerTests: XCTestCase { assertSnapshot(matching: nestedThrice, as: .lines) } } + +#endif From f8c6c599c35186bd6dc5845c5886b81a8db9ad1d Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 18:24:18 +0100 Subject: [PATCH 17/27] Fix `testOptional` snapshot --- .../__Snapshots__/HTMLTests/testOptional.1.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testOptional.1.txt b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testOptional.1.txt index d5c9189a9..9d085442a 100644 --- a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testOptional.1.txt +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testOptional.1.txt @@ -117,7 +117,7 @@ justify-content: center; align-items: center; overflow: hidden;">
text
- + \ No newline at end of file From fd52a056a70d21e652ae232fe9af98ce2dbae8c9 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 18:41:26 +0100 Subject: [PATCH 18/27] Clean up code formatting --- Sources/TokamakCore/Modifiers/PaddingLayout.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/TokamakCore/Modifiers/PaddingLayout.swift b/Sources/TokamakCore/Modifiers/PaddingLayout.swift index 70b5d928b..549201c21 100644 --- a/Sources/TokamakCore/Modifiers/PaddingLayout.swift +++ b/Sources/TokamakCore/Modifiers/PaddingLayout.swift @@ -33,9 +33,10 @@ public extension View { modifier(_PaddingLayout(insets: insets)) } - func padding(_ edges: Edge.Set = .all, - _ length: CGFloat? = nil) -> ModifiedContent - { + func padding( + _ edges: Edge.Set = .all, + _ length: CGFloat? = nil + ) -> ModifiedContent { let insets = length.map { EdgeInsets(_all: $0) } return modifier(_PaddingLayout(edges: edges, insets: insets)) } From d0f64aff0905b89caee97d1e303fd211d2e97c09 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 18:50:39 +0100 Subject: [PATCH 19/27] Copy failed snapshots to a readable directory --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d02d55a9..622dbcba0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,8 @@ jobs: sudo xcode-select --switch /Applications/Xcode_12.4.app/Contents/Developer/ # avoid building unrelated products for testing by specifying the test product explicitly swift build --product TokamakPackageTests - `xcrun --find xctest` .build/debug/TokamakPackageTests.xctest + `xcrun --find xctest` .build/debug/TokamakPackageTests.xctest || + cp -rf /var/folders/**/SnapshotTests . rm -rf Sources/TokamakGTKCHelpers/*.c @@ -60,7 +61,7 @@ jobs: if: ${{ failure() }} with: name: Failed snapshots - path: /var/folders/**/SnapshotTests + path: SnapshotTests gtk_macos_build: runs-on: macos-latest From 9a257218955a0950112ac3e045b94c62e295230a Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 18:54:25 +0100 Subject: [PATCH 20/27] Make the copy script more resilient --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 622dbcba0..34462733d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: # avoid building unrelated products for testing by specifying the test product explicitly swift build --product TokamakPackageTests `xcrun --find xctest` .build/debug/TokamakPackageTests.xctest || - cp -rf /var/folders/**/SnapshotTests . + find /var/folders -iname SnapshotTests -exec cp -r {} . \; rm -rf Sources/TokamakGTKCHelpers/*.c From e6c082c52065f0bb9c457a7046c6d8b63b1959e8 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 19:29:51 +0100 Subject: [PATCH 21/27] Use `--force-color-profile=srgb` Chromium flag --- .../{SnapshotTests.swift => LayoutTests.swift} | 3 ++- .../__Snapshots__/LayoutTests/testPath.1.png | Bin 0 -> 1985 bytes .../__Snapshots__/SnapshotTests/testPath.1.png | Bin 2498 -> 0 bytes 3 files changed, 2 insertions(+), 1 deletion(-) rename Tests/TokamakStaticHTMLTests/{SnapshotTests.swift => LayoutTests.swift} (97%) create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/LayoutTests/testPath.1.png delete mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/SnapshotTests/testPath.1.png diff --git a/Tests/TokamakStaticHTMLTests/SnapshotTests.swift b/Tests/TokamakStaticHTMLTests/LayoutTests.swift similarity index 97% rename from Tests/TokamakStaticHTMLTests/SnapshotTests.swift rename to Tests/TokamakStaticHTMLTests/LayoutTests.swift index a3d00dfb7..d80b17794 100644 --- a/Tests/TokamakStaticHTMLTests/SnapshotTests.swift +++ b/Tests/TokamakStaticHTMLTests/LayoutTests.swift @@ -45,6 +45,7 @@ public extension Snapshotting where Value: View, Format == NSImage { "--headless", "--disable-gpu", "--force-device-scale-factor=1.0", + "--force-color-profile=srgb", "--screenshot", renderedPath.path, ] @@ -78,7 +79,7 @@ struct Star: Shape { } } -final class SnapshotTests: XCTestCase { +final class LayoutTests: XCTestCase { func testPath() { assertSnapshot( matching: Star().fill(Color(red: 1, green: 0.75, blue: 0.1, opacity: 1)), diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/LayoutTests/testPath.1.png b/Tests/TokamakStaticHTMLTests/__Snapshots__/LayoutTests/testPath.1.png new file mode 100644 index 0000000000000000000000000000000000000000..434a1397754e7ff041685dfbf1b2d42694a08614 GIT binary patch literal 1985 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 M07*qoM6N<$g2)k|5&!@I From b688286aaa61dc10b7cbfe1af7641e1d960fc1fd Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 19:32:08 +0100 Subject: [PATCH 22/27] Re-enable spooky hanger test --- Tests/TokamakTests/ReconcilerStressTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/TokamakTests/ReconcilerStressTests.swift b/Tests/TokamakTests/ReconcilerStressTests.swift index 00d20dfd6..8086625b3 100644 --- a/Tests/TokamakTests/ReconcilerStressTests.swift +++ b/Tests/TokamakTests/ReconcilerStressTests.swift @@ -40,8 +40,6 @@ final class ReconcilerStressTests: XCTestCase { let renderer = TestRenderer(SpookyHanger()) let root = renderer.rootTarget - return - let list = root.subviews[0].subviews[0] XCTAssertTrue( From 57d24f974d2743d9dab10753ad046258d94a22a9 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 19:40:20 +0100 Subject: [PATCH 23/27] Clean up testSpookyHanger --- Tests/TokamakTests/ReconcilerStressTests.swift | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Tests/TokamakTests/ReconcilerStressTests.swift b/Tests/TokamakTests/ReconcilerStressTests.swift index 8086625b3..e22fe0c53 100644 --- a/Tests/TokamakTests/ReconcilerStressTests.swift +++ b/Tests/TokamakTests/ReconcilerStressTests.swift @@ -40,18 +40,9 @@ final class ReconcilerStressTests: XCTestCase { let renderer = TestRenderer(SpookyHanger()) let root = renderer.rootTarget - let list = root.subviews[0].subviews[0] - XCTAssertTrue( - root.view + root.subviews[0].view .view is NavigationView>>> ) - - guard let link = list.subviews[0].view.view as? NavigationLink else { - XCTAssert(false, "navigation has no link") - return - } - - _NavigationLinkProxy(link).activate() } } From 57cbc5537d466082371441e9162bc7b37c9aa2a1 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 20:03:16 +0100 Subject: [PATCH 24/27] Fix linter warnings --- Sources/TokamakCore/MountedViews/MountedApp.swift | 3 ++- Sources/TokamakCore/MountedViews/MountedCompositeView.swift | 4 ++-- Sources/TokamakCore/Reflection/Layouts/FieldDescriptor.swift | 1 + Sources/TokamakStaticHTML/Views/Text/Text.swift | 1 - Tests/TokamakTests/GetSetStructTests.swift | 1 - 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/TokamakCore/MountedViews/MountedApp.swift b/Sources/TokamakCore/MountedViews/MountedApp.swift index 078c4d40f..dd79a9f30 100644 --- a/Sources/TokamakCore/MountedViews/MountedApp.swift +++ b/Sources/TokamakCore/MountedViews/MountedApp.swift @@ -41,7 +41,8 @@ final class MountedApp: MountedCompositeElement { /// Mounts a child scene within the app. /// - Parameters: - /// - renderer: A instance conforming to the `Renderer` protocol to render the mounted scene with. + /// - renderer: An instance conforming to the `Renderer` protocol to render the mounted + /// scene with. /// - childBody: The body of the child scene to mount for this app. /// - Returns: Returns an instance of the `MountedScene` class that's already mounted in this app. private func mountChild(_ renderer: R, _ childBody: _AnyScene) -> MountedScene { diff --git a/Sources/TokamakCore/MountedViews/MountedCompositeView.swift b/Sources/TokamakCore/MountedViews/MountedCompositeView.swift index 9b3293be3..0bbd8598a 100644 --- a/Sources/TokamakCore/MountedViews/MountedCompositeView.swift +++ b/Sources/TokamakCore/MountedViews/MountedCompositeView.swift @@ -34,8 +34,8 @@ final class MountedCompositeView: MountedCompositeElement { mountedChildren = [child] child.mount(before: sibling, on: self, with: reconciler) - // `_TargetRef` (and `TargetRefType` generic eraser protocol it conforms to) is a composite view, so it's enough - // to check for it only here. + // `_TargetRef` (and `TargetRefType` generic eraser protocol it conforms to) is a composite + // view, so it's enough check for it only here. if var targetRef = view.view as? TargetRefType { // `_TargetRef` body is not always a host view that has a target, need to traverse // all descendants to find a `MountedHostView` instance. diff --git a/Sources/TokamakCore/Reflection/Layouts/FieldDescriptor.swift b/Sources/TokamakCore/Reflection/Layouts/FieldDescriptor.swift index 4f238261b..691bdec2e 100644 --- a/Sources/TokamakCore/Reflection/Layouts/FieldDescriptor.swift +++ b/Sources/TokamakCore/Reflection/Layouts/FieldDescriptor.swift @@ -32,6 +32,7 @@ func _getTypeByMangledNameInContext( _ genericArguments: UnsafeRawPointer? ) -> Any.Type? +// swiftlint:disable:next line_length /// https://github.com/apple/swift/blob/f2c42509628bed66bf5b8ee02fae778a2ba747a1/include/swift/Reflection/Records.h#L160 struct FieldDescriptor { let mangledTypeNameOffset: Int32 diff --git a/Sources/TokamakStaticHTML/Views/Text/Text.swift b/Sources/TokamakStaticHTML/Views/Text/Text.swift index 93283ec75..57665c050 100644 --- a/Sources/TokamakStaticHTML/Views/Text/Text.swift +++ b/Sources/TokamakStaticHTML/Views/Text/Text.swift @@ -188,7 +188,6 @@ extension Text { let decorationColor = strikethrough?.1?.cssValue(environment) ?? underline?.1?.cssValue(environment) ?? "inherit" - let resolvedFont = font == nil ? nil : _FontProxy(font!).resolve(in: environment) return [ diff --git a/Tests/TokamakTests/GetSetStructTests.swift b/Tests/TokamakTests/GetSetStructTests.swift index df51e12a4..37558ce14 100644 --- a/Tests/TokamakTests/GetSetStructTests.swift +++ b/Tests/TokamakTests/GetSetStructTests.swift @@ -23,7 +23,6 @@ @testable import TokamakCore import XCTest -// swiftlint:disable type_body_length class GetSetStructTests: XCTestCase { // swiftlint:disable force_cast func testGet() throws { From 7aa355652f1338f98a9777bd46efdae6bce54039 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 20:11:59 +0100 Subject: [PATCH 25/27] Fix file_length linter warning --- Sources/TokamakCore/Shapes/Path/Path.swift | 192 ++++++++++++++++++ .../{Path.swift => Path/PathLayout.swift} | 178 +--------------- .../Shapes/{ => Path}/PathSizing.swift | 0 3 files changed, 194 insertions(+), 176 deletions(-) create mode 100644 Sources/TokamakCore/Shapes/Path/Path.swift rename Sources/TokamakCore/Shapes/{Path.swift => Path/PathLayout.swift} (73%) rename Sources/TokamakCore/Shapes/{ => Path}/PathSizing.swift (100%) diff --git a/Sources/TokamakCore/Shapes/Path/Path.swift b/Sources/TokamakCore/Shapes/Path/Path.swift new file mode 100644 index 000000000..1ed21c98c --- /dev/null +++ b/Sources/TokamakCore/Shapes/Path/Path.swift @@ -0,0 +1,192 @@ +// 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 06/28/2020. +// + +import Foundation + +/// The outline of a 2D shape. +public struct Path: Equatable, LosslessStringConvertible { + public class _PathBox: Equatable { + var elements: [Element] = [] + public static func == (lhs: Path._PathBox, rhs: Path._PathBox) -> Bool { + lhs.elements == rhs.elements + } + + init() {} + + init(elements: [Element]) { + self.elements = elements + } + } + + public var description: String { + var pathString = [String]() + for element in elements { + switch element { + case let .move(to: pos): + pathString.append("\(pos.x) \(pos.y) m") + case let .line(to: pos): + pathString.append("\(pos.x) \(pos.y) l") + case let .curve(to: pos, control1: c1, control2: c2): + pathString.append("\(c1.x) \(c1.y) \(c2.x) \(c2.y) \(pos.x) \(pos.y) c") + case let .quadCurve(to: pos, control: c): + pathString.append("\(c.x) \(c.y) \(pos.x) \(pos.y) q") + case .closeSubpath: + pathString.append("h") + } + } + return pathString.joined(separator: " ") + } + + public enum Storage: Equatable { + case empty + case rect(CGRect) + case ellipse(CGRect) + indirect case roundedRect(FixedRoundedRect) + indirect case stroked(StrokedPath) + indirect case trimmed(TrimmedPath) + case path(_PathBox) + } + + public enum Element: Equatable { + case move(to: CGPoint) + case line(to: CGPoint) + case quadCurve(to: CGPoint, control: CGPoint) + case curve(to: CGPoint, control1: CGPoint, control2: CGPoint) + case closeSubpath + } + + public var storage: Storage + public let sizing: _Sizing + + public var elements: [Element] { storage.elements } + + public init() { + storage = .empty + sizing = .fixed + } + + init(storage: Storage, sizing: _Sizing = .fixed) { + self.storage = storage + self.sizing = sizing + } + + public init(_ rect: CGRect) { + self.init(storage: .rect(rect)) + } + + public init(roundedRect rect: CGRect, cornerSize: CGSize, style: RoundedCornerStyle = .circular) { + self.init( + storage: .roundedRect(FixedRoundedRect(rect: rect, cornerSize: cornerSize, style: style)) + ) + } + + public init( + roundedRect rect: CGRect, + cornerRadius: CGFloat, + style: RoundedCornerStyle = .circular + ) { + self.init( + storage: .roundedRect(FixedRoundedRect( + rect: rect, + cornerSize: CGSize(width: cornerRadius, height: cornerRadius), + style: style + )) + ) + } + + public init(ellipseIn rect: CGRect) { + self.init(storage: .ellipse(rect)) + } + + public init(_ callback: (inout Self) -> ()) { + var base = Self() + callback(&base) + self = base + } + + public init?(_ string: String) { + // FIXME: Somehow make this from a string? + self.init() + } + + // FIXME: We don't have CGPath + // public var cgPath: CGPath { + // + // } + public var isEmpty: Bool { + storage == .empty + } + + public var boundingRect: CGRect { + switch storage { + case .empty: return .zero + case let .rect(rect): return rect + case let .ellipse(rect): return rect + case let .roundedRect(fixedRoundedRect): return fixedRoundedRect.rect + case let .stroked(strokedPath): return strokedPath.path.boundingRect + case let .trimmed(trimmedPath): return trimmedPath.path.boundingRect + case let .path(pathBox): + // Note: Copied from TokamakStaticHTML/Shapes/Path.swift + // Should the control points be included in the positions array? + let positions = pathBox.elements.compactMap { elem -> CGPoint? in + switch elem { + case let .move(to: pos): return pos + case let .line(to: pos): return pos + case let .curve(to: pos, control1: _, control2: _): return pos + case let .quadCurve(to: pos, control: _): return pos + case .closeSubpath: return nil + } + } + let xPos = positions.map(\.x).sorted(by: <) + let minX = xPos.first ?? 0 + let maxX = xPos.last ?? 0 + let yPos = positions.map(\.y).sorted(by: <) + let minY = yPos.first ?? 0 + let maxY = yPos.last ?? 0 + + return CGRect( + origin: CGPoint(x: minX, y: minY), + size: CGSize(width: maxX - minX, height: maxY - minY) + ) + } + } + + public func contains(_ p: CGPoint, eoFill: Bool = false) -> Bool { + false + } + + public func forEach(_ body: (Element) -> ()) { + elements.forEach { body($0) } + } + + public func strokedPath(_ style: StrokeStyle) -> Self { + Self(storage: .stroked(StrokedPath(path: self, style: style))) + } + + public func trimmedPath(from: CGFloat, to: CGFloat) -> Self { + Self(storage: .trimmed(TrimmedPath(path: self, from: from, to: to))) + } + + // FIXME: In SwiftUI, but we don't have CGPath... + // public init(_ path: CGPath) + // public init(_ path: CGMutablePath) +} + +public enum RoundedCornerStyle: Hashable, Equatable { + case circular + case continuous +} diff --git a/Sources/TokamakCore/Shapes/Path.swift b/Sources/TokamakCore/Shapes/Path/PathLayout.swift similarity index 73% rename from Sources/TokamakCore/Shapes/Path.swift rename to Sources/TokamakCore/Shapes/Path/PathLayout.swift index 0a847af9e..0b1eb722d 100644 --- a/Sources/TokamakCore/Shapes/Path.swift +++ b/Sources/TokamakCore/Shapes/Path/PathLayout.swift @@ -1,4 +1,4 @@ -// Copyright 2020-2021 Tokamak contributors +// Copyright 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. @@ -12,185 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// Created by Carson Katri on 06/28/2020. +// Created by Max Desiatov on 20/06/2021. // import Foundation -/// The outline of a 2D shape. -public struct Path: Equatable, LosslessStringConvertible { - public class _PathBox: Equatable { - var elements: [Element] = [] - public static func == (lhs: Path._PathBox, rhs: Path._PathBox) -> Bool { - lhs.elements == rhs.elements - } - - init() {} - - init(elements: [Element]) { - self.elements = elements - } - } - - public var description: String { - var pathString = [String]() - for element in elements { - switch element { - case let .move(to: pos): - pathString.append("\(pos.x) \(pos.y) m") - case let .line(to: pos): - pathString.append("\(pos.x) \(pos.y) l") - case let .curve(to: pos, control1: c1, control2: c2): - pathString.append("\(c1.x) \(c1.y) \(c2.x) \(c2.y) \(pos.x) \(pos.y) c") - case let .quadCurve(to: pos, control: c): - pathString.append("\(c.x) \(c.y) \(pos.x) \(pos.y) q") - case .closeSubpath: - pathString.append("h") - } - } - return pathString.joined(separator: " ") - } - - public enum Storage: Equatable { - case empty - case rect(CGRect) - case ellipse(CGRect) - indirect case roundedRect(FixedRoundedRect) - indirect case stroked(StrokedPath) - indirect case trimmed(TrimmedPath) - case path(_PathBox) - } - - public enum Element: Equatable { - case move(to: CGPoint) - case line(to: CGPoint) - case quadCurve(to: CGPoint, control: CGPoint) - case curve(to: CGPoint, control1: CGPoint, control2: CGPoint) - case closeSubpath - } - - public var storage: Storage - public let sizing: _Sizing - - public var elements: [Element] { storage.elements } - - public init() { - storage = .empty - sizing = .fixed - } - - init(storage: Storage, sizing: _Sizing = .fixed) { - self.storage = storage - self.sizing = sizing - } - - public init(_ rect: CGRect) { - self.init(storage: .rect(rect)) - } - - public init(roundedRect rect: CGRect, cornerSize: CGSize, style: RoundedCornerStyle = .circular) { - self.init( - storage: .roundedRect(FixedRoundedRect(rect: rect, cornerSize: cornerSize, style: style)) - ) - } - - public init( - roundedRect rect: CGRect, - cornerRadius: CGFloat, - style: RoundedCornerStyle = .circular - ) { - self.init( - storage: .roundedRect(FixedRoundedRect( - rect: rect, - cornerSize: CGSize(width: cornerRadius, height: cornerRadius), - style: style - )) - ) - } - - public init(ellipseIn rect: CGRect) { - self.init(storage: .ellipse(rect)) - } - - public init(_ callback: (inout Self) -> ()) { - var base = Self() - callback(&base) - self = base - } - - public init?(_ string: String) { - // FIXME: Somehow make this from a string? - self.init() - } - - // FIXME: We don't have CGPath - // public var cgPath: CGPath { - // - // } - public var isEmpty: Bool { - storage == .empty - } - - public var boundingRect: CGRect { - switch storage { - case .empty: return .zero - case let .rect(rect): return rect - case let .ellipse(rect): return rect - case let .roundedRect(fixedRoundedRect): return fixedRoundedRect.rect - case let .stroked(strokedPath): return strokedPath.path.boundingRect - case let .trimmed(trimmedPath): return trimmedPath.path.boundingRect - case let .path(pathBox): - // Note: Copied from TokamakStaticHTML/Shapes/Path.swift - // Should the control points be included in the positions array? - let positions = pathBox.elements.compactMap { elem -> CGPoint? in - switch elem { - case let .move(to: pos): return pos - case let .line(to: pos): return pos - case let .curve(to: pos, control1: _, control2: _): return pos - case let .quadCurve(to: pos, control: _): return pos - case .closeSubpath: return nil - } - } - let xPos = positions.map(\.x).sorted(by: <) - let minX = xPos.first ?? 0 - let maxX = xPos.last ?? 0 - let yPos = positions.map(\.y).sorted(by: <) - let minY = yPos.first ?? 0 - let maxY = yPos.last ?? 0 - - return CGRect( - origin: CGPoint(x: minX, y: minY), - size: CGSize(width: maxX - minX, height: maxY - minY) - ) - } - } - - public func contains(_ p: CGPoint, eoFill: Bool = false) -> Bool { - false - } - - public func forEach(_ body: (Element) -> ()) { - elements.forEach { body($0) } - } - - public func strokedPath(_ style: StrokeStyle) -> Self { - Self(storage: .stroked(StrokedPath(path: self, style: style))) - } - - public func trimmedPath(from: CGFloat, to: CGFloat) -> Self { - Self(storage: .trimmed(TrimmedPath(path: self, from: from, to: to))) - } - - // FIXME: In SwiftUI, but we don't have CGPath... - // public init(_ path: CGPath) - // public init(_ path: CGMutablePath) -} - -public enum RoundedCornerStyle: Hashable, Equatable { - case circular - case continuous -} - public extension Path.Storage { var elements: [Path.Element] { switch self { diff --git a/Sources/TokamakCore/Shapes/PathSizing.swift b/Sources/TokamakCore/Shapes/Path/PathSizing.swift similarity index 100% rename from Sources/TokamakCore/Shapes/PathSizing.swift rename to Sources/TokamakCore/Shapes/Path/PathSizing.swift From 7d9d2bd52d117832cb9d246b0f74568d9b1e1b18 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 20:31:38 +0100 Subject: [PATCH 26/27] Silence linter warning for `Text.attributes` func --- Sources/TokamakStaticHTML/Views/Text/Text.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/TokamakStaticHTML/Views/Text/Text.swift b/Sources/TokamakStaticHTML/Views/Text/Text.swift index 57665c050..b7cd919bc 100644 --- a/Sources/TokamakStaticHTML/Views/Text/Text.swift +++ b/Sources/TokamakStaticHTML/Views/Text/Text.swift @@ -145,6 +145,7 @@ extension Text: AnyHTML { } extension Text { + // swiftlint:disable function_body_length static func attributes( from modifiers: [_Modifier], environment: EnvironmentValues @@ -207,4 +208,5 @@ extension Text { "class": isRedacted ? "_tokamak-text-redacted" : "", ] } + // swiftlint:enable function_body_length } From 92794857d377da3e1552c1b2ace0b77e9b5f69f6 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 20 Jun 2021 20:37:25 +0100 Subject: [PATCH 27/27] Split `PathLayout.swift` to appease the linter --- .../TokamakCore/Shapes/Path/PathLayout.swift | 189 ---------------- .../Shapes/Path/PathMutations.swift | 209 ++++++++++++++++++ 2 files changed, 209 insertions(+), 189 deletions(-) create mode 100644 Sources/TokamakCore/Shapes/Path/PathMutations.swift diff --git a/Sources/TokamakCore/Shapes/Path/PathLayout.swift b/Sources/TokamakCore/Shapes/Path/PathLayout.swift index 0b1eb722d..72fc48a0d 100644 --- a/Sources/TokamakCore/Shapes/Path/PathLayout.swift +++ b/Sources/TokamakCore/Shapes/Path/PathLayout.swift @@ -211,137 +211,6 @@ public extension Path.Storage { } public extension Path { - private mutating func append(_ other: Storage, transform: CGAffineTransform = .identity) { - guard other != .empty else { return } - - // If self.storage is empty, replace with other storage. - // Otherwise append elements to current storage. - switch (storage, transform.isIdentity) { - case (.empty, true): - storage = other - - default: - append(other.elements, transform: transform) - } - } - - private mutating func append(_ elements: [Element], transform: CGAffineTransform = .identity) { - guard !elements.isEmpty else { return } - - let elements_: [Element] - if transform.isIdentity { - elements_ = elements - } else { - elements_ = elements.map { transform.transform(element: $0) } - } - - switch storage { - case let .path(pathBox): - pathBox.elements.append(contentsOf: elements_) - - default: - storage = .path(_PathBox(elements: storage.elements + elements_)) - } - } - - mutating func move(to p: CGPoint) { - append([.move(to: p)]) - } - - mutating func addLine(to p: CGPoint) { - append([.line(to: p)]) - } - - mutating func addQuadCurve(to p: CGPoint, control cp: CGPoint) { - append([.quadCurve(to: p, control: cp)]) - } - - mutating func addCurve(to p: CGPoint, control1 cp1: CGPoint, control2 cp2: CGPoint) { - append([.curve(to: p, control1: cp1, control2: cp2)]) - } - - mutating func closeSubpath() { - append([.closeSubpath]) - } - - mutating func addRect(_ rect: CGRect, transform: CGAffineTransform = .identity) { - append(.rect(rect), transform: transform) - } - - mutating func addRoundedRect( - in rect: CGRect, - cornerSize: CGSize, - style: RoundedCornerStyle = .circular, - transform: CGAffineTransform = .identity - ) { - append( - .roundedRect(FixedRoundedRect(rect: rect, cornerSize: cornerSize, style: style)), - transform: transform - ) - } - - mutating func addEllipse(in rect: CGRect, transform: CGAffineTransform = .identity) { - append(.ellipse(rect), transform: transform) - } - - mutating func addRects(_ rects: [CGRect], transform: CGAffineTransform = .identity) { - rects.forEach { addRect($0, transform: transform) } - } - - mutating func addLines(_ lines: [CGPoint]) { - lines.forEach { addLine(to: $0) } - } - - mutating func addRelativeArc( - center: CGPoint, - radius: CGFloat, - startAngle: Angle, - delta: Angle, - transform: CGAffineTransform = .identity - ) { - addArc( - center: center, - radius: radius, - startAngle: startAngle, - endAngle: startAngle + delta, - clockwise: false, - transform: transform - ) - } - - // There's a great article on bezier curves here: - // https://pomax.github.io/bezierinfo - // FIXME: Handle negative delta - mutating func addArc( - center: CGPoint, - radius: CGFloat, - startAngle: Angle, - endAngle: Angle, - clockwise: Bool, - transform: CGAffineTransform = .identity - ) { - let arc = getArc( - center: center, - radius: radius, - startAngle: endAngle, - endAngle: endAngle + (.radians(.pi * 2) - endAngle) + startAngle, - clockwise: false - ) - append(arc, transform: transform) - } - - // FIXME: How does this arc method work? - mutating func addArc( - tangent1End p1: CGPoint, - tangent2End p2: CGPoint, - radius: CGFloat, - transform: CGAffineTransform = .identity - ) {} - - mutating func addPath(_ path: Path, transform: CGAffineTransform = .identity) { - append(path.storage, transform: transform) - } - var currentPoint: CGPoint? { switch elements.last { case let .move(to: point): return point @@ -394,61 +263,3 @@ public extension CGAffineTransform { } } } - -private func getArc( - center: CGPoint, - radius: CGFloat, - startAngle: Angle, - endAngle: Angle, - clockwise: Bool -) -> [Path.Element] { - if clockwise { - return getArc( - center: center, - radius: radius, - startAngle: endAngle, - endAngle: endAngle + (.radians(.pi * 2) - endAngle) + startAngle, - clockwise: false - ) - } else { - let angle = abs(startAngle.radians - endAngle.radians) - if angle > .pi / 2 { - // Split the angle into 90º chunks - let chunk1 = Angle.radians(startAngle.radians + (.pi / 2)) - return getArc( - center: center, - radius: radius, - startAngle: startAngle, - endAngle: chunk1, - clockwise: clockwise - ) + - getArc( - center: center, - radius: radius, - startAngle: chunk1, - endAngle: endAngle, - clockwise: clockwise - ) - } else { - let angle = CGFloat(angle) - let endPoint = CGPoint( - x: (radius * cos(angle)) + center.x, - y: (radius * sin(angle)) + center.y - ) - let l = (4 / 3) * tan(angle / 4) - let c1 = CGPoint(x: radius + center.x, y: (l * radius) + center.y) - let c2 = CGPoint( - x: ((cos(angle) + l * sin(angle)) * radius) + center.x, - y: ((sin(angle) - l * cos(angle)) * radius) + center.y - ) - - return [ - .curve( - to: endPoint.rotate(startAngle, around: center), - control1: c1.rotate(startAngle, around: center), - control2: c2.rotate(startAngle, around: center) - ), - ] - } - } -} diff --git a/Sources/TokamakCore/Shapes/Path/PathMutations.swift b/Sources/TokamakCore/Shapes/Path/PathMutations.swift new file mode 100644 index 000000000..c3cf9047e --- /dev/null +++ b/Sources/TokamakCore/Shapes/Path/PathMutations.swift @@ -0,0 +1,209 @@ +// Copyright 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 Max Desiatov on 20/06/2021. +// + +import Foundation + +public extension Path { + private mutating func append(_ other: Storage, transform: CGAffineTransform = .identity) { + guard other != .empty else { return } + + // If self.storage is empty, replace with other storage. + // Otherwise append elements to current storage. + switch (storage, transform.isIdentity) { + case (.empty, true): + storage = other + + default: + append(other.elements, transform: transform) + } + } + + private mutating func append(_ elements: [Element], transform: CGAffineTransform = .identity) { + guard !elements.isEmpty else { return } + + let elements_: [Element] + if transform.isIdentity { + elements_ = elements + } else { + elements_ = elements.map { transform.transform(element: $0) } + } + + switch storage { + case let .path(pathBox): + pathBox.elements.append(contentsOf: elements_) + + default: + storage = .path(_PathBox(elements: storage.elements + elements_)) + } + } + + mutating func move(to p: CGPoint) { + append([.move(to: p)]) + } + + mutating func addLine(to p: CGPoint) { + append([.line(to: p)]) + } + + mutating func addQuadCurve(to p: CGPoint, control cp: CGPoint) { + append([.quadCurve(to: p, control: cp)]) + } + + mutating func addCurve(to p: CGPoint, control1 cp1: CGPoint, control2 cp2: CGPoint) { + append([.curve(to: p, control1: cp1, control2: cp2)]) + } + + mutating func closeSubpath() { + append([.closeSubpath]) + } + + mutating func addRect(_ rect: CGRect, transform: CGAffineTransform = .identity) { + append(.rect(rect), transform: transform) + } + + mutating func addRoundedRect( + in rect: CGRect, + cornerSize: CGSize, + style: RoundedCornerStyle = .circular, + transform: CGAffineTransform = .identity + ) { + append( + .roundedRect(FixedRoundedRect(rect: rect, cornerSize: cornerSize, style: style)), + transform: transform + ) + } + + mutating func addEllipse(in rect: CGRect, transform: CGAffineTransform = .identity) { + append(.ellipse(rect), transform: transform) + } + + mutating func addRects(_ rects: [CGRect], transform: CGAffineTransform = .identity) { + rects.forEach { addRect($0, transform: transform) } + } + + mutating func addLines(_ lines: [CGPoint]) { + lines.forEach { addLine(to: $0) } + } + + mutating func addRelativeArc( + center: CGPoint, + radius: CGFloat, + startAngle: Angle, + delta: Angle, + transform: CGAffineTransform = .identity + ) { + addArc( + center: center, + radius: radius, + startAngle: startAngle, + endAngle: startAngle + delta, + clockwise: false, + transform: transform + ) + } + + // There's a great article on bezier curves here: + // https://pomax.github.io/bezierinfo + // FIXME: Handle negative delta + mutating func addArc( + center: CGPoint, + radius: CGFloat, + startAngle: Angle, + endAngle: Angle, + clockwise: Bool, + transform: CGAffineTransform = .identity + ) { + let arc = getArc( + center: center, + radius: radius, + startAngle: endAngle, + endAngle: endAngle + (.radians(.pi * 2) - endAngle) + startAngle, + clockwise: false + ) + append(arc, transform: transform) + } + + // FIXME: How does this arc method work? + mutating func addArc( + tangent1End p1: CGPoint, + tangent2End p2: CGPoint, + radius: CGFloat, + transform: CGAffineTransform = .identity + ) {} + + mutating func addPath(_ path: Path, transform: CGAffineTransform = .identity) { + append(path.storage, transform: transform) + } +} + +func getArc( + center: CGPoint, + radius: CGFloat, + startAngle: Angle, + endAngle: Angle, + clockwise: Bool +) -> [Path.Element] { + if clockwise { + return getArc( + center: center, + radius: radius, + startAngle: endAngle, + endAngle: endAngle + (.radians(.pi * 2) - endAngle) + startAngle, + clockwise: false + ) + } else { + let angle = abs(startAngle.radians - endAngle.radians) + if angle > .pi / 2 { + // Split the angle into 90º chunks + let chunk1 = Angle.radians(startAngle.radians + (.pi / 2)) + return getArc( + center: center, + radius: radius, + startAngle: startAngle, + endAngle: chunk1, + clockwise: clockwise + ) + + getArc( + center: center, + radius: radius, + startAngle: chunk1, + endAngle: endAngle, + clockwise: clockwise + ) + } else { + let angle = CGFloat(angle) + let endPoint = CGPoint( + x: (radius * cos(angle)) + center.x, + y: (radius * sin(angle)) + center.y + ) + let l = (4 / 3) * tan(angle / 4) + let c1 = CGPoint(x: radius + center.x, y: (l * radius) + center.y) + let c2 = CGPoint( + x: ((cos(angle) + l * sin(angle)) * radius) + center.x, + y: ((sin(angle) - l * cos(angle)) * radius) + center.y + ) + + return [ + .curve( + to: endPoint.rotate(startAngle, around: center), + control1: c1.rotate(startAngle, around: center), + control2: c2.rotate(startAngle, around: center) + ), + ] + } + } +}