Skip to content

Commit 0cbc31f

Browse files
Add missing ShapeStyles (#837)
* Add hierarchical shape style and document shape style construction * Add opacity modifier * Add Material style * Add AngularGradient * Add EllipticalGradient * Add RadialGradient * Add ImagePaint * Add simple styles * Add blend_mode modifier * Add shadow modifier * Update lib/live_view_native_swift_ui/types/shape_style/image_paint.ex Co-authored-by: May Matyi <may@matyi.net> Signed-off-by: Carson Katri <Carson.katri@gmail.com> * Update lib/live_view_native_swift_ui/types/shape_style/shadow_style.ex Co-authored-by: May Matyi <may@matyi.net> Signed-off-by: Carson Katri <Carson.katri@gmail.com> * Fix syntax error --------- Signed-off-by: Carson Katri <Carson.katri@gmail.com> Co-authored-by: May Matyi <may@matyi.net>
1 parent d21b1b9 commit 0cbc31f

22 files changed

Lines changed: 921 additions & 35 deletions
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//
2+
// BlendMode.swift
3+
//
4+
//
5+
// Created by Carson Katri on 4/25/23.
6+
//
7+
8+
import SwiftUI
9+
10+
/// Mode for blending colors.
11+
///
12+
/// Possible values:
13+
/// * `normal`
14+
/// * `darken`
15+
/// * `multiply`
16+
/// * `color_burn`
17+
/// * `plus_darker`
18+
/// * `lighten`
19+
/// * `screen`
20+
/// * `color_dodge`
21+
/// * `plus_lighter`
22+
/// * `overlay`
23+
/// * `soft_light`
24+
/// * `hard_light`
25+
/// * `difference`
26+
/// * `exclusion`
27+
/// * `hue`
28+
/// * `saturation`
29+
/// * `color`
30+
/// * `luminosity`
31+
/// * `source_atop`
32+
/// * `destination_over`
33+
/// * `destination_out`
34+
#if swift(>=5.8)
35+
@_documentation(visibility: public)
36+
#endif
37+
extension BlendMode: Decodable {
38+
public init(from decoder: Decoder) throws {
39+
switch try decoder.singleValueContainer().decode(String.self) {
40+
case "normal": self = .normal
41+
case "darken": self = .darken
42+
case "multiply": self = .multiply
43+
case "color_burn": self = .colorBurn
44+
case "plus_darker": self = .plusDarker
45+
case "lighten": self = .lighten
46+
case "screen": self = .screen
47+
case "color_dodge": self = .colorDodge
48+
case "plus_lighter": self = .plusLighter
49+
case "overlay": self = .overlay
50+
case "soft_light": self = .softLight
51+
case "hard_light": self = .hardLight
52+
case "difference": self = .difference
53+
case "exclusion": self = .exclusion
54+
case "hue": self = .hue
55+
case "saturation": self = .saturation
56+
case "color": self = .color
57+
case "luminosity": self = .luminosity
58+
case "source_atop": self = .sourceAtop
59+
case "destination_over": self = .destinationOver
60+
case "destination_out": self = .destinationOut
61+
case let `default`:
62+
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "unknown blend mode '\(`default`)'"))
63+
}
64+
}
65+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// LinearGradient.swift
3+
// LiveViewNative
4+
//
5+
// Created by Carson Katri on 4/21/23.
6+
//
7+
import SwiftUI
8+
9+
/// A shape style that creates a conic gradient.
10+
///
11+
/// To create this shape style, create a map or keyword list with the `gradient` key set to a ``LiveViewNative/SwiftUI/Gradient`` value.
12+
///
13+
/// Specify an `angle` to create a full rotation with an offset.
14+
/// Provide a `start_angle` and `end_angle` to describe a partial rotation.
15+
///
16+
/// ```elixir
17+
/// [gradient: {:colors, [:pink, :blue, 0.9]}, angle: {:degrees, 45}]
18+
/// [gradient: {:colors, [:pink, :blue, 0.9]}, start_angle: {:degrees, 45}, end_angle: {:degrees, 90}]
19+
/// ```
20+
///
21+
/// See ``LiveViewNative/SwiftUI/Angle`` for more details on creating angles.
22+
///
23+
/// Set the `center` argument to a ``LiveViewNative/SwiftUI/UnitPoint`` value to offset the origin of the rotation.
24+
///
25+
/// ```elixir
26+
/// [gradient: {:stops, [{:pink, 0.8}, {:blue, 0.9}]}, angle: {:degrees, 45}, center: {0, 0}]
27+
/// ```
28+
#if swift(>=5.8)
29+
@_documentation(visibility: public)
30+
#endif
31+
extension AngularGradient: Decodable {
32+
public init(from decoder: Decoder) throws {
33+
let container = try decoder.container(keyedBy: CodingKeys.self)
34+
let gradient = try container.decode(Gradient.self, forKey: .gradient)
35+
let center = try container.decode(UnitPoint.self, forKey: .center)
36+
if let angle = try container.decodeIfPresent(Angle.self, forKey: .angle) {
37+
self.init(gradient: gradient, center: center, angle: angle)
38+
} else {
39+
self.init(
40+
gradient: gradient,
41+
center: center,
42+
startAngle: try container.decode(Angle.self, forKey: .startAngle),
43+
endAngle: try container.decode(Angle.self, forKey: .endAngle)
44+
)
45+
}
46+
}
47+
48+
enum CodingKeys: String, CodingKey {
49+
case gradient
50+
case center
51+
case angle
52+
case startAngle = "start_angle"
53+
case endAngle = "end_angle"
54+
}
55+
}

Sources/LiveViewNative/Utils/ShapeStyle/AnyShapeStyle.swift

Lines changed: 237 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,249 @@
77

88
import SwiftUI
99

10+
/// A color, gradient, or other style.
11+
///
12+
/// Create a shape style with a type, options, and modifiers.
13+
///
14+
/// ```elixir
15+
/// {:color, :blue}
16+
/// {:color, :red, [{:opacity, 0.5}]}
17+
/// ```
18+
///
19+
/// ## Shape Styles
20+
/// To create a shape style, use a tuple where the first element is an atom for the style type, and the second element is the style value.
21+
///
22+
/// ```elixir
23+
/// {type, value}
24+
/// ```
25+
///
26+
/// ### :color
27+
/// See ``LiveViewNative/SwiftUI/Color/init(from:)`` for a list of possible color values.
28+
///
29+
/// ```elixir
30+
/// {:color, :blue}
31+
/// ```
32+
///
33+
/// ### :angular_gradient
34+
/// See ``LiveViewNative/SwiftUI/AngularGradient`` for details on creating this style.
35+
///
36+
/// ```elixir
37+
/// {:angular_gradient, [gradient: {:colors, [:pink, :blue]}, angle: {:degrees, 45}]}
38+
/// ```
39+
///
40+
/// ### :elliptical_gradient
41+
/// See ``LiveViewNative/SwiftUI/EllipticalGradient`` for details on creating this style.
42+
///
43+
/// ```elixir
44+
/// {:elliptical_gradient, [gradient: {:colors, [:pink, :blue]}]}
45+
/// ```
46+
///
47+
/// ### :linear_gradient
48+
/// See ``LiveViewNative/SwiftUI/LinearGradient`` for details on creating this style.
49+
///
50+
/// ```elixir
51+
/// {:linear_gradient, [gradient: {:colors, [:pink, :blue]}]}
52+
/// ```
53+
///
54+
/// ### :radial_gradient
55+
/// See ``LiveViewNative/SwiftUI/RadialGradient`` for details on creating this style.
56+
///
57+
/// ```elixir
58+
/// {:radial_gradient, [gradient: {:colors, [:pink, :blue]}, start_radius: 0, end_radius: 100]}
59+
/// ```
60+
///
61+
/// ### :hierarchical
62+
/// See ``LiveViewNative/SwiftUI/HierarchicalShapeStyle`` for a list of possible values.
63+
///
64+
/// ```elixir
65+
/// {:hierarchical, :tertiary}
66+
/// ```
67+
///
68+
/// ### :material
69+
/// See ``LiveViewNative/SwiftUI/Material`` for a list of possible values.
70+
///
71+
/// - Note: Materials are only available on iOS and macOS.
72+
///
73+
/// ```elixir
74+
/// {:material, :regular}
75+
/// ```
76+
///
77+
/// ### :image
78+
/// See ``LiveViewNative/SwiftUI/ImagePaint`` for details on creating this style.
79+
///
80+
/// ```elixir
81+
/// {:image, [image: {:system, "basketball.fill"}]}
82+
/// ```
83+
///
84+
/// ### :selection
85+
/// The selection color in the current context.
86+
///
87+
/// - Note: Only available on iOS and macOS
88+
///
89+
/// ```elixir
90+
/// :selection
91+
/// ```
92+
///
93+
/// ### :separator
94+
/// The separator color in the current context.
95+
///
96+
/// - Note: Only available on macOS
97+
///
98+
/// ```elixir
99+
/// :separator
100+
/// ```
101+
///
102+
/// ### :tint
103+
/// The tint color in the current context.
104+
///
105+
/// ```elixir
106+
/// :tint
107+
/// ```
108+
///
109+
/// ### :foreground
110+
/// The foreground style in the current context.
111+
///
112+
/// ```elixir
113+
/// :foreground
114+
/// ```
115+
///
116+
/// ### :background
117+
/// The background style in the current context.
118+
///
119+
/// ```elixir
120+
/// :background
121+
/// ```
122+
///
123+
/// ## Modifiers
124+
/// A third element can be contained in the style tuple. This element is a list of modifiers.
125+
///
126+
/// ```elixir
127+
/// {type, value, [modifier1, modifier2, ...]}
128+
/// ```
129+
///
130+
/// Modifiers are created with tuples as well, with the first element being the name of the modifier and the second being any options.
131+
///
132+
/// ```elixir
133+
/// {:opacity, 0.5}
134+
/// ```
135+
///
136+
/// Modifiers with only one option can have the value directly as the second element.
137+
/// If a modifier has multiply options, use a keyword list to set the values.
138+
///
139+
/// ### :opacity
140+
/// Arguments:
141+
/// * `opacity` (required) - The transparency of the style, from 0-1.
142+
///
143+
/// ```elixir
144+
/// {:opacity, 0.5}
145+
/// ```
146+
///
147+
/// ### :blend_mode
148+
/// Arguments:
149+
/// * `blend_mode` (required) - The ``LiveViewNative/SwiftUI/BlendMode`` to use for this style.
150+
///
151+
/// ```elixir
152+
/// {:blend_mode, :multiply}
153+
/// ```
154+
///
155+
/// ### :shadow
156+
/// Arguments:
157+
/// * `shadow_style` (required) - The ``LiveViewNative/SwiftUI/ShadowStyle`` to apply.
158+
///
159+
/// ```elixir
160+
/// {:shadow, {:drop, [radius: 10]}}
161+
/// ```
162+
#if swift(>=5.8)
163+
@_documentation(visibility: public)
164+
#endif
10165
extension AnyShapeStyle: Decodable {
11166
public init(from decoder: Decoder) throws {
12167
let container = try decoder.container(keyedBy: CodingKeys.self)
13-
14-
if let color = try? container.decode(SwiftUI.Color?.self, forKey: .style) {
15-
self = Self(color)
16-
} else if let linearGradient = try? container.decode(LinearGradient?.self, forKey: .style) {
17-
self = Self(linearGradient)
18-
} else {
19-
throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: "expected valid value for AnyShapeStyle"))
168+
169+
switch try container.decode(ConcreteStyle.self, forKey: .concreteStyle) {
170+
case .color:
171+
self = Self(try container.decode(SwiftUI.Color.self, forKey: .style))
172+
case .angularGradient:
173+
self = Self(try container.decode(AngularGradient.self, forKey: .style))
174+
case .ellipticalGradient:
175+
self = Self(try container.decode(EllipticalGradient.self, forKey: .style))
176+
case .linearGradient:
177+
self = Self(try container.decode(LinearGradient.self, forKey: .style))
178+
case .radialGradient:
179+
self = Self(try container.decode(RadialGradient.self, forKey: .style))
180+
case .hierarchical:
181+
self = Self(try container.decode(HierarchicalShapeStyle.self, forKey: .style))
182+
#if os(iOS) || os(macOS)
183+
case .material:
184+
self = Self(try container.decode(Material.self, forKey: .style))
185+
#endif
186+
case .image:
187+
self = Self(try container.decode(ImagePaint.self, forKey: .style))
188+
#if os(iOS) || os(macOS)
189+
case .selection:
190+
self = Self(SelectionShapeStyle())
191+
#endif
192+
#if os(macOS)
193+
case .separator:
194+
self = Self(SeparatorShapeStyle())
195+
#endif
196+
case .tint:
197+
self = Self(TintShapeStyle())
198+
case .foreground:
199+
self = Self(ForegroundStyle())
200+
case .background:
201+
self = Self(BackgroundStyle())
202+
}
203+
204+
var modifiers = try container.nestedUnkeyedContainer(forKey: .modifiers)
205+
while !modifiers.isAtEnd {
206+
let modifier = try modifiers.nestedContainer(keyedBy: CodingKeys.Modifier.self)
207+
switch try modifier.decode(Modifier.self, forKey: .type) {
208+
case .opacity:
209+
self = Self(self.opacity(try modifier.decode(Double.self, forKey: .properties)))
210+
case .blendMode:
211+
self = Self(self.blendMode(try modifier.decode(BlendMode.self, forKey: .properties)))
212+
case .shadow:
213+
self = Self(self.shadow(try modifier.decode(ShadowStyle.self, forKey: .properties)))
214+
}
20215
}
21216
}
22217
enum CodingKeys: String, CodingKey {
218+
case concreteStyle = "concrete_style"
23219
case style
220+
case modifiers
221+
222+
enum Modifier: CodingKey {
223+
case type
224+
case properties
225+
}
226+
}
227+
228+
enum ConcreteStyle: String, Decodable {
229+
case color
230+
case angularGradient = "angular_gradient"
231+
case ellipticalGradient = "elliptical_gradient"
232+
case linearGradient = "linear_gradient"
233+
case radialGradient = "radial_gradient"
234+
case hierarchical
235+
#if os(iOS) || os(macOS)
236+
case material
237+
#endif
238+
case image
239+
#if os(iOS) || os(macOS)
240+
case selection
241+
#endif
242+
#if os(macOS)
243+
case separator
244+
#endif
245+
case tint
246+
case foreground
247+
case background
248+
}
249+
250+
enum Modifier: String, Decodable {
251+
case opacity
252+
case blendMode = "blend_mode"
253+
case shadow
24254
}
25255
}

0 commit comments

Comments
 (0)