From 22b2d4c8e048af3efd3956f65829eaf1f229aaf2 Mon Sep 17 00:00:00 2001 From: Luigi Rosso Date: Thu, 18 Nov 2021 16:32:12 -0800 Subject: [PATCH] Adding asset resolver for loading out of band assets --- .../core/importers/file_asset_importer.dart | 22 ++++++- lib/src/generated/assets/asset_base.dart | 29 ++++++++++ lib/src/generated/assets/file_asset_base.dart | 30 ++++++++++ lib/src/generated/rive_core_context.dart | 28 +++++++++ lib/src/rive_core/assets/asset.dart | 6 +- lib/src/rive_core/assets/drawable_asset.dart | 5 ++ lib/src/rive_core/assets/file_asset.dart | 17 ++++++ lib/src/rive_core/assets/image_asset.dart | 12 ++++ lib/src/rive_file.dart | 58 ++++++++++++++++--- 9 files changed, 194 insertions(+), 13 deletions(-) diff --git a/lib/src/core/importers/file_asset_importer.dart b/lib/src/core/importers/file_asset_importer.dart index b461bf1..c6687d0 100644 --- a/lib/src/core/importers/file_asset_importer.dart +++ b/lib/src/core/importers/file_asset_importer.dart @@ -4,12 +4,32 @@ import 'package:rive/src/core/core.dart'; import 'package:rive/src/rive_core/assets/file_asset.dart'; import 'package:rive/src/rive_core/assets/file_asset_contents.dart'; +/// A helper for resolving Rive file assets (like images) that are provided out +/// of band with regards to the .riv file itself. +// ignore: one_member_abstracts +abstract class RiveAssetResolver { + Future loadContents(FileAsset asset); +} + class FileAssetImporter extends ImportStackObject { + final RiveAssetResolver? assetResolver; final FileAsset fileAsset; - FileAssetImporter(this.fileAsset); + FileAssetImporter(this.fileAsset, this.assetResolver); + + bool _loadedContents = false; void loadContents(FileAssetContents contents) { + _loadedContents = true; fileAsset.decode(contents.bytes as Uint8List); } + + @override + bool resolve() { + if (!_loadedContents) { + // try to get them out of band + assetResolver?.loadContents(fileAsset).then(fileAsset.decode); + } + return super.resolve(); + } } diff --git a/lib/src/generated/assets/asset_base.dart b/lib/src/generated/assets/asset_base.dart index 644b97e..f12264d 100644 --- a/lib/src/generated/assets/asset_base.dart +++ b/lib/src/generated/assets/asset_base.dart @@ -9,4 +9,33 @@ abstract class AssetBase extends Core { int get coreType => AssetBase.typeKey; @override Set get coreTypes => {AssetBase.typeKey}; + + /// -------------------------------------------------------------------------- + /// Name field with key 203. + static const String nameInitialValue = ''; + String _name = nameInitialValue; + static const int namePropertyKey = 203; + + /// Name of the asset + String get name => _name; + + /// Change the [_name] field value. + /// [nameChanged] will be invoked only if the field's value has changed. + set name(String value) { + if (_name == value) { + return; + } + String from = _name; + _name = value; + if (hasValidated) { + nameChanged(from, value); + } + } + + void nameChanged(String from, String to); + + @override + void copy(covariant AssetBase source) { + _name = source._name; + } } diff --git a/lib/src/generated/assets/file_asset_base.dart b/lib/src/generated/assets/file_asset_base.dart index eb9b27d..acb808d 100644 --- a/lib/src/generated/assets/file_asset_base.dart +++ b/lib/src/generated/assets/file_asset_base.dart @@ -10,4 +10,34 @@ abstract class FileAssetBase extends Asset { int get coreType => FileAssetBase.typeKey; @override Set get coreTypes => {FileAssetBase.typeKey, AssetBase.typeKey}; + + /// -------------------------------------------------------------------------- + /// AssetId field with key 204. + static const int assetIdInitialValue = 0; + int _assetId = assetIdInitialValue; + static const int assetIdPropertyKey = 204; + + /// Id of the asset as stored on the backend + int get assetId => _assetId; + + /// Change the [_assetId] field value. + /// [assetIdChanged] will be invoked only if the field's value has changed. + set assetId(int value) { + if (_assetId == value) { + return; + } + int from = _assetId; + _assetId = value; + if (hasValidated) { + assetIdChanged(from, value); + } + } + + void assetIdChanged(int from, int to); + + @override + void copy(covariant FileAssetBase source) { + super.copy(source); + _assetId = source._assetId; + } } diff --git a/lib/src/generated/rive_core_context.dart b/lib/src/generated/rive_core_context.dart index 28c5244..7a9483c 100644 --- a/lib/src/generated/rive_core_context.dart +++ b/lib/src/generated/rive_core_context.dart @@ -43,7 +43,9 @@ import 'package:rive/src/generated/animation/transition_number_condition_base.da import 'package:rive/src/generated/animation/transition_trigger_condition_base.dart'; import 'package:rive/src/generated/animation/transition_value_condition_base.dart'; import 'package:rive/src/generated/artboard_base.dart'; +import 'package:rive/src/generated/assets/asset_base.dart'; import 'package:rive/src/generated/assets/drawable_asset_base.dart'; +import 'package:rive/src/generated/assets/file_asset_base.dart'; import 'package:rive/src/generated/assets/file_asset_contents_base.dart'; import 'package:rive/src/generated/assets/folder_base.dart'; import 'package:rive/src/generated/assets/image_asset_base.dart'; @@ -1098,6 +1100,16 @@ class RiveCoreContext { object.ty = value; } break; + case AssetBase.namePropertyKey: + if (object is AssetBase && value is String) { + object.name = value; + } + break; + case FileAssetBase.assetIdPropertyKey: + if (object is FileAssetBase && value is int) { + object.assetId = value; + } + break; case DrawableAssetBase.heightPropertyKey: if (object is DrawableAssetBase && value is double) { object.height = value; @@ -1127,6 +1139,7 @@ class RiveCoreContext { case ComponentBase.namePropertyKey: case AnimationBase.namePropertyKey: case StateMachineComponentBase.namePropertyKey: + case AssetBase.namePropertyKey: return stringType; case ComponentBase.parentIdPropertyKey: case DrawTargetBase.drawableIdPropertyKey: @@ -1180,6 +1193,7 @@ class RiveCoreContext { case ImageBase.assetIdPropertyKey: case DrawRulesBase.drawTargetIdPropertyKey: case TendonBase.boneIdPropertyKey: + case FileAssetBase.assetIdPropertyKey: return uintType; case ConstraintBase.strengthPropertyKey: case DistanceConstraintBase.distancePropertyKey: @@ -1301,6 +1315,8 @@ class RiveCoreContext { return (object as AnimationBase).name; case StateMachineComponentBase.namePropertyKey: return (object as StateMachineComponentBase).name; + case AssetBase.namePropertyKey: + return (object as AssetBase).name; } return ''; } @@ -1411,6 +1427,8 @@ class RiveCoreContext { return (object as DrawRulesBase).drawTargetId; case TendonBase.boneIdPropertyKey: return (object as TendonBase).boneId; + case FileAssetBase.assetIdPropertyKey: + return (object as FileAssetBase).assetId; } return 0; } @@ -1662,6 +1680,11 @@ class RiveCoreContext { object.name = value; } break; + case AssetBase.namePropertyKey: + if (object is AssetBase) { + object.name = value; + } + break; } } @@ -1927,6 +1950,11 @@ class RiveCoreContext { object.boneId = value; } break; + case FileAssetBase.assetIdPropertyKey: + if (object is FileAssetBase) { + object.assetId = value; + } + break; } } diff --git a/lib/src/rive_core/assets/asset.dart b/lib/src/rive_core/assets/asset.dart index 6cd7f7e..43f9eae 100644 --- a/lib/src/rive_core/assets/asset.dart +++ b/lib/src/rive_core/assets/asset.dart @@ -13,14 +13,14 @@ class Asset extends AssetBase { _backboard = value; } + @override + void nameChanged(String from, String to) {} + @override void onAdded() {} @override void onAddedDirty() {} - @override - void parentIdChanged(int from, int to) {} - bool get isUsable => false; } diff --git a/lib/src/rive_core/assets/drawable_asset.dart b/lib/src/rive_core/assets/drawable_asset.dart index 7683746..551c438 100644 --- a/lib/src/rive_core/assets/drawable_asset.dart +++ b/lib/src/rive_core/assets/drawable_asset.dart @@ -10,4 +10,9 @@ abstract class DrawableAsset extends DrawableAssetBase { @override void widthChanged(double from, double to) {} + + @override + void assetIdChanged(int from, int to) { + width = height = 0; + } } diff --git a/lib/src/rive_core/assets/file_asset.dart b/lib/src/rive_core/assets/file_asset.dart index 45a894e..0418810 100644 --- a/lib/src/rive_core/assets/file_asset.dart +++ b/lib/src/rive_core/assets/file_asset.dart @@ -7,6 +7,9 @@ import 'package:rive/src/rive_core/backboard.dart'; export 'package:rive/src/generated/assets/file_asset_base.dart'; abstract class FileAsset extends FileAssetBase { + @override + void assetIdChanged(int from, int to) {} + Future decode(Uint8List bytes); @override @@ -20,6 +23,20 @@ abstract class FileAsset extends FileAssetBase { return super.import(stack); } + + /// Returns the file extension, for example for an image it would be png. + String get fileExtension; + + /// Returns a unique filename, useful for resolving files out of band. + String get uniqueFilename { + // remove final extension + var uniqueFilename = name; + var index = uniqueFilename.lastIndexOf('.'); + if (index != -1) { + uniqueFilename = uniqueFilename.substring(0, index); + } + return '$uniqueFilename-$assetId.$fileExtension'; + } } /// A mixin for any class that references a generic FileAsset. diff --git a/lib/src/rive_core/assets/image_asset.dart b/lib/src/rive_core/assets/image_asset.dart index 422c77b..70f6b1b 100644 --- a/lib/src/rive_core/assets/image_asset.dart +++ b/lib/src/rive_core/assets/image_asset.dart @@ -1,7 +1,10 @@ import 'dart:async'; import 'dart:typed_data'; import 'dart:ui' as ui; + import 'package:rive/src/generated/assets/image_asset_base.dart'; +import 'package:rive/src/rive_core/shapes/image.dart'; + export 'package:rive/src/generated/assets/image_asset_base.dart'; class ImageAsset extends ImageAssetBase { @@ -17,4 +20,13 @@ class ImageAsset extends ImageAssetBase { }); return completer.future; } + + Image getDefaultObject() => Image() + ..asset = this + ..name = name; + + /// The editor works with images as PNGs, even if their sources may have come + /// from other formats. + @override + String get fileExtension => 'png'; } diff --git a/lib/src/rive_file.dart b/lib/src/rive_file.dart index 617a394..ec5d1eb 100644 --- a/lib/src/rive_file.dart +++ b/lib/src/rive_file.dart @@ -89,10 +89,12 @@ class RiveFile { Backboard _backboard = Backboard.unknown; final _artboards = []; + final RiveAssetResolver? _assetResolver; RiveFile._( BinaryReader reader, this.header, + this._assetResolver, ) { /// Property fields table of contents final propertyToField = HashMap(); @@ -182,7 +184,7 @@ class RiveFile { break; } case ImageAssetBase.typeKey: - stackObject = FileAssetImporter(object as FileAsset); + stackObject = FileAssetImporter(object as FileAsset, _assetResolver); stackType = FileAssetBase.typeKey; break; default: @@ -243,22 +245,38 @@ class RiveFile { /// Imports a Rive file from an array of bytes. Will throw /// [RiveFormatErrorException] if data is malformed. Will throw /// [RiveUnsupportedVersionException] if the version is not supported. - factory RiveFile.import(ByteData bytes) { + factory RiveFile.import( + ByteData bytes, { + RiveAssetResolver? assetResolver, + }) { var reader = BinaryReader(bytes); - return RiveFile._(reader, RuntimeHeader.read(reader)); + return RiveFile._(reader, RuntimeHeader.read(reader), assetResolver); } - /// Imports a Rive file from an asset bundle - static Future asset(String bundleKey) async { + /// Imports a Rive file from an asset bundle. Provide [basePath] if any nested + /// Rive asset isn't in the same path as the [bundleKey]. + static Future asset(String bundleKey, {String? basePath}) async { final bytes = await rootBundle.load(bundleKey); - return RiveFile.import(bytes); + if (basePath == null) { + int index = bundleKey.lastIndexOf('/'); + if (index != -1) { + basePath = bundleKey.substring(0, index + 1); + } else { + // ignore: parameter_assignments + basePath = ''; + } + } + return RiveFile.import(bytes, assetResolver: _LocalAssetResolver(basePath)); } - /// Imports a Rive file from a url over http - static Future network(String url) async { + /// Imports a Rive file from a url over http. Provide an [assetResolver] if + /// your file contains images that needed to be loaded with separate network + /// requests. + static Future network(String url, + {RiveAssetResolver? assetResolver}) async { final res = await http.get(Uri.parse(url)); final bytes = ByteData.view(res.bodyBytes.buffer); - return RiveFile.import(bytes); + return RiveFile.import(bytes, assetResolver: assetResolver); } /// Imports a Rive file from local folder @@ -278,3 +296,25 @@ class RiveFile { Artboard? artboardByName(String name) => _artboards.firstWhereOrNull((a) => a.name == name); } + +/// Resolves a Rive asset from the network provided a [baseUrl]. +class NetworkAssetResolver extends RiveAssetResolver { + final String baseUrl; + NetworkAssetResolver(this.baseUrl); + + @override + Future loadContents(FileAsset asset) async { + final res = await http.get(Uri.parse(baseUrl + asset.uniqueFilename)); + return Uint8List.view(res.bodyBytes.buffer); + } +} + +class _LocalAssetResolver extends RiveAssetResolver { + String basePath; + _LocalAssetResolver(this.basePath); + @override + Future loadContents(FileAsset asset) async { + final bytes = await rootBundle.load(basePath + asset.uniqueFilename); + return Uint8List.view(bytes.buffer); + } +}