From 76098ef3b1b3ec7e20fc16e92364d72e26b0ed72 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Fri, 9 Jul 2021 08:54:31 -0400 Subject: [PATCH 1/5] Add scaleEffect modifier --- .../Modifiers/Effects/ScaleEffect.swift | 56 +++++++++++++++++++ .../Modifiers/Effects/ScaleEffect.swift | 24 ++++++++ 2 files changed, 80 insertions(+) create mode 100644 Sources/TokamakCore/Modifiers/Effects/ScaleEffect.swift create mode 100644 Sources/TokamakStaticHTML/Modifiers/Effects/ScaleEffect.swift diff --git a/Sources/TokamakCore/Modifiers/Effects/ScaleEffect.swift b/Sources/TokamakCore/Modifiers/Effects/ScaleEffect.swift new file mode 100644 index 000000000..4fcc7b425 --- /dev/null +++ b/Sources/TokamakCore/Modifiers/Effects/ScaleEffect.swift @@ -0,0 +1,56 @@ +// Copyright 2020-2021 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Created by Carson Katri on 7/9/21. +// + +import Foundation + +@frozen public struct _ScaleEffect: GeometryEffect, Equatable { + public var scale: CGSize + public var anchor: UnitPoint + + @inlinable + public init(scale: CGSize, anchor: UnitPoint = .center) { + self.scale = scale + self.anchor = anchor + } + + public func effectValue(size: CGSize) -> ProjectionTransform { + .init(.init(scaleX: scale.width, y: scale.height)) + } + + public func body(content: Content) -> some View { + content + } +} + +public extension View { + @inlinable + func scaleEffect(_ scale: CGSize, anchor: UnitPoint = .center) -> some View { + modifier(_ScaleEffect(scale: scale, anchor: anchor)) + } + + @inlinable + func scaleEffect(_ s: CGFloat, anchor: UnitPoint = .center) -> some View { + scaleEffect(CGSize(width: s, height: s), anchor: anchor) + } + + @inlinable + func scaleEffect(x: CGFloat = 1.0, y: CGFloat = 1.0, + anchor: UnitPoint = .center) -> some View + { + scaleEffect(CGSize(width: x, height: y), anchor: anchor) + } +} diff --git a/Sources/TokamakStaticHTML/Modifiers/Effects/ScaleEffect.swift b/Sources/TokamakStaticHTML/Modifiers/Effects/ScaleEffect.swift new file mode 100644 index 000000000..e1f5f1db2 --- /dev/null +++ b/Sources/TokamakStaticHTML/Modifiers/Effects/ScaleEffect.swift @@ -0,0 +1,24 @@ +// Copyright 2020 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Created by Carson Katri on 7/9/21. +// + +import TokamakCore + +extension _ScaleEffect: DOMViewModifier { + public var attributes: [HTMLAttribute: String] { + ["style": "transform: scale(\(scale.width), \(scale.height));"] + } +} From a28ebe7abd7ed34599092fe44fe643db5b26af11 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Fri, 9 Jul 2021 15:06:17 -0400 Subject: [PATCH 2/5] Add snapshot test --- .../RenderingTests.swift | 19 ++++++++++++++++++ .../RenderingTests/testScaleEffect.1.png | Bin 0 -> 3061 bytes 2 files changed, 19 insertions(+) create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testScaleEffect.1.png diff --git a/Tests/TokamakStaticHTMLTests/RenderingTests.swift b/Tests/TokamakStaticHTMLTests/RenderingTests.swift index 7185fbb5d..9793f0ab5 100644 --- a/Tests/TokamakStaticHTMLTests/RenderingTests.swift +++ b/Tests/TokamakStaticHTMLTests/RenderingTests.swift @@ -276,6 +276,25 @@ final class RenderingTests: XCTestCase { timeout: defaultSnapshotTimeout ) } + + func testScaleEffect() { + assertSnapshot( + matching: ZStack { + Circle() + .fill(Color.red) + .frame(width: 50, height: 50) + .scaleEffect(2) + .opacity(0.5) + Circle() + .fill(Color.blue) + .frame(width: 50, height: 50) + .zIndex(1) + .opacity(0.5) + }, + as: .image(size: .init(width: 100, height: 100)), + timeout: defaultSnapshotTimeout + ) + } } #endif diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testScaleEffect.1.png b/Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testScaleEffect.1.png new file mode 100644 index 0000000000000000000000000000000000000000..78c1c981d5e20a4bd48652a2308b3057d1169c17 GIT binary patch literal 3061 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 DmV>=X literal 0 HcmV?d00001 From 33413c82e4e59b8e66801cabf4c9c3184cb82ea1 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Mon, 12 Jul 2021 12:37:05 -0400 Subject: [PATCH 3/5] Cleanup --- .../__Snapshots__/HTMLTests/testFontStacks.1.txt | 2 +- .../__Snapshots__/HTMLTests/testFontStacks.2.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.1.txt b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.1.txt index 1122808ba..04f8eb1ad 100644 --- a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.1.txt +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.1.txt @@ -141,4 +141,4 @@ vertical-align: baseline; text-decoration: none; text-decoration-color: inherit; text-align: left;">Hello, world! - \ No newline at end of file + diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.2.txt b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.2.txt index 67c3d5c01..b2b8ae8c3 100644 --- a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.2.txt +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.2.txt @@ -144,4 +144,4 @@ vertical-align: baseline; text-decoration: none; text-decoration-color: inherit; text-align: left;">Hello, world! - \ No newline at end of file + From 727fe68415715d93158b651ceaf0fc3f4f39d85c Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Mon, 12 Jul 2021 12:42:07 -0400 Subject: [PATCH 4/5] Fix font stack tests --- .../__Snapshots__/HTMLTests/testFontStacks.1.txt | 2 +- .../__Snapshots__/HTMLTests/testFontStacks.2.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.1.txt b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.1.txt index 04f8eb1ad..1122808ba 100644 --- a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.1.txt +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.1.txt @@ -141,4 +141,4 @@ vertical-align: baseline; text-decoration: none; text-decoration-color: inherit; text-align: left;">Hello, world! - + \ No newline at end of file diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.2.txt b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.2.txt index b2b8ae8c3..67c3d5c01 100644 --- a/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.2.txt +++ b/Tests/TokamakStaticHTMLTests/__Snapshots__/HTMLTests/testFontStacks.2.txt @@ -144,4 +144,4 @@ vertical-align: baseline; text-decoration: none; text-decoration-color: inherit; text-align: left;">Hello, world! - + \ No newline at end of file From fdd37eccd253f423ccff772905429da7410d60f7 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Mon, 12 Jul 2021 13:43:27 -0400 Subject: [PATCH 5/5] Respect anchor in scale and rotationEffect --- .../Modifiers/Effects/RotationEffect.swift | 7 +++- .../Modifiers/Effects/ScaleEffect.swift | 7 +++- Sources/TokamakStaticHTML/Tokens/Tokens.swift | 6 ++++ .../RenderingTests.swift | 31 +++++++++++++++++- .../testAnchoredModifiers.1.png | Bin 0 -> 6174 bytes 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testAnchoredModifiers.1.png diff --git a/Sources/TokamakStaticHTML/Modifiers/Effects/RotationEffect.swift b/Sources/TokamakStaticHTML/Modifiers/Effects/RotationEffect.swift index b48e30252..40a62ae8e 100644 --- a/Sources/TokamakStaticHTML/Modifiers/Effects/RotationEffect.swift +++ b/Sources/TokamakStaticHTML/Modifiers/Effects/RotationEffect.swift @@ -19,6 +19,11 @@ import TokamakCore extension _RotationEffect: DOMViewModifier { public var attributes: [HTMLAttribute: String] { - ["style": "transform: rotate(\(angle.degrees)deg)"] + [ + "style": """ + transform: rotate(\(angle.degrees)deg); + transform-origin: \(anchor.cssValue); + """, + ] } } diff --git a/Sources/TokamakStaticHTML/Modifiers/Effects/ScaleEffect.swift b/Sources/TokamakStaticHTML/Modifiers/Effects/ScaleEffect.swift index e1f5f1db2..a9c89141d 100644 --- a/Sources/TokamakStaticHTML/Modifiers/Effects/ScaleEffect.swift +++ b/Sources/TokamakStaticHTML/Modifiers/Effects/ScaleEffect.swift @@ -19,6 +19,11 @@ import TokamakCore extension _ScaleEffect: DOMViewModifier { public var attributes: [HTMLAttribute: String] { - ["style": "transform: scale(\(scale.width), \(scale.height));"] + [ + "style": """ + transform: scale(\(scale.width), \(scale.height)); + transform-origin: \(anchor.cssValue); + """, + ] } } diff --git a/Sources/TokamakStaticHTML/Tokens/Tokens.swift b/Sources/TokamakStaticHTML/Tokens/Tokens.swift index ecab78948..e809a244d 100644 --- a/Sources/TokamakStaticHTML/Tokens/Tokens.swift +++ b/Sources/TokamakStaticHTML/Tokens/Tokens.swift @@ -37,3 +37,9 @@ extension GridItem: CustomStringConvertible { } } } + +extension UnitPoint { + var cssValue: String { + "\(x * 100)% \((1 - y) * 100)%" + } +} diff --git a/Tests/TokamakStaticHTMLTests/RenderingTests.swift b/Tests/TokamakStaticHTMLTests/RenderingTests.swift index 9793f0ab5..efb0fb3aa 100644 --- a/Tests/TokamakStaticHTMLTests/RenderingTests.swift +++ b/Tests/TokamakStaticHTMLTests/RenderingTests.swift @@ -288,13 +288,42 @@ final class RenderingTests: XCTestCase { Circle() .fill(Color.blue) .frame(width: 50, height: 50) - .zIndex(1) .opacity(0.5) }, as: .image(size: .init(width: 100, height: 100)), timeout: defaultSnapshotTimeout ) } + + func testAnchoredModifiers() { + assertSnapshot( + matching: ZStack { + Circle() + .fill(Color.red) + .frame(width: 50, height: 50) + .scaleEffect(2, anchor: .topLeading) + .opacity(0.5) + Circle() + .fill(Color.blue) + .frame(width: 50, height: 50) + .scaleEffect(2, anchor: .center) + .opacity(0.5) + + Rectangle() + .fill(Color.red) + .frame(width: 50, height: 50) + .rotationEffect(.degrees(45), anchor: .topLeading) + .opacity(0.5) + Rectangle() + .fill(Color.blue) + .frame(width: 50, height: 50) + .rotationEffect(.degrees(45), anchor: .center) + .opacity(0.5) + }, + as: .image(size: .init(width: 200, height: 200)), + timeout: defaultSnapshotTimeout + ) + } } #endif diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testAnchoredModifiers.1.png b/Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testAnchoredModifiers.1.png new file mode 100644 index 0000000000000000000000000000000000000000..40e7d7e78df1f7f30163532e6d87401798e9dfa1 GIT binary patch literal 6174 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 EA3r=h-2eap literal 0 HcmV?d00001