mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-05-21 15:26:31 +08:00
Adding new clipping.
This commit is contained in:
Binary file not shown.
@ -515,14 +515,14 @@ class RiveCoreContext {
|
|||||||
object.distance = value;
|
object.distance = value;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ClippingShapeBase.shapeIdPropertyKey:
|
case ClippingShapeBase.sourceIdPropertyKey:
|
||||||
if (object is ClippingShapeBase && value is int) {
|
if (object is ClippingShapeBase && value is int) {
|
||||||
object.shapeId = value;
|
object.sourceId = value;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ClippingShapeBase.clipOpValuePropertyKey:
|
case ClippingShapeBase.fillRulePropertyKey:
|
||||||
if (object is ClippingShapeBase && value is int) {
|
if (object is ClippingShapeBase && value is int) {
|
||||||
object.clipOpValue = value;
|
object.fillRule = value;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ClippingShapeBase.isVisiblePropertyKey:
|
case ClippingShapeBase.isVisiblePropertyKey:
|
||||||
@ -703,8 +703,8 @@ class RiveCoreContext {
|
|||||||
case CubicWeightBase.inIndicesPropertyKey:
|
case CubicWeightBase.inIndicesPropertyKey:
|
||||||
case CubicWeightBase.outValuesPropertyKey:
|
case CubicWeightBase.outValuesPropertyKey:
|
||||||
case CubicWeightBase.outIndicesPropertyKey:
|
case CubicWeightBase.outIndicesPropertyKey:
|
||||||
case ClippingShapeBase.shapeIdPropertyKey:
|
case ClippingShapeBase.sourceIdPropertyKey:
|
||||||
case ClippingShapeBase.clipOpValuePropertyKey:
|
case ClippingShapeBase.fillRulePropertyKey:
|
||||||
case DrawRulesBase.drawTargetIdPropertyKey:
|
case DrawRulesBase.drawTargetIdPropertyKey:
|
||||||
case TendonBase.boneIdPropertyKey:
|
case TendonBase.boneIdPropertyKey:
|
||||||
return uintType;
|
return uintType;
|
||||||
@ -844,10 +844,10 @@ class RiveCoreContext {
|
|||||||
return (object as CubicWeightBase).outValues;
|
return (object as CubicWeightBase).outValues;
|
||||||
case CubicWeightBase.outIndicesPropertyKey:
|
case CubicWeightBase.outIndicesPropertyKey:
|
||||||
return (object as CubicWeightBase).outIndices;
|
return (object as CubicWeightBase).outIndices;
|
||||||
case ClippingShapeBase.shapeIdPropertyKey:
|
case ClippingShapeBase.sourceIdPropertyKey:
|
||||||
return (object as ClippingShapeBase).shapeId;
|
return (object as ClippingShapeBase).sourceId;
|
||||||
case ClippingShapeBase.clipOpValuePropertyKey:
|
case ClippingShapeBase.fillRulePropertyKey:
|
||||||
return (object as ClippingShapeBase).clipOpValue;
|
return (object as ClippingShapeBase).fillRule;
|
||||||
case DrawRulesBase.drawTargetIdPropertyKey:
|
case DrawRulesBase.drawTargetIdPropertyKey:
|
||||||
return (object as DrawRulesBase).drawTargetId;
|
return (object as DrawRulesBase).drawTargetId;
|
||||||
case TendonBase.boneIdPropertyKey:
|
case TendonBase.boneIdPropertyKey:
|
||||||
@ -1094,11 +1094,11 @@ class RiveCoreContext {
|
|||||||
case CubicWeightBase.outIndicesPropertyKey:
|
case CubicWeightBase.outIndicesPropertyKey:
|
||||||
(object as CubicWeightBase).outIndices = value;
|
(object as CubicWeightBase).outIndices = value;
|
||||||
break;
|
break;
|
||||||
case ClippingShapeBase.shapeIdPropertyKey:
|
case ClippingShapeBase.sourceIdPropertyKey:
|
||||||
(object as ClippingShapeBase).shapeId = value;
|
(object as ClippingShapeBase).sourceId = value;
|
||||||
break;
|
break;
|
||||||
case ClippingShapeBase.clipOpValuePropertyKey:
|
case ClippingShapeBase.fillRulePropertyKey:
|
||||||
(object as ClippingShapeBase).clipOpValue = value;
|
(object as ClippingShapeBase).fillRule = value;
|
||||||
break;
|
break;
|
||||||
case DrawRulesBase.drawTargetIdPropertyKey:
|
case DrawRulesBase.drawTargetIdPropertyKey:
|
||||||
(object as DrawRulesBase).drawTargetId = value;
|
(object as DrawRulesBase).drawTargetId = value;
|
||||||
|
@ -13,48 +13,46 @@ abstract class ClippingShapeBase extends Component {
|
|||||||
Set<int> get coreTypes => {ClippingShapeBase.typeKey, ComponentBase.typeKey};
|
Set<int> get coreTypes => {ClippingShapeBase.typeKey, ComponentBase.typeKey};
|
||||||
|
|
||||||
/// --------------------------------------------------------------------------
|
/// --------------------------------------------------------------------------
|
||||||
/// ShapeId field with key 92.
|
/// SourceId field with key 92.
|
||||||
int _shapeId;
|
int _sourceId;
|
||||||
static const int shapeIdPropertyKey = 92;
|
static const int sourceIdPropertyKey = 92;
|
||||||
|
|
||||||
/// Identifier used to track the shape to use as a clipping source.
|
/// Identifier used to track the node to use as a clipping source.
|
||||||
int get shapeId => _shapeId;
|
int get sourceId => _sourceId;
|
||||||
|
|
||||||
/// Change the [_shapeId] field value.
|
/// Change the [_sourceId] field value.
|
||||||
/// [shapeIdChanged] will be invoked only if the field's value has changed.
|
/// [sourceIdChanged] will be invoked only if the field's value has changed.
|
||||||
set shapeId(int value) {
|
set sourceId(int value) {
|
||||||
if (_shapeId == value) {
|
if (_sourceId == value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int from = _shapeId;
|
int from = _sourceId;
|
||||||
_shapeId = value;
|
_sourceId = value;
|
||||||
shapeIdChanged(from, value);
|
sourceIdChanged(from, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void shapeIdChanged(int from, int to);
|
void sourceIdChanged(int from, int to);
|
||||||
|
|
||||||
/// --------------------------------------------------------------------------
|
/// --------------------------------------------------------------------------
|
||||||
/// ClipOpValue field with key 93.
|
/// FillRule field with key 93.
|
||||||
int _clipOpValue = 0;
|
int _fillRule = 0;
|
||||||
static const int clipOpValuePropertyKey = 93;
|
static const int fillRulePropertyKey = 93;
|
||||||
|
|
||||||
/// Backing enum value for the clipping operation type (intersection or
|
/// Backing enum value for the clipping fill rule (nonZero or evenOdd).
|
||||||
/// difference).
|
int get fillRule => _fillRule;
|
||||||
int get clipOpValue => _clipOpValue;
|
|
||||||
|
|
||||||
/// Change the [_clipOpValue] field value.
|
/// Change the [_fillRule] field value.
|
||||||
/// [clipOpValueChanged] will be invoked only if the field's value has
|
/// [fillRuleChanged] will be invoked only if the field's value has changed.
|
||||||
/// changed.
|
set fillRule(int value) {
|
||||||
set clipOpValue(int value) {
|
if (_fillRule == value) {
|
||||||
if (_clipOpValue == value) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int from = _clipOpValue;
|
int from = _fillRule;
|
||||||
_clipOpValue = value;
|
_fillRule = value;
|
||||||
clipOpValueChanged(from, value);
|
fillRuleChanged(from, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clipOpValueChanged(int from, int to);
|
void fillRuleChanged(int from, int to);
|
||||||
|
|
||||||
/// --------------------------------------------------------------------------
|
/// --------------------------------------------------------------------------
|
||||||
/// IsVisible field with key 94.
|
/// IsVisible field with key 94.
|
||||||
|
@ -241,25 +241,25 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
|
|||||||
_drawables.clear();
|
_drawables.clear();
|
||||||
_rules.clear();
|
_rules.clear();
|
||||||
buildDrawOrder(_drawables, null, _rules);
|
buildDrawOrder(_drawables, null, _rules);
|
||||||
Set<DrawTarget> rootRules = {};
|
var root = DrawTarget();
|
||||||
for (final nodeRules in _rules) {
|
for (final nodeRules in _rules) {
|
||||||
for (final target in nodeRules.targets) {
|
for (final target in nodeRules.targets) {
|
||||||
|
target.dependents.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final nodeRules in _rules) {
|
||||||
|
for (final target in nodeRules.targets) {
|
||||||
|
root.dependents.add(target);
|
||||||
var dependentRules = target.drawable?.flattenedDrawRules;
|
var dependentRules = target.drawable?.flattenedDrawRules;
|
||||||
if (dependentRules != null) {
|
if (dependentRules != null) {
|
||||||
for (final dependentRule in dependentRules.targets) {
|
for (final dependentRule in dependentRules.targets) {
|
||||||
dependentRule.dependents.add(target);
|
dependentRule.dependents.add(target);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
rootRules.add(target);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var sorter = DependencySorter<Component>();
|
var sorter = DependencySorter<Component>();
|
||||||
sorter.reset();
|
_sortedDrawRules = sorter.sort(root).cast<DrawTarget>().skip(1).toList();
|
||||||
if (rootRules.isNotEmpty) {
|
|
||||||
rootRules.forEach(sorter.visit);
|
|
||||||
}
|
|
||||||
_sortedDrawRules = sorter.order.cast<DrawTarget>();
|
|
||||||
sortDrawOrder();
|
sortDrawOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,27 +34,7 @@ abstract class Drawable extends DrawableBase {
|
|||||||
if (!clip.isVisible) {
|
if (!clip.isVisible) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var shape = clip.shape;
|
canvas.clipPath(clip.clippingPath);
|
||||||
var fillInWorld = shape.fillInWorld;
|
|
||||||
if (!fillInWorld) {
|
|
||||||
canvas.transform(shape.worldTransform.mat4);
|
|
||||||
}
|
|
||||||
if (clip.clipOp == ClipOp.difference) {
|
|
||||||
var path = Path();
|
|
||||||
path.fillType = PathFillType.evenOdd;
|
|
||||||
path.addPath(artboard.path, Offset.zero);
|
|
||||||
path.addPath(clip.shape.fillPath, Offset.zero);
|
|
||||||
canvas.clipPath(path);
|
|
||||||
} else {
|
|
||||||
canvas.clipPath(clip.shape.fillPath);
|
|
||||||
}
|
|
||||||
if (!fillInWorld) {
|
|
||||||
assert(
|
|
||||||
clip.shapeInverseWorld != null,
|
|
||||||
'Expect shapeInverseWorld to have been '
|
|
||||||
'created by the time we draw');
|
|
||||||
canvas.transform(clip.shapeInverseWorld.mat4);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,83 @@
|
|||||||
|
import 'dart:ui';
|
||||||
import 'package:rive/src/rive_core/component_dirt.dart';
|
import 'package:rive/src/rive_core/component_dirt.dart';
|
||||||
import 'package:rive/src/rive_core/math/mat2d.dart';
|
import 'package:rive/src/rive_core/node.dart';
|
||||||
import 'package:rive/src/rive_core/shapes/shape.dart';
|
import 'package:rive/src/rive_core/shapes/shape.dart';
|
||||||
import 'package:rive/src/generated/shapes/clipping_shape_base.dart';
|
import 'package:rive/src/generated/shapes/clipping_shape_base.dart';
|
||||||
export 'package:rive/src/generated/shapes/clipping_shape_base.dart';
|
export 'package:rive/src/generated/shapes/clipping_shape_base.dart';
|
||||||
|
|
||||||
enum ClipOp { intersection, difference }
|
|
||||||
|
|
||||||
class ClippingShape extends ClippingShapeBase {
|
class ClippingShape extends ClippingShapeBase {
|
||||||
ClipOp get clipOp => ClipOp.values[clipOpValue];
|
final Path clippingPath = Path();
|
||||||
set clipOp(ClipOp value) => clipOpValue = value.index;
|
final List<Shape> _shapes = [];
|
||||||
Mat2D _shapeInverseWorld;
|
PathFillType get fillType => PathFillType.values[fillRule];
|
||||||
Mat2D get shapeInverseWorld => _shapeInverseWorld;
|
set fillType(PathFillType type) => fillRule = type.index;
|
||||||
Shape _shape;
|
Node _source;
|
||||||
Shape get shape => _shape;
|
Node get source => _source;
|
||||||
set shape(Shape value) {
|
set source(Node value) {
|
||||||
if (_shape == value) {
|
if (_source == value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_shape = value;
|
_source = value;
|
||||||
shapeId = value?.id;
|
sourceId = value?.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void clipOpValueChanged(int from, int to) {
|
void fillRuleChanged(int from, int to) {
|
||||||
parent?.addDirt(ComponentDirt.clip, recurse: true);
|
parent?.addDirt(ComponentDirt.clip, recurse: true);
|
||||||
|
addDirt(ComponentDirt.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void shapeIdChanged(int from, int to) {
|
void sourceIdChanged(int from, int to) {
|
||||||
shape = context?.resolve(to);
|
_source = context?.resolve(to);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onAddedDirty() {
|
void onAddedDirty() {
|
||||||
super.onAddedDirty();
|
super.onAddedDirty();
|
||||||
if (shapeId != null) {
|
if (sourceId != null) {
|
||||||
shape = context?.resolve(shapeId);
|
_source = context?.resolve(sourceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void buildDependencies() {
|
void buildDependencies() {
|
||||||
super.buildDependencies();
|
super.buildDependencies();
|
||||||
shape?.addDependent(this);
|
_shapes.clear();
|
||||||
|
_source?.forAll((component) {
|
||||||
|
if (component is Shape) {
|
||||||
|
_shapes.add(component);
|
||||||
|
component.pathComposer.addDependent(this);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
addDirt(ComponentDirt.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onRemoved() {
|
||||||
|
super.onRemoved();
|
||||||
|
_shapes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void update(int dirt) {
|
void update(int dirt) {
|
||||||
if (dirt & ComponentDirt.worldTransform != 0 &&
|
if (dirt & (ComponentDirt.worldTransform | ComponentDirt.path) != 0 &&
|
||||||
shape != null &&
|
source != null) {
|
||||||
!shape.fillInWorld) {
|
clippingPath.reset();
|
||||||
_shapeInverseWorld ??= Mat2D();
|
clippingPath.fillType = fillType;
|
||||||
Mat2D.invert(_shapeInverseWorld, shape.worldTransform);
|
for (final shape in _shapes) {
|
||||||
|
if (!shape.fillInWorld) {
|
||||||
|
clippingPath.addPath(shape.fillPath, Offset.zero,
|
||||||
|
matrix4: shape.worldTransform.mat4);
|
||||||
|
} else {
|
||||||
|
clippingPath.addPath(shape.fillPath, Offset.zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void isVisibleChanged(bool from, bool to) {
|
void isVisibleChanged(bool from, bool to) {
|
||||||
_shape?.addDirt(ComponentDirt.paint);
|
_source?.addDirt(ComponentDirt.paint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'dart:ui';
|
||||||
import 'package:rive/src/rive_core/component_dirt.dart';
|
import 'package:rive/src/rive_core/component_dirt.dart';
|
||||||
import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
|
import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
|
||||||
import 'package:rive/src/generated/shapes/paint/fill_base.dart';
|
import 'package:rive/src/generated/shapes/paint/fill_base.dart';
|
||||||
|
@ -34,7 +34,7 @@ class Shape extends ShapeBase with ShapePaintContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _markComposerDirty() {
|
void _markComposerDirty() {
|
||||||
_pathComposer?.addDirt(ComponentDirt.path);
|
_pathComposer?.addDirt(ComponentDirt.path, recurse: true);
|
||||||
invalidateStrokeEffects();
|
invalidateStrokeEffects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,22 +83,7 @@ class TarjansDependencySorter<T extends DependencyGraphNode<T>>
|
|||||||
|
|
||||||
if (!visit(root)) {
|
if (!visit(root)) {
|
||||||
// if we detect cycles, go find them all
|
// if we detect cycles, go find them all
|
||||||
_perm.clear();
|
findCycles(root);
|
||||||
_temp.clear();
|
|
||||||
_cycleNodes.clear();
|
|
||||||
_order.clear();
|
|
||||||
|
|
||||||
var cycles =
|
|
||||||
stronglyConnectedComponents<T>([root], (T node) => node.dependents);
|
|
||||||
|
|
||||||
cycles.forEach((cycle) {
|
|
||||||
// cycles of len 1 are not cycles.
|
|
||||||
if (cycle.length > 1) {
|
|
||||||
cycle.forEach((cycleMember) {
|
|
||||||
_cycleNodes.add(cycleMember);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// revisit the tree, skipping nodes on any cycle.
|
// revisit the tree, skipping nodes on any cycle.
|
||||||
visit(root);
|
visit(root);
|
||||||
@ -107,6 +92,26 @@ class TarjansDependencySorter<T extends DependencyGraphNode<T>>
|
|||||||
return _order;
|
return _order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HashSet<T> findCycles(T n) {
|
||||||
|
_perm.clear();
|
||||||
|
_temp.clear();
|
||||||
|
_cycleNodes.clear();
|
||||||
|
_order.clear();
|
||||||
|
|
||||||
|
var cycles =
|
||||||
|
stronglyConnectedComponents<T>([n], (T node) => node.dependents);
|
||||||
|
|
||||||
|
cycles.forEach((cycle) {
|
||||||
|
// cycles of len 1 are not cycles.
|
||||||
|
if (cycle.length > 1) {
|
||||||
|
cycle.forEach((cycleMember) {
|
||||||
|
_cycleNodes.add(cycleMember);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return _cycleNodes;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool visit(T n) {
|
bool visit(T n) {
|
||||||
if (cycleNodes.contains(n)) {
|
if (cycleNodes.contains(n)) {
|
||||||
|
Reference in New Issue
Block a user