mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-05-21 07:16:35 +08:00

Adds the ability to override width and height for NestedArtboardLayout. This allows each NestedArtboardLayout instance to respond to be sized differently. This is not supported in the NestedArtboard Node and Leaf types, since those are resized using differently (Node uses scale and Leaf uses a combination of scale/fit/alignment). Implemented via FFI as discussed with @luigi-rosso because NestedArtboardLayout modifying the "taken" layoutNode directly could result in race conditions and other conflicts. https://github.com/user-attachments/assets/c323a94f-f392-4c10-ac01-af112f70a256 Diffs= 0dc0b435f Add width/height overrides for NestedArtboardLayout (#7736) 1131f30e6 refactor conditions (#7747) 35a52873c Fix layout shape hug in CPP (#7770) 949c70600 Add a premake message when Xcode command line tools isn't installed (#7756) 4bf7c7545 add data converter and data types for conversion (#7734) Co-authored-by: Philip Chung <philterdesign@gmail.com>
353 lines
11 KiB
Dart
353 lines
11 KiB
Dart
import 'dart:math';
|
|
import 'dart:ui';
|
|
|
|
import 'package:rive/src/core/core.dart';
|
|
import 'package:rive/src/generated/nested_artboard_base.dart';
|
|
import 'package:rive/src/rive_core/animation/nested_remap_animation.dart';
|
|
import 'package:rive/src/rive_core/animation/nested_simple_animation.dart';
|
|
import 'package:rive/src/rive_core/animation/nested_state_machine.dart';
|
|
import 'package:rive/src/rive_core/backboard.dart';
|
|
import 'package:rive/src/rive_core/bounds_provider.dart';
|
|
import 'package:rive/src/rive_core/component.dart';
|
|
import 'package:rive/src/rive_core/component_dirt.dart';
|
|
import 'package:rive/src/rive_core/data_bind/data_bind.dart';
|
|
import 'package:rive/src/rive_core/data_bind/data_context.dart';
|
|
import 'package:rive/src/rive_core/nested_animation.dart';
|
|
import 'package:rive/src/rive_core/viewmodel/viewmodel_instance.dart';
|
|
import 'package:rive_common/math.dart';
|
|
import 'package:rive_common/utilities.dart';
|
|
|
|
export 'package:rive/src/generated/nested_artboard_base.dart';
|
|
|
|
enum NestedArtboardFitType {
|
|
// ignore: lines_longer_than_80_chars
|
|
fill, // Default value - scales to fill available view without maintaining aspect ratio
|
|
contain,
|
|
cover,
|
|
fitWidth,
|
|
fitHeight,
|
|
resizeArtboard,
|
|
none,
|
|
}
|
|
|
|
enum NestedArtboardAlignmentType {
|
|
center, // Default value
|
|
topLeft,
|
|
topCenter,
|
|
topRight,
|
|
centerLeft,
|
|
centerRight,
|
|
bottomLeft,
|
|
bottomCenter,
|
|
bottomRight,
|
|
}
|
|
|
|
/// Represents the nested Artboard that'll actually be mounted and placed into
|
|
/// the [NestedArtboard] component.
|
|
abstract class MountedArtboard {
|
|
void draw(Canvas canvas);
|
|
Mat2D get worldTransform;
|
|
set worldTransform(Mat2D value);
|
|
AABB get bounds;
|
|
double get renderOpacity;
|
|
set renderOpacity(double value);
|
|
bool advance(double seconds, {bool nested});
|
|
set artboardWidth(double width);
|
|
double get artboardWidth;
|
|
set artboardHeight(double height);
|
|
double get artboardHeight;
|
|
double get originalArtboardWidth;
|
|
double get originalArtboardHeight;
|
|
void artboardWidthOverride(double width, int widthUnitValue, bool isRow);
|
|
void artboardHeightOverride(double height, int heightUnitValue, bool isRow);
|
|
void dispose();
|
|
void dataContextFromInstance(ViewModelInstance viewModelInstance,
|
|
DataContext? dataContextValue, bool isRoot);
|
|
void internalDataContext(DataContext dataContextValue,
|
|
DataContext? parentDataContext, bool isRoot);
|
|
void populateDataBinds(List<DataBind> globalDataBinds);
|
|
}
|
|
|
|
class NestedArtboard extends NestedArtboardBase implements Sizable {
|
|
/// [NestedAnimation]s applied to this [NestedArtboard].
|
|
final List<NestedAnimation> _animations = [];
|
|
Iterable<NestedAnimation> get animations => _animations;
|
|
|
|
List<int> dataBindPath = [];
|
|
|
|
NestedArtboardFitType get fitType => NestedArtboardFitType.values[fit];
|
|
NestedArtboardAlignmentType get alignmentType =>
|
|
NestedArtboardAlignmentType.values[alignment];
|
|
|
|
bool get hasNestedStateMachine =>
|
|
_animations.any((animation) => animation is NestedStateMachine);
|
|
|
|
List<NestedStateMachine> get nestedStateMachines =>
|
|
_animations.whereType<NestedStateMachine>().toList(growable: false);
|
|
|
|
/// Used by nested animations/state machines to let the nesting artboard know
|
|
/// it needs to redraw/advance time.
|
|
void markNeedsAdvance() => context.markNeedsAdvance();
|
|
|
|
MountedArtboard? _mountedArtboard;
|
|
MountedArtboard? get mountedArtboard => _mountedArtboard;
|
|
set mountedArtboard(MountedArtboard? value) {
|
|
if (value == _mountedArtboard) {
|
|
return;
|
|
}
|
|
_mountedArtboard?.dispose();
|
|
_mountedArtboard = value;
|
|
_updateMountedTransform();
|
|
_mountedArtboard?.renderOpacity = renderOpacity;
|
|
_mountedArtboard?.advance(0);
|
|
addDirt(ComponentDirt.paint);
|
|
|
|
if (cachedSize != null) {
|
|
controlSize(cachedSize!);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void onRemoved() {
|
|
super.onRemoved();
|
|
_mountedArtboard?.dispose();
|
|
}
|
|
|
|
@override
|
|
void artboardIdChanged(int from, int to) {}
|
|
|
|
@override
|
|
void dataBindPathIdsChanged(List<int> from, List<int> to) {}
|
|
|
|
@override
|
|
void fitChanged(int from, int to) {
|
|
_updateMountedTransform();
|
|
}
|
|
|
|
@override
|
|
void alignmentChanged(int from, int to) {
|
|
_updateMountedTransform();
|
|
}
|
|
|
|
@override
|
|
void onAddedDirty() {
|
|
super.onAddedDirty();
|
|
var reader = BinaryReader.fromList(dataBindPathIds);
|
|
while (!reader.isEOF) {
|
|
dataBindPath.add(reader.readVarUint());
|
|
}
|
|
}
|
|
|
|
@override
|
|
void childAdded(Component child) {
|
|
super.childAdded(child);
|
|
switch (child.coreType) {
|
|
case NestedRemapAnimationBase.typeKey:
|
|
case NestedSimpleAnimationBase.typeKey:
|
|
case NestedStateMachineBase.typeKey:
|
|
_animations.add(child as NestedAnimation);
|
|
break;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void childRemoved(Component child) {
|
|
super.childRemoved(child);
|
|
switch (child.coreType) {
|
|
case NestedRemapAnimationBase.typeKey:
|
|
case NestedSimpleAnimationBase.typeKey:
|
|
case NestedStateMachineBase.typeKey:
|
|
_animations.remove(child as NestedAnimation);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// When used with layouts, we cache the size of the NestedArtboard
|
|
// because the mountedArtboard gets replaced when changes are made to the
|
|
// NestedArtboard's artboard, and we need to maintain size when that happens
|
|
Size? cachedSize;
|
|
|
|
@override
|
|
Size computeIntrinsicSize(Size min, Size max) {
|
|
final bounds = mountedArtboard?.bounds;
|
|
if (bounds == null) {
|
|
return min;
|
|
}
|
|
return Size(bounds.width * scaleX, bounds.height * scaleY);
|
|
}
|
|
|
|
@override
|
|
void controlSize(Size size) {
|
|
cachedSize = size;
|
|
if (mountedArtboard == null) {
|
|
return;
|
|
}
|
|
// Since NestedArtboards only use scale, not width/height, we have to do
|
|
// a bit of a conversion here. There may be a better way.
|
|
|
|
scaleX = size.width / mountedArtboard!.originalArtboardWidth;
|
|
scaleY = size.height / mountedArtboard!.originalArtboardHeight;
|
|
|
|
updateTransform();
|
|
updateWorldTransform();
|
|
}
|
|
|
|
void _updateMountedTransform() {
|
|
var mountedArtboard = _mountedArtboard;
|
|
if (mountedArtboard != null) {
|
|
Mat2D transform = Mat2D();
|
|
Mat2D.copy(transform, worldTransform);
|
|
if (fitType == NestedArtboardFitType.resizeArtboard) {
|
|
// resizeArtboard is a special case because we actually change the
|
|
// width/height of the RuntimeArtboard rather than scaling it
|
|
mountedArtboard.artboardWidth =
|
|
scaleX * mountedArtboard.originalArtboardWidth;
|
|
mountedArtboard.artboardHeight =
|
|
scaleY * mountedArtboard.originalArtboardHeight;
|
|
double computedScaleX = scaleX == 0 ? 0 : (1 / scaleX);
|
|
double computedScaleY = scaleY == 0 ? 0 : (1 / scaleY);
|
|
Mat2D.scaleByValues(transform, computedScaleX, computedScaleY);
|
|
} else {
|
|
// For all others we scale
|
|
mountedArtboard.artboardWidth = mountedArtboard.originalArtboardWidth;
|
|
mountedArtboard.artboardHeight = mountedArtboard.originalArtboardHeight;
|
|
double? scaleMultiplier;
|
|
switch (fitType) {
|
|
case NestedArtboardFitType.cover:
|
|
scaleMultiplier = max(scaleX, scaleY);
|
|
break;
|
|
case NestedArtboardFitType.contain:
|
|
scaleMultiplier = min(scaleX, scaleY);
|
|
break;
|
|
case NestedArtboardFitType.fitWidth:
|
|
scaleMultiplier = scaleX;
|
|
break;
|
|
case NestedArtboardFitType.fitHeight:
|
|
scaleMultiplier = scaleY;
|
|
break;
|
|
case NestedArtboardFitType.none:
|
|
scaleMultiplier = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (scaleMultiplier != null) {
|
|
double computedScaleX =
|
|
scaleX == 0 ? 0 : (1 / scaleX) * scaleMultiplier;
|
|
double computedScaleY =
|
|
scaleY == 0 ? 0 : (1 / scaleY) * scaleMultiplier;
|
|
Mat2D.scaleByValues(transform, computedScaleX, computedScaleY);
|
|
// Only do alignment if we are not using fit type Fill
|
|
double translateX = 0;
|
|
double translateY = 0;
|
|
double artboardWidth = mountedArtboard.originalArtboardWidth;
|
|
double artboardHeight = mountedArtboard.originalArtboardHeight;
|
|
// Adjust x position if we're aligned center or right
|
|
if (alignmentType == NestedArtboardAlignmentType.topCenter ||
|
|
alignmentType == NestedArtboardAlignmentType.center ||
|
|
alignmentType == NestedArtboardAlignmentType.bottomCenter) {
|
|
translateX =
|
|
(artboardWidth * scaleX - artboardWidth * scaleMultiplier) / 2;
|
|
} else if (alignmentType == NestedArtboardAlignmentType.topRight ||
|
|
alignmentType == NestedArtboardAlignmentType.centerRight ||
|
|
alignmentType == NestedArtboardAlignmentType.bottomRight) {
|
|
translateX =
|
|
artboardWidth * scaleX - artboardWidth * scaleMultiplier;
|
|
}
|
|
// Adjust y position if we're aligned center or bottom
|
|
if (alignmentType == NestedArtboardAlignmentType.centerLeft ||
|
|
alignmentType == NestedArtboardAlignmentType.center ||
|
|
alignmentType == NestedArtboardAlignmentType.centerRight) {
|
|
translateY =
|
|
(artboardHeight * scaleY - artboardHeight * scaleMultiplier) /
|
|
2;
|
|
} else if (alignmentType == NestedArtboardAlignmentType.bottomLeft ||
|
|
alignmentType == NestedArtboardAlignmentType.bottomCenter ||
|
|
alignmentType == NestedArtboardAlignmentType.bottomRight) {
|
|
translateY =
|
|
artboardHeight * scaleY - artboardHeight * scaleMultiplier;
|
|
}
|
|
if (translateX != 0) {
|
|
transform[4] += translateX;
|
|
}
|
|
if (translateY != 0) {
|
|
transform[5] += translateY;
|
|
}
|
|
}
|
|
}
|
|
mountedArtboard.worldTransform = transform;
|
|
}
|
|
}
|
|
|
|
/// Convert a world position to local for the mounted artboard.
|
|
Vec2D? worldToLocal(Vec2D position) {
|
|
var mounted = mountedArtboard;
|
|
if (mounted == null) {
|
|
return null;
|
|
}
|
|
var toMountedArtboard = Mat2D();
|
|
if (!Mat2D.invert(toMountedArtboard, mounted.worldTransform)) {
|
|
return null;
|
|
}
|
|
return Vec2D.transformMat2D(Vec2D(), position, toMountedArtboard);
|
|
}
|
|
|
|
@override
|
|
void updateWorldTransform() {
|
|
super.updateWorldTransform();
|
|
_updateMountedTransform();
|
|
}
|
|
|
|
bool advance(double elapsedSeconds) {
|
|
if (mountedArtboard == null || isCollapsed) {
|
|
return false;
|
|
}
|
|
|
|
bool keepGoing = false;
|
|
for (final animation in _animations) {
|
|
if (animation.isEnabled) {
|
|
if (animation.advance(elapsedSeconds, mountedArtboard!)) {
|
|
keepGoing = true;
|
|
}
|
|
}
|
|
}
|
|
if (mountedArtboard!.advance(elapsedSeconds)) {
|
|
keepGoing = true;
|
|
}
|
|
return keepGoing;
|
|
}
|
|
|
|
@override
|
|
void update(int dirt) {
|
|
super.update(dirt);
|
|
// RenderOpacity gets updated with the worldTransform (accumulates through
|
|
// hierarchy), so if we see worldTransform is dirty, update our internal
|
|
// render opacities.
|
|
if (dirt & ComponentDirt.worldTransform != 0) {
|
|
mountedArtboard?.renderOpacity = renderOpacity;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void draw(Canvas canvas) {
|
|
bool clipped = clip(canvas);
|
|
mountedArtboard?.draw(canvas);
|
|
|
|
if (clipped) {
|
|
canvas.restore();
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool import(ImportStack stack) {
|
|
var backboardImporter =
|
|
stack.latest<BackboardImporter>(BackboardBase.typeKey);
|
|
if (backboardImporter != null) {
|
|
backboardImporter.addNestedArtboard(this);
|
|
}
|
|
|
|
return super.import(stack);
|
|
}
|
|
}
|