feat: 圆形和三角形组件
This commit is contained in:
parent
62dbd2594c
commit
e74040a444
99
wake/SharedUI/Graphics/Circle.swift
Normal file
99
wake/SharedUI/Graphics/Circle.swift
Normal file
@ -0,0 +1,99 @@
|
||||
import SwiftUI
|
||||
|
||||
/// 一个可配置的圆形视图封装,便于在项目中复用。
|
||||
/// 使用示例:
|
||||
/// CircleView(diameter: 24, color: .orange, stroke: .white, lineWidth: 2)
|
||||
public struct CircleView: View {
|
||||
public var diameter: CGFloat
|
||||
public var color: Color
|
||||
public var stroke: Color? = nil
|
||||
public var lineWidth: CGFloat = 1
|
||||
|
||||
public init(
|
||||
diameter: CGFloat,
|
||||
color: Color = .black,
|
||||
stroke: Color? = nil,
|
||||
lineWidth: CGFloat = 1
|
||||
) {
|
||||
self.diameter = diameter
|
||||
self.color = color
|
||||
self.stroke = stroke
|
||||
self.lineWidth = lineWidth
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Group {
|
||||
if let stroke = stroke, lineWidth > 0 {
|
||||
Circle()
|
||||
.fill(color)
|
||||
.overlay(
|
||||
Circle().stroke(stroke, lineWidth: lineWidth)
|
||||
)
|
||||
} else {
|
||||
Circle().fill(color)
|
||||
}
|
||||
}
|
||||
.frame(width: diameter, height: diameter)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// 水平渲染多个等尺寸圆形的小组件。
|
||||
public struct CircleRow: View {
|
||||
public var count: Int
|
||||
public var diameter: CGFloat
|
||||
public var color: Color
|
||||
public var spacing: CGFloat
|
||||
|
||||
public init(
|
||||
count: Int,
|
||||
diameter: CGFloat,
|
||||
color: Color = .black,
|
||||
spacing: CGFloat = 8
|
||||
) {
|
||||
self.count = count
|
||||
self.diameter = diameter
|
||||
self.color = color
|
||||
self.spacing = spacing
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack(spacing: spacing) {
|
||||
ForEach(0..<(max(0, count)), id: \.self) { _ in
|
||||
CircleView(diameter: diameter, color: color)
|
||||
}
|
||||
}
|
||||
.accessibilityElement(children: .ignore)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct CircleView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
CircleView(diameter: 40, color: .black, stroke: .white, lineWidth: 2)
|
||||
.padding()
|
||||
.previewDisplayName("Single Circle")
|
||||
|
||||
ZStack {
|
||||
Color(.black)
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(.white)
|
||||
.shadow(color: Color.black.opacity(0.06), radius: 6, x: 0, y: 2)
|
||||
.overlay(
|
||||
HStack {
|
||||
CircleRow(count: 6, diameter: 10, color: .black, spacing: 6)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
)
|
||||
.frame(height: 56)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.previewDisplayName("Circle Row")
|
||||
}
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
202
wake/SharedUI/Graphics/Triangle.swift
Normal file
202
wake/SharedUI/Graphics/Triangle.swift
Normal file
@ -0,0 +1,202 @@
|
||||
import SwiftUI
|
||||
|
||||
/// 三角形方向
|
||||
public enum TriangleDirection: Equatable {
|
||||
case up
|
||||
case down
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
/// 一个可插入(inset)的三角形 Shape,支持四个方向。
|
||||
struct TriangleShape: InsettableShape {
|
||||
var direction: TriangleDirection = .up
|
||||
var insetAmount: CGFloat = 0
|
||||
|
||||
var animatableData: CGFloat {
|
||||
get { insetAmount }
|
||||
set { insetAmount = newValue }
|
||||
}
|
||||
|
||||
func inset(by amount: CGFloat) -> TriangleShape {
|
||||
var copy = self
|
||||
copy.insetAmount += amount
|
||||
return copy
|
||||
}
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
let r = rect.insetBy(dx: insetAmount, dy: insetAmount)
|
||||
|
||||
// 顶点布局:根据方向决定三点位置
|
||||
let p1: CGPoint
|
||||
let p2: CGPoint
|
||||
let p3: CGPoint
|
||||
|
||||
switch direction {
|
||||
case .up:
|
||||
p1 = CGPoint(x: r.midX, y: r.minY)
|
||||
p2 = CGPoint(x: r.maxX, y: r.maxY)
|
||||
p3 = CGPoint(x: r.minX, y: r.maxY)
|
||||
|
||||
case .down:
|
||||
p1 = CGPoint(x: r.midX, y: r.maxY)
|
||||
p2 = CGPoint(x: r.minX, y: r.minY)
|
||||
p3 = CGPoint(x: r.maxX, y: r.minY)
|
||||
|
||||
case .left:
|
||||
p1 = CGPoint(x: r.minX, y: r.midY)
|
||||
p2 = CGPoint(x: r.maxX, y: r.minY)
|
||||
p3 = CGPoint(x: r.maxX, y: r.maxY)
|
||||
|
||||
case .right:
|
||||
p1 = CGPoint(x: r.maxX, y: r.midY)
|
||||
p2 = CGPoint(x: r.minX, y: r.maxY)
|
||||
p3 = CGPoint(x: r.minX, y: r.minY)
|
||||
}
|
||||
|
||||
var path = Path()
|
||||
path.move(to: p1)
|
||||
path.addLine(to: p2)
|
||||
path.addLine(to: p3)
|
||||
path.closeSubpath()
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
/// 可配置的三角形视图封装,便于在项目中复用。
|
||||
/// 示例:
|
||||
/// TriangleView(width: 20, height: 18, direction: .up, color: .black, stroke: .white, lineWidth: 2)
|
||||
public struct TriangleView: View {
|
||||
public var width: CGFloat
|
||||
public var height: CGFloat
|
||||
public var direction: TriangleDirection
|
||||
public var color: Color
|
||||
public var stroke: Color? = nil
|
||||
public var lineWidth: CGFloat = 1
|
||||
public var rotation: Angle = .degrees(0)
|
||||
|
||||
public init(
|
||||
width: CGFloat,
|
||||
height: CGFloat,
|
||||
direction: TriangleDirection = .up,
|
||||
color: Color = .black,
|
||||
stroke: Color? = nil,
|
||||
lineWidth: CGFloat = 1,
|
||||
rotation: Angle = .degrees(0)
|
||||
) {
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.direction = direction
|
||||
self.color = color
|
||||
self.stroke = stroke
|
||||
self.lineWidth = lineWidth
|
||||
self.rotation = rotation
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
let shape = TriangleShape(direction: direction)
|
||||
Group {
|
||||
if let stroke = stroke, lineWidth > 0 {
|
||||
shape.fill(color)
|
||||
.overlay(
|
||||
shape.stroke(stroke, lineWidth: lineWidth)
|
||||
)
|
||||
} else {
|
||||
shape.fill(color)
|
||||
}
|
||||
}
|
||||
.frame(width: width, height: height)
|
||||
.rotationEffect(rotation)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// 水平渲染多个等尺寸三角形的小组件。
|
||||
public struct TriangleRow: View {
|
||||
public var count: Int
|
||||
public var itemSize: CGSize
|
||||
public var direction: TriangleDirection
|
||||
public var color: Color
|
||||
public var spacing: CGFloat
|
||||
public var rotation: Angle
|
||||
|
||||
public init(
|
||||
count: Int,
|
||||
itemSize: CGSize,
|
||||
direction: TriangleDirection = .up,
|
||||
color: Color = .black,
|
||||
spacing: CGFloat = 8,
|
||||
rotation: Angle = .degrees(0)
|
||||
) {
|
||||
self.count = count
|
||||
self.itemSize = itemSize
|
||||
self.direction = direction
|
||||
self.color = color
|
||||
self.spacing = spacing
|
||||
self.rotation = rotation
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack(spacing: spacing) {
|
||||
ForEach(0..<(max(0, count)), id: \.self) { _ in
|
||||
TriangleView(
|
||||
width: itemSize.width,
|
||||
height: itemSize.height,
|
||||
direction: direction,
|
||||
color: color,
|
||||
rotation: rotation
|
||||
)
|
||||
}
|
||||
}
|
||||
.accessibilityElement(children: .ignore)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct Triangle_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
// 单个三角形
|
||||
TriangleView(width: 24, height: 20, direction: .up, color: .black, stroke: .white, lineWidth: 2)
|
||||
.padding()
|
||||
.previewDisplayName("Single Up")
|
||||
|
||||
TriangleView(width: 24, height: 20, direction: .right, color: .orange)
|
||||
.padding()
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.previewDisplayName("Single Right")
|
||||
|
||||
TriangleView(width: 24, height: 20, direction: .up, color: .blue, rotation: .degrees(132))
|
||||
.padding()
|
||||
.previewDisplayName("Rotated 132")
|
||||
|
||||
// 行展示
|
||||
ZStack {
|
||||
Color(white: 0.97)
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 18, style: .continuous)
|
||||
.fill(.white)
|
||||
.shadow(color: Color.black.opacity(0.06), radius: 6, x: 0, y: 2)
|
||||
.overlay(
|
||||
HStack {
|
||||
TriangleRow(
|
||||
count: 6,
|
||||
itemSize: CGSize(width: 16, height: 14),
|
||||
direction: .up,
|
||||
color: .black,
|
||||
spacing: 6,
|
||||
rotation: .degrees(180)
|
||||
)
|
||||
}
|
||||
.padding(.horizontal, 18)
|
||||
)
|
||||
.frame(height: 64)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.previewDisplayName("Row")
|
||||
}
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Loading…
x
Reference in New Issue
Block a user