From c15236d7fca825b01db129d75f31be3df304c876 Mon Sep 17 00:00:00 2001 From: Luigi Rosso Date: Fri, 28 Aug 2020 18:27:54 -0700 Subject: [PATCH] Adding trim path support. --- CHANGELOG.md | 4 + README.md | 2 +- example/pubspec.lock | 2 +- lib/src/generated/rive_core_context.dart | 48 ++++++++++ .../shapes/paint/trim_path_base.dart | 90 ++++++++++++++++++ lib/src/rive_core/animation/interpolator.dart | 1 - lib/src/rive_core/artboard.dart | 2 + lib/src/rive_core/bones/skin.dart | 3 - lib/src/rive_core/component_dirt.dart | 1 + lib/src/rive_core/runtime/runtime_header.dart | 2 +- lib/src/rive_core/shapes/paint/stroke.dart | 20 +++- .../rive_core/shapes/paint/stroke_effect.dart | 6 ++ lib/src/rive_core/shapes/paint/trim_path.dart | 82 ++++++++++++++++ .../shapes/paint/trim_path_drawing.dart | 94 +++++++++++++++++++ lib/src/rive_core/shapes/shape.dart | 10 +- .../shapes/shape_paint_container.dart | 6 ++ pubspec.yaml | 2 +- 17 files changed, 362 insertions(+), 13 deletions(-) create mode 100644 lib/src/generated/shapes/paint/trim_path_base.dart create mode 100644 lib/src/rive_core/shapes/paint/stroke_effect.dart create mode 100644 lib/src/rive_core/shapes/paint/trim_path.dart create mode 100644 lib/src/rive_core/shapes/paint/trim_path_drawing.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index f96c401..a84809d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [0.5.2] - 2020-08-28 18:24:45 + +- Adding trim paths. + ## [0.5.1] - 2020-08-26 18:09:13 - Bumping version number to match the runtime file version (5.1). diff --git a/README.md b/README.md index c2a1943..c755684 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## Add to pubspec.yaml ```yaml dependencies: - rive: ^0.0.1+3 + rive: ^0.5.2 ``` ## Example diff --git a/example/pubspec.lock b/example/pubspec.lock index 7499034..cb16ca4 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -94,7 +94,7 @@ packages: path: ".." relative: true source: path - version: "0.5.1" + version: "0.5.2" sky_engine: dependency: transitive description: flutter diff --git a/lib/src/generated/rive_core_context.dart b/lib/src/generated/rive_core_context.dart index 660a75c..2ce723d 100644 --- a/lib/src/generated/rive_core_context.dart +++ b/lib/src/generated/rive_core_context.dart @@ -38,6 +38,7 @@ import 'package:rive/src/generated/shapes/paint/radial_gradient_base.dart'; import 'package:rive/src/generated/shapes/paint/shape_paint_base.dart'; import 'package:rive/src/generated/shapes/paint/solid_color_base.dart'; import 'package:rive/src/generated/shapes/paint/stroke_base.dart'; +import 'package:rive/src/generated/shapes/paint/trim_path_base.dart'; import 'package:rive/src/generated/shapes/parametric_path_base.dart'; import 'package:rive/src/generated/shapes/path_composer_base.dart'; import 'package:rive/src/generated/shapes/path_vertex_base.dart'; @@ -76,6 +77,7 @@ import 'package:rive/src/rive_core/shapes/paint/linear_gradient.dart'; import 'package:rive/src/rive_core/shapes/paint/radial_gradient.dart'; import 'package:rive/src/rive_core/shapes/paint/solid_color.dart'; import 'package:rive/src/rive_core/shapes/paint/stroke.dart'; +import 'package:rive/src/rive_core/shapes/paint/trim_path.dart'; import 'package:rive/src/rive_core/shapes/path_composer.dart'; import 'package:rive/src/rive_core/shapes/points_path.dart'; import 'package:rive/src/rive_core/shapes/rectangle.dart'; @@ -115,6 +117,8 @@ class RiveCoreContext { return SolidColor(); case GradientStopBase.typeKey: return GradientStop(); + case TrimPathBase.typeKey: + return TrimPath(); case FillBase.typeKey: return Fill(); case NodeBase.typeKey: @@ -348,6 +352,26 @@ class RiveCoreContext { object.position = value; } break; + case TrimPathBase.startPropertyKey: + if (object is TrimPathBase && value is double) { + object.start = value; + } + break; + case TrimPathBase.endPropertyKey: + if (object is TrimPathBase && value is double) { + object.end = value; + } + break; + case TrimPathBase.offsetPropertyKey: + if (object is TrimPathBase && value is double) { + object.offset = value; + } + break; + case TrimPathBase.modeValuePropertyKey: + if (object is TrimPathBase && value is int) { + object.modeValue = value; + } + break; case FillBase.fillRulePropertyKey: if (object is FillBase && value is int) { object.fillRule = value; @@ -653,6 +677,7 @@ class RiveCoreContext { case ComponentBase.parentIdPropertyKey: case StrokeBase.capPropertyKey: case StrokeBase.joinPropertyKey: + case TrimPathBase.modeValuePropertyKey: case FillBase.fillRulePropertyKey: case DrawableBase.drawOrderPropertyKey: case DrawableBase.blendModeValuePropertyKey: @@ -682,6 +707,9 @@ class RiveCoreContext { case LinearGradientBase.opacityPropertyKey: case StrokeBase.thicknessPropertyKey: case GradientStopBase.positionPropertyKey: + case TrimPathBase.startPropertyKey: + case TrimPathBase.endPropertyKey: + case TrimPathBase.offsetPropertyKey: case TransformComponentBase.rotationPropertyKey: case TransformComponentBase.scaleXPropertyKey: case TransformComponentBase.scaleYPropertyKey: @@ -772,6 +800,8 @@ class RiveCoreContext { return (object as StrokeBase).cap; case StrokeBase.joinPropertyKey: return (object as StrokeBase).join; + case TrimPathBase.modeValuePropertyKey: + return (object as TrimPathBase).modeValue; case FillBase.fillRulePropertyKey: return (object as FillBase).fillRule; case DrawableBase.drawOrderPropertyKey: @@ -838,6 +868,12 @@ class RiveCoreContext { return (object as StrokeBase).thickness; case GradientStopBase.positionPropertyKey: return (object as GradientStopBase).position; + case TrimPathBase.startPropertyKey: + return (object as TrimPathBase).start; + case TrimPathBase.endPropertyKey: + return (object as TrimPathBase).end; + case TrimPathBase.offsetPropertyKey: + return (object as TrimPathBase).offset; case TransformComponentBase.rotationPropertyKey: return (object as TransformComponentBase).rotation; case TransformComponentBase.scaleXPropertyKey: @@ -1001,6 +1037,9 @@ class RiveCoreContext { case StrokeBase.joinPropertyKey: (object as StrokeBase).join = value; break; + case TrimPathBase.modeValuePropertyKey: + (object as TrimPathBase).modeValue = value; + break; case FillBase.fillRulePropertyKey: (object as FillBase).fillRule = value; break; @@ -1092,6 +1131,15 @@ class RiveCoreContext { case GradientStopBase.positionPropertyKey: (object as GradientStopBase).position = value; break; + case TrimPathBase.startPropertyKey: + (object as TrimPathBase).start = value; + break; + case TrimPathBase.endPropertyKey: + (object as TrimPathBase).end = value; + break; + case TrimPathBase.offsetPropertyKey: + (object as TrimPathBase).offset = value; + break; case TransformComponentBase.rotationPropertyKey: (object as TransformComponentBase).rotation = value; break; diff --git a/lib/src/generated/shapes/paint/trim_path_base.dart b/lib/src/generated/shapes/paint/trim_path_base.dart new file mode 100644 index 0000000..4b10e46 --- /dev/null +++ b/lib/src/generated/shapes/paint/trim_path_base.dart @@ -0,0 +1,90 @@ +/// Core automatically generated +/// lib/src/generated/shapes/paint/trim_path_base.dart. +/// Do not modify manually. + +import 'package:rive/src/generated/component_base.dart'; +import 'package:rive/src/rive_core/component.dart'; + +abstract class TrimPathBase extends Component { + static const int typeKey = 47; + @override + int get coreType => TrimPathBase.typeKey; + @override + Set get coreTypes => {TrimPathBase.typeKey, ComponentBase.typeKey}; + + /// -------------------------------------------------------------------------- + /// Start field with key 114. + double _start = 0; + static const int startPropertyKey = 114; + double get start => _start; + + /// Change the [_start] field value. + /// [startChanged] will be invoked only if the field's value has changed. + set start(double value) { + if (_start == value) { + return; + } + double from = _start; + _start = value; + startChanged(from, value); + } + + void startChanged(double from, double to); + + /// -------------------------------------------------------------------------- + /// End field with key 115. + double _end = 0; + static const int endPropertyKey = 115; + double get end => _end; + + /// Change the [_end] field value. + /// [endChanged] will be invoked only if the field's value has changed. + set end(double value) { + if (_end == value) { + return; + } + double from = _end; + _end = value; + endChanged(from, value); + } + + void endChanged(double from, double to); + + /// -------------------------------------------------------------------------- + /// Offset field with key 116. + double _offset = 0; + static const int offsetPropertyKey = 116; + double get offset => _offset; + + /// Change the [_offset] field value. + /// [offsetChanged] will be invoked only if the field's value has changed. + set offset(double value) { + if (_offset == value) { + return; + } + double from = _offset; + _offset = value; + offsetChanged(from, value); + } + + void offsetChanged(double from, double to); + + /// -------------------------------------------------------------------------- + /// ModeValue field with key 117. + int _modeValue = 0; + static const int modeValuePropertyKey = 117; + int get modeValue => _modeValue; + + /// Change the [_modeValue] field value. + /// [modeValueChanged] will be invoked only if the field's value has changed. + set modeValue(int value) { + if (_modeValue == value) { + return; + } + int from = _modeValue; + _modeValue = value; + modeValueChanged(from, value); + } + + void modeValueChanged(int from, int to); +} diff --git a/lib/src/rive_core/animation/interpolator.dart b/lib/src/rive_core/animation/interpolator.dart index 7ec2734..88d4e4c 100644 --- a/lib/src/rive_core/animation/interpolator.dart +++ b/lib/src/rive_core/animation/interpolator.dart @@ -1,4 +1,3 @@ - abstract class Interpolator { int get id; double transform(double value); diff --git a/lib/src/rive_core/artboard.dart b/lib/src/rive_core/artboard.dart index 81a0813..7f08b8e 100644 --- a/lib/src/rive_core/artboard.dart +++ b/lib/src/rive_core/artboard.dart @@ -82,6 +82,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer { @override void heightChanged(double from, double to) { addDirt(ComponentDirt.worldTransform); + invalidateStrokeEffects(); } void onComponentDirty(Component component) { @@ -122,6 +123,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer { @override void widthChanged(double from, double to) { addDirt(ComponentDirt.worldTransform); + invalidateStrokeEffects(); } @override diff --git a/lib/src/rive_core/bones/skin.dart b/lib/src/rive_core/bones/skin.dart index 1f70afb..c081a22 100644 --- a/lib/src/rive_core/bones/skin.dart +++ b/lib/src/rive_core/bones/skin.dart @@ -3,7 +3,6 @@ import 'package:rive/src/rive_core/bones/skinnable.dart'; import 'package:rive/src/rive_core/bones/tendon.dart'; import 'package:rive/src/rive_core/component.dart'; import 'package:rive/src/rive_core/math/mat2d.dart'; -import 'package:rive/src/rive_core/math/transform_components.dart'; import 'package:rive/src/rive_core/shapes/path_vertex.dart'; import 'package:rive/src/generated/bones/skin_base.dart'; export 'package:rive/src/generated/bones/skin_base.dart'; @@ -35,8 +34,6 @@ class Skin extends SkinBase { for (final tendon in _tendons) { var boneWorld = tendon.bone.worldTransform; var wt = Mat2D.multiply(temp, boneWorld, tendon.inverseBind); - var tc = TransformComponents(); - Mat2D.decompose(boneWorld, tc); _boneTransforms[bidx++] = wt[0]; _boneTransforms[bidx++] = wt[1]; _boneTransforms[bidx++] = wt[2]; diff --git a/lib/src/rive_core/component_dirt.dart b/lib/src/rive_core/component_dirt.dart index 6d93a8d..4af0a6a 100644 --- a/lib/src/rive_core/component_dirt.dart +++ b/lib/src/rive_core/component_dirt.dart @@ -9,4 +9,5 @@ class ComponentDirt { static const int paint = 1 << 7; static const int stops = 1 << 8; static const int clip = 1 << 9; + static const int blendMode = 1 << 10; } diff --git a/lib/src/rive_core/runtime/runtime_header.dart b/lib/src/rive_core/runtime/runtime_header.dart index ca3c829..8cf26af 100644 --- a/lib/src/rive_core/runtime/runtime_header.dart +++ b/lib/src/rive_core/runtime/runtime_header.dart @@ -5,7 +5,7 @@ import 'exceptions/rive_format_error_exception.dart'; class RuntimeHeader { static const int majorVersion = 5; - static const int minorVersion = 1; + static const int minorVersion = 2; static const String fingerprint = 'RIVE'; final int ownerId; final int fileId; diff --git a/lib/src/rive_core/shapes/paint/stroke.dart b/lib/src/rive_core/shapes/paint/stroke.dart index c6923eb..5929462 100644 --- a/lib/src/rive_core/shapes/paint/stroke.dart +++ b/lib/src/rive_core/shapes/paint/stroke.dart @@ -1,11 +1,25 @@ import 'package:flutter/material.dart'; import 'package:rive/src/rive_core/component_dirt.dart'; +import 'package:rive/src/rive_core/shapes/paint/stroke_effect.dart'; import 'package:rive/src/rive_core/shapes/shape.dart'; import 'package:rive/src/rive_core/shapes/shape_paint_container.dart'; import 'package:rive/src/generated/shapes/paint/stroke_base.dart'; export 'package:rive/src/generated/shapes/paint/stroke_base.dart'; class Stroke extends StrokeBase { + StrokeEffect _effect; + StrokeEffect get effect => _effect; + // ignore: use_setters_to_change_properties + void addStrokeEffect(StrokeEffect effect) { + _effect = effect; + } + + void removeStrokeEffect(StrokeEffect effect) { + if (effect == _effect) { + _effect = null; + } + } + @override Paint makePaint() => Paint() ..style = PaintingStyle.stroke @@ -52,11 +66,15 @@ class Stroke extends StrokeBase { } } + void invalidateEffects() { + _effect?.invalidateEffect(); + } + @override void draw(Canvas canvas, Path path) { if (!isVisible) { return; } - canvas.drawPath(path, paint); + canvas.drawPath(_effect?.effectPath(path) ?? path, paint); } } diff --git a/lib/src/rive_core/shapes/paint/stroke_effect.dart b/lib/src/rive_core/shapes/paint/stroke_effect.dart new file mode 100644 index 0000000..eeb9823 --- /dev/null +++ b/lib/src/rive_core/shapes/paint/stroke_effect.dart @@ -0,0 +1,6 @@ +import 'dart:ui'; + +abstract class StrokeEffect { + Path effectPath(Path source); + void invalidateEffect(); +} diff --git a/lib/src/rive_core/shapes/paint/trim_path.dart b/lib/src/rive_core/shapes/paint/trim_path.dart new file mode 100644 index 0000000..87116ae --- /dev/null +++ b/lib/src/rive_core/shapes/paint/trim_path.dart @@ -0,0 +1,82 @@ +import 'dart:ui'; +import 'package:rive/src/rive_core/component_dirt.dart'; +import 'package:rive/src/rive_core/shapes/paint/stroke.dart'; +import 'package:rive/src/rive_core/shapes/paint/stroke_effect.dart'; +import 'package:rive/src/rive_core/shapes/paint/trim_path_drawing.dart'; +import 'package:rive/src/generated/shapes/paint/trim_path_base.dart'; +export 'package:rive/src/generated/shapes/paint/trim_path_base.dart'; + +enum TrimPathMode { none, sequential, synchronized } + +class TrimPath extends TrimPathBase implements StrokeEffect { + final Path _trimmedPath = Path(); + Path _renderPath; + @override + Path effectPath(Path source) { + if (_renderPath != null) { + return _renderPath; + } + _trimmedPath.reset(); + var isSequential = mode == TrimPathMode.sequential; + double renderStart = start.clamp(0, 1).toDouble(); + double renderEnd = end.clamp(0, 1).toDouble(); + bool inverted = renderStart > renderEnd; + if ((renderStart - renderEnd).abs() != 1.0) { + renderStart = (renderStart + offset) % 1.0; + renderEnd = (renderEnd + offset) % 1.0; + if (renderStart < 0) { + renderStart += 1.0; + } + if (renderEnd < 0) { + renderEnd += 1.0; + } + if (inverted) { + final double swap = renderEnd; + renderEnd = renderStart; + renderStart = swap; + } + if (renderEnd >= renderStart) { + updateTrimPath( + source, _trimmedPath, renderStart, renderEnd, false, isSequential); + } else { + updateTrimPath( + source, _trimmedPath, renderEnd, renderStart, true, isSequential); + } + } else { + return _renderPath = source; + } + return _renderPath = _trimmedPath; + } + + Stroke get stroke => parent as Stroke; + TrimPathMode get mode => TrimPathMode.values[modeValue]; + set mode(TrimPathMode value) => modeValue = value.index; + @override + void invalidateEffect() { + _renderPath = null; + stroke?.shapePaintContainer?.addDirt(ComponentDirt.paint); + } + + @override + void endChanged(double from, double to) => invalidateEffect(); + @override + void modeValueChanged(int from, int to) => invalidateEffect(); + @override + void offsetChanged(double from, double to) => invalidateEffect(); + @override + void startChanged(double from, double to) => invalidateEffect(); + @override + void update(int dirt) {} + @override + void onAdded() { + super.onAdded(); + stroke?.addStrokeEffect(this); + _renderPath = null; + } + + @override + void onRemoved() { + stroke?.removeStrokeEffect(this); + super.onRemoved(); + } +} diff --git a/lib/src/rive_core/shapes/paint/trim_path_drawing.dart b/lib/src/rive_core/shapes/paint/trim_path_drawing.dart new file mode 100644 index 0000000..680e281 --- /dev/null +++ b/lib/src/rive_core/shapes/paint/trim_path_drawing.dart @@ -0,0 +1,94 @@ +import 'dart:ui'; + +double _appendPathSegmentSequential(Iterator metricsIterator, + Path to, double offset, double start, double stop) { + double nextOffset = offset; + do { + PathMetric metric = metricsIterator.current; + nextOffset = offset + metric.length; + if (start < nextOffset) { + Path extracted = metric.extractPath(start - offset, stop - offset); + if (extracted != null) { + to.addPath(extracted, Offset.zero); + } + if (stop < nextOffset) { + break; + } + } + // ignore: parameter_assignments + offset = nextOffset; + } while (metricsIterator.moveNext()); + return offset; +} + +void _appendPathSegmentSync( + PathMetric metric, Path to, double offset, double start, double stop) { + double nextOffset = offset + metric.length; + if (start < nextOffset) { + Path extracted = metric.extractPath(start - offset, stop - offset); + if (extracted != null) { + to.addPath(extracted, Offset.zero); + } + } +} + +void _trimPathSequential( + Path path, Path result, double startT, double stopT, bool complement) { + PathMetrics metrics = path.computeMetrics(); + double totalLength = 0.0; + for (final PathMetric metric in metrics) { + totalLength += metric.length; + } + metrics = path.computeMetrics(); + double trimStart = totalLength * startT; + double trimStop = totalLength * stopT; + double offset = 0.0; + Iterator metricsIterator = metrics.iterator; + metricsIterator.moveNext(); + if (complement) { + if (trimStart > 0.0) { + offset = _appendPathSegmentSequential( + metricsIterator, result, offset, 0.0, trimStart); + } + if (trimStop < totalLength) { + offset = _appendPathSegmentSequential( + metricsIterator, result, offset, trimStop, totalLength); + } + } else { + if (trimStart < trimStop) { + offset = _appendPathSegmentSequential( + metricsIterator, result, offset, trimStart, trimStop); + } + } +} + +void _trimPathSync( + Path path, Path result, double startT, double stopT, bool complement) { + final PathMetrics metrics = path.computeMetrics(); + for (final PathMetric metric in metrics) { + double length = metric.length; + double trimStart = length * startT; + double trimStop = length * stopT; + if (complement) { + if (trimStart > 0.0) { + _appendPathSegmentSync(metric, result, 0.0, 0.0, trimStart); + } + if (trimStop < length) { + _appendPathSegmentSync(metric, result, 0.0, trimStop, length); + } + } else { + if (trimStart < trimStop) { + _appendPathSegmentSync(metric, result, 0.0, trimStart, trimStop); + } + } + } +} + +void updateTrimPath(Path path, Path result, double startT, double stopT, + bool complement, bool isSequential) { + if (isSequential) { + _trimPathSequential(path, result, startT, stopT, complement); + } else { + _trimPathSync(path, result, startT, stopT, complement); + } +} diff --git a/lib/src/rive_core/shapes/shape.dart b/lib/src/rive_core/shapes/shape.dart index bab18d7..d562795 100644 --- a/lib/src/rive_core/shapes/shape.dart +++ b/lib/src/rive_core/shapes/shape.dart @@ -33,16 +33,18 @@ class Shape extends ShapeBase with ShapePaintContainer { return paths.add(path); } - void pathChanged(Path path) { + void _markComposerDirty() { _pathComposer?.addDirt(ComponentDirt.path); + invalidateStrokeEffects(); } + void pathChanged(Path path) => _markComposerDirty(); void paintChanged() { addDirt(ComponentDirt.path); for (final d in dependents) { d.addDirt(ComponentDirt.worldTransform); } - _pathComposer?.addDirt(ComponentDirt.path); + _markComposerDirty(); } @override @@ -60,7 +62,7 @@ class Shape extends ShapeBase with ShapePaintContainer { @override void update(int dirt) { super.update(dirt); - if (dirt & ComponentDirt.paint != 0) { + if (dirt & ComponentDirt.blendMode != 0) { for (final fill in fills) { fill.blendMode = blendMode; } @@ -152,7 +154,7 @@ class Shape extends ShapeBase with ShapePaintContainer { } } - void _markBlendModeDirty() => addDirt(ComponentDirt.paint); + void _markBlendModeDirty() => addDirt(ComponentDirt.blendMode); @override void onPaintMutatorChanged(ShapePaintMutator mutator) { paintChanged(); diff --git a/lib/src/rive_core/shapes/shape_paint_container.dart b/lib/src/rive_core/shapes/shape_paint_container.dart index 4ab1028..d24944e 100644 --- a/lib/src/rive_core/shapes/shape_paint_container.dart +++ b/lib/src/rive_core/shapes/shape_paint_container.dart @@ -14,6 +14,12 @@ abstract class ShapePaintContainer { void onFillsChanged(); @protected void onStrokesChanged(); + void invalidateStrokeEffects() { + for (final stroke in strokes) { + stroke.invalidateEffects(); + } + } + bool addFill(Fill fill) { if (fills.add(fill)) { onFillsChanged(); diff --git a/pubspec.yaml b/pubspec.yaml index ed4770e..9a8eaab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: rive description: Rive 2 Flutter Runtime -version: 0.5.1 +version: 0.5.2 repository: https://github.com/rive-app/rive-flutter homepage: https://rive.app