# Tutorials ⟩ Extensions

## Custom Types

{% tabs %}
{% tab title="files" %}
* Rectangular.swift (4)
* Vector2D.swift (5)
{% endtab %}

{% tab title="4" %}
````swift
//
//  Rectangular.swift
//  -----------------
//  1.0.0: 2021/11/18
//  1.0.1: 2021/11/24 + 🌀CGPoint + 🅿️ Rectangular
//  1.0.2: 2021/11/25
//    - fix `inscribedSquare` bug,
//    + Self.unitSquare
//    + 🌀UnitPoint + 🅿️ Rectangular
//

import SwiftUI

// 🅿️ Rectangular
public protocol Rectangular {
    var origin: CGPoint { get }
    var size  : CGSize  { get }
}

// default behaviors
extension Rectangular {
    
    // MARK: - Bounds -
    
    /// the bounds (x-coordinates) of the range of width
    public var boundsX: (CGFloat, CGFloat) {
        let x1 = origin.x
        let x2 = x1 + size.width
        return (x1, x2)
    }
    
    /// the bounds (y-coordinates) of the range of height
    public var boundsY: (CGFloat, CGFloat) {
        let y1 = origin.y
        let y2 = y1 + size.height
        return (y1, y2)
    }

    // MARK: - Coordinates -
    
    /// lower bound (x-coordinate) of the width
    public var minX: CGFloat {
        return min(boundsX.0, boundsX.1)
    }
    
    /// upper bound (x-coordinate) of the width
    public var maxX: CGFloat {
        return max(boundsX.0, boundsX.1)
    }
    
    /// midpoint (x-coordinate) of the width
    public var midX: CGFloat {
        origin.x + size.width / 2
    }
    
    /// lower bound (y-coordinate) of the height
    public var minY: CGFloat {
        return min(boundsY.0, boundsY.1)
    }
    
    /// upper bound (y-coordinate) of the height
    public var maxY: CGFloat {
        return max(boundsY.0, boundsY.1)
    }
    
    /// midpoint (y-coordinate) of the height
    public var midY: CGFloat {
        origin.y + size.height / 2
    }
    
    // MARK: - Points -
    
    // rect.top, ...
    public var top        : CGPoint { CGPoint(x: midX, y: minY) }
    public var bottom     : CGPoint { CGPoint(x: midX, y: maxY) }
    public var left       : CGPoint { CGPoint(x: minX, y: midY) }
    public var right      : CGPoint { CGPoint(x: maxX, y: midY) }
    public var center     : CGPoint { CGPoint(x: midX, y: midY) }
    public var bottomLeft : CGPoint { CGPoint(x: minX, y: maxY) }
    public var bottomRight: CGPoint { CGPoint(x: maxX, y: maxY) }
    public var topLeft    : CGPoint { CGPoint(x: minX, y: minY) }
    public var topRight   : CGPoint { CGPoint(x: maxX, y: minY) }
    
    /// 4 corner points of a rect
    public var corners: [CGPoint] { [bottomLeft, bottomRight, topRight, topLeft] }
    
    /// a point relative to the rectangle's size (width, height)
    ///
    /// ```
    /// rect[1,0] == rect.topRight
    /// rect[0,1] == rect.bottomLeft
    /// ```
    public subscript(_ s: CGFloat, _ t: CGFloat) -> CGPoint {
        let x = minX + s * abs(width)
        let y = minY + t * abs(height)
        return CGPoint(x: x, y: y)
    }
    
    // MARK: - Dimensions -
    
    public var width  : CGFloat { abs(size.width)  }    // ⚠️ abs() 的設定對已經有 width 屬性的類別應該無效❗️
    public var height : CGFloat { abs(size.height) }
    public var minSide: CGFloat { min(abs(width), abs(height)) }
    public var maxSide: CGFloat { max(abs(width), abs(height)) }
    
    public var aspectRatio: CGFloat { abs(width/height) }
    
    // MARK: - Rect -
    
    /// unit square of conforming type
    public static var unitSquare: CGRect {
        CGRect(origin: [0.0, 0.0], size: [1.0, 1.0])
    }
    
    public var rect: CGRect {
        CGRect(origin: origin, size: size)
    }
    
    public var boundingSquare: CGRect {
        let d = maxSide / 2
        let x = midX - d
        let y = midY - d
        return CGRect(x: x, y: y, width: maxSide, height: maxSide)
    }
    
    public var inscribedSquare: CGRect {
        let d = minSide / 2
        let x = midX - d
        let y = midY - d
        return CGRect(x: x, y: y, width: minSide, height: minSide)
    }
    
}

// MARK: - Conforming Types -

// 🌀CGRect + 🅿️ Rectangular
extension CGRect: Rectangular { }

// 🌀CGSize + 🅿️ Rectangular
extension CGSize: Rectangular {
    public var origin: CGPoint { .zero }
    public var size  : CGSize  {  self }
}

// 🌀CGPoint + 🅿️ Rectangular
extension CGPoint: Rectangular {
    public var origin: CGPoint { .zero }
    public var size  : CGSize  {  CGSize(width: x, height: y) }
}

// 🌀UnitPoint + 🅿️ Rectangular
extension UnitPoint: Rectangular {
    public var origin: CGPoint { .zero }
    public var size  : CGSize  {  CGSize(width: x, height: y) }
}

// 🌀GeometryProxy + 🅿️ Rectangular
extension GeometryProxy: Rectangular {
    public var origin: CGPoint { .zero }
}

````
{% endtab %}

{% tab title="5" %}
````swift
//
//  Vector2D.swift
//  --------------
//
//  1.0.0: 2020.10.10
//

import SwiftUI

// ⭐️ define custom operators
infix operator ×  : MultiplicationPrecedence  // cross (determinant)
infix operator •  : MultiplicationPrecedence  // dot   (• : opt + 8)
infix operator ** : MultiplicationPrecedence  // non-proportional scale
infix operator **=: MultiplicationPrecedence

// 🅿️ Vector2D
public protocol Vector2D: ExpressibleByArrayLiteral {
    
    associatedtype Field: FloatingPoint     // support +,-,*,/
    
    // x, y coordinates
    var x: Field { get }
    var y: Field { get }
    
    // initializer
    init(x: Field, y: Field)
    
    // vector addition: u + v
    static func + (u: Self, v: Self) -> Self
    // additive inverse: -v
    static prefix func - (u: Self) -> Self
    
    // scalar multiplication: a * v
    // has default implementation in extension
}

// MARK: - Vector2D protocol extension -

// default behaviors
extension Vector2D {
    
    /* ------- Constants ------- */
    
    public static var i: Self { return Self.init(x: 1, y: 0) }
    public static var j: Self { return Self.init(x: 0, y: 1) }
    
    /* ------- Initializers ------- */
    
    /// make `Vector2D` conform to `ExpressibleByArrayLiteral`
    ///
    /// Example:
    /// ```
    /// let p: CGPoint = [1, 2]
    /// ```
    public init(arrayLiteral elements: Field...) {
        let a = elements + [0, 0]   // padding 0's
        self.init(x: a[0], y: a[1])
    }
    
    /// convenience initializer
    ///
    /// example:
    /// ```
    /// let p = CGPoint(1, 2)
    /// ```
    public init(_ x: Field, _ y: Field) {
        self.init(x: x, y: y)
    }
    
    /* ------- Linear Combinations ------- */
    
    /// u + v
    public static func + (u: Self, v: Self) -> Self {
        return Self.init(x: u.x + v.x, y: u.y + v.y)
    }
    /// -v (additive inverse)
    public static prefix func - (v: Self) -> Self {
        return Self.init(x: -v.x, y: -v.y)
    }
    
    /// vector subtraction
    /// `u - v == u + (-v)`
    public static func - (u: Self, v: Self) -> Self {
        return u + (-v)
    }
    
    /* ------- Scalar Multiplication ------- */
    
    /// a * v
    public static func * (a: Field, v: Self) -> Self {
        return Self.init(x: a * v.x, y: a * v.y)
    }
    /// v * a
    public static func * (v: Self, a: Field) -> Self {
        return a * v
    }
    /// v / a
    public static func / (v: Self, a: Field) -> Self {
        return Self.init(x: v.x / a, y: v.y / a)
    }
    
    /* ------- Dot Product, Cross Product ------- */
    
    /// cross product: `u × v`
    public static func × (u: Self, v: Self) -> Field {
        return u.x * v.y - u.y * v.x  // x1 y2 - x2 y1
    }
    /// dot product: `u • v`
    public static func • (u: Self, v: Self) -> Field {
        return u.x * v.x + u.y * v.y  // x1 x2 + y1 y2
    }
    
    /* ------- Complex Number ------- */
    
    /// complex multiplication: `u * v`
    public static func * (u: Self, v: Self) -> Self {
        let (a,b,c,d) = (u.x, u.y, v.x, v.y)       // u = a + bi, v = a - bi
        return Self.init(x: a * c - b * d, y: a * d + b * c) // uv = (ac-bd) + (ad+bc)i
    }
    
    /// complex multiplication: u *= v
    public static func *= (u: inout Self, v: Self) {
        u = u * v
    }
    
    /* ------- Other Operations ------- */
    
    /// (non-proportional scale)
    /// if `u = [a,b], v = [c,d]`, then `u ** v = [ac, bd]`
    public static func ** (u: Self, v: Self) -> Self {
        return Self.init(x: u.x * v.x, y: u.y * v.y)
    }
    
}

// MARK: - Field == CGFloat -

extension Vector2D where Field == CGFloat {
    
    /* ------- Polar Coordinates ------- */
    
    /// polar form of a 2D vector
    /// - Parameters:
    ///   - r: radius
    ///   - a: angle (in radian)
    /// - Returns: `Self`: the conforming type
    public static func polar(_ r: Field, _ a: Field) -> Self {
        Self.init(x: r * cos(a), y: r * sin(a))
    }
    
}

// MARK: - Conforming Types -

// 🌀CGPoint + Vector2D
extension CGPoint: Vector2D {}

// 🌀UnitPoint + Vector2D
extension UnitPoint: Vector2D {}

// 🌀CGSize + Vector2D
extension CGSize: Vector2D {
    public var x: CGFloat { width }
    public var y: CGFloat { height }
    public init(x: CGFloat, y: CGFloat) {
        self.init(width: x, height: y)
    }
}

// 🌀CGVector + Vector2D
extension CGVector: Vector2D {
    public var x: CGFloat { dx }
    public var y: CGFloat { dy }
    public init(x: CGFloat, y: CGFloat) {
        self.init(dx: x, dy: y)
    }
}


````
{% endtab %}
{% endtabs %}

## Extensions

{% tabs %}
{% tab title="files" %}
* View+ext.swift (2)
* CGSize+ext.swift (3)
* AnyTransition+ext.swift (6)
* Animation+ext.swift (7)
{% endtab %}

{% tab title="2" %}
````swift
import SwiftUI

extension View {
    /// set the device (by device name) for preview.
    ///
    /// example:
    /// ```
    /// // deviceName: String
    /// .previewDevice(deviceName)
    /// ```
    /// - Parameter deviceName: device name, e.g. `"iPhone X"`
    /// - Returns: `some View`
    public func previewDevice(_ deviceName: String) -> some View {
        previewDevice(PreviewDevice(rawValue: deviceName))
    }
}
````
{% endtab %}

{% tab title="3" %}
````swift
import CoreGraphics

extension CGSize {
    /// `size.point`: turn a `CGSize` into `CGPoint`
    ///
    /// Example:
    /// ```
    /// let size = CGSize(width: 20, height: 30)
    /// let p = size.point      // CGPoint(x: 20, y: 30)
    /// ```
    public var point: CGPoint { CGPoint(x: width, y: height) }
}
````
{% endtab %}

{% tab title="6" %}
```swift
import SwiftUI

extension AnyTransition {
    /// AnyTransition.moveAndFade
    
    static var moveAndFade: AnyTransition {
        /// Use `asymmetric(insertion:removal:)` to provide different transitions
        /// for when the view appears and disappears.
        .asymmetric(
            insertion: .move(edge: .trailing).combined(with: .opacity),
            removal  : .scale.combined(with: .opacity)
        )
    }
}
```
{% endtab %}

{% tab title="7" %}
```swift
import SwiftUI

extension Animation {
    /// `Animation.ripple()`
    /// - Returns: `Animation`
    static func ripple(index: Int) -> Animation {
        
        Animation
            /// a reduced damping fraction makes the views hop.
            .spring(dampingFraction: 0.5)
            /// speed up the animation, to shorten the time
            /// it takes to move to its new position.
            .speed(2)
            /// add a delay to each animation based on
            /// the view's `index` on the graph.
            .delay(0.03 * Double(index))
    }
}
```
{% endtab %}
{% endtabs %}
