diff --git a/Package.swift b/Package.swift index 5451bc5..b06b15d 100644 --- a/Package.swift +++ b/Package.swift @@ -13,7 +13,7 @@ let package = Package( targets: ["LiveViewNativeCharts"]), ], dependencies: [ - .package(url: "https://github.com/liveview-native/liveview-client-swiftui", from: "0.3.0") + .package(url: "https://github.com/liveview-native/liveview-client-swiftui", from: "0.4.0-rc.0") ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/Sources/LiveViewNativeCharts/AxisContent/AxisMarks.swift b/Sources/LiveViewNativeCharts/AxisContent/AxisMarks.swift index 254f2b0..d75bfb5 100644 --- a/Sources/LiveViewNativeCharts/AxisContent/AxisMarks.swift +++ b/Sources/LiveViewNativeCharts/AxisContent/AxisMarks.swift @@ -66,10 +66,11 @@ import SwiftUI #if swift(>=5.8) @_documentation(visibility: public) #endif -struct AxisMarks: ComposedAxisContent { +struct AxisMarks: @preconcurrency ComposedAxisContent { let element: ElementNode let context: AxisContentBuilder.Context + @MainActor var body: some AxisContent { let preset = (try? element.attributeValue(AxisMarkPreset.self, for: "preset")) ?? .automatic let position = (try? element.attributeValue(AxisMarkPosition.self, for: "position")) ?? .automatic @@ -138,7 +139,7 @@ struct AxisMarks: ComposedAxisContent { let children = element.children() let values = children .compactMap { - try? $0.attributes + try? $0.attributes() .first(where: { $0.name == "value" }) .map({ try Double.init(from: $0, on: element) }) } @@ -146,7 +147,7 @@ struct AxisMarks: ComposedAxisContent { AnyAxisMark( try! AxisContentBuilder.build( children.filter({ - (try? $0.attributes + (try? $0.attributes() .first(where: { $0.name == "value" }) .map({ try Double.init(from: $0, on: element) }) ) diff --git a/Sources/LiveViewNativeCharts/AxisMark/AxisMarkBuilder.swift b/Sources/LiveViewNativeCharts/AxisMark/AxisMarkBuilder.swift index ec44858..6828b8a 100644 --- a/Sources/LiveViewNativeCharts/AxisMark/AxisMarkBuilder.swift +++ b/Sources/LiveViewNativeCharts/AxisMark/AxisMarkBuilder.swift @@ -22,18 +22,22 @@ struct AxisMarkBuilder: ContentBuilder { case axisValueLabel = "AxisValueLabel" } - enum ModifierType: ContentModifier { + enum ModifierType: ContentModifier, @preconcurrency Decodable { typealias Builder = AxisMarkBuilder case font(FontModifier) case foregroundStyle(AxisMarkForegroundStyleModifier) case offset(AxisMarkOffsetModifier) - static func parser(in context: ParseableModifierContext) -> some Parser { - OneOf { - FontModifier.parser(in: context).map(Self.font) - AxisMarkForegroundStyleModifier.parser(in: context).map(Self.foregroundStyle) - AxisMarkOffsetModifier.parser(in: context).map(Self.offset) + init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + if let modifier = try? container.decode(FontModifier.self) { + self = .font(modifier) + } else if let modifier = try? container.decode(AxisMarkForegroundStyleModifier.self) { + self = .foregroundStyle(modifier) + } else { + self = .offset(try container.decode(AxisMarkOffsetModifier.self)) } } @@ -79,6 +83,7 @@ struct AxisMarkBuilder: ContentBuilder { } } +@MainActor protocol ComposedAxisMark { associatedtype Body: AxisMark @Charts.AxisMarkBuilder diff --git a/Sources/LiveViewNativeCharts/AxisMark/AxisTick.swift b/Sources/LiveViewNativeCharts/AxisMark/AxisTick.swift index fb7b648..369ed1e 100644 --- a/Sources/LiveViewNativeCharts/AxisMark/AxisTick.swift +++ b/Sources/LiveViewNativeCharts/AxisMark/AxisTick.swift @@ -52,7 +52,7 @@ struct AxisTick: ComposedAxisMark { #if swift(>=5.8) @_documentation(visibility: public) #endif -extension Charts.AxisTick.Length: AttributeDecodable { +extension Charts.AxisTick.Length: @retroactive AttributeDecodable { public init(from attribute: LiveViewNativeCore.Attribute?) throws { guard let value = attribute?.value else { throw AttributeDecodingError.missingAttribute(Self.self) } diff --git a/Sources/LiveViewNativeCharts/AxisMark/AxisValue.swift b/Sources/LiveViewNativeCharts/AxisMark/AxisValue.swift index 90c1e5b..743ec30 100644 --- a/Sources/LiveViewNativeCharts/AxisMark/AxisValue.swift +++ b/Sources/LiveViewNativeCharts/AxisMark/AxisValue.swift @@ -29,6 +29,7 @@ import LiveViewNative #if swift(>=5.8) @_documentation(visibility: public) #endif +@MainActor struct AxisValue: ComposedAxisMark { let element: ElementNode let context: AxisMarkBuilder.Context diff --git a/Sources/LiveViewNativeCharts/AxisMark/AxisValueLabel.swift b/Sources/LiveViewNativeCharts/AxisMark/AxisValueLabel.swift index 7d5b115..e45b9c0 100644 --- a/Sources/LiveViewNativeCharts/AxisMark/AxisValueLabel.swift +++ b/Sources/LiveViewNativeCharts/AxisMark/AxisValueLabel.swift @@ -37,6 +37,7 @@ import LiveViewNativeCore #if swift(>=5.8) @_documentation(visibility: public) #endif +@MainActor struct AxisValueLabel: ComposedAxisMark { let element: ElementNode let context: AxisMarkBuilder.Context @@ -136,6 +137,10 @@ struct AxisValueLabel: ComposedAxisMark { fatalError("Unknown format '\(format)'") } } else { + let childViews = AxisMarkBuilder.buildChildViews( + of: element, + in: context + ) Charts.AxisValueLabel( centered: centered, anchor: anchor, @@ -146,16 +151,13 @@ struct AxisValueLabel: ComposedAxisMark { horizontalSpacing: horizontalSpacing, verticalSpacing: verticalSpacing ) { - AxisMarkBuilder.buildChildViews( - of: element, - in: context - ) + childViews } } } } -extension AxisValueLabelCollisionResolution: AttributeDecodable { +extension AxisValueLabelCollisionResolution: @retroactive AttributeDecodable { public init(from attribute: LiveViewNativeCore.Attribute?) throws { guard let value = attribute?.value else { throw AttributeDecodingError.missingAttribute(Self.self) } @@ -170,7 +172,7 @@ extension AxisValueLabelCollisionResolution: AttributeDecodable { } } -extension AxisValueLabelOrientation: AttributeDecodable { +extension AxisValueLabelOrientation: @retroactive AttributeDecodable { public init(from attribute: LiveViewNativeCore.Attribute?) throws { guard let value = attribute?.value else { throw AttributeDecodingError.missingAttribute(Self.self) } diff --git a/Sources/LiveViewNativeCharts/ChartContentBuilder.swift b/Sources/LiveViewNativeCharts/ChartContentBuilder.swift index 3bdb0a6..e83ced5 100644 --- a/Sources/LiveViewNativeCharts/ChartContentBuilder.swift +++ b/Sources/LiveViewNativeCharts/ChartContentBuilder.swift @@ -25,7 +25,7 @@ struct ChartContentBuilder: ContentBuilder { case ruleMark = "RuleMark" } - enum ModifierType: ContentModifier { + enum ModifierType: ContentModifier, @preconcurrency Decodable { typealias Builder = ChartContentBuilder case alignsMarkStylesWithPlotArea(AlignsMarkStylesWithPlotAreaModifier) @@ -37,16 +37,25 @@ struct ChartContentBuilder: ContentBuilder { case symbolSize(SymbolSizeModifier) case zIndex(ZIndexModifier) - static func parser(in context: ParseableModifierContext) -> some Parser { - OneOf { - AlignsMarkStylesWithPlotAreaModifier.parser(in: context).map(Self.alignsMarkStylesWithPlotArea) - CornerRadiusModifier.parser(in: context).map(Self.cornerRadius) - ForegroundStyleModifier.parser(in: context).map(Self.foregroundStyle) - InterpolationMethodModifier.parser(in: context).map(Self.interpolationMethod) - OffsetModifier.parser(in: context).map(Self.offset) - SymbolModifier.parser(in: context).map(Self.symbol) - SymbolSizeModifier.parser(in: context).map(Self.symbolSize) - ZIndexModifier.parser(in: context).map(Self.zIndex) + init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + if let modifier = try? container.decode(AlignsMarkStylesWithPlotAreaModifier.self) { + self = .alignsMarkStylesWithPlotArea(modifier) + } else if let modifier = try? container.decode(CornerRadiusModifier.self) { + self = .cornerRadius(modifier) + } else if let modifier = try? container.decode(ForegroundStyleModifier.self) { + self = .foregroundStyle(modifier) + } else if let modifier = try? container.decode(InterpolationMethodModifier.self) { + self = .interpolationMethod(modifier) + } else if let modifier = try? container.decode(OffsetModifier.self) { + self = .offset(modifier) + } else if let modifier = try? container.decode(SymbolModifier.self) { + self = .symbol(modifier) + } else if let modifier = try? container.decode(SymbolSizeModifier.self) { + self = .symbolSize(modifier) + } else { + self = .zIndex(try container.decode(ZIndexModifier.self)) } } diff --git a/Sources/LiveViewNativeCharts/ChartsRegistry.swift b/Sources/LiveViewNativeCharts/ChartsRegistry.swift index a0c34fa..a8cfced 100644 --- a/Sources/LiveViewNativeCharts/ChartsRegistry.swift +++ b/Sources/LiveViewNativeCharts/ChartsRegistry.swift @@ -20,6 +20,7 @@ public extension Addons { case chart = "Chart" } + @MainActor public static func lookup(_ name: TagName, element: ElementNode) -> some View { switch name { case .chart: @@ -27,7 +28,7 @@ public extension Addons { } } - public struct CustomModifier: ViewModifier, ParseableModifierValue { + public struct CustomModifier: ViewModifier, @preconcurrency Decodable { enum Storage { case chartBackground(ChartBackgroundModifier) case chartLegend(ChartLegendModifier) @@ -38,16 +39,19 @@ public extension Addons { } let storage: Storage - public static func parser(in context: ParseableModifierContext) -> some Parser { - CustomModifierGroupParser(output: Self.self) { - ChartBackgroundModifier.parser(in: context).map({ Self(storage: .chartBackground($0)) }) - ChartLegendModifier.parser(in: context).map({ Self(storage: .chartLegend($0)) }) - ChartOverlayModifier.parser(in: context).map({ Self(storage: .chartOverlay($0)) }) - ChartXAxisModifier.parser(in: context).map({ Self(storage: .chartXAxis($0)) }) - ChartYAxisModifier.parser(in: context).map({ Self(storage: .chartYAxis($0)) }) - ChartContentBuilder.ModifierType.parser(in: context).map({ _ in Self(storage: .noop) }) - AxisContentBuilder.ModifierType.parser(in: context).map({ _ in Self(storage: .noop) }) - AxisMarkBuilder.ModifierType.parser(in: context).map({ _ in Self(storage: .noop) }) + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + if let modifier = try? container.decode(ChartBackgroundModifier.self) { + self.storage = .chartBackground(modifier) + } else if let modifier = try? container.decode(ChartLegendModifier.self) { + self.storage = .chartLegend(modifier) + } else if let modifier = try? container.decode(ChartOverlayModifier.self) { + self.storage = .chartOverlay(modifier) + } else if let modifier = try? container.decode(ChartXAxisModifier.self) { + self.storage = .chartXAxis(modifier) + } else { + self.storage = .chartYAxis(try container.decode(ChartYAxisModifier.self)) } } diff --git a/Sources/LiveViewNativeCharts/Modifiers/AlignsMarkStylesWithPlotAreaModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/AlignsMarkStylesWithPlotAreaModifier.swift index d09628d..a13a296 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/AlignsMarkStylesWithPlotAreaModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/AlignsMarkStylesWithPlotAreaModifier.swift @@ -9,15 +9,14 @@ import Charts import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct AlignsMarkStylesWithPlotAreaModifier: ContentModifier { +@ASTDecodable("alignsMarkStylesWithPlotArea") +@MainActor +struct AlignsMarkStylesWithPlotAreaModifier: ContentModifier, @preconcurrency Decodable { typealias Builder = ChartContentBuilder - static let name = "alignsMarkStylesWithPlotArea" + let aligns: AttributeReference - let aligns: Bool - - init(aligns: Bool) { + init(aligns: AttributeReference) { self.aligns = aligns } @@ -26,6 +25,6 @@ struct AlignsMarkStylesWithPlotAreaModifier: ContentModifier { on element: ElementNode, in context: Builder.Context ) -> Builder.Content { - content.alignsMarkStylesWithPlotArea(aligns) + content.alignsMarkStylesWithPlotArea(aligns.resolve(on: element, in: context)) } } diff --git a/Sources/LiveViewNativeCharts/Modifiers/ChartLegendModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/ChartLegendModifier.swift index 8415f0b..59c1745 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/ChartLegendModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/ChartLegendModifier.swift @@ -10,22 +10,21 @@ import SwiftUI import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct ChartLegendModifier: ViewModifier { - static var name: String { "chartLegend" } - +@ASTDecodable("chartLegend") +@MainActor +struct ChartLegendModifier: ViewModifier, @preconcurrency Decodable { enum Storage { - case visibility(Visibility) + case visibility(Visibility.Resolvable) case content( - position: AnnotationPosition, - alignment: Alignment?, - spacing: CGFloat?, + position: AttributeReference, + alignment: Alignment.Resolvable?, + spacing: CGFloat.Resolvable?, content: ViewReference ) case position( - position: AnnotationPosition, - alignment: Alignment?, - spacing: CGFloat? + position: AttributeReference, + alignment: Alignment.Resolvable?, + spacing: CGFloat.Resolvable? ) } let storage: Storage @@ -33,31 +32,33 @@ struct ChartLegendModifier: ViewModifier { @ObservedElement private var element @LiveContext private var context - init(_ visibility: Visibility) { + init(_ visibility: Visibility.Resolvable) { self.storage = .visibility(visibility) } + @MainActor init( - position: AnnotationPosition = .automatic, - alignment: Alignment? = nil, - spacing: CGFloat? = nil, + position: AttributeReference? = nil, + alignment: Alignment.Resolvable? = nil, + spacing: CGFloat.Resolvable? = nil, content: ViewReference ) { self.storage = .content( - position: position, + position: position ?? .constant(.automatic), alignment: alignment, spacing: spacing, content: content ) } + @MainActor init( - position: AnnotationPosition = .automatic, - alignment: Alignment? = nil, - spacing: CGFloat? = nil + position: AttributeReference? = nil, + alignment: Alignment.Resolvable? = nil, + spacing: CGFloat.Resolvable? = nil ) { self.storage = .position( - position: position, + position: position ?? .constant(.automatic), alignment: alignment, spacing: spacing ) @@ -66,13 +67,13 @@ struct ChartLegendModifier: ViewModifier { func body(content: Content) -> some View { switch self.storage { case let .visibility(visibility): - content.chartLegend(visibility) + content.chartLegend(visibility.resolve(on: element, in: context)) case let .content(position, alignment, spacing, _content): - content.chartLegend(position: position, alignment: alignment, spacing: spacing) { + content.chartLegend(position: position.resolve(on: element, in: context).resolve(on: element, in: context), alignment: alignment?.resolve(on: element, in: context), spacing: spacing?.resolve(on: element, in: context)) { _content.resolve(on: element, in: context) } case let .position(position, alignment, spacing): - content.chartLegend(position: position, alignment: alignment, spacing: spacing) + content.chartLegend(position: position.resolve(on: element, in: context).resolve(on: element, in: context), alignment: alignment?.resolve(on: element, in: context), spacing: spacing?.resolve(on: element, in: context)) } } } diff --git a/Sources/LiveViewNativeCharts/Modifiers/ChartOverlayModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/ChartOverlayModifier.swift index feae32a..4d94c48 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/ChartOverlayModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/ChartOverlayModifier.swift @@ -10,23 +10,21 @@ import SwiftUI import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct ChartOverlayModifier: ViewModifier { - static var name: String { "chartOverlay" } - - private let alignment: Alignment +@ASTDecodable("chartOverlay") +struct ChartOverlayModifier: ViewModifier, @preconcurrency Decodable { + private let alignment: Alignment.Resolvable private let content: ViewReference @ObservedElement private var element @LiveContext private var context - init(alignment: Alignment, content: ViewReference) { + init(alignment: Alignment.Resolvable, content: ViewReference) { self.alignment = alignment self.content = content } func body(content: Content) -> some View { - content.chartOverlay(alignment: alignment) { _ in + content.chartOverlay(alignment: alignment.resolve(on: element, in: context)) { _ in self.content.resolve(on: element, in: context) } } diff --git a/Sources/LiveViewNativeCharts/Modifiers/ChartXAxisModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/ChartXAxisModifier.swift index 367f236..8cb2aa8 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/ChartXAxisModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/ChartXAxisModifier.swift @@ -10,12 +10,10 @@ import SwiftUI import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct ChartXAxisModifier: ViewModifier { - static var name: String { "chartXAxis" } - +@ASTDecodable("chartXAxis") +struct ChartXAxisModifier: ViewModifier, @preconcurrency Decodable { enum Storage { - case visibility(Visibility) + case visibility(Visibility.Resolvable) case content(ViewReference) } let storage: Storage @@ -23,7 +21,7 @@ struct ChartXAxisModifier: ViewModifier { @ObservedElement(observeChildren: true) private var element @ContentBuilderContext private var context - init(_ visibility: Visibility) { + init(_ visibility: Visibility.Resolvable) { self.storage = .visibility(visibility) } @@ -34,7 +32,7 @@ struct ChartXAxisModifier: ViewModifier { func body(content: Content) -> some View { switch self.storage { case let .visibility(visibility): - content.chartXAxis(visibility) + content.chartXAxis(visibility.resolve(on: element, in: context)) case let .content(reference): content.chartXAxis { AnyAxisContent( diff --git a/Sources/LiveViewNativeCharts/Modifiers/ChartYAxisModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/ChartYAxisModifier.swift index 40d546a..e3061aa 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/ChartYAxisModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/ChartYAxisModifier.swift @@ -10,12 +10,10 @@ import SwiftUI import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct ChartYAxisModifier: ViewModifier { - static var name: String { "chartYAxis" } - +@ASTDecodable("chartYAxis") +struct ChartYAxisModifier: ViewModifier, @preconcurrency Decodable { enum Storage { - case visibility(Visibility) + case visibility(Visibility.Resolvable) case content(ViewReference) } let storage: Storage @@ -23,7 +21,7 @@ struct ChartYAxisModifier: ViewModifier { @ObservedElement(observeChildren: true) private var element @ContentBuilderContext private var context - init(_ visibility: Visibility) { + init(_ visibility: Visibility.Resolvable) { self.storage = .visibility(visibility) } @@ -34,7 +32,7 @@ struct ChartYAxisModifier: ViewModifier { func body(content: Content) -> some View { switch self.storage { case let .visibility(visibility): - content.chartYAxis(visibility) + content.chartYAxis(visibility.resolve(on: element, in: context)) case let .content(reference): content.chartYAxis { AnyAxisContent( diff --git a/Sources/LiveViewNativeCharts/Modifiers/CornerRadiusModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/CornerRadiusModifier.swift index b9328b5..cef17a8 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/CornerRadiusModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/CornerRadiusModifier.swift @@ -10,12 +10,10 @@ import SwiftUI import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct CornerRadiusModifier: ContentModifier { +@ASTDecodable("cornerRadius") +struct CornerRadiusModifier: ContentModifier, @preconcurrency Decodable { typealias Builder = ChartContentBuilder - static let name = "cornerRadius" - private let radius: CGFloat private let style: RoundedCornerStyle diff --git a/Sources/LiveViewNativeCharts/Modifiers/FontModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/FontModifier.swift index bdc95c3..e43048f 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/FontModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/FontModifier.swift @@ -10,15 +10,14 @@ import SwiftUI import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct FontModifier: ContentModifier { +@ASTDecodable("font") +@MainActor +struct FontModifier: ContentModifier, @preconcurrency Decodable { typealias Builder = AxisMarkBuilder - static let name = "font" + private let font: Font.Resolvable - private let font: Font - - init(_ font: Font) { + init(_ font: Font.Resolvable) { self.font = font } @@ -27,6 +26,6 @@ struct FontModifier: ContentModifier { on element: ElementNode, in context: Builder.Context ) -> Builder.Content { - content.font(font) + content.font(font.resolve(on: element, in: context)) } } diff --git a/Sources/LiveViewNativeCharts/Modifiers/ForegroundStyleModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/ForegroundStyleModifier.swift index 4f73bd0..daaba40 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/ForegroundStyleModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/ForegroundStyleModifier.swift @@ -10,20 +10,19 @@ import SwiftUI import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct ForegroundStyleModifier: ContentModifier { +@ASTDecodable("foregroundStyle") +@MainActor +struct ForegroundStyleModifier: ContentModifier, @preconcurrency Decodable { typealias Builder = ChartContentBuilder - static let name = "foregroundStyle" - enum Storage { - case primary(AnyShapeStyle.Resolvable) + case primary(StylesheetResolvableShapeStyle) case value(AnyPlottableValue) } let storage: Storage - init(_ primary: AnyShapeStyle.Resolvable) { + init(_ primary: StylesheetResolvableShapeStyle) { self.storage = .primary(primary) } @@ -31,6 +30,7 @@ struct ForegroundStyleModifier: ContentModifier { self.storage = .value(value) } + @MainActor func apply( to content: Builder.Content, on element: ElementNode, @@ -38,18 +38,20 @@ struct ForegroundStyleModifier: ContentModifier { ) -> Builder.Content { switch storage { case let .primary(primary): - return content.foregroundStyle(primary.resolve(on: element, in: context.context)) + return content.foregroundStyle(primary.resolve(on: element, in: context)) case let .value(value): + let resolvedPlottable = value.value.resolve(on: element, in: context).value return unbox( content: content, label: value.label, - value.value.resolve(on: element, in: context.context).value, + resolvedPlottable, on: element, in: context ) } } + @MainActor func unbox( content: Builder.Content, label: AnyPlottableValue.Label, @@ -66,15 +68,14 @@ struct ForegroundStyleModifier: ContentModifier { } } -@ParseableExpression -struct AxisMarkForegroundStyleModifier: ContentModifier { +@ASTDecodable("foregroundStyle") +@MainActor +struct AxisMarkForegroundStyleModifier: ContentModifier, @preconcurrency Decodable { typealias Builder = AxisMarkBuilder - static let name = "foregroundStyle" - - let primary: AnyShapeStyle.Resolvable + let primary: StylesheetResolvableShapeStyle - init(_ primary: AnyShapeStyle.Resolvable) { + init(_ primary: StylesheetResolvableShapeStyle) { self.primary = primary } @@ -83,6 +84,6 @@ struct AxisMarkForegroundStyleModifier: ContentModifier { on element: ElementNode, in context: Builder.Context ) -> Builder.Content { - return content.foregroundStyle(primary.resolve(on: element, in: context.context)) + return content.foregroundStyle(primary.resolve(on: element, in: context)) } } diff --git a/Sources/LiveViewNativeCharts/Modifiers/InterpolationMethodModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/InterpolationMethodModifier.swift index ee14bf3..545e85e 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/InterpolationMethodModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/InterpolationMethodModifier.swift @@ -9,15 +9,14 @@ import Charts import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct InterpolationMethodModifier: ContentModifier { +@ASTDecodable("interpolationMethod") +@MainActor +struct InterpolationMethodModifier: ContentModifier, @preconcurrency Decodable { typealias Builder = ChartContentBuilder - static let name = "interpolationMethod" + let method: InterpolationMethod.Resolvable - let method: InterpolationMethod - - init(_ method: InterpolationMethod) { + init(_ method: InterpolationMethod.Resolvable) { self.method = method } @@ -26,6 +25,6 @@ struct InterpolationMethodModifier: ContentModifier { on element: ElementNode, in context: Builder.Context ) -> Builder.Content { - content.interpolationMethod(method) + content.interpolationMethod(method.resolve(on: element, in: context)) } } diff --git a/Sources/LiveViewNativeCharts/Modifiers/OffsetModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/OffsetModifier.swift index 9e350e8..7a16f09 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/OffsetModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/OffsetModifier.swift @@ -9,16 +9,15 @@ import Charts import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct OffsetModifier: ContentModifier { +@ASTDecodable("offset") +@MainActor +struct OffsetModifier: ContentModifier, @preconcurrency Decodable { typealias Builder = ChartContentBuilder - static let name = "offset" - - let x: Double - let y: Double + let x: Double.Resolvable + let y: Double.Resolvable - init(x: Double, y: Double) { + init(x: Double.Resolvable, y: Double.Resolvable) { self.x = x self.y = y } @@ -28,20 +27,21 @@ struct OffsetModifier: ContentModifier { on element: ElementNode, in context: Builder.Context ) -> Builder.Content { - content.offset(x: x, y: y) + content.offset(x: x.resolve(on: element, in: context), y: y.resolve(on: element, in: context)) } } -@ParseableExpression -struct AxisMarkOffsetModifier: ContentModifier { +@ASTDecodable("offset") +@MainActor +struct AxisMarkOffsetModifier: ContentModifier, @preconcurrency Decodable { typealias Builder = AxisMarkBuilder static let name = "offset" - let x: Double - let y: Double + let x: Double.Resolvable + let y: Double.Resolvable - init(x: Double = 0, y: Double = 0) { + init(x: Double.Resolvable = .__constant(0), y: Double.Resolvable = .__constant(0)) { self.x = x self.y = y } @@ -51,6 +51,6 @@ struct AxisMarkOffsetModifier: ContentModifier { on element: ElementNode, in context: Builder.Context ) -> Builder.Content { - content.offset(x: x, y: y) + content.offset(x: x.resolve(on: element, in: context), y: y.resolve(on: element, in: context)) } } diff --git a/Sources/LiveViewNativeCharts/Modifiers/SymbolModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/SymbolModifier.swift index 61ec949..abaf751 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/SymbolModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/SymbolModifier.swift @@ -10,17 +10,16 @@ import SwiftUI import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -enum SymbolModifier: ContentModifier { +@ASTDecodable("symbol") +@MainActor +enum SymbolModifier: ContentModifier, @preconcurrency Decodable { typealias Builder = ChartContentBuilder - static let name = "symbol" - - case shape(BasicChartSymbolShape) + case shape(BasicChartSymbolShape.Resolvable) case value(AnyPlottableValue) case view(ViewReference) - init(_ symbol: BasicChartSymbolShape) { + init(_ symbol: BasicChartSymbolShape.Resolvable) { self = .shape(symbol) } @@ -39,9 +38,10 @@ enum SymbolModifier: ContentModifier { ) -> Builder.Content { switch self { case .shape(let shape): - return content.symbol(shape) + return content.symbol(shape.resolve(on: element, in: context)) case .value(let value): - return unbox(content: content, label: value.label, value.value.resolve(on: element, in: context.context).value, on: element, in: context) + let resolvedValue = value.value.resolve(on: element, in: context).value + return unbox(content: content, label: value.label, resolvedValue, on: element, in: context) case .view(let view): return content .symbol { diff --git a/Sources/LiveViewNativeCharts/Modifiers/SymbolSizeModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/SymbolSizeModifier.swift index 5f5f7a6..cbb31fb 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/SymbolSizeModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/SymbolSizeModifier.swift @@ -10,12 +10,11 @@ import SwiftUI import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct SymbolSizeModifier: ContentModifier { +@ASTDecodable("symbolSize") +@MainActor +struct SymbolSizeModifier: ContentModifier, @preconcurrency Decodable { typealias Builder = ChartContentBuilder - static let name = "symbolSize" - enum Storage { case value(AnyPlottableValue) case area(CGFloat) @@ -43,7 +42,8 @@ struct SymbolSizeModifier: ContentModifier { ) -> Builder.Content { switch storage { case let .value(value): - return unbox(content: content, label: value.label, value.value.resolve(on: element, in: context.context).value, on: element, in: context) + let resolvedValue = value.value.resolve(on: element, in: context).value + return unbox(content: content, label: value.label, resolvedValue, on: element, in: context) case let .area(area): return content.symbolSize(area) case let .size(size): diff --git a/Sources/LiveViewNativeCharts/Modifiers/View Fundamentals Modifiers/ChartBackgroundModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/View Fundamentals Modifiers/ChartBackgroundModifier.swift index 89b3a83..0cd5de6 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/View Fundamentals Modifiers/ChartBackgroundModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/View Fundamentals Modifiers/ChartBackgroundModifier.swift @@ -10,23 +10,21 @@ import SwiftUI import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct ChartBackgroundModifier: ViewModifier { - static var name: String { "chartBackground" } - - let alignment: Alignment +@ASTDecodable("chartBackground") +struct ChartBackgroundModifier: ViewModifier, @preconcurrency Decodable { + let alignment: Alignment.Resolvable let content: ViewReference @ObservedElement private var element @LiveContext private var context - init(alignment: Alignment, content: ViewReference) { + init(alignment: Alignment.Resolvable, content: ViewReference) { self.alignment = alignment self.content = content } func body(content: Content) -> some View { - content.chartBackground(alignment: self.alignment) { _ in + content.chartBackground(alignment: self.alignment.resolve(on: element, in: context)) { _ in self.content.resolve(on: element, in: context) } } diff --git a/Sources/LiveViewNativeCharts/Modifiers/ZIndexModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/ZIndexModifier.swift index 9c177a9..2648737 100644 --- a/Sources/LiveViewNativeCharts/Modifiers/ZIndexModifier.swift +++ b/Sources/LiveViewNativeCharts/Modifiers/ZIndexModifier.swift @@ -9,16 +9,14 @@ import Charts import LiveViewNative import LiveViewNativeStylesheet -@ParseableExpression -struct ZIndexModifier: ContentModifier { +@ASTDecodable("zIndex") +struct ZIndexModifier: ContentModifier, @preconcurrency Decodable { typealias Builder = ChartContentBuilder - static let name = "zIndex" - - let value: Double + let value: Double.Resolvable @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) - init(_ value: Double) { + init(_ value: Double.Resolvable) { self.value = value } @@ -28,7 +26,7 @@ struct ZIndexModifier: ContentModifier { in context: Builder.Context ) -> Builder.Content { if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) { - return content.zIndex(value) + return content.zIndex(value.resolve(on: element, in: context)) } else { return content } diff --git a/Sources/LiveViewNativeCharts/PlottableValue.swift b/Sources/LiveViewNativeCharts/PlottableValue.swift index 0567466..1160c4b 100644 --- a/Sources/LiveViewNativeCharts/PlottableValue.swift +++ b/Sources/LiveViewNativeCharts/PlottableValue.swift @@ -50,46 +50,39 @@ extension ElementNode { #if swift(>=5.8) @_documentation(visibility: public) #endif -struct AnyPlottableValue: ParseableModifierValue { +@ASTDecodable("PlottableValue") +struct AnyPlottableValue: @preconcurrency Decodable { let label: Label let value: AttributeReference - enum Label { - case constant(String) - case text(TextReference) + static func value(_ label: TextReference, _ value: AttributeReference) -> Self { + .init(label: .text(label), value: value) } - static func parser(in context: ParseableModifierContext) -> some Parser { - ImplicitStaticMember { - ParseablePlottableValue.parser(in: context).map(\.value) - } + static func value(_ label: String, _ value: AttributeReference) -> Self { + .init(label: .constant(label), value: value) } - @ParseableExpression - struct ParseablePlottableValue { - static var name: String { "value" } - - let value: AnyPlottableValue - - init(_ label: TextReference, _ value: AttributeReference) { - self.value = .init(label: .text(label), value: value) - } - - init(_ label: String, _ value: AttributeReference) { - self.value = .init(label: .constant(label), value: value) - } + enum Label { + case constant(String) + case text(TextReference) } } -struct AnyPlottable: ParseableModifierValue, AttributeDecodable { +struct AnyPlottable: Decodable, AttributeDecodable { let value: any Plottable - static func parser(in context: ParseableModifierContext) -> some Parser { - OneOf { - Int.parser(in: context).map({ Self.init(value: $0) }) - Double.parser(in: context).map({ Self.init(value: $0) }) - Date.parser(in: context).map({ Self.init(value: $0) }) - String.parser(in: context).map({ Self.init(value: $0) }) + init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + + if let value = try? container.decode(Int.self) { + self.value = value + } else if let value = try? container.decode(Double.self) { + self.value = value + } else if let value = try? container.decode(Date.self) { + self.value = value + } else { + self.value = try container.decode(String.self) } } diff --git a/Sources/LiveViewNativeCharts/Types/AnnotationPosition.swift b/Sources/LiveViewNativeCharts/Types/AnnotationPosition.swift index da598da..7b17ddb 100644 --- a/Sources/LiveViewNativeCharts/Types/AnnotationPosition.swift +++ b/Sources/LiveViewNativeCharts/Types/AnnotationPosition.swift @@ -6,7 +6,9 @@ // import Charts +import LiveViewNative import LiveViewNativeStylesheet +import LiveViewNativeCore /// See [`Charts.AnnotationPosition`](https://developer.apple.com/documentation/charts/AnnotationPosition) for more details. /// @@ -21,19 +23,72 @@ import LiveViewNativeStylesheet /// * `.topTrailing` /// * `.bottomLeading` /// * `.bottomTrailing` -extension AnnotationPosition: ParseableModifierValue { - public static func parser(in context: ParseableModifierContext) -> some Parser { - ImplicitStaticMember([ - "automatic": .automatic, - "overlay": .overlay, - "top": .top, - "bottom": .bottom, - "leading": .leading, - "trailing": .trailing, - "topLeading": .topLeading, - "topTrailing": .topTrailing, - "bottomLeading": .bottomLeading, - "bottomTrailing": .bottomTrailing, - ]) +extension AnnotationPosition { + @ASTDecodable("AnnotationPosition") + enum Resolvable: @preconcurrency AttributeDecodable, StylesheetResolvable, @preconcurrency Decodable { + case automatic + case overlay + case top + case bottom + case leading + case trailing + case topLeading + case topTrailing + case bottomLeading + case bottomTrailing + } +} + +extension AnnotationPosition.Resolvable { + func resolve(on element: ElementNode, in context: LiveContext) -> AnnotationPosition { + switch self { + case .automatic: + return .automatic + case .overlay: + return .overlay + case .top: + return .top + case .bottom: + return .bottom + case .leading: + return .leading + case .trailing: + return .trailing + case .topLeading: + return .topLeading + case .topTrailing: + return .topTrailing + case .bottomLeading: + return .bottomLeading + case .bottomTrailing: + return .bottomTrailing + } + } + + init(from attribute: Attribute?, on element: ElementNode) throws { + switch attribute?.value { + case "automatic": + self = .automatic + case "overlay": + self = .overlay + case "top": + self = .top + case "bottom": + self = .bottom + case "leading": + self = .leading + case "trailing": + self = .trailing + case "topLeading": + self = .topLeading + case "topTrailing": + self = .topTrailing + case "bottomLeading": + self = .bottomLeading + case "bottomTrailing": + self = .bottomTrailing + default: + throw AttributeDecodingError.badValue(Self.self) + } } } diff --git a/Sources/LiveViewNativeCharts/Types/BasicChartSymbolShape.swift b/Sources/LiveViewNativeCharts/Types/BasicChartSymbolShape.swift index 951cf4c..c2f7a99 100644 --- a/Sources/LiveViewNativeCharts/Types/BasicChartSymbolShape.swift +++ b/Sources/LiveViewNativeCharts/Types/BasicChartSymbolShape.swift @@ -7,19 +7,39 @@ import Charts import SwiftUI +import LiveViewNative import LiveViewNativeStylesheet -extension BasicChartSymbolShape: ParseableModifierValue { - public static func parser(in context: ParseableModifierContext) -> some Parser { - ImplicitStaticMember([ - "circle": .circle, - "square": .square, - "triangle": .triangle, - "diamond": .diamond, - "pentagon": .pentagon, - "plus": .plus, - "cross": .cross, - "asterisk": .asterisk, - ]) +extension BasicChartSymbolShape { + enum Resolvable: Decodable, StylesheetResolvable { + case circle + case square + case triangle + case diamond + case pentagon + case plus + case cross + case asterisk + + func resolve(on element: ElementNode, in context: LiveContext) -> BasicChartSymbolShape where R : RootRegistry { + switch self { + case .circle: + return .circle + case .square: + return .square + case .triangle: + return .triangle + case .diamond: + return .diamond + case .pentagon: + return .pentagon + case .plus: + return .plus + case .cross: + return .cross + case .asterisk: + return .asterisk + } + } } } diff --git a/Sources/LiveViewNativeCharts/Types/InterpolationMethod.swift b/Sources/LiveViewNativeCharts/Types/InterpolationMethod.swift index e3aa24b..3b6d142 100644 --- a/Sources/LiveViewNativeCharts/Types/InterpolationMethod.swift +++ b/Sources/LiveViewNativeCharts/Types/InterpolationMethod.swift @@ -9,18 +9,34 @@ import Charts import LiveViewNative import LiveViewNativeStylesheet -extension InterpolationMethod: ParseableModifierValue { - public static func parser(in context: ParseableModifierContext) -> some Parser { - ImplicitStaticMember([ - "cardinal": .cardinal, - "catmullRom": .catmullRom, - "linear": .linear, - "monotone": .monotone, - "stepCenter": .stepCenter, - "stepEnd": .stepEnd, - "stepStart": .stepStart, - - - ]) +extension InterpolationMethod { + @ASTDecodable("InterpolationMethod") + enum Resolvable: @preconcurrency Decodable, StylesheetResolvable { + case cardinal + case catmullRom + case linear + case monotone + case stepCenter + case stepEnd + case stepStart + + func resolve(on element: ElementNode, in context: LiveContext) -> InterpolationMethod where R : RootRegistry { + switch self { + case .cardinal: + return .cardinal + case .catmullRom: + return .catmullRom + case .linear: + return .linear + case .monotone: + return .monotone + case .stepCenter: + return .stepCenter + case .stepEnd: + return .stepEnd + case .stepStart: + return .stepStart + } + } } } diff --git a/Sources/LiveViewNativeCharts/Types/MarkDimension.swift b/Sources/LiveViewNativeCharts/Types/MarkDimension.swift index 7831912..a27e864 100644 --- a/Sources/LiveViewNativeCharts/Types/MarkDimension.swift +++ b/Sources/LiveViewNativeCharts/Types/MarkDimension.swift @@ -16,7 +16,7 @@ import LiveViewNativeCore /// * A fixed number: `75` /// * A ratio with the `%` suffix: `50%` /// * An inset with the `-` prefix: `-10` -extension MarkDimension: AttributeDecodable { +extension MarkDimension: @retroactive AttributeDecodable { public init(from attribute: LiveViewNativeCore.Attribute?) throws { guard let value = attribute?.value else { throw AttributeDecodingError.missingAttribute(Self.self) } diff --git a/Sources/LiveViewNativeCharts/Types/MarkStackingMethod.swift b/Sources/LiveViewNativeCharts/Types/MarkStackingMethod.swift index 1105ba6..54b8916 100644 --- a/Sources/LiveViewNativeCharts/Types/MarkStackingMethod.swift +++ b/Sources/LiveViewNativeCharts/Types/MarkStackingMethod.swift @@ -16,7 +16,7 @@ import LiveViewNativeCore /// * `normalized` /// * `center` /// * `unstacked` -extension MarkStackingMethod: AttributeDecodable { +extension MarkStackingMethod: @retroactive AttributeDecodable { public init(from attribute: LiveViewNativeCore.Attribute?) throws { guard let value = attribute?.value else { throw AttributeDecodingError.missingAttribute(Self.self) }