-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMustache.swift
More file actions
104 lines (96 loc) · 4.72 KB
/
Copy pathMustache.swift
File metadata and controls
104 lines (96 loc) · 4.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import SwiftUI
/// The Vectorito brand mark — a symmetric handlebar mustache. The right wing's bezier
/// path is transcribed from the design (WING_RIGHT, viewBox 240×110); the left is mirrored.
struct MustacheShape: Shape {
func path(in rect: CGRect) -> Path {
let sx = rect.width / 240, sy = rect.height / 110
func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint {
CGPoint(x: rect.minX + x * sx, y: rect.minY + y * sy)
}
func wing(mirrored: Bool) -> Path {
// mirror across x = 120 → x' = 240 - x
func mx(_ x: CGFloat) -> CGFloat { mirrored ? 240 - x : x }
func q(_ x: CGFloat, _ y: CGFloat) -> CGPoint { p(mx(x), y) }
var path = Path()
path.move(to: q(120, 47))
path.addCurve(to: q(176, 34), control1: q(136, 36), control2: q(154, 31))
path.addCurve(to: q(222, 65), control1: q(200, 37), control2: q(217, 49))
path.addCurve(to: q(208, 79), control1: q(225, 75), control2: q(218, 83))
path.addCurve(to: q(185, 57), control1: q(199, 75), control2: q(201, 63))
path.addCurve(to: q(133, 62), control1: q(167, 50), control2: q(147, 54))
path.addCurve(to: q(120, 57), control1: q(127, 65), control2: q(123, 62))
path.closeSubpath()
return path
}
var path = wing(mirrored: false)
path.addPath(wing(mirrored: true))
// center knot ellipse cx120 cy55 rx9 ry11
path.addEllipse(in: CGRect(x: p(111, 44).x, y: p(111, 44).y,
width: 18 * sx, height: 22 * sy))
return path
}
}
struct Mustache: View {
var size: CGFloat = 220
var color: Color = Theme.ink
var wink: Bool = false
private var h: CGFloat { size * (110.0 / 240.0) }
var body: some View {
ZStack {
MustacheShape().fill(color)
if wink {
GeometryReader { geo in
let sx = geo.size.width / 240, sy = geo.size.height / 110
Circle()
.fill(color)
.frame(width: 13 * sx, height: 13 * sy)
.position(x: 92 * sx, y: 22 * sy)
}
}
}
.frame(width: size, height: h)
}
}
/// Bezier-scan overlay shown while Vectorito "reads" the image: detected anchor squares,
/// agave control points, and a sweeping marigold scan line. Coordinates are in 0…100 %.
struct BezierScan: View {
var progress: Double
private let anchors: [(CGFloat, CGFloat)] = [
(18, 28), (38, 64), (62, 22), (70, 70), (50, 44),
(82, 50), (28, 80), (88, 32), (12, 58), (55, 88),
]
var body: some View {
Canvas { ctx, size in
let w = size.width, h = size.height
func pt(_ a: (CGFloat, CGFloat)) -> CGPoint { CGPoint(x: a.0 / 100 * w, y: a.1 / 100 * h) }
// connecting handle lines
for i in anchors.indices where progress > Double(i) / Double(anchors.count) {
let a = pt(anchors[i]), b = pt(anchors[(i + 1) % anchors.count])
var p = Path(); p.move(to: a); p.addLine(to: b)
ctx.stroke(p, with: .color(Theme.marigold.opacity(0.5)),
style: StrokeStyle(lineWidth: 0.8, dash: [3, 3]))
}
// anchor nodes + control points
for i in anchors.indices where progress > Double(i) / Double(anchors.count) {
let a = pt(anchors[i])
let c = CGPoint(x: a.x + 4 / 100 * w, y: a.y - 3 / 100 * h)
var hp = Path(); hp.move(to: a); hp.addLine(to: c)
ctx.stroke(hp, with: .color(Theme.agave.opacity(0.7)), lineWidth: 0.6)
let sq = CGRect(x: a.x - 2.2, y: a.y - 2.2, width: 4.4, height: 4.4)
ctx.fill(Path(sq), with: .color(.white))
ctx.stroke(Path(sq), with: .color(Theme.terracotta), lineWidth: 1)
ctx.fill(Path(ellipseIn: CGRect(x: c.x - 1.2, y: c.y - 1.2, width: 2.4, height: 2.4)),
with: .color(Theme.agave))
}
// sweep line + glow
let x = progress * w
var sweep = Path(); sweep.move(to: CGPoint(x: x, y: 0)); sweep.addLine(to: CGPoint(x: x, y: h))
ctx.fill(Path(CGRect(x: Swift.max(0, x - 14), y: 0, width: 14, height: h)),
with: .linearGradient(
Gradient(colors: [Theme.marigold.opacity(0), Theme.marigold.opacity(0.5)]),
startPoint: CGPoint(x: Swift.max(0, x - 14), y: 0), endPoint: CGPoint(x: x, y: 0)))
ctx.stroke(sweep, with: .color(Theme.marigold.opacity(0.9)), lineWidth: 1.4)
}
.allowsHitTesting(false)
}
}