So far:
- [x] Editor
- [x] Dart Tests
- [x] Flutter Runtime
- [x] C++ Runtime
- [x] C++ Tests

Hierarchy interaction:
<img width="280" alt="CleanShot 2023-03-24 at 21 44 53@2x" src="https://user-images.githubusercontent.com/454182/227696646-09f9cbe5-c482-4bab-aae9-b0b36c16047e.png">

Nesting:
<img width="394" alt="CleanShot 2023-03-24 at 21 45 23@2x" src="https://user-images.githubusercontent.com/454182/227696653-b1132ba1-5471-4c6d-9b59-20387389ae12.png">

Inspector active solo selection:
<img width="253" alt="CleanShot 2023-03-24 at 21 45 33@2x" src="https://user-images.githubusercontent.com/454182/227696660-6676acfa-15ab-4ae2-a866-4b7898bc1f52.png">

Animatable with timeline hierarchy value too:
<img width="510" alt="CleanShot 2023-03-24 at 21 46 07@2x" src="https://user-images.githubusercontent.com/454182/227696686-255064c9-43fd-4213-9e3f-9cd46cca9de3.png">

Diffs=
daaf140ba Solos (#5047)
44ef23f7e Text fix variable and opacity (#5017)
3c396d3b8 Text style variation at runtime. (#5014)
This commit is contained in:
luigi-rosso
2023-03-28 23:39:09 +00:00
parent 94344e123a
commit 428b1a6dad
15 changed files with 230 additions and 61 deletions

View File

@ -1 +1 @@
41b699beb873be31f4d38ba79bd613cc83f74eca
daaf140ba0a062ba9ec7f4eafa1e198d9121b220

View File

@ -1,4 +1,10 @@
include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- windows/**
- ios/**
- macos/**
- web/**
linter:
rules:

View File

@ -123,6 +123,7 @@ 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';
import 'package:rive/src/rive_core/solo.dart';
import 'package:rive/src/rive_core/text/text.dart';
import 'package:rive/src/rive_core/text/text_style.dart';
import 'package:rive/src/rive_core/text/text_style_axis.dart';
@ -152,6 +153,8 @@ class RiveCoreContext {
return Node();
case NestedArtboardBase.typeKey:
return NestedArtboard();
case SoloBase.typeKey:
return Solo();
case AnimationBase.typeKey:
return Animation();
case LinearAnimationBase.typeKey:
@ -508,6 +511,11 @@ class RiveCoreContext {
object.animationId = value;
}
break;
case SoloBase.activeComponentIdPropertyKey:
if (object is SoloBase && value is int) {
object.activeComponentId = value;
}
break;
case AnimationBase.namePropertyKey:
if (object is AnimationBase && value is String) {
object.name = value;
@ -1300,6 +1308,7 @@ class RiveCoreContext {
case DrawableBase.drawableFlagsPropertyKey:
case NestedArtboardBase.artboardIdPropertyKey:
case NestedAnimationBase.animationIdPropertyKey:
case SoloBase.activeComponentIdPropertyKey:
case LinearAnimationBase.fpsPropertyKey:
case LinearAnimationBase.durationPropertyKey:
case LinearAnimationBase.loopValuePropertyKey:
@ -1527,6 +1536,8 @@ class RiveCoreContext {
return (object as NestedArtboardBase).artboardId;
case NestedAnimationBase.animationIdPropertyKey:
return (object as NestedAnimationBase).animationId;
case SoloBase.activeComponentIdPropertyKey:
return (object as SoloBase).activeComponentId;
case LinearAnimationBase.fpsPropertyKey:
return (object as LinearAnimationBase).fps;
case LinearAnimationBase.durationPropertyKey:
@ -2000,6 +2011,11 @@ class RiveCoreContext {
object.animationId = value;
}
break;
case SoloBase.activeComponentIdPropertyKey:
if (object is SoloBase) {
object.activeComponentId = value;
}
break;
case LinearAnimationBase.fpsPropertyKey:
if (object is LinearAnimationBase) {
object.fps = value;

View File

@ -0,0 +1,54 @@
/// Core automatically generated lib/src/generated/solo_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/transform_component_base.dart';
import 'package:rive/src/generated/world_transform_component_base.dart';
import 'package:rive/src/rive_core/node.dart';
abstract class SoloBase extends Node {
static const int typeKey = 147;
@override
int get coreType => SoloBase.typeKey;
@override
Set<int> get coreTypes => {
SoloBase.typeKey,
NodeBase.typeKey,
TransformComponentBase.typeKey,
WorldTransformComponentBase.typeKey,
ContainerComponentBase.typeKey,
ComponentBase.typeKey
};
/// --------------------------------------------------------------------------
/// ActiveComponentId field with key 296.
static const int activeComponentIdInitialValue = 0;
int _activeComponentId = activeComponentIdInitialValue;
static const int activeComponentIdPropertyKey = 296;
/// Identifier of the active child in the solo set.
int get activeComponentId => _activeComponentId;
/// Change the [_activeComponentId] field value.
/// [activeComponentIdChanged] will be invoked only if the field's value has
/// changed.
set activeComponentId(int value) {
if (_activeComponentId == value) {
return;
}
int from = _activeComponentId;
_activeComponentId = value;
if (hasValidated) {
activeComponentIdChanged(from, value);
}
}
void activeComponentIdChanged(int from, int to);
@override
void copy(covariant SoloBase source) {
super.copy(source);
_activeComponentId = source._activeComponentId;
}
}

View File

@ -138,10 +138,12 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
Component component = _dependencyOrder[i];
_dirtDepth = i;
int d = component.dirt;
if (d == 0) {
if (d == 0 || (d & ComponentDirt.collapsed) != 0) {
continue;
}
component.dirt = 0;
component.dirt &= ComponentDirt.collapsed;
component.update(d);
if (_dirtDepth < i) {
break;

View File

@ -1,5 +1,6 @@
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';
@ -10,6 +11,23 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
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
@ -18,7 +36,7 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
// Used during update process.
int graphOrder = 0;
int dirt = 0xFFFF;
int dirt = ComponentDirt.filthy;
// This is really only for sanity and earlying out of recursive loops.
static const int maxTreeDepth = 5000;
@ -112,6 +130,7 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
if (_parent == value) {
return;
}
dirt &= ~ComponentDirt.collapsed;
var old = _parent;
_parent = value;
parentId = value?.id ?? Core.missingId;

View File

@ -1,44 +1,50 @@
class ComponentDirt {
static const int dependents = 1 << 0;
/// Means to not process the update!
static const int collapsed = 1 << 0;
static const int dependents = 1 << 1;
/// General flag for components are dirty (if this is up, the update cycle
/// runs). It gets automatically applied with any other dirt.
static const int components = 1 << 1;
static const int components = 1 << 2;
/// Draw order needs to be re-computed.
static const int drawOrder = 1 << 2;
static const int drawOrder = 1 << 3;
/// Draw order needs to be re-computed.
static const int naturalDrawOrder = 1 << 3;
static const int naturalDrawOrder = 1 << 4;
/// Path is dirty and needs to be rebuilt.
static const int path = 1 << 4;
static const int path = 1 << 5;
/// Text shape is dirty, the shaper needs to re-run.
static const int textShape = 1 << 4;
static const int textShape = 1 << 5;
/// Vertices have changed, re-order cached lists.
static const int vertices = 1 << 5;
static const int vertices = 1 << 6;
/// Used by any component that needs to recompute their local transform.
/// Usually components that have their transform dirty will also have their
/// worldTransform dirty.
static const int transform = 1 << 6;
static const int transform = 1 << 7;
/// Used by any component that needs to update its world transform.
static const int worldTransform = 1 << 7;
static const int worldTransform = 1 << 8;
/// Dirt used to mark some stored paint needs to be rebuilt or that we just
/// want to trigger an update cycle so painting occurs.
static const int paint = 1 << 8;
static const int paint = 1 << 9;
/// Used by the gradients track when the stops need to be re-ordered.
static const int stops = 1 << 9;
static const int stops = 1 << 10;
/// Used by ClippingShape to help Shape know when to recalculate its list of
/// clipping sources.
static const int clip = 1 << 10;
static const int clip = 1 << 11;
/// Set when blend modes need to be updated.
static const int blendMode = 1 << 11;
static const int blendMode = 1 << 12;
/// All dirty. Every flag (apart from Collapsed) is set.
static const int filthy = 0xFFFE;
}

View File

@ -3,47 +3,10 @@ class ComponentFlags {
/// drawables and paths).
static const int hidden = 1 << 0;
// Whether the component was locked for editing in the editor.
/// Whether the component was locked for editing in the editor.
static const int locked = 1 << 1;
/// These options are used by [TransformComponentConstraint]s, any other
/// component that's not in that class hierarchy can re-use these values as
/// they won't collide.
///
/// -----------
/// Whether the [TransformComponentConstraint]'s constrained value is offset
/// from the design time value (in the matching space).
static const int offset = 1 << 2;
/// Whether the transform component is copied.
static const int copy = 1 << 3;
/// Set when a minimum value should be applied to the constrained value.
static const int min = 1 << 4;
/// Set when a maximum value should be applied to the constrained value.
static const int max = 1 << 5;
/// Whether an X transform component is copied.
static const int copyX = 1 << 3;
/// Set when a minimum value should be applied to the constrained X value.
static const int minX = 1 << 4;
/// Set when a maximum value should be applied to the constrained X value.
static const int maxX = 1 << 5;
/// Whether a Y transform component is copied.
static const int copyY = 1 << 6;
/// Set when a minimum value should be applied to the constrained Y value.
static const int minY = 1 << 7;
/// Set when a maximum value should be applied to the constrained Y value.
static const int maxY = 1 << 8;
/// End of TransformComponentConstraint
///
/// ----------
/// Whether this Component is disconnected from the hierarchy meaning it won't
/// receive any update cycles nor will any drawables draw.
static const int disconnected = 1 << 2;
}

View File

@ -22,7 +22,9 @@ abstract class ContainerComponent extends ContainerComponentBase {
}
@mustCallSuper
void childAdded(Component child) {}
void childAdded(Component child) {
_propagateCollapseToChildren(isCollapsed);
}
void childRemoved(Component child) {}
@ -71,4 +73,19 @@ abstract class ContainerComponent extends ContainerComponentBase {
}
}
}
void _propagateCollapseToChildren(bool collapse) {
for (final child in children) {
child.propagateCollapse(collapse);
}
}
@override
bool propagateCollapse(bool collapse) {
if (!super.propagateCollapse(collapse)) {
return false;
}
_propagateCollapseToChildren(collapse);
return true;
}
}

View File

@ -85,5 +85,7 @@ abstract class Drawable extends DrawableBase {
@override
void drawableFlagsChanged(int from, int to) => addDirt(ComponentDirt.paint);
bool get isHidden => (drawableFlags & ComponentFlags.hidden) != 0;
bool get isHidden =>
(drawableFlags & ComponentFlags.hidden) != 0 ||
(dirt & ComponentDirt.collapsed) != 0;
}

View File

@ -85,4 +85,18 @@ class PathComposer extends Component {
_recomputePath();
}
}
void syncCollapse() {
var collapsed = (dirt & ComponentDirt.collapsed) != 0;
if (collapsed == shape.isCollapsed) {
return;
}
if (collapsed) {
dirt |= ComponentDirt.collapsed;
} else {
dirt &= ~ComponentDirt.collapsed;
}
onDirty(dirt);
artboard?.onComponentDirty(this);
}
}

View File

@ -46,6 +46,12 @@ class Shape extends ShapeBase with ShapePaintContainer {
_worldBounds = _localBounds = null;
}
@override
void onDirty(int mask) {
pathComposer.syncCollapse();
super.onDirty(mask);
}
bool addPath(Path path) {
paintChanged();
var added = paths.add(path);

View File

@ -0,0 +1,53 @@
import 'package:rive/src/generated/solo_base.dart';
import 'package:rive/src/rive_core/component.dart';
import 'package:rive/src/rive_core/container_component.dart';
export 'package:rive/src/generated/solo_base.dart';
class Solo extends SoloBase {
Component? _activeComponent;
@override
void activeComponentIdChanged(int from, int to) =>
activeComponent = context.resolve(to);
Component? get activeComponent => _activeComponent;
set activeComponent(Component? value) {
if (_activeComponent == value) {
return;
}
_activeComponent = value;
activeComponentId = value?.id ?? Core.missingId;
_updateCollapse();
}
void _updateCollapse() {
for (final child in children) {
if (child == _activeComponent) {
child.propagateCollapse(false);
} else {
child.propagateCollapse(true);
}
}
}
@override
void childRemoved(Component child) {
super.childRemoved(child);
_updateCollapse();
}
@override
void childAdded(Component child) {
super.childAdded(child);
_updateCollapse();
}
@override
void onAddedDirty() {
super.onAddedDirty();
if (activeComponentId != Core.missingId) {
activeComponent = context.resolve(activeComponentId);
}
}
}

View File

@ -329,6 +329,14 @@ class Text extends TextBase with TextStyleContainer {
computeShape();
}
}
if (dirt & ComponentDirt.worldTransform != 0) {
for (final style in styles) {
for (final paint in style.shapePaints) {
paint.renderOpacity = renderOpacity;
}
}
markPaintDirty();
}
}
@override

View File

@ -104,17 +104,20 @@ class TextStyle extends TextStyleBase
_variationHelper?.buildDependencies();
}
void removeVariations() => _variations.toSet().forEach(context.removeObject);
@override
set asset(FontAsset? value) {
if (asset == value) {
return;
}
_variations.toSet().forEach(context.removeObject);
super.asset = value;
if (asset?.whenDecoded(_fontDecoded, notifyAlreadyDecoded: false) ??
false) {
// Already decoded.
_markShapeDirty();
_variationHelper?.addDirt(ComponentDirt.textShape);
}
}