Adding trim path support.

This commit is contained in:
Luigi Rosso
2020-08-28 18:27:54 -07:00
parent a02607dd89
commit c15236d7fc
17 changed files with 362 additions and 13 deletions

View File

@ -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).

View File

@ -7,7 +7,7 @@
## Add to pubspec.yaml
```yaml
dependencies:
rive: ^0.0.1+3
rive: ^0.5.2
```
## Example

View File

@ -94,7 +94,7 @@ packages:
path: ".."
relative: true
source: path
version: "0.5.1"
version: "0.5.2"
sky_engine:
dependency: transitive
description: flutter

View File

@ -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;

View File

@ -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<int> 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);
}

View File

@ -1,4 +1,3 @@
abstract class Interpolator {
int get id;
double transform(double value);

View File

@ -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

View File

@ -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];

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -0,0 +1,6 @@
import 'dart:ui';
abstract class StrokeEffect {
Path effectPath(Path source);
void invalidateEffect();
}

View File

@ -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();
}
}

View File

@ -0,0 +1,94 @@
import 'dart:ui';
double _appendPathSegmentSequential(Iterator<PathMetric> 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<PathMetric> 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);
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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