From ecbd2e1a760191d0cf6dc27e08febde46a9c4d3a Mon Sep 17 00:00:00 2001 From: Davide Bianco Date: Sun, 22 Oct 2023 19:13:43 +0200 Subject: [PATCH] Add animated_vector_gen package --- packages/animated_vector_gen/.gitignore | 7 + packages/animated_vector_gen/CHANGELOG.md | 3 + packages/animated_vector_gen/README.md | 39 +++ packages/animated_vector_gen/lib/builder.dart | 6 + .../animated_vector_gen/lib/src/json.dart | 298 ++++++++++++++++++ .../lib/src/shapeshifter_asset.dart | 92 ++++++ .../src/templates/animated_vector_data.dart | 24 ++ .../lib/src/templates/animation_property.dart | 52 +++ .../lib/src/templates/clip_element.dart | 38 +++ .../lib/src/templates/group_element.dart | 85 +++++ .../lib/src/templates/path_element.dart | 107 +++++++ .../lib/src/templates/root_element.dart | 41 +++ .../lib/src/templates/template.dart | 12 + .../lib/src/templates/utils.dart | 51 +++ packages/animated_vector_gen/pubspec.yaml | 18 ++ 15 files changed, 873 insertions(+) create mode 100644 packages/animated_vector_gen/.gitignore create mode 100644 packages/animated_vector_gen/CHANGELOG.md create mode 100644 packages/animated_vector_gen/README.md create mode 100644 packages/animated_vector_gen/lib/builder.dart create mode 100644 packages/animated_vector_gen/lib/src/json.dart create mode 100644 packages/animated_vector_gen/lib/src/shapeshifter_asset.dart create mode 100644 packages/animated_vector_gen/lib/src/templates/animated_vector_data.dart create mode 100644 packages/animated_vector_gen/lib/src/templates/animation_property.dart create mode 100644 packages/animated_vector_gen/lib/src/templates/clip_element.dart create mode 100644 packages/animated_vector_gen/lib/src/templates/group_element.dart create mode 100644 packages/animated_vector_gen/lib/src/templates/path_element.dart create mode 100644 packages/animated_vector_gen/lib/src/templates/root_element.dart create mode 100644 packages/animated_vector_gen/lib/src/templates/template.dart create mode 100644 packages/animated_vector_gen/lib/src/templates/utils.dart create mode 100644 packages/animated_vector_gen/pubspec.yaml diff --git a/packages/animated_vector_gen/.gitignore b/packages/animated_vector_gen/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/packages/animated_vector_gen/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/animated_vector_gen/CHANGELOG.md b/packages/animated_vector_gen/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/packages/animated_vector_gen/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/animated_vector_gen/README.md b/packages/animated_vector_gen/README.md new file mode 100644 index 0000000..8b55e73 --- /dev/null +++ b/packages/animated_vector_gen/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/animated_vector_gen/lib/builder.dart b/packages/animated_vector_gen/lib/builder.dart new file mode 100644 index 0000000..022b99e --- /dev/null +++ b/packages/animated_vector_gen/lib/builder.dart @@ -0,0 +1,6 @@ +import 'package:animated_vector_gen/src/shapeshifter_asset.dart'; +import 'package:build/build.dart'; +import 'package:source_gen/source_gen.dart'; + +Builder animatedVectorBuilder(BuilderOptions options) => + SharedPartBuilder([ShapeshifterAssetGenerator()], 'vector'); diff --git a/packages/animated_vector_gen/lib/src/json.dart b/packages/animated_vector_gen/lib/src/json.dart new file mode 100644 index 0000000..ff6d4da --- /dev/null +++ b/packages/animated_vector_gen/lib/src/json.dart @@ -0,0 +1,298 @@ +import 'dart:convert'; + +import 'package:animated_vector_gen/src/templates/animated_vector_data.dart'; +import 'package:animated_vector_gen/src/templates/animation_property.dart'; +import 'package:animated_vector_gen/src/templates/clip_element.dart'; +import 'package:animated_vector_gen/src/templates/group_element.dart'; +import 'package:animated_vector_gen/src/templates/path_element.dart'; +import 'package:animated_vector_gen/src/templates/root_element.dart'; +import 'package:animated_vector_gen/src/templates/template.dart'; +import 'package:animated_vector_gen/src/templates/utils.dart'; + +AnimatedVectorDataTemplate jsonToCode(String rawJson) { + final Map json = jsonDecode(rawJson) as Map; + + final Map layers = json.get("layers"); + final Map vectorLayer = layers.get("vectorLayer"); + final Map animation = + json.get>("timeline").get("animation"); + final List<_JsonAnimationProperty> blocks = animation + .get>("blocks") + .map((e) => _JsonAnimationProperty.fromJson(e as Map)) + .toList(); + final List children = vectorLayer.get("children"); + + return AnimatedVectorDataTemplate( + viewportSize: ( + vectorLayer.get("width"), + vectorLayer.get("height") + ), + duration: animation.get("duration"), + root: RootElementTemplate( + alpha: vectorLayer.maybeGet("alpha"), + elements: _elementsFromJson( + children.cast>(), + blocks, + ), + properties: RootAnimationPropertiesTemplate( + alpha: _parseProperties( + blocks, + vectorLayer.get("id"), + "alpha", + ), + ), + ), + ); +} + +List _elementsFromJson( + List> json, + List<_JsonAnimationProperty> animations, +) { + final List elements = []; + + for (final Map child in json) { + final String id = child.get("id"); + final String type = child.get("type"); + + switch (type) { + case "path": + final element = PathElementTemplate( + pathData: child.get("pathData"), + fillColor: _colorFromHex(child.maybeGet("fillColor")), + fillAlpha: child.maybeGet("fillAlpha")?.toDouble(), + strokeColor: _colorFromHex(child.maybeGet("strokeColor")), + strokeAlpha: child.maybeGet("strokeAlpha")?.toDouble(), + strokeWidth: child.maybeGet("strokeWidth")?.toDouble(), + strokeCap: child.maybeGet("strokeLinecap"), + strokeJoin: child.maybeGet("strokeLinejoin"), + strokeMiterLimit: child.maybeGet("strokeMiterLimit")?.toDouble(), + trimStart: child.maybeGet("trimPathStart")?.toDouble(), + trimEnd: child.maybeGet("trimPathEnd")?.toDouble(), + trimOffset: child.maybeGet("trimPathOffset")?.toDouble(), + properties: PathAnimationPropertiesTemplate( + pathData: _parseProperties(animations, id, "pathData"), + fillColor: _parseProperties(animations, id, "fillColor"), + fillAlpha: _parseProperties(animations, id, "fillAlpha"), + strokeColor: _parseProperties(animations, id, "strokeColor"), + strokeAlpha: + _parseProperties(animations, id, "strokeAlpha"), + strokeWidth: + _parseProperties(animations, id, "strokeWidth"), + trimStart: + _parseProperties(animations, id, "trimPathStart"), + trimEnd: _parseProperties(animations, id, "trimPathEnd"), + trimOffset: + _parseProperties(animations, id, "trimPathOffset"), + ), + ); + elements.add(element); + case "mask": + final element = ClipElementTemplate( + pathData: child.get("pathData"), + properties: ClipAnimationPropertiesTemplate( + pathData: _parseProperties(animations, id, "pathData"), + ), + ); + elements.add(element); + case "group": + final element = GroupElementTemplate( + translateX: child.maybeGet("translateX")?.toDouble(), + translateY: child.maybeGet("translateY")?.toDouble(), + scaleX: child.maybeGet("scaleX")?.toDouble(), + scaleY: child.maybeGet("scaleY")?.toDouble(), + pivotX: child.maybeGet("pivotX")?.toDouble(), + pivotY: child.maybeGet("pivotY")?.toDouble(), + rotation: child.maybeGet("rotation")?.toDouble(), + elements: _elementsFromJson( + child.get>("children").cast>(), + animations, + ), + properties: GroupAnimationPropertiesTemplate( + translateX: _parseProperties(animations, id, "translateX"), + translateY: _parseProperties(animations, id, "translateY"), + scaleX: _parseProperties(animations, id, "scaleX"), + scaleY: _parseProperties(animations, id, "scaleY"), + pivotX: _parseProperties(animations, id, "pivotX"), + pivotY: _parseProperties(animations, id, "pivotY"), + rotation: _parseProperties(animations, id, "rotation"), + ), + ); + elements.add(element); + } + } + + return elements; +} + +List> _parseProperties( + List<_JsonAnimationProperty> properties, + String layerId, + String propertyName, +) { + return properties + .where((a) => a.layerId == layerId && a.propertyName == propertyName) + .map( + (a) => AnimationPropertyTemplate( + tween: LerpTemplate( + begin: a.begin as T, + end: a.end as T, + type: a.type, + ), + type: a.type, + start: a.startTime, + end: a.endTime, + curve: a.interpolator, + ), + ) + .toList(); +} + +class _JsonAnimationProperty { + final String layerId; + final String propertyName; + final T? begin; + final T? end; + final int startTime; + final int endTime; + final String interpolator; + final ValueType type; + + const _JsonAnimationProperty({ + required this.layerId, + required this.propertyName, + required this.begin, + required this.end, + required this.startTime, + required this.endTime, + required this.interpolator, + required this.type, + }); + + static _JsonAnimationProperty fromJson(Map json) { + final String layerId = json.get("layerId"); + final String propertyName = json.get("propertyName"); + final int startTime = json.get("startTime"); + final int endTime = json.get("endTime"); + final String interpolator = + _interpolatorFromString(json.get("interpolator")); + final String type = json.get("type"); + + switch (type) { + case "path": + return _JsonAnimationProperty( + layerId: layerId, + propertyName: propertyName, + startTime: startTime, + endTime: endTime, + interpolator: interpolator, + begin: json.get("fromValue"), + end: json.get("toValue"), + type: ValueType.pathData, + ); + case "color": + return _JsonAnimationProperty( + layerId: layerId, + propertyName: propertyName, + startTime: startTime, + endTime: endTime, + interpolator: interpolator, + begin: _colorFromHex(json.get("fromValue")), + end: _colorFromHex(json.get("toValue")), + type: ValueType.color, + ); + case "number": + return _JsonAnimationProperty( + layerId: layerId, + propertyName: propertyName, + startTime: startTime, + endTime: endTime, + interpolator: interpolator, + begin: json.get("fromValue").toDouble(), + end: json.get("toValue").toDouble(), + type: ValueType.number, + ); + default: + throw UnsupportedAnimationProperty(type); + } + } + + static String _interpolatorFromString(String interpolator) { + switch (interpolator) { + case "FAST_OUT_SLOW_IN": + return "ShapeshifterCurves.fastOutSlowIn"; + case "FAST_OUT_LINEAR_IN": + return "ShapeshifterCurves.fastOutLinearIn"; + case "LINEAR_OUT_SLOW_IN": + return "ShapeshifterCurves.linearOutSlowIn"; + case "ACCELERATE_DECELERATE": + return "ShapeshifterCurves.accelerateDecelerate"; + case "ACCELERATE": + return "ShapeshifterCurves.accelerate"; + case "DECELERATE": + return "ShapeshifterCurves.decelerate"; + case "ANTICIPATE": + return "ShapeshifterCurves.anticipate"; + case "OVERSHOOT": + return "ShapeshifterCurves.overshoot"; + case "BOUNCE": + return "ShapeshifterCurves.bounce"; + case "ANTICIPATE_OVERSHOOT": + return "ShapeshifterCurves.anticipateOvershoot"; + case "LINEAR": + default: + return "ShapeshifterCurves.linear"; + } + } +} + +int? _colorFromHex(String? hex) { + if (hex == null) return null; + + String cleanHex = hex.replaceAll("#", ""); + cleanHex = switch (cleanHex.length) { + 6 => ["FF", cleanHex].join(), + 3 => ["FF", cleanHex, cleanHex].join(), + 2 => ["FF", cleanHex, cleanHex, cleanHex].join(), + _ => cleanHex, + }; + final int? colorValue = int.tryParse(cleanHex, radix: 16); + + return colorValue; +} + +class UnsupportedAnimationProperty implements Exception { + final String property; + + const UnsupportedAnimationProperty(this.property); + + @override + String toString() { + return "The property '$property' is not handled from the lib or not valid"; + } +} + +class MissingPropertyException implements Exception { + final String property; + + const MissingPropertyException(this.property); + + @override + String toString() { + return "MissingPropertyException: The provided json doesn't have required property '$property'"; + } +} + +extension MapGet on Map { + T get(K key) { + if (!containsKey(key)) { + throw MissingPropertyException(key.toString()); + } + + return this[key]! as T; + } + + T? maybeGet(K key) { + return this[key] as T?; + } +} diff --git a/packages/animated_vector_gen/lib/src/shapeshifter_asset.dart b/packages/animated_vector_gen/lib/src/shapeshifter_asset.dart new file mode 100644 index 0000000..bf89510 --- /dev/null +++ b/packages/animated_vector_gen/lib/src/shapeshifter_asset.dart @@ -0,0 +1,92 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:animated_vector_annotations/animated_vector_annotations.dart'; +import 'package:animated_vector_gen/src/json.dart'; +import 'package:build/build.dart'; +import 'package:code_builder/code_builder.dart'; +import 'package:glob/glob.dart'; +import 'package:source_gen/source_gen.dart' + show + AnnotatedElement, + ConstantReader, + Generator, + LibraryReader, + TypeChecker; + +class ShapeshifterAssetGenerator extends Generator { + TypeChecker get typeChecker => + const TypeChecker.fromRuntime(ShapeshifterAsset); + + @override + Future generate(LibraryReader library, BuildStep buildStep) async { + final List elements = []; + elements.addAll(library.annotatedWith(typeChecker)); + + for (final cls in library.classes) { + for (final element in cls.fields) { + final annotation = typeChecker.firstAnnotationOf(element); + if (annotation != null) { + elements.add(AnnotatedElement(ConstantReader(annotation), element)); + } + } + } + + final List parts = []; + for (final element in elements) { + final generated = await generateForAnnotatedElement( + element.element, + element.annotation, + buildStep, + ); + parts.add(generated); + } + + return parts.join("\n\n"); + } + + Future generateForAnnotatedElement( + Element element, + ConstantReader annotation, + BuildStep buildStep, + ) async { + if (element is! VariableElement || !element.isStatic) { + throw Exception( + "Only a toplevel variable or a static class field can be annotated with @ShapeshifterAsset.\nTried annotating ${element.source}.", + ); + } + + if (!element.isConst) { + throw Exception("The annotated field must be const.\n${element.source}."); + } + + final filePath = annotation.read("path").stringValue; + final assets = await buildStep.findAssets(Glob(filePath)).toList(); + if (assets.isEmpty) { + throw Exception( + "No file found for path $filePath, make sure the path is correct and the file exists", + ); + } + + if (assets.length > 1) { + throw Exception( + "Multiple inputs found for path $filePath, make sure to enter a fully qualified path", + ); + } + + final vectorJson = await buildStep.readAsString(assets.first); + final result = jsonToCode(vectorJson); + + final emitter = DartEmitter(); + final field = Field( + (b) => b + ..name = "_\$${element.name}" + ..type = refer( + "AnimatedVectorData", + "package:animated_vector/animated_vector.dart", + ) + ..modifier = FieldModifier.constant + ..assignment = Code('$result'), + ); + + return field.accept(emitter).toString(); + } +} diff --git a/packages/animated_vector_gen/lib/src/templates/animated_vector_data.dart b/packages/animated_vector_gen/lib/src/templates/animated_vector_data.dart new file mode 100644 index 0000000..8e0c7be --- /dev/null +++ b/packages/animated_vector_gen/lib/src/templates/animated_vector_data.dart @@ -0,0 +1,24 @@ +import 'package:animated_vector_gen/src/templates/root_element.dart'; +import 'package:animated_vector_gen/src/templates/template.dart'; +import 'package:animated_vector_gen/src/templates/utils.dart'; + +class AnimatedVectorDataTemplate extends Template { + final (num, num) viewportSize; + final num duration; + final RootElementTemplate root; + + const AnimatedVectorDataTemplate({ + required this.viewportSize, + required this.duration, + required this.root, + }); + + @override + String? build() { + return buildConstructorCall("AnimatedVectorData", { + "viewportSize": "Size(${viewportSize.$1}, ${viewportSize.$2})", + "duration": "Duration(milliseconds: $duration)", + "root": root, + }); + } +} diff --git a/packages/animated_vector_gen/lib/src/templates/animation_property.dart b/packages/animated_vector_gen/lib/src/templates/animation_property.dart new file mode 100644 index 0000000..bab7bd9 --- /dev/null +++ b/packages/animated_vector_gen/lib/src/templates/animation_property.dart @@ -0,0 +1,52 @@ +import 'package:animated_vector_gen/src/templates/template.dart'; +import 'package:animated_vector_gen/src/templates/utils.dart'; + +class AnimationPropertyTemplate extends Template { + final LerpTemplate tween; + final ValueType type; + final int? start; + final int end; + final String? curve; + + const AnimationPropertyTemplate({ + required this.tween, + required this.type, + required this.start, + required this.end, + required this.curve, + }); + + @override + String? build() { + return buildConstructorCall("AnimationProperty<${type.typeName}>", { + "tween": tween, + "interval": buildConstructorCall("AnimationInterval", { + "start": start != null && start! > 0 + ? "Duration(milliseconds: $start)" + : null, + "end": "Duration(milliseconds: $end)", + }), + "curve": curve, + }); + } +} + +class LerpTemplate extends Template { + final ValueType type; + final T? begin; + final T? end; + + const LerpTemplate({ + required this.type, + required this.begin, + required this.end, + }); + + @override + String? build() { + return buildConstructorCall(type.lerpName, { + "begin": wrapWithConstructor(begin, type), + "end": wrapWithConstructor(end, type), + }); + } +} diff --git a/packages/animated_vector_gen/lib/src/templates/clip_element.dart b/packages/animated_vector_gen/lib/src/templates/clip_element.dart new file mode 100644 index 0000000..48821cb --- /dev/null +++ b/packages/animated_vector_gen/lib/src/templates/clip_element.dart @@ -0,0 +1,38 @@ +import 'package:animated_vector_gen/src/templates/animation_property.dart'; +import 'package:animated_vector_gen/src/templates/template.dart'; +import 'package:animated_vector_gen/src/templates/utils.dart'; + +class ClipElementTemplate extends ElementTemplate { + final String pathData; + final ClipAnimationPropertiesTemplate? properties; + + const ClipElementTemplate({ + required this.pathData, + required this.properties, + }); + + @override + String? build() { + return buildConstructorCall("ClipPathElement", { + "pathData": wrapWithConstructor(pathData, ValueType.pathData), + "properties": properties?.build(), + }); + } +} + +class ClipAnimationPropertiesTemplate extends Template { + final List pathData; + + const ClipAnimationPropertiesTemplate({ + required this.pathData, + }); + + @override + String? build() { + if (everythingEmpty([pathData])) return null; + + return buildConstructorCall("ClipPathAnimationProperties", { + "pathData": pathData, + }); + } +} diff --git a/packages/animated_vector_gen/lib/src/templates/group_element.dart b/packages/animated_vector_gen/lib/src/templates/group_element.dart new file mode 100644 index 0000000..15a87d1 --- /dev/null +++ b/packages/animated_vector_gen/lib/src/templates/group_element.dart @@ -0,0 +1,85 @@ +import 'package:animated_vector_gen/src/templates/animation_property.dart'; +import 'package:animated_vector_gen/src/templates/template.dart'; +import 'package:animated_vector_gen/src/templates/utils.dart'; + +class GroupElementTemplate extends ElementTemplate { + final num? translateX; + final num? translateY; + final num? scaleX; + final num? scaleY; + final num? pivotX; + final num? pivotY; + final num? rotation; + final List elements; + final GroupAnimationPropertiesTemplate? properties; + + const GroupElementTemplate({ + required this.translateX, + required this.translateY, + required this.scaleX, + required this.scaleY, + required this.pivotX, + required this.pivotY, + required this.rotation, + required this.elements, + required this.properties, + }); + + @override + String? build() { + return buildConstructorCall("GroupElement", { + "translateX": handleDefault(translateX, 0.0), + "translateY": handleDefault(translateY, 0.0), + "scaleX": handleDefault(scaleX, 1.0), + "scaleY": handleDefault(scaleY, 1.0), + "pivotX": handleDefault(pivotX, 0.0), + "pivotY": handleDefault(pivotY, 0.0), + "rotation": handleDefault(rotation, 0.0), + "elements": elements, + "properties": properties?.build(), + }); + } +} + +class GroupAnimationPropertiesTemplate extends Template { + final List translateX; + final List translateY; + final List scaleX; + final List scaleY; + final List pivotX; + final List pivotY; + final List rotation; + + const GroupAnimationPropertiesTemplate({ + required this.translateX, + required this.translateY, + required this.scaleX, + required this.scaleY, + required this.pivotX, + required this.pivotY, + required this.rotation, + }); + + @override + String? build() { + if (everythingEmpty([ + translateX, + translateY, + scaleX, + scaleY, + pivotX, + pivotY, + rotation, + ])) return null; + + return buildConstructorCall("GroupAnimationProperties", { + "translateX": translateX, + "translateY": translateY, + "scaleX": scaleX, + "scaleY": scaleY, + "pivotX": pivotX, + "pivotY": pivotY, + "rotation": rotation, + }); + } +} diff --git a/packages/animated_vector_gen/lib/src/templates/path_element.dart b/packages/animated_vector_gen/lib/src/templates/path_element.dart new file mode 100644 index 0000000..1955e6b --- /dev/null +++ b/packages/animated_vector_gen/lib/src/templates/path_element.dart @@ -0,0 +1,107 @@ +import 'package:animated_vector_gen/src/templates/animation_property.dart'; +import 'package:animated_vector_gen/src/templates/template.dart'; +import 'package:animated_vector_gen/src/templates/utils.dart'; + +class PathElementTemplate extends ElementTemplate { + final String pathData; + final int? fillColor; + final num? fillAlpha; + final int? strokeColor; + final num? strokeAlpha; + final num? strokeWidth; + final String? strokeCap; + final String? strokeJoin; + final num? strokeMiterLimit; + final num? trimStart; + final num? trimEnd; + final num? trimOffset; + final PathAnimationPropertiesTemplate? properties; + + const PathElementTemplate({ + required this.pathData, + required this.fillColor, + required this.fillAlpha, + required this.strokeColor, + required this.strokeAlpha, + required this.strokeWidth, + required this.strokeCap, + required this.strokeJoin, + required this.strokeMiterLimit, + required this.trimStart, + required this.trimEnd, + required this.trimOffset, + required this.properties, + }); + + @override + String? build() { + return buildConstructorCall("PathElement", { + "pathData": wrapWithConstructor(pathData, ValueType.pathData), + "fillColor": + fillColor != null ? "Color(${colorFormat(fillColor!)})" : null, + "fillAlpha": handleDefault(fillAlpha, 1.0), + "strokeColor": + strokeColor != null ? "Color(${colorFormat(strokeColor!)})" : null, + "strokeAlpha": handleDefault(strokeAlpha, 1.0), + "strokeWidth": handleDefault(strokeWidth, 1.0), + "strokeCap": strokeCap != null ? "StrokeCap.$strokeCap" : null, + "strokeJoin": strokeJoin != null ? "StrokeJoin.$strokeJoin" : null, + "strokeMiterLimit": handleDefault(strokeMiterLimit, 4.0), + "trimStart": handleDefault(trimStart, 0.0), + "trimEnd": handleDefault(trimEnd, 1.0), + "trimOffset": handleDefault(trimOffset, 0.0), + "properties": properties?.build(), + }); + } +} + +class PathAnimationPropertiesTemplate extends Template { + final List pathData; + final List fillColor; + final List fillAlpha; + final List strokeColor; + final List strokeAlpha; + final List strokeWidth; + final List trimStart; + final List trimEnd; + final List trimOffset; + + const PathAnimationPropertiesTemplate({ + required this.pathData, + required this.fillColor, + required this.fillAlpha, + required this.strokeColor, + required this.strokeAlpha, + required this.strokeWidth, + required this.trimStart, + required this.trimEnd, + required this.trimOffset, + }); + + @override + String? build() { + if (everythingEmpty([ + pathData, + fillColor, + fillAlpha, + strokeColor, + strokeAlpha, + strokeWidth, + trimStart, + trimEnd, + trimOffset, + ])) return null; + + return buildConstructorCall("PathAnimationProperties", { + "pathData": pathData, + "fillColor": fillColor, + "fillAlpha": fillAlpha, + "strokeColor": strokeColor, + "strokeAlpha": strokeAlpha, + "strokeWidth": strokeWidth, + "trimStart": trimStart, + "trimEnd": trimEnd, + "trimOffset": trimOffset, + }); + } +} diff --git a/packages/animated_vector_gen/lib/src/templates/root_element.dart b/packages/animated_vector_gen/lib/src/templates/root_element.dart new file mode 100644 index 0000000..3527d33 --- /dev/null +++ b/packages/animated_vector_gen/lib/src/templates/root_element.dart @@ -0,0 +1,41 @@ +import 'package:animated_vector_gen/src/templates/animation_property.dart'; +import 'package:animated_vector_gen/src/templates/template.dart'; +import 'package:animated_vector_gen/src/templates/utils.dart'; + +class RootElementTemplate extends Template { + final num? alpha; + final List elements; + final RootAnimationPropertiesTemplate? properties; + + const RootElementTemplate({ + required this.alpha, + required this.elements, + required this.properties, + }); + + @override + String? build() { + return buildConstructorCall("RootVectorElement", { + "alpha": handleDefault(alpha, 1.0), + "elements": elements, + "properties": properties?.build(), + }); + } +} + +class RootAnimationPropertiesTemplate extends Template { + final List alpha; + + const RootAnimationPropertiesTemplate({ + required this.alpha, + }); + + @override + String? build() { + if (everythingEmpty([alpha])) return null; + + return buildConstructorCall("RootVectorAnimationProperties", { + "alpha": alpha, + }); + } +} diff --git a/packages/animated_vector_gen/lib/src/templates/template.dart b/packages/animated_vector_gen/lib/src/templates/template.dart new file mode 100644 index 0000000..899344f --- /dev/null +++ b/packages/animated_vector_gen/lib/src/templates/template.dart @@ -0,0 +1,12 @@ +abstract class Template { + const Template(); + + String? build(); + + @override + String toString() => build().toString(); +} + +abstract class ElementTemplate extends Template { + const ElementTemplate(); +} diff --git a/packages/animated_vector_gen/lib/src/templates/utils.dart b/packages/animated_vector_gen/lib/src/templates/utils.dart new file mode 100644 index 0000000..4bd7dda --- /dev/null +++ b/packages/animated_vector_gen/lib/src/templates/utils.dart @@ -0,0 +1,51 @@ +String buildConstructorCall(String name, Map fields) { + final buffer = StringBuffer("$name(\n"); + + for (final MapEntry(:key, :value) in fields.entries) { + if (value == null) continue; + if (value is List && value.isEmpty) continue; + final val = switch (value) { + final List list => "[${list.join(", ")},]", + _ => value, + }; + + buffer.writeln(" $key: $val,"); + } + + buffer.write(")"); + return buffer.toString(); +} + +String colorFormat(int color) { + return "0x${color.toRadixString(16).padLeft(8, "0").toUpperCase()}"; +} + +bool everythingEmpty(List> properties) { + return properties.every((element) => element.isEmpty); +} + +String? wrapWithConstructor(Object? value, ValueType type) { + if (value == null) return null; + + return switch (type) { + ValueType.pathData => "PathData.parse('$value',)", + ValueType.color => "Color(${colorFormat(value as int)})", + ValueType.number => value.toString(), + }; +} + +enum ValueType { + color("Color", "ColorLerp"), + pathData("PathData", "PathDataLerp"), + number("double", "ValueLerp"); + + final String typeName; + final String lerpName; + + const ValueType(this.typeName, this.lerpName); +} + +T? handleDefault(T? value, T defaultValue) { + if (value == defaultValue) return null; + return value; +} diff --git a/packages/animated_vector_gen/pubspec.yaml b/packages/animated_vector_gen/pubspec.yaml new file mode 100644 index 0000000..8986707 --- /dev/null +++ b/packages/animated_vector_gen/pubspec.yaml @@ -0,0 +1,18 @@ +name: animated_vector_gen +description: "Generators for animated_vector" +version: 0.0.1 + +environment: + sdk: ^3.1.0 + +dependencies: + analyzer: ^6.3.0 + animated_vector_annotations: + git: + url: https://github.com/HrX03/animated_vector + path: packages/animated_vector_annotations + build: ^2.4.1 + code_builder: ^4.7.0 + dart_style: ^2.3.3 + glob: ^2.1.2 + source_gen: ^1.4.0