mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-08-06 16:40:27 +08:00

Diffs= e192d691d chore: release futter 0.13.6 (#7377) Co-authored-by: Gordon <pggordonhayes@gmail.com>
287 lines
7.5 KiB
Dart
287 lines
7.5 KiB
Dart
import 'package:rive/src/generated/component_base.dart';
|
|
import 'package:rive/src/rive_core/artboard.dart';
|
|
import 'package:rive/src/rive_core/component_dirt.dart';
|
|
import 'package:rive/src/rive_core/container_component.dart';
|
|
import 'package:rive_common/utilities.dart';
|
|
|
|
export 'package:rive/src/generated/component_base.dart';
|
|
|
|
abstract class Component extends ComponentBase<RuntimeArtboard>
|
|
implements DependencyGraphNode<Component>, Parentable<Component> {
|
|
Artboard? _artboard;
|
|
dynamic _userData;
|
|
|
|
/// Whether this Component's update processes at all.
|
|
bool get isCollapsed => (dirt & ComponentDirt.collapsed) != 0;
|
|
|
|
bool propagateCollapse(bool collapse) {
|
|
if (isCollapsed == collapse) {
|
|
return false;
|
|
}
|
|
if (collapse) {
|
|
dirt |= ComponentDirt.collapsed;
|
|
} else {
|
|
dirt &= ~ComponentDirt.collapsed;
|
|
}
|
|
onDirty(dirt);
|
|
artboard?.onComponentDirty(this);
|
|
return true;
|
|
}
|
|
|
|
/// Override to true if you want some object inheriting from Component to not
|
|
/// have a parent. Most objects will validate that they have a parent during
|
|
/// the onAdded callback otherwise they are considered invalid and are culled
|
|
/// from core.
|
|
bool get canBeOrphaned => false;
|
|
|
|
// Used during update process.
|
|
int graphOrder = 0;
|
|
int dirt = ComponentDirt.filthy;
|
|
|
|
// This is really only for sanity and earlying out of recursive loops.
|
|
static const int maxTreeDepth = 5000;
|
|
|
|
bool addDirt(int value, {bool recurse = false}) {
|
|
if ((dirt & value) == value) {
|
|
// Already marked.
|
|
return false;
|
|
}
|
|
|
|
// Make sure dirt is set before calling anything that can set more dirt.
|
|
dirt |= value;
|
|
|
|
onDirty(dirt);
|
|
artboard?.onComponentDirty(this);
|
|
|
|
if (!recurse) {
|
|
return true;
|
|
}
|
|
|
|
for (final d in dependents) {
|
|
d.addDirt(value, recurse: recurse);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void onDirty(int mask) {}
|
|
void update(int dirt);
|
|
|
|
/// The artboard this component belongs to.
|
|
|
|
Artboard? get artboard => _artboard;
|
|
|
|
// Note that this isn't a setter as we don't want anything externally changing
|
|
// the artboard.
|
|
@protected
|
|
@mustCallSuper
|
|
void changeArtboard(Artboard? value) {
|
|
_artboard?.removeComponent(this);
|
|
_artboard = value;
|
|
_artboard?.addComponent(this);
|
|
}
|
|
|
|
/// Called whenever we're resolving the artboard, we piggy back on that
|
|
/// process to visit ancestors in the tree. This is a good opportunity to
|
|
/// check if we have an ancestor of a specific type. For example, a Path needs
|
|
/// to know which Shape it's within.
|
|
@mustCallSuper
|
|
void visitAncestor(Component ancestor) {}
|
|
|
|
/// Find the artboard in the hierarchy.
|
|
bool resolveArtboard() {
|
|
int sanity = maxTreeDepth;
|
|
for (Component? curr = this;
|
|
curr != null && sanity > 0;
|
|
curr = curr.parent, sanity--) {
|
|
visitAncestor(curr);
|
|
if (curr is Artboard) {
|
|
if (artboard != curr) {
|
|
changeArtboard(curr);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
changeArtboard(null);
|
|
return false;
|
|
}
|
|
|
|
dynamic get userData => _userData;
|
|
set userData(dynamic value) {
|
|
if (value == _userData) {
|
|
return;
|
|
}
|
|
dynamic last = _userData;
|
|
_userData = value;
|
|
userDataChanged(last, value);
|
|
}
|
|
|
|
void userDataChanged(dynamic from, dynamic to) {}
|
|
|
|
@override
|
|
void parentIdChanged(int from, int to) {
|
|
parent = context.resolve(to);
|
|
}
|
|
|
|
ContainerComponent? _parent;
|
|
@override
|
|
ContainerComponent? get parent => _parent;
|
|
|
|
set parent(ContainerComponent? value) {
|
|
if (_parent == value) {
|
|
return;
|
|
}
|
|
propagateCollapse(false);
|
|
var old = _parent;
|
|
_parent = value;
|
|
parentId = value?.id ?? Core.missingId;
|
|
parentChanged(old, value);
|
|
}
|
|
|
|
@protected
|
|
void parentChanged(ContainerComponent? from, ContainerComponent? to) {
|
|
from?.children.remove(this);
|
|
from?.childRemoved(this);
|
|
|
|
if (to != null) {
|
|
to.children.add(this);
|
|
|
|
to.childAdded(this);
|
|
}
|
|
|
|
// We need to resolve our artboard.
|
|
markRebuildDependencies();
|
|
}
|
|
|
|
/// Components that depend on this component.
|
|
final Set<Component> _dependents = {};
|
|
|
|
/// Components that this component depends on.
|
|
final Set<Component> _dependsOn = {};
|
|
|
|
@override
|
|
Set<Component> get dependents => _dependents;
|
|
|
|
Set<Component> get dependencies {
|
|
Set<Component> components = {};
|
|
allDependencies(components);
|
|
return components;
|
|
}
|
|
|
|
void allDependencies(Set<Component> dependencies) {
|
|
for (final dependency in _dependsOn) {
|
|
if (dependencies.add(dependency)) {
|
|
dependency.allDependencies(dependencies);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Mark [dependent] as a component which must update after this. Provide
|
|
/// [via] as the Component registering the dependency when it is not
|
|
/// [dependent] itself. At edit time this allows the editor to rebuild both
|
|
/// [dependent] and [via] when [dependent] has its dependencies cleared.
|
|
bool addDependent(Component dependent, {Component? via}) {
|
|
assert(artboard == dependent.artboard,
|
|
'Components must be in the same artboard.');
|
|
|
|
if (!_dependents.add(dependent)) {
|
|
return false;
|
|
}
|
|
dependent._dependsOn.add(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool isValidParent(Component parent) => parent is ContainerComponent;
|
|
|
|
void markRebuildDependencies() {
|
|
if (!context.markDependenciesDirty(this)) {
|
|
// no context, or already dirty.
|
|
return;
|
|
}
|
|
|
|
for (final dependent in _dependents) {
|
|
dependent.markRebuildDependencies();
|
|
}
|
|
}
|
|
|
|
void clearDependencies() {
|
|
for (final parentDep in _dependsOn) {
|
|
parentDep._dependents.remove(this);
|
|
}
|
|
_dependsOn.clear();
|
|
}
|
|
|
|
void buildDependencies() {}
|
|
|
|
/// Something we depend on has been removed. It's important to clear out any
|
|
/// stored references to that dependency so it can be garbage collected (if
|
|
/// necessary).
|
|
void onDependencyRemoved(Component dependent) {}
|
|
|
|
@override
|
|
void onAdded() {}
|
|
|
|
@override
|
|
void onAddedDirty() {
|
|
if (parentId != Core.missingId) {
|
|
parent = context.resolve(parentId);
|
|
}
|
|
}
|
|
|
|
/// When a component has been removed from the Core Context, we clean up any
|
|
/// dangling references left on the parent and on any other dependent
|
|
/// component. It's important for specialization of Component to respond to
|
|
/// override [onDependencyRemoved] and clean up any further stored references
|
|
/// to that component (for example the target of a Constraint).
|
|
@override
|
|
@mustCallSuper
|
|
void onRemoved() {
|
|
super.onRemoved();
|
|
for (final parentDep in _dependsOn) {
|
|
parentDep._dependents.remove(this);
|
|
}
|
|
_dependsOn.clear();
|
|
|
|
for (final dependent in _dependents) {
|
|
dependent.onDependencyRemoved(this);
|
|
}
|
|
_dependents.clear();
|
|
|
|
// silently clear from the parent in order to not cause any further undo
|
|
// stack changes
|
|
if (parent != null) {
|
|
parent!.children.remove(this);
|
|
parent!.childRemoved(this);
|
|
}
|
|
|
|
// The artboard containing this component will need its dependencies
|
|
// re-sorted.
|
|
|
|
if (artboard != null) {
|
|
context.markDependencyOrderDirty();
|
|
changeArtboard(null);
|
|
}
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return '${super.toString()} ($id) -> $name';
|
|
}
|
|
|
|
@override
|
|
void nameChanged(String from, String to) {
|
|
/// Changing name doesn't really do anything.
|
|
}
|
|
|
|
@override
|
|
bool import(ImportStack stack) {
|
|
var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
|
|
if (artboardImporter == null) {
|
|
return false;
|
|
}
|
|
artboardImporter.addComponent(this);
|
|
|
|
return super.import(stack);
|
|
}
|
|
}
|