Skip to content

Commit

Permalink
Merge pull request #35 from chenhaiteng/develop
Browse files Browse the repository at this point in the history
Prepare release
  • Loading branch information
chenhaiteng authored Aug 26, 2021
2 parents 5cc526c + ae5113e commit b477f64
Show file tree
Hide file tree
Showing 26 changed files with 901 additions and 272 deletions.
34 changes: 24 additions & 10 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// swift-tools-version:5.3
// swift-tools-version:5.4
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "Rings",
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)],
platforms: [.macOS(.v11), .iOS(.v14), .tvOS(.v13), .watchOS(.v6)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "CommonExts",
targets: ["CommonExts"]),
name: "Common",
targets: ["Common"]),
.library(
name: "Rings",
targets: ["Rings"]),
Expand All @@ -19,24 +19,38 @@ let package = Package(
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(name: "CoreGraphicsExtension", url: "https://github.com/chenhaiteng/CoreGraphicsExtension.git", from: "0.2.0"),
.package(name: "ArchimedeanSpiral", url: "https://github.com/chenhaiteng/ArchimedeanSpiral.git", from: "1.0.12")
.package(name: "ArchimedeanSpiral", url: "https://github.com/chenhaiteng/ArchimedeanSpiral.git", from: "1.0.12"),
.package(name: "GradientBuilder", url: "https://github.com/chenhaiteng/GradientBuilder.git", .branch("main")),
.package(name: "SequenceBuilder", url: "https://github.com/andtie/SequenceBuilder.git", .branch("main"))
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(name: "CommonExts",
dependencies: []),
.target(name: "Common",
dependencies: [],
exclude: ["PropertyWrapper/Clamping.md"]),
.target(
name: "Rings",
dependencies: ["CoreGraphicsExtension", "CommonExts", "ArchimedeanSpiral"],
dependencies: ["CoreGraphicsExtension", "Common", "ArchimedeanSpiral", "GradientBuilder", "SequenceBuilder"],
exclude: ["RingText.md",
"ClockIndex.md",
"ArchimedeanSpiralText.md",
"HandAiguille.md",
"SphericText.md"]),
"SphericText.md",
"Knob.md",
"KnobComponents/Layers/ArcKnobLayer.md",
"KnobComponents/Layers/ArcKnobDemo.gif"]),
.testTarget(
name: "RingsTests",
dependencies: ["Rings",
"CoreGraphicsExtension", "CommonExts"]),
"CoreGraphicsExtension", "Common"],
exclude: ["RingText.md",
"ClockIndex.md",
"ArchimedeanSpiralText.md",
"HandAiguille.md",
"SphericText.md",
"Knob.md",
"KnobComponents/Layers/ArcKnobLayer.md",
"KnobComponents/Layers/ArcKnobDemo.gif"]),
]
)
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
![Screen Shot 2021-06-15 at 13 01 13](https://user-images.githubusercontent.com/1284944/121996229-c6fd8480-cdda-11eb-9c3e-345681b3e641.png)

# Rings ![GitHub](https://img.shields.io/github/license/chenhaiteng/Rings?style=plastic) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/chenhaiteng/Rings)

**Rings** is a collection of controls which have similar shapes of ring, circle...
Expand Down Expand Up @@ -53,7 +55,7 @@ targets: [
## RingText

### What it looks like
![RingDemo](https://user-images.githubusercontent.com/1284944/115984682-fb26a700-a5da-11eb-8a59-a1554ec41bdf.gif)
![RingDemo](Sources/Rings/RingTextDemo.gif)

### ![How to use it](Sources/Rings/RingText.md)

Expand Down
File renamed without changes.
61 changes: 61 additions & 0 deletions Sources/Common/PropertyWrapper/Clamping.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
## Clamping

### Purpose:
1. For values that have upper and lower bound, provide a way to not write duplicate code.
2. In swift, when one try to preprocess an property before apply it, it needs following code:
```swift
private var _computedVarStorage: Int
public var computedVar: Int {
get {
return _computedVarStorage
}
set {
_computedVarStorage = newValue*10
}
}
```
Because the get/set syntax will change variable to computed variable, it always need write a getter, even through we want a write-only property.

Also, to keep the modified value, a extra storage variable is needed, and it looks ugly.

3. In additionally, we also want the solution could be apply to protocol.

### Solution:
1. Use @propertyWrapper to apply clamp; replace max, min and degree into one declaration.
```swift
@Clamping(max: 0.0, min: 360.0) var degree = 0.0
@Clamping(0.0...1.0) var value = 0.5
```
2. In some situation, it need modify the range later, property wrapper use projectedValue to implement this requirement:
```swift
@Clamping(0.0...1.0) var value = 0.5
$value = 0.0...10.0
```
3. Although @propertyWrapper is useful, it hard to apply this mechanism to protocol. However, there has a workaround:
```swift
protocol ClampProtocol {
var degree: Double // Can declare as @Clamping
var range: ClosedRange<Double> // Can map to projectedValue in @Clamping
}

struct ClampStruct {
@Clamping(0.0...360.0) var degree = 0.0
var range: ClosedRange<Double> {
get { $degree }
set { $degree = newValue }
}
}
```
With this workaround, all class/struct implement ClampProtocol can apply @Clamping to simplify the effort to write clamp function, and keep it interface clear.

The detail about the implementation of @Clamping could be refer to : [Clamping.swift](Clamping.swift)

### References:

[Property Wrapper - Swift Doc](https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID617)

[Property Wrapper(SE-0258) - Swift Evolution](https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md)

[Swift: Why does a variable with a setter must also have a getter? - stackoverflow](https://stackoverflow.com/a/34677538/505763)

[PropertyWrappers and protocol declaration? - stackoverflow](https://stackoverflow.com/a/57657870/505763)
46 changes: 46 additions & 0 deletions Sources/Common/PropertyWrapper/Clamping.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Clamping.swift
//
//
// Created by Chen-Hai Teng on 2021/6/11.
//

import Foundation

@propertyWrapper
public struct Clamping<T> where T:Comparable {
private(set) var value: T
var range: ClosedRange<T>
public var wrappedValue: T {
get {
return value
}
set {
value = clamp(newValue)
}
}

public var projectedValue: ClosedRange<T> {
get { range }
set {
range = newValue
value = clamp(value)
}
}

private func clamp(_ v: T) -> T {
max(min(v, range.upperBound), range.lowerBound)
}

public init(wrappedValue: T, _ range: ClosedRange<T>) {
self.range = range
value = wrappedValue // 1st phase, initialize properties.
value = clamp(wrappedValue) // 2nd phase, custom the value of property
}

public init(wrappedValue: T, max: T, min: T) {
self.range = min...max
value = wrappedValue
value = clamp(wrappedValue)
}
}
File renamed without changes.
2 changes: 1 addition & 1 deletion Sources/Rings/ClockIndex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import SwiftUI
import CoreGraphicsExtension
import CommonExts
import Common

public enum ClockIndexError: Error {
case outOfBounds(String)
Expand Down
9 changes: 5 additions & 4 deletions Sources/Rings/HandAiguille.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ https://user-images.githubusercontent.com/1284944/117106480-83aeff80-adb2-11eb-8

### Usage:
```swift
// Create empty hand aiguille with default size, and set the hand aiguille background red
// Create empty hand aiguille with default size.
// When there is no embedded view in hand aiguille, it shows gray rectangle as placeholder.
HandAiguille() {
}.handBackground(Color.red)
}

// Create empty hand aiguille with time provider, and specify its time unit.
@State var hourProvider: Double = 0.0
HandAiguille(time: $hourProvider, unit: .hour) {
}.handBackgroudn(Color.red)
}

// Create hand aiguille with Image
@State var secsProvider: Double = 0.0
Expand All @@ -21,5 +22,5 @@ https://user-images.githubusercontent.com/1284944/117106480-83aeff80-adb2-11eb-8
}

// Create apple watch style hand
HandFactory.standard.makeAppleWatchStyleHand(time: $secsProvider)
HandFactory.makeAppleWatchStyleHand(time: $secsProvider)
```
21 changes: 6 additions & 15 deletions Sources/Rings/HandAiguille.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct HandAiguille<Content: View, T: BinaryFloatingPoint> : View {
private var timeUnit: TimeUnit

private var showBlueprint: Bool = false
private var handBackground: AnyView = AnyView(Color.clear)
private var handPlaceholder: Color = .gray

public init(size: CGSize = CGSize(width: 3.0, height: 50.0), offset: T = 1.5, time: Binding<T> = .constant(0), unit: TimeUnit = .second, @ViewBuilder content: @escaping () -> Content) {
self.handSize = size
Expand All @@ -61,7 +61,7 @@ public struct HandAiguille<Content: View, T: BinaryFloatingPoint> : View {
p.stroke(Color.blue)
}
if(content() is EmptyView) {
handBackground.frame(width: handSize.width, height: handSize.height, alignment: .center).offset(y: -yoffset).rotationEffect(angleOfTime(time))
handPlaceholder.frame(width: handSize.width, height: handSize.height, alignment: .center).offset(y: -yoffset).rotationEffect(angleOfTime(time))
} else {
content().frame(width: handSize.width, height: handSize.height, alignment: .center).offset(y: -yoffset).rotationEffect(angleOfTime(time))
}
Expand Down Expand Up @@ -97,18 +97,10 @@ extension HandAiguille {
tmp.showBlueprint = isOn
}
}

public func handBackground<Background>(_ background: Background) -> Self where Background : View {
setProperty{ tmp in
tmp.handBackground = AnyView(background)
}
}
}

public struct HandFactory {
public static let standard = HandFactory()
private let rectRatio: CGFloat = 0.2
public func makeAppleWatchStyleHand<T: BinaryFloatingPoint>(size: CGSize = CGSize(width: 4.0, height: 60.0), timeProvider: Binding<T>, unit: TimeUnit = .second) -> some View {
public enum HandFactory {
public static func makeAppleWatchStyleHand<T: BinaryFloatingPoint>(size: CGSize = CGSize(width: 4.0, height: 60.0), timeProvider: Binding<T>, unit: TimeUnit = .second) -> some View {
HandAiguille(size: size, offset: 1.5, time: timeProvider, unit: unit) {
VStack(spacing: 0) {
Capsule().stroke().frame(width: size.width)
Expand All @@ -119,7 +111,6 @@ public struct HandFactory {
}
}


struct RoundedBorder: ViewModifier {
var cornerRadius:CGFloat = 5
var color = Color.white
Expand Down Expand Up @@ -160,8 +151,8 @@ struct AppleStyleHandPreview: View {
}.blueprint(showBlueprint).frame(width: 100, height: 100, alignment: .center)
}
ZStack {
HandFactory.standard.makeAppleWatchStyleHand(size: CGSize(width: 5.0, height: 80.0),timeProvider: $emulateTime, unit: .hour).frame(width: 120, height: 120, alignment: .center)
HandFactory.standard.makeAppleWatchStyleHand(size: CGSize(width: 4.0, height: 60.0),timeProvider: $emulateTime, unit: .minute).frame(width: 120, height: 120, alignment: .center)
HandFactory.makeAppleWatchStyleHand(size: CGSize(width: 5.0, height: 80.0),timeProvider: $emulateTime, unit: .hour).frame(width: 120, height: 120, alignment: .center)
HandFactory.makeAppleWatchStyleHand(size: CGSize(width: 4.0, height: 60.0),timeProvider: $emulateTime, unit: .minute).frame(width: 120, height: 120, alignment: .center)
}
}

Expand Down
54 changes: 31 additions & 23 deletions Sources/Rings/Knob.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,49 @@

### Usage

Following shows a basic Knob drawing value along the circumference.
```swift
// Baisc Knob drawing value along the circumference.
@State knobValue : Double // default range: 0.0...1.0
Knob($knobValue) // Create a Knob with default mapping(LinearMapping)
.addLayer(ArcKnobLayer() // Add ArcKnobLayer to draw circumference.
.arcWidth(10.0)
.arcColor(.blue.opacity(0.7)))
.frame(width: 100.0, height: 100.0)
@State knobValue : Double // default range: 0.0...1.0
Knob($knobValue) {
ArcKnobLayer() // Add ArcKnobLayer to draw circumference.
.arcWidth(10.0)
.arcColor {
.blue.opacity(0.7)
}
}.frame(width: 100.0, height: 100.0) // setup knob size
```
<img src="https://user-images.githubusercontent.com/1284944/120065862-1d15bc80-c0a6-11eb-876f-687db7b35d00.gif" alt="drawing" width="200"/>
![Demo1](KnobDemo1.gif)

---

By adding a ring layer, it makes a Knob which has a circular track.
```swift
// A Knob drawing value along circular track.
@State knobValue : Double // default range: 0.0...1.0.
Knob($knobValue) // Create a Knob with default mapping(LinearMapping)
.addLayer(RingKnobLayer() // Add RingKnobLayer as the track.
.ringWidth(10.0)
.ringColor(.red.opacity(0.5)))
.addLayer(ArcKnobLayer() // Add ArcKnobLayer to draw circumference.
.arcWidth(10.0)
.arcColor(.blue.opacity(0.7)))
.frame(width: 100.0, height: 100.0)
@State knobValue : Double // default range: 0.0...1.0.
Knob($knobValue) {
RingKnobLayer() // Add RingKnobLayer as the track. It has no need to setup value and mapping on RingKnobLayer.
.ringWidth(10.0)
.color {
.red.opacity(0.5)
}
ArcKnobLayer() // Add ArcKnobLayer to draw circumference.
.arcWidth(10.0)
.arcColor {
.blue.opacity(0.7)
}
}.frame(width: 100.0, height: 100.0)
```
<img src="https://user-images.githubusercontent.com/1284944/120066040-2bb0a380-c0a7-11eb-865e-e4f2220ffead.gif" alt="drawing" width="200"/>
![Demo2](KnobDemo2.gif)

Also, it can make the knob and its track much richer by adjusting each layer. For more detail, see [ArcKnobLayer](KnobComponents/Layers/ArcKnobLayer.md)

---

Finally, it can apply images on knob.
```swift
// A Knob with rotate image
@State knobValue : Double // default range: 0.0...1.0, the range of knob value depends on mapping object.
Knob($knobValue)
.addLayer(ImageKnobLayer(Image("SimpleKnob")))
.frame(width: 150, height: 150)
Knob($knobValue) {
ImageKnobLayer(Image("SimpleKnob"))
}.frame(width: 150, height: 150)
```
<img src="https://user-images.githubusercontent.com/1284944/120066082-61ee2300-c0a7-11eb-97e5-4a64b0bd4e8e.gif" alt="drawing" width="200"/>

Expand Down
Loading

0 comments on commit b477f64

Please sign in to comment.