mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-06-26 01:18:13 +08:00
Adding support for stars, shapes, and trim path fixes.
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,3 +1,14 @@
|
||||
## [0.6.4] - 2020-12-11 15:43:01
|
||||
|
||||
- Adding support for parametric polygon and star shapes.
|
||||
- Fixes to trim paths that wrap around the entire shape.
|
||||
- Expose mix value in SimpleAnimation, allows for mixing multiple animations together.
|
||||
|
||||
## [0.6.3] - 2020-11-17 16:02:47
|
||||
|
||||
- Added support for parametric path origins.
|
||||
- Fixes for rendering artboards with non-zero origin values.
|
||||
|
||||
## [0.6.2+3] - 2020-11-11 12:13:00
|
||||
|
||||
- Added Artboard tests.
|
||||
|
@ -44,8 +44,10 @@ 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';
|
||||
import 'package:rive/src/generated/shapes/points_path_base.dart';
|
||||
import 'package:rive/src/generated/shapes/polygon_base.dart';
|
||||
import 'package:rive/src/generated/shapes/rectangle_base.dart';
|
||||
import 'package:rive/src/generated/shapes/shape_base.dart';
|
||||
import 'package:rive/src/generated/shapes/star_base.dart';
|
||||
import 'package:rive/src/generated/shapes/straight_vertex_base.dart';
|
||||
import 'package:rive/src/generated/shapes/triangle_base.dart';
|
||||
import 'package:rive/src/generated/transform_component_base.dart';
|
||||
@ -82,8 +84,10 @@ 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/polygon.dart';
|
||||
import 'package:rive/src/rive_core/shapes/rectangle.dart';
|
||||
import 'package:rive/src/rive_core/shapes/shape.dart';
|
||||
import 'package:rive/src/rive_core/shapes/star.dart';
|
||||
import 'package:rive/src/rive_core/shapes/straight_vertex.dart';
|
||||
import 'package:rive/src/rive_core/shapes/triangle.dart';
|
||||
|
||||
@ -147,6 +151,10 @@ class RiveCoreContext {
|
||||
return Ellipse();
|
||||
case ClippingShapeBase.typeKey:
|
||||
return ClippingShape();
|
||||
case PolygonBase.typeKey:
|
||||
return Polygon();
|
||||
case StarBase.typeKey:
|
||||
return Star();
|
||||
case PathComposerBase.typeKey:
|
||||
return PathComposer();
|
||||
case CubicDetachedVertexBase.typeKey:
|
||||
@ -540,6 +548,21 @@ class RiveCoreContext {
|
||||
object.isVisible = value;
|
||||
}
|
||||
break;
|
||||
case PolygonBase.pointsPropertyKey:
|
||||
if (object is PolygonBase && value is int) {
|
||||
object.points = value;
|
||||
}
|
||||
break;
|
||||
case PolygonBase.cornerRadiusPropertyKey:
|
||||
if (object is PolygonBase && value is double) {
|
||||
object.cornerRadius = value;
|
||||
}
|
||||
break;
|
||||
case StarBase.innerRadiusPropertyKey:
|
||||
if (object is StarBase && value is double) {
|
||||
object.innerRadius = value;
|
||||
}
|
||||
break;
|
||||
case CubicDetachedVertexBase.inRotationPropertyKey:
|
||||
if (object is CubicDetachedVertexBase && value is double) {
|
||||
object.inRotation = value;
|
||||
@ -715,6 +738,7 @@ class RiveCoreContext {
|
||||
case CubicWeightBase.outIndicesPropertyKey:
|
||||
case ClippingShapeBase.sourceIdPropertyKey:
|
||||
case ClippingShapeBase.fillRulePropertyKey:
|
||||
case PolygonBase.pointsPropertyKey:
|
||||
case DrawRulesBase.drawTargetIdPropertyKey:
|
||||
case TendonBase.boneIdPropertyKey:
|
||||
return uintType;
|
||||
@ -753,6 +777,8 @@ class RiveCoreContext {
|
||||
case RectangleBase.cornerRadiusPropertyKey:
|
||||
case CubicMirroredVertexBase.rotationPropertyKey:
|
||||
case CubicMirroredVertexBase.distancePropertyKey:
|
||||
case PolygonBase.cornerRadiusPropertyKey:
|
||||
case StarBase.innerRadiusPropertyKey:
|
||||
case CubicDetachedVertexBase.inRotationPropertyKey:
|
||||
case CubicDetachedVertexBase.inDistancePropertyKey:
|
||||
case CubicDetachedVertexBase.outRotationPropertyKey:
|
||||
@ -860,6 +886,8 @@ class RiveCoreContext {
|
||||
return (object as ClippingShapeBase).sourceId;
|
||||
case ClippingShapeBase.fillRulePropertyKey:
|
||||
return (object as ClippingShapeBase).fillRule;
|
||||
case PolygonBase.pointsPropertyKey:
|
||||
return (object as PolygonBase).points;
|
||||
case DrawRulesBase.drawTargetIdPropertyKey:
|
||||
return (object as DrawRulesBase).drawTargetId;
|
||||
case TendonBase.boneIdPropertyKey:
|
||||
@ -940,6 +968,10 @@ class RiveCoreContext {
|
||||
return (object as CubicMirroredVertexBase).rotation;
|
||||
case CubicMirroredVertexBase.distancePropertyKey:
|
||||
return (object as CubicMirroredVertexBase).distance;
|
||||
case PolygonBase.cornerRadiusPropertyKey:
|
||||
return (object as PolygonBase).cornerRadius;
|
||||
case StarBase.innerRadiusPropertyKey:
|
||||
return (object as StarBase).innerRadius;
|
||||
case CubicDetachedVertexBase.inRotationPropertyKey:
|
||||
return (object as CubicDetachedVertexBase).inRotation;
|
||||
case CubicDetachedVertexBase.inDistancePropertyKey:
|
||||
@ -1116,6 +1148,9 @@ class RiveCoreContext {
|
||||
case ClippingShapeBase.fillRulePropertyKey:
|
||||
(object as ClippingShapeBase).fillRule = value;
|
||||
break;
|
||||
case PolygonBase.pointsPropertyKey:
|
||||
(object as PolygonBase).points = value;
|
||||
break;
|
||||
case DrawRulesBase.drawTargetIdPropertyKey:
|
||||
(object as DrawRulesBase).drawTargetId = value;
|
||||
break;
|
||||
@ -1232,6 +1267,12 @@ class RiveCoreContext {
|
||||
case CubicMirroredVertexBase.distancePropertyKey:
|
||||
(object as CubicMirroredVertexBase).distance = value;
|
||||
break;
|
||||
case PolygonBase.cornerRadiusPropertyKey:
|
||||
(object as PolygonBase).cornerRadius = value;
|
||||
break;
|
||||
case StarBase.innerRadiusPropertyKey:
|
||||
(object as StarBase).innerRadius = value;
|
||||
break;
|
||||
case CubicDetachedVertexBase.inRotationPropertyKey:
|
||||
(object as CubicDetachedVertexBase).inRotation = value;
|
||||
break;
|
||||
|
69
lib/src/generated/shapes/polygon_base.dart
Normal file
69
lib/src/generated/shapes/polygon_base.dart
Normal file
@ -0,0 +1,69 @@
|
||||
/// Core automatically generated lib/src/generated/shapes/polygon_base.dart.
|
||||
/// Do not modify manually.
|
||||
|
||||
import 'package:rive/src/generated/component_base.dart';
|
||||
import 'package:rive/src/generated/container_component_base.dart';
|
||||
import 'package:rive/src/generated/node_base.dart';
|
||||
import 'package:rive/src/generated/shapes/parametric_path_base.dart';
|
||||
import 'package:rive/src/generated/shapes/path_base.dart';
|
||||
import 'package:rive/src/generated/transform_component_base.dart';
|
||||
import 'package:rive/src/rive_core/shapes/parametric_path.dart';
|
||||
|
||||
abstract class PolygonBase extends ParametricPath {
|
||||
static const int typeKey = 51;
|
||||
@override
|
||||
int get coreType => PolygonBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {
|
||||
PolygonBase.typeKey,
|
||||
ParametricPathBase.typeKey,
|
||||
PathBase.typeKey,
|
||||
NodeBase.typeKey,
|
||||
TransformComponentBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// Points field with key 125.
|
||||
int _points = 5;
|
||||
static const int pointsPropertyKey = 125;
|
||||
|
||||
/// The number of points for the polygon.
|
||||
int get points => _points;
|
||||
|
||||
/// Change the [_points] field value.
|
||||
/// [pointsChanged] will be invoked only if the field's value has changed.
|
||||
set points(int value) {
|
||||
if (_points == value) {
|
||||
return;
|
||||
}
|
||||
int from = _points;
|
||||
_points = value;
|
||||
pointsChanged(from, value);
|
||||
}
|
||||
|
||||
void pointsChanged(int from, int to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// CornerRadius field with key 126.
|
||||
double _cornerRadius = 0;
|
||||
static const int cornerRadiusPropertyKey = 126;
|
||||
|
||||
/// The corner radius.
|
||||
double get cornerRadius => _cornerRadius;
|
||||
|
||||
/// Change the [_cornerRadius] field value.
|
||||
/// [cornerRadiusChanged] will be invoked only if the field's value has
|
||||
/// changed.
|
||||
set cornerRadius(double value) {
|
||||
if (_cornerRadius == value) {
|
||||
return;
|
||||
}
|
||||
double from = _cornerRadius;
|
||||
_cornerRadius = value;
|
||||
cornerRadiusChanged(from, value);
|
||||
}
|
||||
|
||||
void cornerRadiusChanged(double from, double to);
|
||||
}
|
50
lib/src/generated/shapes/star_base.dart
Normal file
50
lib/src/generated/shapes/star_base.dart
Normal file
@ -0,0 +1,50 @@
|
||||
/// Core automatically generated lib/src/generated/shapes/star_base.dart.
|
||||
/// Do not modify manually.
|
||||
|
||||
import 'package:rive/src/generated/component_base.dart';
|
||||
import 'package:rive/src/generated/container_component_base.dart';
|
||||
import 'package:rive/src/generated/node_base.dart';
|
||||
import 'package:rive/src/generated/shapes/parametric_path_base.dart';
|
||||
import 'package:rive/src/generated/shapes/path_base.dart';
|
||||
import 'package:rive/src/generated/shapes/polygon_base.dart';
|
||||
import 'package:rive/src/generated/transform_component_base.dart';
|
||||
import 'package:rive/src/rive_core/shapes/polygon.dart';
|
||||
|
||||
abstract class StarBase extends Polygon {
|
||||
static const int typeKey = 52;
|
||||
@override
|
||||
int get coreType => StarBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {
|
||||
StarBase.typeKey,
|
||||
PolygonBase.typeKey,
|
||||
ParametricPathBase.typeKey,
|
||||
PathBase.typeKey,
|
||||
NodeBase.typeKey,
|
||||
TransformComponentBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// InnerRadius field with key 127.
|
||||
double _innerRadius = 0.5;
|
||||
static const int innerRadiusPropertyKey = 127;
|
||||
|
||||
/// Percentage of width/height to project inner points of the star.
|
||||
double get innerRadius => _innerRadius;
|
||||
|
||||
/// Change the [_innerRadius] field value.
|
||||
/// [innerRadiusChanged] will be invoked only if the field's value has
|
||||
/// changed.
|
||||
set innerRadius(double value) {
|
||||
if (_innerRadius == value) {
|
||||
return;
|
||||
}
|
||||
double from = _innerRadius;
|
||||
_innerRadius = value;
|
||||
innerRadiusChanged(from, value);
|
||||
}
|
||||
|
||||
void innerRadiusChanged(double from, double to);
|
||||
}
|
@ -1,94 +1,118 @@
|
||||
import 'dart:math';
|
||||
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;
|
||||
class _FirstExtractedPath {
|
||||
final Path path;
|
||||
double length;
|
||||
final PathMetric metric;
|
||||
_FirstExtractedPath(this.path, this.metric, this.length);
|
||||
}
|
||||
|
||||
_FirstExtractedPath _appendPathSegmentSequential(
|
||||
Iterable<PathMetric> metrics, Path result, double start, double stop,
|
||||
{_FirstExtractedPath first}) {
|
||||
double nextOffset = 0;
|
||||
double offset = 0;
|
||||
for (final metric in metrics) {
|
||||
nextOffset += metric.length;
|
||||
if (start < nextOffset) {
|
||||
Path extracted = metric.extractPath(start - offset, stop - offset);
|
||||
var st = max(0.0, start - offset);
|
||||
var et = min(metric.length, stop - offset);
|
||||
var extractLength = et - st;
|
||||
Path extracted = metric.extractPath(st, et);
|
||||
if (extracted != null) {
|
||||
to.addPath(extracted, Offset.zero);
|
||||
if (first == null) {
|
||||
// ignore: parameter_assignments
|
||||
first = _FirstExtractedPath(extracted, metric, extractLength);
|
||||
} else if (first.metric == metric) {
|
||||
first.length += extractLength;
|
||||
first.path.extendWithPath(extracted, Offset.zero);
|
||||
} else {
|
||||
if (metric.isClosed && extractLength == metric.length) {
|
||||
extracted.close();
|
||||
}
|
||||
result.addPath(extracted, Offset.zero);
|
||||
}
|
||||
}
|
||||
if (stop < nextOffset) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ignore: parameter_assignments
|
||||
offset = nextOffset;
|
||||
} while (metricsIterator.moveNext());
|
||||
return offset;
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
void _appendPathSegmentSync(
|
||||
PathMetric metric, Path to, double offset, double start, double stop) {
|
||||
double nextOffset = offset + metric.length;
|
||||
PathMetric metric, Path to, double start, double stop,
|
||||
{bool startWithMoveTo = true}) {
|
||||
double nextOffset = metric.length;
|
||||
if (start < nextOffset) {
|
||||
Path extracted = metric.extractPath(start - offset, stop - offset);
|
||||
Path extracted = metric.extractPath(start, stop);
|
||||
if (extracted != null) {
|
||||
to.addPath(extracted, Offset.zero);
|
||||
if (startWithMoveTo) {
|
||||
to.addPath(extracted, Offset.zero);
|
||||
} else {
|
||||
to.extendWithPath(extracted, Offset.zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _trimPathSequential(
|
||||
Path path, Path result, double startT, double stopT, bool complement) {
|
||||
PathMetrics metrics = path.computeMetrics();
|
||||
var metrics = path.computeMetrics().toList(growable: false);
|
||||
double totalLength = 0.0;
|
||||
for (final PathMetric metric in metrics) {
|
||||
for (final 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();
|
||||
_FirstExtractedPath first;
|
||||
if (complement) {
|
||||
if (trimStart > 0.0) {
|
||||
offset = _appendPathSegmentSequential(
|
||||
metricsIterator, result, offset, 0.0, trimStart);
|
||||
}
|
||||
if (trimStop < totalLength) {
|
||||
offset = _appendPathSegmentSequential(
|
||||
metricsIterator, result, offset, trimStop, totalLength);
|
||||
first =
|
||||
_appendPathSegmentSequential(metrics, result, trimStop, totalLength);
|
||||
}
|
||||
} else {
|
||||
if (trimStart < trimStop) {
|
||||
offset = _appendPathSegmentSequential(
|
||||
metricsIterator, result, offset, trimStart, trimStop);
|
||||
if (trimStart > 0.0) {
|
||||
_appendPathSegmentSequential(metrics, result, 0.0, trimStart,
|
||||
first: first);
|
||||
}
|
||||
} else if (trimStart < trimStop) {
|
||||
first = _appendPathSegmentSequential(metrics, result, trimStart, trimStop);
|
||||
}
|
||||
if (first != null) {
|
||||
if (first.length == first.metric.length) {
|
||||
first.path.close();
|
||||
}
|
||||
result.addPath(first.path, Offset.zero);
|
||||
}
|
||||
}
|
||||
|
||||
void _trimPathSync(
|
||||
Path path, Path result, double startT, double stopT, bool complement) {
|
||||
final PathMetrics metrics = path.computeMetrics();
|
||||
for (final PathMetric metric in metrics) {
|
||||
final metrics = path.computeMetrics().toList(growable: false);
|
||||
for (final metric in metrics) {
|
||||
double length = metric.length;
|
||||
double trimStart = length * startT;
|
||||
double trimStop = length * stopT;
|
||||
if (complement) {
|
||||
bool extractStart = trimStop < length;
|
||||
if (extractStart) {
|
||||
_appendPathSegmentSync(metric, result, trimStop, length);
|
||||
}
|
||||
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);
|
||||
_appendPathSegmentSync(metric, result, 0.0, trimStart,
|
||||
startWithMoveTo: !extractStart || !metric.isClosed);
|
||||
}
|
||||
} else if (trimStart < trimStop) {
|
||||
_appendPathSegmentSync(metric, result, 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);
|
||||
}
|
||||
}
|
||||
bool complement, bool isSequential) =>
|
||||
isSequential
|
||||
? _trimPathSequential(path, result, startT, stopT, complement)
|
||||
: _trimPathSync(path, result, startT, stopT, complement);
|
||||
|
29
lib/src/rive_core/shapes/polygon.dart
Normal file
29
lib/src/rive_core/shapes/polygon.dart
Normal file
@ -0,0 +1,29 @@
|
||||
import 'dart:math';
|
||||
import 'package:rive/src/rive_core/shapes/path_vertex.dart';
|
||||
import 'package:rive/src/rive_core/bones/weight.dart';
|
||||
import 'package:rive/src/rive_core/shapes/straight_vertex.dart';
|
||||
import 'package:rive/src/generated/shapes/polygon_base.dart';
|
||||
export 'package:rive/src/generated/shapes/polygon_base.dart';
|
||||
|
||||
class Polygon extends PolygonBase {
|
||||
@override
|
||||
void cornerRadiusChanged(double from, double to) => markPathDirty();
|
||||
@override
|
||||
void pointsChanged(int from, int to) => markPathDirty();
|
||||
@override
|
||||
List<PathVertex<Weight>> get vertices {
|
||||
var vertexList = List<StraightVertex>(points);
|
||||
var halfWidth = width / 2;
|
||||
var halfHeight = height / 2;
|
||||
var angle = -pi / 2;
|
||||
var inc = 2 * pi / points;
|
||||
for (int i = 0; i < points; i++) {
|
||||
vertexList[i] = StraightVertex()
|
||||
..x = cos(angle) * halfWidth
|
||||
..y = sin(angle) * halfHeight
|
||||
..radius = cornerRadius;
|
||||
angle += inc;
|
||||
}
|
||||
return vertexList;
|
||||
}
|
||||
}
|
35
lib/src/rive_core/shapes/star.dart
Normal file
35
lib/src/rive_core/shapes/star.dart
Normal file
@ -0,0 +1,35 @@
|
||||
import 'dart:math';
|
||||
import 'package:rive/src/rive_core/bones/weight.dart';
|
||||
import 'package:rive/src/rive_core/shapes/path_vertex.dart';
|
||||
import 'package:rive/src/rive_core/shapes/straight_vertex.dart';
|
||||
import 'package:rive/src/generated/shapes/star_base.dart';
|
||||
export 'package:rive/src/generated/shapes/star_base.dart';
|
||||
|
||||
class Star extends StarBase {
|
||||
@override
|
||||
void innerRadiusChanged(double from, double to) => markPathDirty();
|
||||
@override
|
||||
List<PathVertex<Weight>> get vertices {
|
||||
var actualPoints = points * 2;
|
||||
var vertexList = List<StraightVertex>(actualPoints);
|
||||
var halfWidth = width / 2;
|
||||
var halfHeight = height / 2;
|
||||
var innerHalfWidth = width * innerRadius / 2;
|
||||
var innerHalfHeight = height * innerRadius / 2;
|
||||
var angle = -pi / 2;
|
||||
var inc = 2 * pi / actualPoints;
|
||||
for (int i = 0; i < actualPoints; i++) {
|
||||
vertexList[i++] = StraightVertex()
|
||||
..x = cos(angle) * halfWidth
|
||||
..y = sin(angle) * halfHeight
|
||||
..radius = cornerRadius;
|
||||
angle += inc;
|
||||
vertexList[i] = StraightVertex()
|
||||
..x = cos(angle) * innerHalfWidth
|
||||
..y = sin(angle) * innerHalfHeight
|
||||
..radius = cornerRadius;
|
||||
angle += inc;
|
||||
}
|
||||
return vertexList;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
name: rive
|
||||
description: Rive 2 Flutter Runtime. This package provides runtime functionality for playing back and interacting with animations built with the Rive editor available at https://rive.app.
|
||||
version: 0.6.3
|
||||
version: 0.6.4
|
||||
repository: https://github.com/rive-app/rive-flutter
|
||||
homepage: https://rive.app
|
||||
|
||||
|
Reference in New Issue
Block a user