Skip to content

Commit 41753ce

Browse files
authored
Merge pull request #149 from liveviewnative/carsonk/shape-support
Support Circle, Ellipse, Capsule, and ContainerRelativeShape
2 parents 97facc6 + af855ff commit 41753ce

File tree

4 files changed

+111
-13
lines changed

4 files changed

+111
-13
lines changed

Sources/LiveViewNative/BuiltinRegistry.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ struct BuiltinRegistry {
4444
Shape(element: element, context: context, shape: Rectangle())
4545
case "roundedrectangle":
4646
Shape(element: element, context: context, shape: RoundedRectangle(from: element))
47+
case "circle":
48+
Shape(element: element, context: context, shape: Circle())
49+
case "ellipse":
50+
Shape(element: element, context: context, shape: Ellipse())
51+
case "capsule":
52+
Shape(element: element, context: context, shape: Capsule(from: element))
53+
case "containerrelativeshape":
54+
Shape(element: element, context: context, shape: ContainerRelativeShape())
4755
case "lvn-link":
4856
Link(element: element, context: context)
4957
case "divider":

Sources/LiveViewNative/Views/Shapes/Shape.swift

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,33 @@ struct Shape<S: SwiftUI.Shape>: View {
2828

2929
extension RoundedRectangle {
3030
init(from element: ElementNode) {
31-
let radius: Double
32-
if let s = element.attributeValue(for: "corner-radius"), let d = Double(s) {
33-
radius = d
34-
} else {
35-
radius = 5
31+
let radius = element.attributeValue(for: "corner-radius").flatMap(Double.init) ?? 0
32+
self.init(
33+
cornerSize: .init(
34+
width: element.attributeValue(for: "corner-width").flatMap(Double.init) ?? radius,
35+
height: element.attributeValue(for: "corner-height").flatMap(Double.init) ?? radius
36+
),
37+
style: (element.attributeValue(for: "style").flatMap(RoundedCornerStyle.init) ?? .circular).style
38+
)
39+
}
40+
}
41+
42+
extension Capsule {
43+
init(from element: ElementNode) {
44+
self.init(
45+
style: (element.attributeValue(for: "style").flatMap(RoundedCornerStyle.init) ?? .circular).style
46+
)
47+
}
48+
}
49+
50+
private enum RoundedCornerStyle: String {
51+
case circular
52+
case continuous
53+
54+
var style: SwiftUI.RoundedCornerStyle {
55+
switch self {
56+
case .circular: return .circular
57+
case .continuous: return .continuous
3658
}
37-
self.init(cornerRadius: radius)
3859
}
3960
}

Tests/RenderingTests/ShapeTests.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// ShapeTests.swift
3+
//
4+
//
5+
// Created by Carson Katri on 1/18/23.
6+
//
7+
8+
import XCTest
9+
import SwiftUI
10+
@testable import LiveViewNative
11+
12+
@MainActor
13+
final class ShapeTests: XCTestCase {
14+
func testRectangle() throws {
15+
try assertMatch(#"<rectangle />"#) {
16+
Rectangle()
17+
}
18+
}
19+
20+
func testRoundedRectangle() throws {
21+
try assertMatch(#"<roundedrectangle corner-radius="5" />"#, size: .init(width: 100, height: 50)) {
22+
RoundedRectangle(cornerRadius: 5)
23+
}
24+
try assertMatch(#"<roundedrectangle corner-width="5" corner-height="10" />"#, size: .init(width: 100, height: 50)) {
25+
RoundedRectangle(cornerSize: .init(width: 5, height: 10))
26+
}
27+
try assertMatch(#"<roundedrectangle corner-width="5" corner-radius="15" />"#, size: .init(width: 100, height: 50)) {
28+
RoundedRectangle(cornerSize: .init(width: 5, height: 15))
29+
}
30+
try assertMatch(#"<roundedrectangle corner-height="5" corner-radius="15" />"#, size: .init(width: 100, height: 50)) {
31+
RoundedRectangle(cornerSize: .init(width: 15, height: 5))
32+
}
33+
try assertMatch(#"<roundedrectangle corner-radius="10" style="continuous" />"#, size: .init(width: 100, height: 50)) {
34+
RoundedRectangle(cornerRadius: 10, style: .continuous)
35+
}
36+
}
37+
38+
func testCircle() throws {
39+
try assertMatch(#"<circle />"#) {
40+
Circle()
41+
}
42+
}
43+
44+
func testEllipse() throws {
45+
try assertMatch(#"<ellipse />"#, size: .init(width: 100, height: 50)) {
46+
Ellipse()
47+
}
48+
}
49+
50+
func testCapsule() throws {
51+
try assertMatch(#"<capsule />"#, size: .init(width: 100, height: 50)) {
52+
Capsule()
53+
}
54+
try assertMatch(#"<capsule />"#, size: .init(width: 50, height: 100)) {
55+
Capsule()
56+
}
57+
try assertMatch(#"<capsule style="continuous" />"#, size: .init(width: 100, height: 50)) {
58+
Capsule(style: .continuous)
59+
}
60+
}
61+
62+
func testContainerRelativeShape() throws {
63+
try assertMatch(#"<containerrelativeshape />"#) {
64+
ContainerRelativeShape()
65+
}
66+
}
67+
}

Tests/RenderingTests/assertMatch.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,29 @@ func assertMatch(
1717
_ file: String = #file,
1818
_ line: Int = #line,
1919
_ function: StaticString = #function,
20-
@ViewBuilder _ view: () -> some View,
21-
environment: @escaping (inout EnvironmentValues) -> () = { _ in }
20+
environment: @escaping (inout EnvironmentValues) -> () = { _ in },
21+
size: CGSize? = nil,
22+
@ViewBuilder _ view: () -> some View
2223
) throws {
23-
try assertMatch(name: "\(URL(filePath: file).lastPathComponent)-\(line)-\(function)", markup, view, environment: environment)
24+
try assertMatch(name: "\(URL(filePath: file).lastPathComponent)-\(line)-\(function)", markup, environment: environment, size: size, view)
2425
}
2526

2627
@MainActor
2728
func assertMatch(
2829
name: String,
2930
_ markup: String,
30-
@ViewBuilder _ view: () -> some View,
31-
environment: @escaping (inout EnvironmentValues) -> () = { _ in }
31+
environment: @escaping (inout EnvironmentValues) -> () = { _ in },
32+
size: CGSize? = nil,
33+
@ViewBuilder _ view: () -> some View
3234
) throws {
3335
let session = LiveSessionCoordinator(URL(string: "http://localhost")!)
3436
let document = try LiveViewNativeCore.Document.parse(markup)
3537
let viewTree = session.rootCoordinator.builder.fromNodes(
3638
document[document.root()].children(),
3739
context: LiveContext(coordinator: session.rootCoordinator, url: session.url)
3840
).environment(\.coordinatorEnvironment, CoordinatorEnvironment(session.rootCoordinator, document: document))
39-
let markupImage = ImageRenderer(content: viewTree.transformEnvironment(\.self, transform: environment)).uiImage?.pngData()
40-
let viewImage = ImageRenderer(content: view().transformEnvironment(\.self, transform: environment)).uiImage?.pngData()
41+
let markupImage = ImageRenderer(content: viewTree.transformEnvironment(\.self, transform: environment).frame(width: size?.width, height: size?.height)).uiImage?.pngData()
42+
let viewImage = ImageRenderer(content: view().transformEnvironment(\.self, transform: environment).frame(width: size?.width, height: size?.height)).uiImage?.pngData()
4143

4244
if markupImage == viewImage {
4345
XCTAssert(true)

0 commit comments

Comments
 (0)