liveview-native / liveview-client-swiftui

MIT License
353 stars 31 forks source link

`ShapeStyle` Support #168

Closed carson-katri closed 1 year ago

carson-katri commented 1 year ago

Besides Color, SwiftUI has the ShapeStyle protocol, which can be used with many UI elements. Here are some of the styles available:

as well as modifiers for things like opacity and shadow.

A custom Elixir type that represents AnyShapeStyle that can be JSON encoded could be a good solution. Here is what the encoded values could look like given the Swift representation below:

ShapeStyle.color(hex: "#FF0000")
# {"color":{"_0":{"hex":{"_0":#FF0000"}}}}
ShapeStyle.red
# {"color":{"_0":{"system":{"_0":"red"}}}}
ShapeStyle.primary
# {"hierarchical":{"level":"primary"}}
ShapeStyle.angular_gradient(stops: [{ShapeStyle.red, 0}, {ShapeStyle.blue, 1}], start_angle: 0, end_angle: 90, center: {0.5, 0.5})
# {"angularGradient":{"stops":[{"color":{"system":{"_0":"red"}},"location":0},{"color":{"system":{"_0":"blue"}},"location":1}],"startAngle":0,"endAngle":90,"center":{"x":0.5,"y":0.5}}}
ShapeStyle.conic_gradient(stops: [{ShapeStyle.red, 0}, {ShapeStyle.blue, 1}], angle: 45, center: {0.5, 0.5})
# {"conicGradient":{"stops":[{"color":{"system":{"_0":"red"}},"location":0},{"color":{"system":{"_0":"blue"}},"location":1}],"center":{"x":0.5,"y":0.5},"angle":45}}
ShapeStyle.elliptical_gradient(stops: [{ShapeStyle.red, 0}, {ShapeStyle.blue, 1}], start_radius_fraction: 0, end_radius_fraction: 0.75, center: {0.5, 0.5})
# {"ellipticalGradient":{"stops":[{"color":{"system":{"_0":"red"}},"location":0},{"color":{"system":{"_0":"blue"}},"location":1}],"startRadiusFraction":0,"endRadiusFraction":0.75,"center":{"x":0.5,"y":0.5}}}
ShapeStyle.linear_gradient(stops: [{ShapeStyle.red, 0}, {ShapeStyle.blue, 1}], start_point: {0, 0}, end_point: {1, 0})
# {"linearGradient":{"stops":[{"color":{"system":{"_0":"red"}},"location":0},{"color":{"system":{"_0":"blue"}},"location":1}],"endPoint":{"x":1,"y":0},"startPoint":{"x":0,"y":0}}}
ShapeStyle.radial_gradient(stops: [{ShapeStyle.red, 0}, {ShapeStyle.blue, 1}], start_radius: 0, end_radius: 100, center: {0.5, 0.5})
# {"radialGradient":{"stops":[{"color":{"system":{"_0":"red"}},"location":0},{"color":{"system":{"_0":"blue"}},"location":1}],"startRadius":0,"endRadius":100,"center":{"x":0.5,"y":0.5}}}
ShapeStyle.ultra_thick_material
# {"material":{"_0":"ultraThick"}}
ShapeStyle.image(name: "asset", source_rect: {0, 0, 1, 1}, scale: 1)
# {"image":{"name":"asset","sourceRect":{"y":0,"x":0,"width":1,"height":1},"scale":1}}
ShapeStyle.foreground
# {"foreground":{}}
ShapeStyle.background
# {"background":{}}
ShapeStyle.selection
# {"selection":{}}
ShapeStyle.separator
# {"separator":{}}
ShapeStyle.tint
# {"tint":{}}
ShapeStyle.foreground |> ShapeStyle.blend_mode(:color_burn)
# {"blendMode":{"root":{"foreground":{}},"blendMode":"colorBurn"}}
ShapeStyle.tint |> ShapeStyle.opacity(0.5)
# {"opacity":{"opacity":0.5,"root":{"tint":{}}}}
ShapeStyle.tint |> ShapeStyle.shadow(style: :drop, color: ShapeStyle.black, radius: 10, x: 5, y: 5)
# {"shadow":{"radius":10,"y":5,"style":"drop","root":{"tint":{}},"x":5,"color":{"system":{"_0":"black"}}}}

Swift Representation

This is the type that would get decoded. It can then be mapped to AnyShapeStyle.

indirect enum CodableShapeStyle: Codable {
    case color(CodableColor)

    case hierarchical(level: HierarchyLevel)

    case angularGradient(stops: [GradientStop], center: CodableUnitPoint, startAngle: Double, endAngle: Double)
    case conicGradient(stops: [GradientStop], center: CodableUnitPoint, angle: Double)
    case ellipticalGradient(stops: [GradientStop], center: CodableUnitPoint, startRadiusFraction: Double, endRadiusFraction: Double)
    case linearGradient(stops: [GradientStop], startPoint: CodableUnitPoint, endPoint: CodableUnitPoint)
    case radialGradient(stops: [GradientStop], center: CodableUnitPoint, startRadius: Double, endRadius: Double)

    case material(CodableMaterial)

    case image(name: String, sourceRect: Rect, scale: Double)

    case foreground
    case background
    case selection
    case separator
    case tint

    case blendMode(root: Self, blendMode: CodableBlendMode)
    case opacity(root: Self, opacity: Double)
    case shadow(root: Self, style: CodableShadowStyle, color: CodableColor, radius: Double, x: Double, y: Double)

    enum CodableColor: Codable {
        case system(String)
        case hex(String)
    }

    enum HierarchyLevel: String, Codable {
        case primary
        case secondary
        case tertiary
        case quaternary
    }

    struct GradientStop: Codable {
        let color: CodableColor
        let location: Double
    }

    struct CodableUnitPoint: Codable {
        let x: Double
        let y: Double
    }

    enum CodableMaterial: String, Codable {
        case ultraThin
        case thin
        case regular
        case thick
        case ultraThick
        case bar
    }

    struct Rect: Codable {
        let x: Double
        let y: Double
        let width: Double
        let height: Double
    }

    enum CodableBlendMode: String, Codable {
        case normal

        case darken
        case multiply
        case colorBurn
        case plusDarker

        case lighten
        case screen
        case colorDodge
        case plusLighter

        case overlay
        case softLight
        case hardLight

        case difference
        case exclusion

        case hue
        case saturation
        case color
        case luminosity

        case sourceAtop
        case destinationOver
        case destinationOut
    }

    enum CodableShadowStyle: String, Codable {
        case drop
        case inner
    }
}
AZholtkevych commented 1 year ago

Done by @supernintendo