mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-06-23 15:29:20 +08:00
Mesh deform for flutter runtime.
This commit is contained in:
@ -3,6 +3,9 @@ import 'dart:collection';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart';
|
||||
|
||||
export 'dart:typed_data';
|
||||
export 'package:flutter/foundation.dart';
|
||||
|
||||
export 'package:rive/src/animation_list.dart';
|
||||
export 'package:rive/src/asset_list.dart';
|
||||
export 'package:rive/src/blend_animations.dart';
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/rive_core/assets/file_asset.dart';
|
||||
import 'package:rive/src/rive_core/assets/file_asset_contents.dart';
|
||||
@ -21,7 +19,7 @@ class FileAssetImporter extends ImportStackObject {
|
||||
|
||||
void loadContents(FileAssetContents contents) {
|
||||
_loadedContents = true;
|
||||
fileAsset.decode(contents.bytes as Uint8List);
|
||||
fileAsset.decode(contents.bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -13,27 +13,27 @@ abstract class FileAssetContentsBase<T extends CoreContext> extends Core<T> {
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// Bytes field with key 212.
|
||||
static const List<int> bytesInitialValue = [];
|
||||
List<int> _bytes = bytesInitialValue;
|
||||
static final Uint8List bytesInitialValue = Uint8List(0);
|
||||
Uint8List _bytes = bytesInitialValue;
|
||||
static const int bytesPropertyKey = 212;
|
||||
|
||||
/// Byte data of the file.
|
||||
List<int> get bytes => _bytes;
|
||||
Uint8List get bytes => _bytes;
|
||||
|
||||
/// Change the [_bytes] field value.
|
||||
/// [bytesChanged] will be invoked only if the field's value has changed.
|
||||
set bytes(List<int> value) {
|
||||
if (_bytes == value) {
|
||||
set bytes(Uint8List value) {
|
||||
if (listEquals(_bytes, value)) {
|
||||
return;
|
||||
}
|
||||
List<int> from = _bytes;
|
||||
Uint8List from = _bytes;
|
||||
_bytes = value;
|
||||
if (hasValidated) {
|
||||
bytesChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void bytesChanged(List<int> from, List<int> to);
|
||||
void bytesChanged(Uint8List from, Uint8List to);
|
||||
|
||||
@override
|
||||
void copy(covariant FileAssetContentsBase source) {
|
||||
|
@ -27,7 +27,7 @@ import 'package:rive/src/generated/nested_animation_base.dart';
|
||||
import 'package:rive/src/generated/shapes/paint/shape_paint_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/path_vertex_base.dart';
|
||||
import 'package:rive/src/generated/shapes/vertex_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/animation/animation.dart';
|
||||
@ -82,11 +82,14 @@ import 'package:rive/src/rive_core/draw_target.dart';
|
||||
import 'package:rive/src/rive_core/nested_artboard.dart';
|
||||
import 'package:rive/src/rive_core/node.dart';
|
||||
import 'package:rive/src/rive_core/shapes/clipping_shape.dart';
|
||||
import 'package:rive/src/rive_core/shapes/contour_mesh_vertex.dart';
|
||||
import 'package:rive/src/rive_core/shapes/cubic_asymmetric_vertex.dart';
|
||||
import 'package:rive/src/rive_core/shapes/cubic_detached_vertex.dart';
|
||||
import 'package:rive/src/rive_core/shapes/cubic_mirrored_vertex.dart';
|
||||
import 'package:rive/src/rive_core/shapes/ellipse.dart';
|
||||
import 'package:rive/src/rive_core/shapes/image.dart';
|
||||
import 'package:rive/src/rive_core/shapes/mesh.dart';
|
||||
import 'package:rive/src/rive_core/shapes/mesh_vertex.dart';
|
||||
import 'package:rive/src/rive_core/shapes/paint/fill.dart';
|
||||
import 'package:rive/src/rive_core/shapes/paint/gradient_stop.dart';
|
||||
import 'package:rive/src/rive_core/shapes/paint/linear_gradient.dart';
|
||||
@ -100,6 +103,8 @@ 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/text.dart';
|
||||
import 'package:rive/src/rive_core/shapes/text_run.dart';
|
||||
import 'package:rive/src/rive_core/shapes/triangle.dart';
|
||||
|
||||
// ignore: avoid_classes_with_only_static_members
|
||||
@ -198,6 +203,8 @@ class RiveCoreContext {
|
||||
return TrimPath();
|
||||
case FillBase.typeKey:
|
||||
return Fill();
|
||||
case MeshVertexBase.typeKey:
|
||||
return MeshVertex();
|
||||
case ShapeBase.typeKey:
|
||||
return Shape();
|
||||
case WeightBase.typeKey:
|
||||
@ -208,8 +215,14 @@ class RiveCoreContext {
|
||||
return CubicWeight();
|
||||
case CubicAsymmetricVertexBase.typeKey:
|
||||
return CubicAsymmetricVertex();
|
||||
case MeshBase.typeKey:
|
||||
return Mesh();
|
||||
case TextRunBase.typeKey:
|
||||
return TextRun();
|
||||
case PointsPathBase.typeKey:
|
||||
return PointsPath();
|
||||
case ContourMeshVertexBase.typeKey:
|
||||
return ContourMeshVertex();
|
||||
case RectangleBase.typeKey:
|
||||
return Rectangle();
|
||||
case CubicMirroredVertexBase.typeKey:
|
||||
@ -226,6 +239,8 @@ class RiveCoreContext {
|
||||
return Star();
|
||||
case ImageBase.typeKey:
|
||||
return Image();
|
||||
case TextBase.typeKey:
|
||||
return Text();
|
||||
case CubicDetachedVertexBase.typeKey:
|
||||
return CubicDetachedVertex();
|
||||
case DrawRulesBase.typeKey:
|
||||
@ -730,21 +745,31 @@ class RiveCoreContext {
|
||||
object.fillRule = value;
|
||||
}
|
||||
break;
|
||||
case PathBase.pathFlagsPropertyKey:
|
||||
if (object is PathBase && value is int) {
|
||||
object.pathFlags = value;
|
||||
}
|
||||
break;
|
||||
case PathVertexBase.xPropertyKey:
|
||||
if (object is PathVertexBase && value is double) {
|
||||
case VertexBase.xPropertyKey:
|
||||
if (object is VertexBase && value is double) {
|
||||
object.x = value;
|
||||
}
|
||||
break;
|
||||
case PathVertexBase.yPropertyKey:
|
||||
if (object is PathVertexBase && value is double) {
|
||||
case VertexBase.yPropertyKey:
|
||||
if (object is VertexBase && value is double) {
|
||||
object.y = value;
|
||||
}
|
||||
break;
|
||||
case MeshVertexBase.uPropertyKey:
|
||||
if (object is MeshVertexBase && value is double) {
|
||||
object.u = value;
|
||||
}
|
||||
break;
|
||||
case MeshVertexBase.vPropertyKey:
|
||||
if (object is MeshVertexBase && value is double) {
|
||||
object.v = value;
|
||||
}
|
||||
break;
|
||||
case PathBase.pathFlagsPropertyKey:
|
||||
if (object is PathBase && value is int) {
|
||||
object.pathFlags = value;
|
||||
}
|
||||
break;
|
||||
case WeightBase.valuesPropertyKey:
|
||||
if (object is WeightBase && value is int) {
|
||||
object.values = value;
|
||||
@ -795,6 +820,21 @@ class RiveCoreContext {
|
||||
object.outDistance = value;
|
||||
}
|
||||
break;
|
||||
case MeshBase.triangleIndexBytesPropertyKey:
|
||||
if (object is MeshBase && value is Uint8List) {
|
||||
object.triangleIndexBytes = value;
|
||||
}
|
||||
break;
|
||||
case TextRunBase.pointSizePropertyKey:
|
||||
if (object is TextRunBase && value is double) {
|
||||
object.pointSize = value;
|
||||
}
|
||||
break;
|
||||
case TextRunBase.textLengthPropertyKey:
|
||||
if (object is TextRunBase && value is int) {
|
||||
object.textLength = value;
|
||||
}
|
||||
break;
|
||||
case PointsPathBase.isClosedPropertyKey:
|
||||
if (object is PointsPathBase && value is bool) {
|
||||
object.isClosed = value;
|
||||
@ -890,6 +930,11 @@ class RiveCoreContext {
|
||||
object.assetId = value;
|
||||
}
|
||||
break;
|
||||
case TextBase.valuePropertyKey:
|
||||
if (object is TextBase && value is String) {
|
||||
object.value = value;
|
||||
}
|
||||
break;
|
||||
case CubicDetachedVertexBase.inRotationPropertyKey:
|
||||
if (object is CubicDetachedVertexBase && value is double) {
|
||||
object.inRotation = value;
|
||||
@ -1051,7 +1096,7 @@ class RiveCoreContext {
|
||||
}
|
||||
break;
|
||||
case FileAssetContentsBase.bytesPropertyKey:
|
||||
if (object is FileAssetContentsBase && value is List<int>) {
|
||||
if (object is FileAssetContentsBase && value is Uint8List) {
|
||||
object.bytes = value;
|
||||
}
|
||||
break;
|
||||
@ -1069,6 +1114,7 @@ class RiveCoreContext {
|
||||
case ComponentBase.namePropertyKey:
|
||||
case AnimationBase.namePropertyKey:
|
||||
case StateMachineComponentBase.namePropertyKey:
|
||||
case TextBase.valuePropertyKey:
|
||||
case AssetBase.namePropertyKey:
|
||||
return stringType;
|
||||
case ComponentBase.parentIdPropertyKey:
|
||||
@ -1117,6 +1163,7 @@ class RiveCoreContext {
|
||||
case CubicWeightBase.inIndicesPropertyKey:
|
||||
case CubicWeightBase.outValuesPropertyKey:
|
||||
case CubicWeightBase.outIndicesPropertyKey:
|
||||
case TextRunBase.textLengthPropertyKey:
|
||||
case ClippingShapeBase.sourceIdPropertyKey:
|
||||
case ClippingShapeBase.fillRulePropertyKey:
|
||||
case PolygonBase.pointsPropertyKey:
|
||||
@ -1161,12 +1208,15 @@ class RiveCoreContext {
|
||||
case TrimPathBase.startPropertyKey:
|
||||
case TrimPathBase.endPropertyKey:
|
||||
case TrimPathBase.offsetPropertyKey:
|
||||
case PathVertexBase.xPropertyKey:
|
||||
case PathVertexBase.yPropertyKey:
|
||||
case VertexBase.xPropertyKey:
|
||||
case VertexBase.yPropertyKey:
|
||||
case MeshVertexBase.uPropertyKey:
|
||||
case MeshVertexBase.vPropertyKey:
|
||||
case StraightVertexBase.radiusPropertyKey:
|
||||
case CubicAsymmetricVertexBase.rotationPropertyKey:
|
||||
case CubicAsymmetricVertexBase.inDistancePropertyKey:
|
||||
case CubicAsymmetricVertexBase.outDistancePropertyKey:
|
||||
case TextRunBase.pointSizePropertyKey:
|
||||
case ParametricPathBase.widthPropertyKey:
|
||||
case ParametricPathBase.heightPropertyKey:
|
||||
case ParametricPathBase.originXPropertyKey:
|
||||
@ -1230,6 +1280,7 @@ class RiveCoreContext {
|
||||
case SolidColorBase.colorValuePropertyKey:
|
||||
case GradientStopBase.colorValuePropertyKey:
|
||||
return colorType;
|
||||
case MeshBase.triangleIndexBytesPropertyKey:
|
||||
case FileAssetContentsBase.bytesPropertyKey:
|
||||
return bytesType;
|
||||
default:
|
||||
@ -1245,6 +1296,8 @@ class RiveCoreContext {
|
||||
return (object as AnimationBase).name;
|
||||
case StateMachineComponentBase.namePropertyKey:
|
||||
return (object as StateMachineComponentBase).name;
|
||||
case TextBase.valuePropertyKey:
|
||||
return (object as TextBase).value;
|
||||
case AssetBase.namePropertyKey:
|
||||
return (object as AssetBase).name;
|
||||
}
|
||||
@ -1345,6 +1398,8 @@ class RiveCoreContext {
|
||||
return (object as CubicWeightBase).outValues;
|
||||
case CubicWeightBase.outIndicesPropertyKey:
|
||||
return (object as CubicWeightBase).outIndices;
|
||||
case TextRunBase.textLengthPropertyKey:
|
||||
return (object as TextRunBase).textLength;
|
||||
case ClippingShapeBase.sourceIdPropertyKey:
|
||||
return (object as ClippingShapeBase).sourceId;
|
||||
case ClippingShapeBase.fillRulePropertyKey:
|
||||
@ -1437,10 +1492,14 @@ class RiveCoreContext {
|
||||
return (object as TrimPathBase).end;
|
||||
case TrimPathBase.offsetPropertyKey:
|
||||
return (object as TrimPathBase).offset;
|
||||
case PathVertexBase.xPropertyKey:
|
||||
return (object as PathVertexBase).x;
|
||||
case PathVertexBase.yPropertyKey:
|
||||
return (object as PathVertexBase).y;
|
||||
case VertexBase.xPropertyKey:
|
||||
return (object as VertexBase).x;
|
||||
case VertexBase.yPropertyKey:
|
||||
return (object as VertexBase).y;
|
||||
case MeshVertexBase.uPropertyKey:
|
||||
return (object as MeshVertexBase).u;
|
||||
case MeshVertexBase.vPropertyKey:
|
||||
return (object as MeshVertexBase).v;
|
||||
case StraightVertexBase.radiusPropertyKey:
|
||||
return (object as StraightVertexBase).radius;
|
||||
case CubicAsymmetricVertexBase.rotationPropertyKey:
|
||||
@ -1449,6 +1508,8 @@ class RiveCoreContext {
|
||||
return (object as CubicAsymmetricVertexBase).inDistance;
|
||||
case CubicAsymmetricVertexBase.outDistancePropertyKey:
|
||||
return (object as CubicAsymmetricVertexBase).outDistance;
|
||||
case TextRunBase.pointSizePropertyKey:
|
||||
return (object as TextRunBase).pointSize;
|
||||
case ParametricPathBase.widthPropertyKey:
|
||||
return (object as ParametricPathBase).width;
|
||||
case ParametricPathBase.heightPropertyKey:
|
||||
@ -1585,12 +1646,14 @@ class RiveCoreContext {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static List<int> getBytes(Core object, int propertyKey) {
|
||||
static Uint8List getBytes(Core object, int propertyKey) {
|
||||
switch (propertyKey) {
|
||||
case MeshBase.triangleIndexBytesPropertyKey:
|
||||
return (object as MeshBase).triangleIndexBytes;
|
||||
case FileAssetContentsBase.bytesPropertyKey:
|
||||
return (object as FileAssetContentsBase).bytes;
|
||||
}
|
||||
return [];
|
||||
return Uint8List(0);
|
||||
}
|
||||
|
||||
static void setString(Core object, int propertyKey, String value) {
|
||||
@ -1610,6 +1673,11 @@ class RiveCoreContext {
|
||||
object.name = value;
|
||||
}
|
||||
break;
|
||||
case TextBase.valuePropertyKey:
|
||||
if (object is TextBase) {
|
||||
object.value = value;
|
||||
}
|
||||
break;
|
||||
case AssetBase.namePropertyKey:
|
||||
if (object is AssetBase) {
|
||||
object.name = value;
|
||||
@ -1850,6 +1918,11 @@ class RiveCoreContext {
|
||||
object.outIndices = value;
|
||||
}
|
||||
break;
|
||||
case TextRunBase.textLengthPropertyKey:
|
||||
if (object is TextRunBase) {
|
||||
object.textLength = value;
|
||||
}
|
||||
break;
|
||||
case ClippingShapeBase.sourceIdPropertyKey:
|
||||
if (object is ClippingShapeBase) {
|
||||
object.sourceId = value;
|
||||
@ -2070,16 +2143,26 @@ class RiveCoreContext {
|
||||
object.offset = value;
|
||||
}
|
||||
break;
|
||||
case PathVertexBase.xPropertyKey:
|
||||
if (object is PathVertexBase) {
|
||||
case VertexBase.xPropertyKey:
|
||||
if (object is VertexBase) {
|
||||
object.x = value;
|
||||
}
|
||||
break;
|
||||
case PathVertexBase.yPropertyKey:
|
||||
if (object is PathVertexBase) {
|
||||
case VertexBase.yPropertyKey:
|
||||
if (object is VertexBase) {
|
||||
object.y = value;
|
||||
}
|
||||
break;
|
||||
case MeshVertexBase.uPropertyKey:
|
||||
if (object is MeshVertexBase) {
|
||||
object.u = value;
|
||||
}
|
||||
break;
|
||||
case MeshVertexBase.vPropertyKey:
|
||||
if (object is MeshVertexBase) {
|
||||
object.v = value;
|
||||
}
|
||||
break;
|
||||
case StraightVertexBase.radiusPropertyKey:
|
||||
if (object is StraightVertexBase) {
|
||||
object.radius = value;
|
||||
@ -2100,6 +2183,11 @@ class RiveCoreContext {
|
||||
object.outDistance = value;
|
||||
}
|
||||
break;
|
||||
case TextRunBase.pointSizePropertyKey:
|
||||
if (object is TextRunBase) {
|
||||
object.pointSize = value;
|
||||
}
|
||||
break;
|
||||
case ParametricPathBase.widthPropertyKey:
|
||||
if (object is ParametricPathBase) {
|
||||
object.width = value;
|
||||
@ -2413,8 +2501,13 @@ class RiveCoreContext {
|
||||
}
|
||||
}
|
||||
|
||||
static void setBytes(Core object, int propertyKey, List<int> value) {
|
||||
static void setBytes(Core object, int propertyKey, Uint8List value) {
|
||||
switch (propertyKey) {
|
||||
case MeshBase.triangleIndexBytesPropertyKey:
|
||||
if (object is MeshBase) {
|
||||
object.triangleIndexBytes = value;
|
||||
}
|
||||
break;
|
||||
case FileAssetContentsBase.bytesPropertyKey:
|
||||
if (object is FileAssetContentsBase) {
|
||||
object.bytes = value;
|
||||
|
22
lib/src/generated/shapes/contour_mesh_vertex_base.dart
Normal file
22
lib/src/generated/shapes/contour_mesh_vertex_base.dart
Normal file
@ -0,0 +1,22 @@
|
||||
/// Core automatically generated
|
||||
/// lib/src/generated/shapes/contour_mesh_vertex_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/shapes/vertex_base.dart';
|
||||
import 'package:rive/src/rive_core/shapes/mesh_vertex.dart';
|
||||
|
||||
abstract class ContourMeshVertexBase extends MeshVertex {
|
||||
static const int typeKey = 111;
|
||||
@override
|
||||
int get coreType => ContourMeshVertexBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {
|
||||
ContourMeshVertexBase.typeKey,
|
||||
MeshVertexBase.typeKey,
|
||||
VertexBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
import 'package:rive/src/generated/component_base.dart';
|
||||
import 'package:rive/src/generated/container_component_base.dart';
|
||||
import 'package:rive/src/generated/shapes/path_vertex_base.dart';
|
||||
import 'package:rive/src/generated/shapes/vertex_base.dart';
|
||||
import 'package:rive/src/rive_core/shapes/cubic_vertex.dart';
|
||||
|
||||
abstract class CubicAsymmetricVertexBase extends CubicVertex {
|
||||
@ -16,6 +17,7 @@ abstract class CubicAsymmetricVertexBase extends CubicVertex {
|
||||
CubicAsymmetricVertexBase.typeKey,
|
||||
CubicVertexBase.typeKey,
|
||||
PathVertexBase.typeKey,
|
||||
VertexBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'package:rive/src/generated/component_base.dart';
|
||||
import 'package:rive/src/generated/container_component_base.dart';
|
||||
import 'package:rive/src/generated/shapes/path_vertex_base.dart';
|
||||
import 'package:rive/src/generated/shapes/vertex_base.dart';
|
||||
import 'package:rive/src/rive_core/shapes/cubic_vertex.dart';
|
||||
|
||||
abstract class CubicDetachedVertexBase extends CubicVertex {
|
||||
@ -16,6 +17,7 @@ abstract class CubicDetachedVertexBase extends CubicVertex {
|
||||
CubicDetachedVertexBase.typeKey,
|
||||
CubicVertexBase.typeKey,
|
||||
PathVertexBase.typeKey,
|
||||
VertexBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'package:rive/src/generated/component_base.dart';
|
||||
import 'package:rive/src/generated/container_component_base.dart';
|
||||
import 'package:rive/src/generated/shapes/path_vertex_base.dart';
|
||||
import 'package:rive/src/generated/shapes/vertex_base.dart';
|
||||
import 'package:rive/src/rive_core/shapes/cubic_vertex.dart';
|
||||
|
||||
abstract class CubicMirroredVertexBase extends CubicVertex {
|
||||
@ -16,6 +17,7 @@ abstract class CubicMirroredVertexBase extends CubicVertex {
|
||||
CubicMirroredVertexBase.typeKey,
|
||||
CubicVertexBase.typeKey,
|
||||
PathVertexBase.typeKey,
|
||||
VertexBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'package:rive/src/generated/component_base.dart';
|
||||
import 'package:rive/src/generated/container_component_base.dart';
|
||||
import 'package:rive/src/generated/shapes/vertex_base.dart';
|
||||
import 'package:rive/src/rive_core/bones/cubic_weight.dart';
|
||||
import 'package:rive/src/rive_core/shapes/path_vertex.dart';
|
||||
|
||||
@ -15,6 +16,7 @@ abstract class CubicVertexBase extends PathVertex<CubicWeight> {
|
||||
Set<int> get coreTypes => {
|
||||
CubicVertexBase.typeKey,
|
||||
PathVertexBase.typeKey,
|
||||
VertexBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
67
lib/src/generated/shapes/forced_edge_base.dart
Normal file
67
lib/src/generated/shapes/forced_edge_base.dart
Normal file
@ -0,0 +1,67 @@
|
||||
/// Core automatically generated lib/src/generated/shapes/forced_edge_base.dart.
|
||||
/// Do not modify manually.
|
||||
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
|
||||
abstract class ForcedEdgeBase extends Component {
|
||||
static const int typeKey = 112;
|
||||
@override
|
||||
int get coreType => ForcedEdgeBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {ForcedEdgeBase.typeKey, ComponentBase.typeKey};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// FromId field with key 219.
|
||||
static const int fromIdInitialValue = 0;
|
||||
int _fromId = fromIdInitialValue;
|
||||
static const int fromIdPropertyKey = 219;
|
||||
|
||||
/// Identifier used to track MeshVertex the force edge extends from.
|
||||
int get fromId => _fromId;
|
||||
|
||||
/// Change the [_fromId] field value.
|
||||
/// [fromIdChanged] will be invoked only if the field's value has changed.
|
||||
set fromId(int value) {
|
||||
if (_fromId == value) {
|
||||
return;
|
||||
}
|
||||
int from = _fromId;
|
||||
_fromId = value;
|
||||
if (hasValidated) {
|
||||
fromIdChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void fromIdChanged(int from, int to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// ToId field with key 220.
|
||||
static const int toIdInitialValue = 0;
|
||||
int _toId = toIdInitialValue;
|
||||
static const int toIdPropertyKey = 220;
|
||||
|
||||
/// Identifier used to track MeshVertex the force edge extends to.
|
||||
int get toId => _toId;
|
||||
|
||||
/// Change the [_toId] field value.
|
||||
/// [toIdChanged] will be invoked only if the field's value has changed.
|
||||
set toId(int value) {
|
||||
if (_toId == value) {
|
||||
return;
|
||||
}
|
||||
int from = _toId;
|
||||
_toId = value;
|
||||
if (hasValidated) {
|
||||
toIdChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void toIdChanged(int from, int to);
|
||||
|
||||
@override
|
||||
void copy(covariant ForcedEdgeBase source) {
|
||||
super.copy(source);
|
||||
_fromId = source._fromId;
|
||||
_toId = source._toId;
|
||||
}
|
||||
}
|
47
lib/src/generated/shapes/mesh_base.dart
Normal file
47
lib/src/generated/shapes/mesh_base.dart
Normal file
@ -0,0 +1,47 @@
|
||||
/// Core automatically generated lib/src/generated/shapes/mesh_base.dart.
|
||||
/// Do not modify manually.
|
||||
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/component_base.dart';
|
||||
import 'package:rive/src/generated/container_component_base.dart';
|
||||
import 'package:rive/src/rive_core/container_component.dart';
|
||||
|
||||
abstract class MeshBase extends ContainerComponent {
|
||||
static const int typeKey = 109;
|
||||
@override
|
||||
int get coreType => MeshBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes =>
|
||||
{MeshBase.typeKey, ContainerComponentBase.typeKey, ComponentBase.typeKey};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// TriangleIndexBytes field with key 223.
|
||||
static final Uint8List triangleIndexBytesInitialValue = Uint8List(0);
|
||||
Uint8List _triangleIndexBytes = triangleIndexBytesInitialValue;
|
||||
static const int triangleIndexBytesPropertyKey = 223;
|
||||
|
||||
/// Byte data for the triangle indices.
|
||||
Uint8List get triangleIndexBytes => _triangleIndexBytes;
|
||||
|
||||
/// Change the [_triangleIndexBytes] field value.
|
||||
/// [triangleIndexBytesChanged] will be invoked only if the field's value has
|
||||
/// changed.
|
||||
set triangleIndexBytes(Uint8List value) {
|
||||
if (listEquals(_triangleIndexBytes, value)) {
|
||||
return;
|
||||
}
|
||||
Uint8List from = _triangleIndexBytes;
|
||||
_triangleIndexBytes = value;
|
||||
if (hasValidated) {
|
||||
triangleIndexBytesChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void triangleIndexBytesChanged(Uint8List from, Uint8List to);
|
||||
|
||||
@override
|
||||
void copy(covariant MeshBase source) {
|
||||
super.copy(source);
|
||||
_triangleIndexBytes = source._triangleIndexBytes;
|
||||
}
|
||||
}
|
74
lib/src/generated/shapes/mesh_vertex_base.dart
Normal file
74
lib/src/generated/shapes/mesh_vertex_base.dart
Normal file
@ -0,0 +1,74 @@
|
||||
/// Core automatically generated lib/src/generated/shapes/mesh_vertex_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/rive_core/shapes/vertex.dart';
|
||||
|
||||
abstract class MeshVertexBase extends Vertex {
|
||||
static const int typeKey = 108;
|
||||
@override
|
||||
int get coreType => MeshVertexBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {
|
||||
MeshVertexBase.typeKey,
|
||||
VertexBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// U field with key 215.
|
||||
static const double uInitialValue = 0;
|
||||
double _u = uInitialValue;
|
||||
static const int uPropertyKey = 215;
|
||||
|
||||
/// U value for the texture coordinate of the vertex.
|
||||
double get u => _u;
|
||||
|
||||
/// Change the [_u] field value.
|
||||
/// [uChanged] will be invoked only if the field's value has changed.
|
||||
set u(double value) {
|
||||
if (_u == value) {
|
||||
return;
|
||||
}
|
||||
double from = _u;
|
||||
_u = value;
|
||||
if (hasValidated) {
|
||||
uChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void uChanged(double from, double to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// V field with key 216.
|
||||
static const double vInitialValue = 0;
|
||||
double _v = vInitialValue;
|
||||
static const int vPropertyKey = 216;
|
||||
|
||||
/// V value for the texture coordinate of the vertex.
|
||||
double get v => _v;
|
||||
|
||||
/// Change the [_v] field value.
|
||||
/// [vChanged] will be invoked only if the field's value has changed.
|
||||
set v(double value) {
|
||||
if (_v == value) {
|
||||
return;
|
||||
}
|
||||
double from = _v;
|
||||
_v = value;
|
||||
if (hasValidated) {
|
||||
vChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void vChanged(double from, double to);
|
||||
|
||||
@override
|
||||
void copy(covariant MeshVertexBase source) {
|
||||
super.copy(source);
|
||||
_u = source._u;
|
||||
_v = source._v;
|
||||
}
|
||||
}
|
@ -3,71 +3,18 @@
|
||||
|
||||
import 'package:rive/src/generated/component_base.dart';
|
||||
import 'package:rive/src/generated/container_component_base.dart';
|
||||
import 'package:rive/src/rive_core/container_component.dart';
|
||||
import 'package:rive/src/rive_core/bones/weight.dart';
|
||||
import 'package:rive/src/rive_core/shapes/vertex.dart';
|
||||
|
||||
abstract class PathVertexBase extends ContainerComponent {
|
||||
abstract class PathVertexBase<T extends Weight> extends Vertex<T> {
|
||||
static const int typeKey = 14;
|
||||
@override
|
||||
int get coreType => PathVertexBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {
|
||||
PathVertexBase.typeKey,
|
||||
VertexBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// X field with key 24.
|
||||
static const double xInitialValue = 0;
|
||||
double _x = xInitialValue;
|
||||
static const int xPropertyKey = 24;
|
||||
|
||||
/// X value for the translation of the vertex.
|
||||
double get x => _x;
|
||||
|
||||
/// Change the [_x] field value.
|
||||
/// [xChanged] will be invoked only if the field's value has changed.
|
||||
set x(double value) {
|
||||
if (_x == value) {
|
||||
return;
|
||||
}
|
||||
double from = _x;
|
||||
_x = value;
|
||||
if (hasValidated) {
|
||||
xChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void xChanged(double from, double to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// Y field with key 25.
|
||||
static const double yInitialValue = 0;
|
||||
double _y = yInitialValue;
|
||||
static const int yPropertyKey = 25;
|
||||
|
||||
/// Y value for the translation of the vertex.
|
||||
double get y => _y;
|
||||
|
||||
/// Change the [_y] field value.
|
||||
/// [yChanged] will be invoked only if the field's value has changed.
|
||||
set y(double value) {
|
||||
if (_y == value) {
|
||||
return;
|
||||
}
|
||||
double from = _y;
|
||||
_y = value;
|
||||
if (hasValidated) {
|
||||
yChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void yChanged(double from, double to);
|
||||
|
||||
@override
|
||||
void copy(covariant PathVertexBase source) {
|
||||
super.copy(source);
|
||||
_x = source._x;
|
||||
_y = source._y;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'package:rive/src/generated/component_base.dart';
|
||||
import 'package:rive/src/generated/container_component_base.dart';
|
||||
import 'package:rive/src/generated/shapes/vertex_base.dart';
|
||||
import 'package:rive/src/rive_core/bones/weight.dart';
|
||||
import 'package:rive/src/rive_core/shapes/path_vertex.dart';
|
||||
|
||||
@ -15,6 +16,7 @@ abstract class StraightVertexBase extends PathVertex<Weight> {
|
||||
Set<int> get coreTypes => {
|
||||
StraightVertexBase.typeKey,
|
||||
PathVertexBase.typeKey,
|
||||
VertexBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
53
lib/src/generated/shapes/text_base.dart
Normal file
53
lib/src/generated/shapes/text_base.dart
Normal file
@ -0,0 +1,53 @@
|
||||
/// Core automatically generated lib/src/generated/shapes/text_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 TextBase extends Node {
|
||||
static const int typeKey = 110;
|
||||
@override
|
||||
int get coreType => TextBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {
|
||||
TextBase.typeKey,
|
||||
NodeBase.typeKey,
|
||||
TransformComponentBase.typeKey,
|
||||
WorldTransformComponentBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// Value field with key 218.
|
||||
static const String valueInitialValue = '';
|
||||
String _value = valueInitialValue;
|
||||
static const int valuePropertyKey = 218;
|
||||
|
||||
/// The value stored in the Text object.
|
||||
String get value => _value;
|
||||
|
||||
/// Change the [_value] field value.
|
||||
/// [valueChanged] will be invoked only if the field's value has changed.
|
||||
set value(String value) {
|
||||
if (_value == value) {
|
||||
return;
|
||||
}
|
||||
String from = _value;
|
||||
_value = value;
|
||||
if (hasValidated) {
|
||||
valueChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void valueChanged(String from, String to);
|
||||
|
||||
@override
|
||||
void copy(covariant TextBase source) {
|
||||
super.copy(source);
|
||||
_value = source._value;
|
||||
}
|
||||
}
|
80
lib/src/generated/shapes/text_run_base.dart
Normal file
80
lib/src/generated/shapes/text_run_base.dart
Normal file
@ -0,0 +1,80 @@
|
||||
/// Core automatically generated lib/src/generated/shapes/text_run_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/transform_component_base.dart';
|
||||
import 'package:rive/src/generated/world_transform_component_base.dart';
|
||||
import 'package:rive/src/rive_core/drawable.dart';
|
||||
|
||||
abstract class TextRunBase extends Drawable {
|
||||
static const int typeKey = 113;
|
||||
@override
|
||||
int get coreType => TextRunBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {
|
||||
TextRunBase.typeKey,
|
||||
DrawableBase.typeKey,
|
||||
NodeBase.typeKey,
|
||||
TransformComponentBase.typeKey,
|
||||
WorldTransformComponentBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// PointSize field with key 221.
|
||||
static const double pointSizeInitialValue = 16;
|
||||
double _pointSize = pointSizeInitialValue;
|
||||
static const int pointSizePropertyKey = 221;
|
||||
|
||||
/// The point size for text styled by this run.
|
||||
double get pointSize => _pointSize;
|
||||
|
||||
/// Change the [_pointSize] field value.
|
||||
/// [pointSizeChanged] will be invoked only if the field's value has changed.
|
||||
set pointSize(double value) {
|
||||
if (_pointSize == value) {
|
||||
return;
|
||||
}
|
||||
double from = _pointSize;
|
||||
_pointSize = value;
|
||||
if (hasValidated) {
|
||||
pointSizeChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void pointSizeChanged(double from, double to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// TextLength field with key 222.
|
||||
static const int textLengthInitialValue = 0;
|
||||
int _textLength = textLengthInitialValue;
|
||||
static const int textLengthPropertyKey = 222;
|
||||
|
||||
/// The length of the text styled by this run.
|
||||
int get textLength => _textLength;
|
||||
|
||||
/// Change the [_textLength] field value.
|
||||
/// [textLengthChanged] will be invoked only if the field's value has changed.
|
||||
set textLength(int value) {
|
||||
if (_textLength == value) {
|
||||
return;
|
||||
}
|
||||
int from = _textLength;
|
||||
_textLength = value;
|
||||
if (hasValidated) {
|
||||
textLengthChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void textLengthChanged(int from, int to);
|
||||
|
||||
@override
|
||||
void copy(covariant TextRunBase source) {
|
||||
super.copy(source);
|
||||
_pointSize = source._pointSize;
|
||||
_textLength = source._textLength;
|
||||
}
|
||||
}
|
73
lib/src/generated/shapes/vertex_base.dart
Normal file
73
lib/src/generated/shapes/vertex_base.dart
Normal file
@ -0,0 +1,73 @@
|
||||
/// Core automatically generated lib/src/generated/shapes/vertex_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/rive_core/container_component.dart';
|
||||
|
||||
abstract class VertexBase extends ContainerComponent {
|
||||
static const int typeKey = 107;
|
||||
@override
|
||||
int get coreType => VertexBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {
|
||||
VertexBase.typeKey,
|
||||
ContainerComponentBase.typeKey,
|
||||
ComponentBase.typeKey
|
||||
};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// X field with key 24.
|
||||
static const double xInitialValue = 0;
|
||||
double _x = xInitialValue;
|
||||
static const int xPropertyKey = 24;
|
||||
|
||||
/// X value for the translation of the vertex.
|
||||
double get x => _x;
|
||||
|
||||
/// Change the [_x] field value.
|
||||
/// [xChanged] will be invoked only if the field's value has changed.
|
||||
set x(double value) {
|
||||
if (_x == value) {
|
||||
return;
|
||||
}
|
||||
double from = _x;
|
||||
_x = value;
|
||||
if (hasValidated) {
|
||||
xChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void xChanged(double from, double to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// Y field with key 25.
|
||||
static const double yInitialValue = 0;
|
||||
double _y = yInitialValue;
|
||||
static const int yPropertyKey = 25;
|
||||
|
||||
/// Y value for the translation of the vertex.
|
||||
double get y => _y;
|
||||
|
||||
/// Change the [_y] field value.
|
||||
/// [yChanged] will be invoked only if the field's value has changed.
|
||||
set y(double value) {
|
||||
if (_y == value) {
|
||||
return;
|
||||
}
|
||||
double from = _y;
|
||||
_y = value;
|
||||
if (hasValidated) {
|
||||
yChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void yChanged(double from, double to);
|
||||
|
||||
@override
|
||||
void copy(covariant VertexBase source) {
|
||||
super.copy(source);
|
||||
_x = source._x;
|
||||
_y = source._y;
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/animation/blend_animation_base.dart';
|
||||
import 'package:rive/src/generated/artboard_base.dart';
|
||||
import 'package:rive/src/rive_core/animation/layer_state.dart';
|
||||
import 'package:rive/src/rive_core/animation/linear_animation.dart';
|
||||
import 'package:rive/src/rive_core/artboard.dart';
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/rive_core/animation/blend_animation.dart';
|
||||
import 'package:rive/src/rive_core/animation/blend_state.dart';
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/animation/cubic_interpolator_base.dart';
|
||||
import 'package:rive/src/rive_core/animation/interpolator.dart';
|
||||
|
@ -7,7 +7,5 @@ abstract class TransitionValueCondition extends TransitionValueConditionBase {
|
||||
TransitionConditionOp get op => TransitionConditionOp.values[opValue];
|
||||
|
||||
@override
|
||||
void opValueChanged(int from, int to) {
|
||||
// TODO: implement opValueChanged
|
||||
}
|
||||
void opValueChanged(int from, int to) {}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:rive/src/generated/assets/asset_base.dart';
|
||||
|
||||
export 'package:rive/src/generated/assets/asset_base.dart';
|
||||
|
||||
class Asset extends AssetBase {
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/assets/file_asset_base.dart';
|
||||
import 'package:rive/src/rive_core/backboard.dart';
|
||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:rive/src/generated/assets/image_asset_base.dart';
|
||||
import 'package:rive/src/rive_core/shapes/image.dart';
|
||||
|
||||
@ -11,6 +12,11 @@ class ImageAsset extends ImageAssetBase {
|
||||
ui.Image? _image;
|
||||
ui.Image? get image => _image;
|
||||
|
||||
ImageAsset();
|
||||
|
||||
@visibleForTesting
|
||||
ImageAsset.fromTestImage(this._image);
|
||||
|
||||
@override
|
||||
Future<void> decode(Uint8List bytes) {
|
||||
var completer = Completer<void>();
|
||||
|
@ -6,7 +6,7 @@ import 'package:rive/src/rive_core/bones/skinnable.dart';
|
||||
import 'package:rive/src/rive_core/bones/tendon.dart';
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
import 'package:rive/src/rive_core/math/mat2d.dart';
|
||||
import 'package:rive/src/rive_core/shapes/path_vertex.dart';
|
||||
import 'package:rive/src/rive_core/shapes/vertex.dart';
|
||||
|
||||
export 'package:rive/src/generated/bones/skin_base.dart';
|
||||
|
||||
@ -61,7 +61,7 @@ class Skin extends SkinBase {
|
||||
}
|
||||
}
|
||||
|
||||
void deform(List<PathVertex> vertices) {
|
||||
void deform(List<Vertex> vertices) {
|
||||
for (final vertex in vertices) {
|
||||
vertex.deform(_worldTransform, _boneTransforms);
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import 'package:rive/src/rive_core/bones/skin.dart';
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
|
||||
import 'package:rive/src/rive_core/shapes/vertex.dart';
|
||||
|
||||
/// An abstraction to give a common interface to any container component that
|
||||
/// can contain a skin to bind bones to.
|
||||
abstract class Skinnable {
|
||||
abstract class Skinnable<T extends Vertex> {
|
||||
// _skin is null when this object isn't connected to bones.
|
||||
Skin? _skin;
|
||||
Skin? get skin => _skin;
|
||||
@ -28,3 +30,7 @@ abstract class Skinnable {
|
||||
|
||||
void markSkinDirty();
|
||||
}
|
||||
|
||||
abstract class SkinnableProvider<T extends Vertex> {
|
||||
Skinnable<T>? get skinnable;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/component_base.dart';
|
||||
import 'package:rive/src/rive_core/artboard.dart';
|
||||
|
@ -14,6 +14,9 @@ class ComponentDirt {
|
||||
/// Path is dirty and needs to be rebuilt.
|
||||
static const int path = 1 << 4;
|
||||
|
||||
/// Text shape is dirty, the shaper needs to re-run.
|
||||
static const int textShape = 1 << 4;
|
||||
|
||||
/// Vertices have changed, re-order cached lists.
|
||||
static const int vertices = 1 << 5;
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/container_component_base.dart';
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
@ -9,6 +8,9 @@ typedef bool DescentCallback(Component component);
|
||||
|
||||
abstract class ContainerComponent extends ContainerComponentBase {
|
||||
final ContainerChildren children = ContainerChildren();
|
||||
|
||||
/// Adds the child to the children list and re-wires the parent reference of
|
||||
/// the child to the parent. Effectively detach and append.
|
||||
void appendChild(Component child) {
|
||||
child.parent = this;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Just a way to get around the protected notifyListeners so we can use trigger
|
||||
// multiple events from a single object.
|
||||
|
4
lib/src/rive_core/shapes/contour_mesh_vertex.dart
Normal file
4
lib/src/rive_core/shapes/contour_mesh_vertex.dart
Normal file
@ -0,0 +1,4 @@
|
||||
import 'package:rive/src/generated/shapes/contour_mesh_vertex_base.dart';
|
||||
export 'package:rive/src/generated/shapes/contour_mesh_vertex_base.dart';
|
||||
|
||||
class ContourMeshVertex extends ContourMeshVertexBase {}
|
@ -4,13 +4,27 @@ import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/shapes/image_base.dart';
|
||||
import 'package:rive/src/rive_core/assets/file_asset.dart';
|
||||
import 'package:rive/src/rive_core/assets/image_asset.dart';
|
||||
import 'package:rive/src/rive_core/bones/skinnable.dart';
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
import 'package:rive/src/rive_core/math/aabb.dart';
|
||||
import 'package:rive/src/rive_core/math/mat2d.dart';
|
||||
import 'package:rive/src/rive_core/shapes/mesh.dart';
|
||||
import 'package:rive/src/rive_core/shapes/mesh_vertex.dart';
|
||||
|
||||
export 'package:rive/src/generated/shapes/image_base.dart';
|
||||
|
||||
class Image extends ImageBase with FileAssetReferencer<ImageAsset> {
|
||||
class Image extends ImageBase
|
||||
with FileAssetReferencer<ImageAsset>, SkinnableProvider<MeshVertex> {
|
||||
ui.Image? get image => asset?.image;
|
||||
Mesh? _mesh;
|
||||
Mesh? get mesh => _mesh;
|
||||
bool get hasMesh => _mesh != null;
|
||||
|
||||
@override
|
||||
AABB get localBounds {
|
||||
if (hasMesh && _mesh!.draws) {
|
||||
return _mesh!.bounds;
|
||||
}
|
||||
if (asset == null) {
|
||||
return AABB.empty();
|
||||
}
|
||||
@ -36,8 +50,34 @@ class Image extends ImageBase with FileAssetReferencer<ImageAsset> {
|
||||
final height = asset!.height;
|
||||
|
||||
canvas.save();
|
||||
canvas.transform(worldTransform.mat4);
|
||||
canvas.transform(renderTransform.mat4);
|
||||
if (_mesh == null || !_mesh!.draws) {
|
||||
canvas.drawImage(uiImage, ui.Offset(-width / 2, -height / 2), paint);
|
||||
} else {
|
||||
paint.shader = ui.ImageShader(
|
||||
uiImage,
|
||||
ui.TileMode.clamp,
|
||||
ui.TileMode.clamp,
|
||||
Float64List.fromList(<double>[
|
||||
1 / width,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1 / height,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]));
|
||||
_mesh!.draw(canvas, paint);
|
||||
}
|
||||
canvas.restore();
|
||||
|
||||
if (clipped) {
|
||||
@ -62,4 +102,25 @@ class Image extends ImageBase with FileAssetReferencer<ImageAsset> {
|
||||
super.copy(source);
|
||||
asset = source.asset;
|
||||
}
|
||||
|
||||
@override
|
||||
void childAdded(Component child) {
|
||||
super.childAdded(child);
|
||||
if (child is Mesh) {
|
||||
_mesh = child;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void childRemoved(Component child) {
|
||||
super.childRemoved(child);
|
||||
if (child is Mesh && _mesh == child) {
|
||||
_mesh = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Skinnable<MeshVertex>? get skinnable => _mesh;
|
||||
|
||||
Mat2D get renderTransform => _mesh?.worldTransform ?? worldTransform;
|
||||
}
|
||||
|
147
lib/src/rive_core/shapes/mesh.dart
Normal file
147
lib/src/rive_core/shapes/mesh.dart
Normal file
@ -0,0 +1,147 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:rive/src/generated/shapes/mesh_base.dart';
|
||||
import 'package:rive/src/rive_core/bones/skinnable.dart';
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
import 'package:rive/src/rive_core/component_dirt.dart';
|
||||
import 'package:rive/src/rive_core/drawable.dart';
|
||||
import 'package:rive/src/rive_core/math/aabb.dart';
|
||||
import 'package:rive/src/rive_core/math/mat2d.dart';
|
||||
import 'package:rive/src/rive_core/shapes/contour_mesh_vertex.dart';
|
||||
import 'package:rive/src/rive_core/shapes/image.dart';
|
||||
import 'package:rive/src/rive_core/shapes/mesh_vertex.dart';
|
||||
import 'package:rive/src/rive_core/transform_component.dart';
|
||||
import 'package:rive/src/utilities/binary_buffer/binary_reader.dart';
|
||||
|
||||
export 'package:rive/src/generated/shapes/mesh_base.dart';
|
||||
|
||||
class Mesh extends MeshBase with Skinnable<MeshVertex> {
|
||||
// When bound to bones pathTransform should be the identity as it'll already
|
||||
// be in world space.
|
||||
|
||||
final List<MeshVertex> _vertices = [];
|
||||
@override
|
||||
List<MeshVertex> get vertices => _vertices;
|
||||
|
||||
ui.Vertices? _uiVertices;
|
||||
Uint16List _triangleIndices = Uint16List(0);
|
||||
Uint16List get triangleIndices => _triangleIndices;
|
||||
|
||||
int get contourVertexCount {
|
||||
int i = 0;
|
||||
while (i < _vertices.length && _vertices[i] is ContourMeshVertex) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
bool get isValid => _uiVertices != null;
|
||||
|
||||
bool get draws {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
TransformComponent get transformComponent => parent as Image;
|
||||
Drawable get drawable => parent as Drawable;
|
||||
|
||||
@override
|
||||
bool validate() => super.validate() && parent is TransformComponent;
|
||||
|
||||
@override
|
||||
void childAdded(Component child) {
|
||||
super.childAdded(child);
|
||||
if (child is MeshVertex && !_vertices.contains(child)) {
|
||||
_vertices.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
void markDrawableDirty() => addDirt(ComponentDirt.vertices);
|
||||
|
||||
@override
|
||||
void childRemoved(Component child) {
|
||||
super.childRemoved(child);
|
||||
if (child is MeshVertex) {
|
||||
_vertices.remove(child);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void buildDependencies() {
|
||||
super.buildDependencies();
|
||||
parent?.addDependent(this);
|
||||
skin?.addDependent(this);
|
||||
}
|
||||
|
||||
AABB bounds = AABB.empty();
|
||||
|
||||
@override
|
||||
void update(int dirt) {
|
||||
if (dirt & ComponentDirt.vertices != 0) {
|
||||
skin?.deform(_vertices);
|
||||
|
||||
bounds = AABB.empty();
|
||||
var vertices = <ui.Offset>[];
|
||||
var uv = <ui.Offset>[];
|
||||
for (final vertex in _vertices) {
|
||||
var point = vertex.renderTranslation;
|
||||
|
||||
vertices.add(ui.Offset(point[0], point[1]));
|
||||
bounds.expandToPoint(point);
|
||||
uv.add(ui.Offset(vertex.u, vertex.v));
|
||||
}
|
||||
if (_triangleIndices.isEmpty) {
|
||||
_uiVertices = null;
|
||||
} else {
|
||||
_uiVertices = ui.Vertices(
|
||||
ui.VertexMode.triangles,
|
||||
vertices,
|
||||
textureCoordinates: uv,
|
||||
indices: _triangleIndices,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw(ui.Canvas canvas, ui.Paint paint) {
|
||||
assert(_uiVertices != null);
|
||||
canvas.drawVertices(_uiVertices!, ui.BlendMode.srcOver, paint);
|
||||
}
|
||||
|
||||
void _deserializeTriangleIndices() {
|
||||
var reader = BinaryReader.fromList(triangleIndexBytes);
|
||||
List<int> triangles = [];
|
||||
while (!reader.isEOF) {
|
||||
triangles.add(reader.readVarUint());
|
||||
}
|
||||
_triangleIndices = Uint16List.fromList(triangles);
|
||||
|
||||
markDrawableDirty();
|
||||
}
|
||||
|
||||
@override
|
||||
void onAdded() {
|
||||
super.onAdded();
|
||||
|
||||
_deserializeTriangleIndices();
|
||||
}
|
||||
|
||||
@override
|
||||
void markSkinDirty() => addDirt(ComponentDirt.vertices);
|
||||
|
||||
@override
|
||||
bool addDirt(int value, {bool recurse = false}) {
|
||||
if (value == ComponentDirt.vertices) {
|
||||
// throw 'why';
|
||||
}
|
||||
return super.addDirt(value, recurse: recurse);
|
||||
}
|
||||
|
||||
@override
|
||||
Mat2D get worldTransform =>
|
||||
skin != null ? Mat2D.identity : transformComponent.worldTransform;
|
||||
|
||||
@override
|
||||
void triangleIndexBytesChanged(List<int> from, List<int> to) =>
|
||||
_deserializeTriangleIndices();
|
||||
}
|
26
lib/src/rive_core/shapes/mesh_vertex.dart
Normal file
26
lib/src/rive_core/shapes/mesh_vertex.dart
Normal file
@ -0,0 +1,26 @@
|
||||
import 'package:rive/src/generated/shapes/mesh_vertex_base.dart';
|
||||
import 'package:rive/src/rive_core/node.dart';
|
||||
import 'package:rive/src/rive_core/shapes/mesh.dart';
|
||||
import 'package:rive/src/rive_core/transform_component.dart';
|
||||
|
||||
export 'package:rive/src/generated/shapes/mesh_vertex_base.dart';
|
||||
|
||||
class MeshVertex extends MeshVertexBase {
|
||||
Mesh? get mesh => parent as Mesh?;
|
||||
|
||||
@override
|
||||
TransformComponent get transformComponent =>
|
||||
mesh?.transformComponent ?? Node();
|
||||
|
||||
@override
|
||||
bool validate() => super.validate() && parent is Mesh;
|
||||
|
||||
@override
|
||||
void markGeometryDirty() => mesh?.markDrawableDirty();
|
||||
|
||||
@override
|
||||
void uChanged(double from, double to) {}
|
||||
|
||||
@override
|
||||
void vChanged(double from, double to) {}
|
||||
}
|
@ -1,77 +1,12 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:rive/src/generated/shapes/path_vertex_base.dart';
|
||||
import 'package:rive/src/rive_core/bones/weight.dart';
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
import 'package:rive/src/rive_core/math/mat2d.dart';
|
||||
import 'package:rive/src/rive_core/math/vec2d.dart';
|
||||
import 'package:rive/src/rive_core/shapes/path.dart';
|
||||
|
||||
export 'package:rive/src/generated/shapes/path_vertex_base.dart';
|
||||
|
||||
abstract class PathVertex<T extends Weight> extends PathVertexBase {
|
||||
T? _weight;
|
||||
T? get weight => _weight;
|
||||
|
||||
abstract class PathVertex<T extends Weight> extends PathVertexBase<T> {
|
||||
Path? get path => parent as Path?;
|
||||
|
||||
@override
|
||||
void update(int dirt) {}
|
||||
|
||||
final Vec2D _renderTranslation = Vec2D();
|
||||
Vec2D get translation => Vec2D.fromValues(x, y);
|
||||
Vec2D get renderTranslation => _renderTranslation;
|
||||
|
||||
set translation(Vec2D value) {
|
||||
x = value[0];
|
||||
y = value[1];
|
||||
}
|
||||
|
||||
@override
|
||||
void onAddedDirty() {
|
||||
super.onAddedDirty();
|
||||
_renderTranslation[0] = x;
|
||||
_renderTranslation[1] = y;
|
||||
}
|
||||
|
||||
@override
|
||||
void xChanged(double from, double to) {
|
||||
_renderTranslation[0] = to;
|
||||
|
||||
path?.markPathDirty();
|
||||
}
|
||||
|
||||
@override
|
||||
void yChanged(double from, double to) {
|
||||
_renderTranslation[1] = to;
|
||||
|
||||
path?.markPathDirty();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return translation.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
void childAdded(Component component) {
|
||||
super.childAdded(component);
|
||||
if (component is T) {
|
||||
_weight = component;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void childRemoved(Component component) {
|
||||
super.childRemoved(component);
|
||||
if (_weight == component) {
|
||||
_weight = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Deform only gets called when we are weighted.
|
||||
void deform(Mat2D world, Float32List boneTransforms) {
|
||||
Weight.deform(x, y, weight!.indices, weight!.values, world, boneTransforms,
|
||||
_weight!.translation);
|
||||
}
|
||||
void markGeometryDirty() => path?.markPathDirty();
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import 'package:rive/src/rive_core/shapes/path_vertex.dart';
|
||||
|
||||
export 'package:rive/src/generated/shapes/points_path_base.dart';
|
||||
|
||||
class PointsPath extends PointsPathBase with Skinnable {
|
||||
class PointsPath extends PointsPathBase with Skinnable<PathVertex> {
|
||||
final List<PathVertex> _vertices = [];
|
||||
|
||||
PointsPath() {
|
||||
|
81
lib/src/rive_core/shapes/text.dart
Normal file
81
lib/src/rive_core/shapes/text.dart
Normal file
@ -0,0 +1,81 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:rive/src/generated/shapes/text_base.dart';
|
||||
import 'package:rive/src/rive_core/component_dirt.dart';
|
||||
import 'package:rive/src/rive_core/shapes/text_run.dart';
|
||||
import 'package:rive/src/rive_core/src/text/paragraph.dart' as rive;
|
||||
|
||||
export 'package:rive/src/generated/shapes/text_base.dart';
|
||||
|
||||
class Text extends TextBase with rive.Paragraph {
|
||||
List<TextRun> _textRuns = [];
|
||||
List<int> _textCodes = [];
|
||||
Text() {
|
||||
frame = const Rect.fromLTWH(0, 0, 200, 200);
|
||||
}
|
||||
@override
|
||||
List<TextRun> get textRuns => _textRuns;
|
||||
|
||||
@override
|
||||
List<int> get textCodes => _textCodes;
|
||||
|
||||
@override
|
||||
void removeRun(TextRun run, int index) {
|
||||
run.remove();
|
||||
sortChildren();
|
||||
}
|
||||
|
||||
@override
|
||||
bool validate() => super.validate() && areTextRunsValid;
|
||||
@override
|
||||
void onAddedDirty() {
|
||||
super.onAddedDirty();
|
||||
_textCodes = value.codeUnits.toList(growable: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void textCodesChanged() {
|
||||
value = String.fromCharCodes(textCodes);
|
||||
markTextShapeDirty();
|
||||
}
|
||||
|
||||
@override
|
||||
void modifyRuns(int start, int end, rive.TextRunMutator mutator) {
|
||||
super.modifyRuns(start, end, mutator);
|
||||
markTextShapeDirty();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void sortChildren() {
|
||||
// Cache the runs.
|
||||
_textRuns = children.whereType<TextRun>().toList();
|
||||
markTextShapeDirty();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void valueChanged(String from, String to) {
|
||||
_textCodes = value.codeUnits.toList(growable: true);
|
||||
super.textCodesChanged();
|
||||
markTextShapeDirty();
|
||||
}
|
||||
|
||||
void markTextShapeDirty() {
|
||||
addDirt(ComponentDirt.textShape);
|
||||
}
|
||||
|
||||
@override
|
||||
void update(int dirt) {
|
||||
super.update(dirt);
|
||||
if ((dirt & ComponentDirt.textShape) != 0) {
|
||||
for (final textRun in _textRuns) {
|
||||
textRun.clearGlyphRuns();
|
||||
}
|
||||
visitGlyphs((glyphRun, start, end, originX, originY) {
|
||||
var textRun = glyphRun.textRun as TextRun;
|
||||
textRun.addGlyphRun(glyphRun, start, end, originX, originY);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
85
lib/src/rive_core/shapes/text_run.dart
Normal file
85
lib/src/rive_core/shapes/text_run.dart
Normal file
@ -0,0 +1,85 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:rive/src/generated/shapes/text_run_base.dart';
|
||||
import 'package:rive/src/rive_core/shapes/text.dart';
|
||||
import 'package:rive/src/rive_core/src/text/raw_path.dart' as rive;
|
||||
import 'package:rive/src/rive_core/src/text/rive_font.dart';
|
||||
import 'package:rive/src/rive_core/src/text/shape_text.dart' as rive;
|
||||
|
||||
export 'package:rive/src/generated/shapes/text_run_base.dart';
|
||||
|
||||
class _DrawableGlyphRun {
|
||||
final rive.GlyphRun run;
|
||||
final int start;
|
||||
final int end;
|
||||
final double ox;
|
||||
final double oy;
|
||||
|
||||
_DrawableGlyphRun(this.run, this.start, this.end, this.ox, this.oy);
|
||||
}
|
||||
|
||||
class TextRun extends TextRunBase implements rive.TextRun {
|
||||
@override
|
||||
RiveFont font = RiveFont.builtIn;
|
||||
final List<_DrawableGlyphRun> _glyphRuns = [];
|
||||
|
||||
void clearGlyphRuns() => _glyphRuns.clear();
|
||||
|
||||
void addGlyphRun(
|
||||
rive.GlyphRun glyphRun, int start, int end, double x, double y) {
|
||||
_glyphRuns.add(_DrawableGlyphRun(glyphRun, start, end, x, y));
|
||||
}
|
||||
|
||||
Object? attr;
|
||||
@override
|
||||
bool validate() {
|
||||
return super.validate() && parent is Text;
|
||||
}
|
||||
|
||||
Text get text => parent as Text;
|
||||
|
||||
@override
|
||||
void draw(Canvas canvas) {
|
||||
canvas.save();
|
||||
canvas.transform(text.worldTransform.mat4);
|
||||
for (final drawableRun in _glyphRuns) {
|
||||
_visitRawPaths(drawableRun.run, drawableRun.start, drawableRun.end,
|
||||
drawableRun.ox, drawableRun.oy, (rive.RawPath rp) {
|
||||
canvas.drawPath(_toPath(rp), Paint()..color = const Color(0xFFFFFFFF));
|
||||
});
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@override
|
||||
void pointSizeChanged(double from, double to) {
|
||||
text.markTextShapeDirty();
|
||||
}
|
||||
|
||||
@override
|
||||
void textLengthChanged(int from, int to) {}
|
||||
|
||||
@override
|
||||
rive.TextRun cloneRun() {
|
||||
var cloned = super.clone();
|
||||
assert(cloned is TextRun);
|
||||
return cloned as TextRun;
|
||||
}
|
||||
}
|
||||
|
||||
// Should generalize all of this...
|
||||
Path _toPath(rive.RawPath rp) {
|
||||
Path p = Path();
|
||||
rp.sinker(p.moveTo, p.lineTo, p.quadraticBezierTo, p.cubicTo, p.close);
|
||||
return p;
|
||||
}
|
||||
|
||||
void _visitRawPaths(rive.GlyphRun run, int start, int end, double x, double y,
|
||||
Function(rive.RawPath) visitor) {
|
||||
for (int i = start; i < end; ++i) {
|
||||
final rive.RawPath? p = run.font.getRawPath(run.glyphs[i]);
|
||||
if (p != null) {
|
||||
visitor(p.scalexy(run.pointSize, run.pointSize, x + run.xpos[i], y));
|
||||
}
|
||||
}
|
||||
}
|
63
lib/src/rive_core/shapes/vertex.dart
Normal file
63
lib/src/rive_core/shapes/vertex.dart
Normal file
@ -0,0 +1,63 @@
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/shapes/vertex_base.dart';
|
||||
import 'package:rive/src/rive_core/bones/weight.dart';
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
import 'package:rive/src/rive_core/math/mat2d.dart';
|
||||
import 'package:rive/src/rive_core/math/vec2d.dart';
|
||||
|
||||
export 'package:rive/src/generated/shapes/vertex_base.dart';
|
||||
|
||||
abstract class Vertex<T extends Weight> extends VertexBase {
|
||||
T? _weight;
|
||||
T? get weight => _weight;
|
||||
|
||||
Vec2D get translation => Vec2D.fromValues(x, y);
|
||||
Vec2D get renderTranslation => weight?.translation ?? translation;
|
||||
|
||||
set translation(Vec2D value) {
|
||||
x = value[0];
|
||||
y = value[1];
|
||||
}
|
||||
|
||||
@override
|
||||
void xChanged(double from, double to) {
|
||||
markGeometryDirty();
|
||||
}
|
||||
|
||||
void markGeometryDirty();
|
||||
|
||||
@override
|
||||
void yChanged(double from, double to) {
|
||||
markGeometryDirty();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return translation.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
void childAdded(Component component) {
|
||||
super.childAdded(component);
|
||||
if (component is T) {
|
||||
_weight = component;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void childRemoved(Component component) {
|
||||
super.childRemoved(component);
|
||||
if (_weight == component) {
|
||||
_weight = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Deform only gets called when we are weighted.
|
||||
void deform(Mat2D world, Float32List boneTransforms) {
|
||||
Weight.deform(x, y, weight!.indices, weight!.values, world, boneTransforms,
|
||||
_weight!.translation);
|
||||
}
|
||||
|
||||
@override
|
||||
void update(int dirt) {}
|
||||
}
|
2
lib/src/rive_core/src/text/built_in_font.dart
Normal file
2
lib/src/rive_core/src/text/built_in_font.dart
Normal file
File diff suppressed because one or more lines are too long
684
lib/src/rive_core/src/text/paragraph.dart
Normal file
684
lib/src/rive_core/src/text/paragraph.dart
Normal file
@ -0,0 +1,684 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'raw_path.dart';
|
||||
import 'shape_text.dart';
|
||||
|
||||
bool contains(Rect r, double x, double y) {
|
||||
return r.left <= x && x <= r.right && r.top <= y && y <= r.bottom;
|
||||
}
|
||||
|
||||
Rect outset(Rect r, double dx, double dy) {
|
||||
return Rect.fromLTRB(r.left - dx, r.top - dy, r.right + dx, r.bottom + dy);
|
||||
}
|
||||
|
||||
double pin(double min, double max, double value) {
|
||||
return math.min(math.max(value, min), max);
|
||||
}
|
||||
|
||||
// DO NOT MODIFY the run
|
||||
typedef TextRunVisitor = void Function(TextRun);
|
||||
|
||||
// Returns either the original, or a new (cloned+modified) run.
|
||||
// DO NOT MODIFY run.textLength
|
||||
typedef TextRunMutator = TextRun Function(TextRun);
|
||||
|
||||
// Given a shaped run and lineIndex (e.g. for drawing)
|
||||
// DO NOT MODIFY the GlyphRun
|
||||
typedef GlyphRunVisitor = void Function(
|
||||
GlyphRun, int start, int end, double originX, double originY);
|
||||
|
||||
enum LinePref {
|
||||
start,
|
||||
end,
|
||||
}
|
||||
|
||||
class VBar {
|
||||
double x, top, bottom;
|
||||
|
||||
VBar(this.x, this.top, this.bottom);
|
||||
}
|
||||
|
||||
abstract class Paragraph {
|
||||
Rect _frame = const Rect.fromLTRB(0, 0, 0, 0);
|
||||
List<int> get textCodes;
|
||||
List<TextRun> get textRuns;
|
||||
// cached output
|
||||
List<GlyphRun> _runs = [];
|
||||
List<GlyphLine> _lines = [];
|
||||
bool _dirty = true;
|
||||
|
||||
bool get areTextRunsValid {
|
||||
int len = 0;
|
||||
textRuns.forEach((run) => len += run.textLength);
|
||||
return len == textCodes.length;
|
||||
}
|
||||
|
||||
void _validateTextRuns() {
|
||||
assert(areTextRunsValid);
|
||||
}
|
||||
|
||||
String get text => String.fromCharCodes(textCodes);
|
||||
|
||||
void _validateShaping() {
|
||||
if (textCodes.isEmpty) {
|
||||
assert(_lines.length == 1);
|
||||
assert(_runs.length == 1);
|
||||
// ignore: prefer_is_empty
|
||||
assert(_runs[0].glyphs.length == 0);
|
||||
assert(_runs[0].xpos.length == 1);
|
||||
return;
|
||||
}
|
||||
|
||||
GlyphLine line = _lines[0];
|
||||
line.validate();
|
||||
for (int i = 1; i < _lines.length; ++i) {
|
||||
final GlyphLine nextLine = _lines[i];
|
||||
nextLine.validate();
|
||||
assert(line.textEnd == nextLine.textStart);
|
||||
line = nextLine;
|
||||
}
|
||||
assert(line.textEnd == textCodes.length);
|
||||
}
|
||||
|
||||
void _makeAligned() {
|
||||
double Y = _frame.top;
|
||||
for (final line in _lines) {
|
||||
double asc = 0;
|
||||
double des = 0;
|
||||
for (int i = line.startRun; i <= line.wsRun; ++i) {
|
||||
final run = _runs[i];
|
||||
asc = math.min(asc, run.font.ascent * run.pointSize);
|
||||
des = math.max(des, run.font.descent * run.pointSize);
|
||||
}
|
||||
line.top = Y;
|
||||
Y -= asc;
|
||||
line.baseline = Y;
|
||||
Y += des;
|
||||
line.bottom = Y;
|
||||
}
|
||||
// TODO: good place to perform left/center/right alignment
|
||||
}
|
||||
|
||||
void _ensureShapedLines() {
|
||||
if (_dirty) {
|
||||
_validateTextRuns();
|
||||
if (textCodes.isEmpty) {
|
||||
return;
|
||||
// _runs = [
|
||||
// GlyphRun(_defaultRun.font, _defaultRun.size, null, 0,
|
||||
// Uint16List.fromList([]), Float32List.fromList([0]))
|
||||
// ];
|
||||
// _lines = [GlyphLine(_runs, 0, 0, 0, 0, 0, 0, 0)];
|
||||
} else {
|
||||
final breaks = GlyphRun.findWordBreaks(textCodes);
|
||||
_runs = GlyphRun.shapeText(textCodes, textRuns);
|
||||
_lines = GlyphLine.lineBreak(textCodes, breaks, _runs, _frame.width);
|
||||
_makeAligned();
|
||||
}
|
||||
_validateShaping();
|
||||
_dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void trunsChanged() {
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void textCodesChanged() {
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
void _layoutChanged() {
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
bool get isEmpty {
|
||||
return textLength == 0;
|
||||
}
|
||||
|
||||
bool get isNotEmpty {
|
||||
return textLength != 0;
|
||||
}
|
||||
|
||||
int get textLength {
|
||||
return textCodes.length;
|
||||
}
|
||||
|
||||
Rect get bounds {
|
||||
_ensureShapedLines();
|
||||
if (_lines.isEmpty) {
|
||||
return Rect.zero;
|
||||
} else {
|
||||
return Rect.fromLTRB(
|
||||
_frame.left, _frame.top, _frame.right, _lines.last.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
Rect get frame {
|
||||
return _frame;
|
||||
}
|
||||
|
||||
set frame(Rect rect) {
|
||||
if (rect == _frame) {
|
||||
return;
|
||||
}
|
||||
if (_frame.width != rect.width) {
|
||||
_layoutChanged();
|
||||
}
|
||||
_frame = rect;
|
||||
}
|
||||
|
||||
List<GlyphLine> get lines {
|
||||
_ensureShapedLines();
|
||||
return _lines;
|
||||
}
|
||||
|
||||
double offsetToFrameX(GlyphLine line, int textOffset) {
|
||||
return _frame.left + line.offsetToX(textOffset);
|
||||
}
|
||||
|
||||
int offsetToLineIndex(int textOffset, LinePref pref) {
|
||||
assert(textOffset >= 0 && textOffset <= textLength);
|
||||
_ensureShapedLines();
|
||||
for (int i = 0; i < _lines.length; ++i) {
|
||||
final line = _lines[i];
|
||||
assert(line.textStart <= textOffset);
|
||||
if (textOffset <= line.textEnd) {
|
||||
if (pref == LinePref.start && textOffset == line.textEnd) {
|
||||
continue;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return _lines.length - 1;
|
||||
}
|
||||
|
||||
GlyphLine offsetToLine(int textOffset, LinePref pref) {
|
||||
int index = offsetToLineIndex(textOffset, pref);
|
||||
return _lines[index];
|
||||
}
|
||||
|
||||
// [ index, offset ]
|
||||
List<int> _findRunIndexOffset(int offset) {
|
||||
assert(offset >= 0 && offset <= textCodes.length);
|
||||
|
||||
int prevLenths = 0;
|
||||
for (int i = 0; i < textRuns.length; ++i) {
|
||||
final int len = textRuns[i].textLength;
|
||||
if (offset < len) {
|
||||
return [i, prevLenths];
|
||||
}
|
||||
prevLenths += len;
|
||||
// ignore: parameter_assignments
|
||||
offset -= len;
|
||||
}
|
||||
return [textRuns.length - 1, textCodes.length]; // last run
|
||||
}
|
||||
|
||||
int insert(int offset, String str) {
|
||||
if (str.isEmpty) {
|
||||
return 0;
|
||||
}
|
||||
assert(textRuns.isNotEmpty);
|
||||
final pair = _findRunIndexOffset(offset);
|
||||
final int index = pair[0];
|
||||
textRuns[index].textLength += str.length;
|
||||
trunsChanged();
|
||||
final units = str.codeUnits;
|
||||
textCodes.insertAll(offset, units);
|
||||
textCodesChanged();
|
||||
return units.length;
|
||||
}
|
||||
|
||||
void delete(int start, int end) {
|
||||
assert(start <= end);
|
||||
assert(start >= 0);
|
||||
assert(end <= textLength);
|
||||
if (start == end) {
|
||||
return;
|
||||
}
|
||||
|
||||
textCodes.removeRange(start, end);
|
||||
textCodesChanged();
|
||||
|
||||
int i;
|
||||
for (i = 0;; ++i) {
|
||||
final run = textRuns[i];
|
||||
if (start < run.textLength) {
|
||||
break;
|
||||
}
|
||||
// ignore: parameter_assignments
|
||||
start -= run.textLength;
|
||||
// ignore: parameter_assignments
|
||||
end -= run.textLength;
|
||||
}
|
||||
TextRun run = textRuns[i];
|
||||
assert(start >= 0 && start < run.textLength);
|
||||
if (start > 0) {
|
||||
// trim leading run
|
||||
final int amount = math.min(end, run.textLength) - start;
|
||||
assert(amount > 0 && amount < run.textLength);
|
||||
run.textLength -= amount;
|
||||
// ignore: parameter_assignments
|
||||
start += amount;
|
||||
assert(start <= end);
|
||||
// ignore: parameter_assignments
|
||||
end = end - start; // now end is just a length
|
||||
i += 1;
|
||||
}
|
||||
// remove whole runs
|
||||
while (end > 0 && end >= textRuns[i].textLength) {
|
||||
var run = textRuns[i];
|
||||
// ignore: parameter_assignments
|
||||
end -= run.textLength;
|
||||
_removeRunAt(i);
|
||||
}
|
||||
if (end > 0) {
|
||||
textRuns[i].textLength -= end;
|
||||
}
|
||||
trunsChanged();
|
||||
}
|
||||
|
||||
void setRun(int start, int end, TextRun newRun) {
|
||||
modifyRuns(start, end, (TextRun orig) {
|
||||
return newRun;
|
||||
});
|
||||
}
|
||||
|
||||
void removeRun(covariant TextRun run, int index) {
|
||||
textRuns.removeAt(index);
|
||||
}
|
||||
|
||||
void insertRun(covariant TextRun run, covariant TextRun? before,
|
||||
covariant TextRun? after, int index) {
|
||||
textRuns.insert(index, run);
|
||||
}
|
||||
|
||||
void _removeRunAt(int index) {
|
||||
var run = textRuns[index];
|
||||
removeRun(run, index);
|
||||
}
|
||||
|
||||
void _insertRun(int index, TextRun run) {
|
||||
TextRun? before, after;
|
||||
if (index > 0 && index <= textRuns.length) {
|
||||
before = textRuns[index - 1];
|
||||
}
|
||||
if (index < textRuns.length) {
|
||||
after = textRuns[index];
|
||||
}
|
||||
|
||||
insertRun(run, before, after, index);
|
||||
}
|
||||
|
||||
/// Resets everything to just the [text] with the [run].
|
||||
void setAll(String text, TextRun run) {
|
||||
run.textLength = text.length;
|
||||
while (textRuns.isNotEmpty) {
|
||||
_removeRunAt(0);
|
||||
}
|
||||
_insertRun(0, run);
|
||||
textCodes.clear();
|
||||
textCodes.insertAll(0, text.codeUnits);
|
||||
_validateTextRuns();
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
// Lambda returns a new TextRun, or null to indicated that the run
|
||||
// assed in need not be changed.
|
||||
void modifyRuns(int start, int end, TextRunMutator mutator) {
|
||||
assert(start >= 0 && start <= end);
|
||||
assert(end <= textCodes.length);
|
||||
int modLength = end - start;
|
||||
if (modLength == 0) {
|
||||
return; // snothing to modify
|
||||
}
|
||||
|
||||
final first = _findRunIndexOffset(start);
|
||||
int trunIndex = first[0];
|
||||
int globalOffset = first[1];
|
||||
assert(globalOffset < textCodes.length);
|
||||
|
||||
// Handle splitting the first run
|
||||
if (start > globalOffset) {
|
||||
TextRun origRun = textRuns[trunIndex];
|
||||
TextRun newRun = mutator(origRun);
|
||||
if (origRun != newRun) {
|
||||
final int skipLength = start - globalOffset;
|
||||
newRun.textLength = origRun.textLength - skipLength;
|
||||
origRun.textLength = skipLength;
|
||||
_insertRun(++trunIndex, newRun);
|
||||
trunsChanged();
|
||||
if (newRun.textLength > modLength) {
|
||||
// oops, need to trim and readd oldRun afterwards
|
||||
origRun = origRun.cloneRun();
|
||||
origRun.textLength = newRun.textLength - modLength;
|
||||
newRun.textLength = modLength;
|
||||
_insertRun(++trunIndex, origRun);
|
||||
return;
|
||||
}
|
||||
modLength -= newRun.textLength;
|
||||
}
|
||||
trunIndex += 1;
|
||||
}
|
||||
|
||||
// Replace whole runs?
|
||||
while (modLength > 0 && modLength >= textRuns[trunIndex].textLength) {
|
||||
TextRun origRun = textRuns[trunIndex];
|
||||
modLength -= origRun.textLength;
|
||||
TextRun newRun = mutator(origRun);
|
||||
if (origRun != newRun) {
|
||||
removeRun(origRun, trunIndex);
|
||||
_insertRun(trunIndex, newRun);
|
||||
trunsChanged();
|
||||
}
|
||||
trunIndex += 1;
|
||||
}
|
||||
|
||||
// Trim the last run?
|
||||
if (modLength > 0) {
|
||||
TextRun origRun = textRuns[trunIndex];
|
||||
assert(modLength < origRun.textLength);
|
||||
TextRun newRun = mutator(origRun);
|
||||
if (origRun != newRun) {
|
||||
newRun.textLength = modLength;
|
||||
origRun.textLength -= modLength;
|
||||
_insertRun(trunIndex, newRun);
|
||||
trunsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// HOW CAN WE COMPARE RUNS?
|
||||
if (false) {
|
||||
// consolidate equal runs...
|
||||
for (int i = 1; i < textRuns.length; ++i) {
|
||||
if (textRuns[i - 1] == textRuns[i]) {
|
||||
textRuns[i - 1].textLength += textRuns[i].textLength;
|
||||
var run = textRuns[i - 1];
|
||||
_removeRunAt(i);
|
||||
i -= 1; // to undo the loop's ++i
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visitGlyphs(GlyphRunVisitor visitor) {
|
||||
_ensureShapedLines();
|
||||
|
||||
for (final GlyphLine line in _lines) {
|
||||
int start = line.startIndex;
|
||||
for (int i = line.startRun; i <= line.wsRun; ++i) {
|
||||
final run = _runs[i];
|
||||
int wsEnd = run.glyphs.length;
|
||||
if (i == line.wsRun) {
|
||||
// last run on this line
|
||||
wsEnd = line.wsIndex;
|
||||
}
|
||||
visitor(run, start, wsEnd, _frame.left - line.startX, line.baseline);
|
||||
start = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<GlyphRun> rawGlyphRuns() {
|
||||
_ensureShapedLines();
|
||||
return _runs;
|
||||
}
|
||||
|
||||
void visitText(TextRunVisitor visitor) {
|
||||
textRuns.forEach(visitor);
|
||||
}
|
||||
|
||||
Rect offsetsToRect(GlyphLine line, int start, int end) {
|
||||
assert(start >= line.textStart);
|
||||
assert(end <= line.textEnd);
|
||||
final double left = offsetToFrameX(line, start);
|
||||
final double right = offsetToFrameX(line, end);
|
||||
return Rect.fromLTRB(left, line.top, right, line.bottom);
|
||||
}
|
||||
|
||||
int xyToOffset(double x, double y) {
|
||||
// ignore: parameter_assignments
|
||||
x = pin(_frame.left, _frame.right, x);
|
||||
// ignore: parameter_assignments
|
||||
y = pin(_lines.first.top, _lines.last.bottom, y);
|
||||
|
||||
// move x into xpos coordinates
|
||||
// ignore: parameter_assignments
|
||||
x -= _frame.left;
|
||||
|
||||
for (final GlyphLine line in _lines) {
|
||||
if (y <= line.bottom) {
|
||||
// ignore: parameter_assignments
|
||||
x += line.startX; // now we're relative to the xpos in this line
|
||||
GlyphRun run = line.runs.last;
|
||||
for (final GlyphRun r in line.runs) {
|
||||
if (x < r.right) {
|
||||
run = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int index = run.xpos.length - 1;
|
||||
for (int i = 1; i < run.xpos.length; ++i) {
|
||||
if (x < run.xpos[i]) {
|
||||
final mid = (run.xpos[i] + run.xpos[i - 1]) * 0.5;
|
||||
index = x < mid ? i - 1 : i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return run.textStart + index;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
VBar getVBar(int textOffset, LinePref pref) {
|
||||
final line = offsetToLine(textOffset, pref);
|
||||
final x = offsetToFrameX(line, textOffset);
|
||||
return VBar(x, line.top, line.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
|
||||
enum CursorType {
|
||||
line,
|
||||
path,
|
||||
}
|
||||
|
||||
class Cursor {
|
||||
Paragraph para;
|
||||
// these are logical, not necessarily sorted
|
||||
int _start = 0;
|
||||
int _end = 0;
|
||||
|
||||
Cursor(this.para);
|
||||
|
||||
CursorType get type {
|
||||
return _start == _end ? CursorType.line : CursorType.path;
|
||||
}
|
||||
|
||||
String get text =>
|
||||
String.fromCharCodes(para.textCodes.sublist(fromIndex, toIndex));
|
||||
|
||||
int get start {
|
||||
return _start;
|
||||
}
|
||||
|
||||
int get end {
|
||||
return _end;
|
||||
}
|
||||
|
||||
int get fromIndex {
|
||||
return math.min(_start, _end);
|
||||
}
|
||||
|
||||
int get toIndex {
|
||||
return math.max(_start, _end);
|
||||
}
|
||||
|
||||
bool get isCollapsed => _start == _end;
|
||||
|
||||
int _legalize(int index) {
|
||||
return math.max(math.min(index, para.textLength), 0);
|
||||
}
|
||||
|
||||
void setIndex(int index) {
|
||||
setRange(index, index);
|
||||
}
|
||||
|
||||
void setRange(int start, int end) {
|
||||
_start = _legalize(start);
|
||||
_end = _legalize(end);
|
||||
}
|
||||
|
||||
void insert(String str) {
|
||||
final int from = fromIndex;
|
||||
para.delete(from, toIndex);
|
||||
_start = _end = from + para.insert(from, str);
|
||||
}
|
||||
|
||||
void delete() {
|
||||
if (_start == _end) {
|
||||
if (_start > 0) {
|
||||
para.delete(_start - 1, _start);
|
||||
_start = _end = _start - 1;
|
||||
}
|
||||
} else {
|
||||
para.delete(fromIndex, toIndex);
|
||||
_start = _end = fromIndex;
|
||||
}
|
||||
}
|
||||
|
||||
void arrowLeft(bool extend) {
|
||||
if (extend) {
|
||||
_end = _legalize(_end - 1);
|
||||
} else {
|
||||
if (_start == _end) {
|
||||
_start = _end = _legalize(_end - 1);
|
||||
}
|
||||
_start = _end = fromIndex;
|
||||
}
|
||||
}
|
||||
// todo: need to not pre-swap _start and _end
|
||||
|
||||
void arrowRight(bool extend) {
|
||||
if (extend) {
|
||||
_end = _legalize(_end + 1);
|
||||
} else {
|
||||
if (_start == _end) {
|
||||
_start = _end = _legalize(_end + 1);
|
||||
} else {
|
||||
_start = _end = toIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void arrowUp(bool extend) {
|
||||
LinePref pref = LinePref.end; // store this somehow?
|
||||
|
||||
int index = para.offsetToLineIndex(_end, pref);
|
||||
if (index == 0) {
|
||||
_end = 0;
|
||||
} else {
|
||||
final double currX = para.offsetToFrameX(para._lines[index], _end);
|
||||
{
|
||||
int i = para.xyToOffset(currX, para._lines[index].baseline);
|
||||
assert(i == _end);
|
||||
}
|
||||
index -= 1;
|
||||
_end = para.xyToOffset(currX, para._lines[index].baseline);
|
||||
}
|
||||
if (!extend) {
|
||||
_start = _end;
|
||||
}
|
||||
}
|
||||
|
||||
void arrowDown(bool extend) {
|
||||
LinePref pref = LinePref.end; // store this somehow?
|
||||
|
||||
int index = para.offsetToLineIndex(_end, pref);
|
||||
if (index == para._lines.length - 1) {
|
||||
_end = para.textLength;
|
||||
} else {
|
||||
final double currX = para.offsetToFrameX(para._lines[index], _end);
|
||||
index += 1;
|
||||
_end = para.xyToOffset(currX, para._lines[index].baseline);
|
||||
}
|
||||
if (!extend) {
|
||||
_start = _end;
|
||||
}
|
||||
}
|
||||
|
||||
void pointerDown(double x, double y) {
|
||||
// ignore: parameter_assignments
|
||||
x = pin(para.frame.left, para.frame.right, x);
|
||||
// ignore: parameter_assignments
|
||||
y = pin(para.frame.top, para.frame.bottom, y);
|
||||
_start = _end = para.xyToOffset(x, y);
|
||||
}
|
||||
|
||||
bool pointerMove(double x, double y) {
|
||||
final newEnd = para.xyToOffset(x, y);
|
||||
final changed = _end != newEnd;
|
||||
_end = newEnd;
|
||||
return changed;
|
||||
}
|
||||
|
||||
void pointerUp(double x, double y) {}
|
||||
|
||||
// PointerTracker pointerTracker(double x, double y) {
|
||||
// final t = _CursorPointerTracker(this);
|
||||
// t.down(x, y);
|
||||
// return t;
|
||||
// }
|
||||
|
||||
void modifyRuns(TextRunMutator mutator) {
|
||||
para.modifyRuns(fromIndex, toIndex, mutator);
|
||||
}
|
||||
|
||||
VBar getVBar(LinePref pref) {
|
||||
return para.getVBar(_end, pref);
|
||||
}
|
||||
|
||||
RawPath getRawPath([LinePref pref = LinePref.end]) {
|
||||
final b = RawPathBuilder();
|
||||
if (type == CursorType.line) {
|
||||
final bar = getVBar(pref);
|
||||
const width = 1.0;
|
||||
b.addLTRB(bar.x, bar.top, bar.x + width, bar.bottom);
|
||||
} else {
|
||||
final int from = fromIndex;
|
||||
final int to = toIndex;
|
||||
int index = para.offsetToLineIndex(from, LinePref.start);
|
||||
int last = para.offsetToLineIndex(to, LinePref.end);
|
||||
if (index == last) {
|
||||
GlyphLine line = para.lines[index];
|
||||
b.addRect(para.offsetsToRect(line, from, to));
|
||||
} else {
|
||||
GlyphLine line = para.lines[index];
|
||||
b.addRect(para.offsetsToRect(line, from, line.textEnd));
|
||||
while (++index < last) {
|
||||
line = para.lines[index];
|
||||
b.addRect(para.offsetsToRect(line, line.textStart, line.textEnd));
|
||||
}
|
||||
line = para.lines[last];
|
||||
b.addRect(para.offsetsToRect(line, line.textStart, to));
|
||||
}
|
||||
}
|
||||
return b.detach();
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleParagraph extends Paragraph {
|
||||
final List<int> _textCodes = [];
|
||||
final List<TextRun> _truns = [];
|
||||
|
||||
@override
|
||||
List<int> get textCodes => _textCodes;
|
||||
|
||||
@override
|
||||
List<TextRun> get textRuns => _truns;
|
||||
}
|
149
lib/src/rive_core/src/text/raw_path.dart
Normal file
149
lib/src/rive_core/src/text/raw_path.dart
Normal file
@ -0,0 +1,149 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const int kMove_PathVerb = 0;
|
||||
const int kLine_PathVerb = 1;
|
||||
const int kQuad_PathVerb = 2;
|
||||
const int kConic_PathVerb = 3;
|
||||
const int kCubic_PathVerb = 4;
|
||||
const int kClose_PathVerb = 5;
|
||||
|
||||
class IPathSink {
|
||||
void moveTo(double x, double y) {}
|
||||
void lineTo(double x, double y) {}
|
||||
void quadTo(double x, double y, double x1, double y1) {}
|
||||
void cubicTo(
|
||||
double x, double y, double x1, double y1, double x2, double y2) {}
|
||||
void close() {}
|
||||
}
|
||||
|
||||
class RawPath {
|
||||
Float32List pts;
|
||||
Uint8List verbs;
|
||||
|
||||
RawPath(this.pts, this.verbs);
|
||||
|
||||
void sinker(
|
||||
Function(double, double) moveTo,
|
||||
Function(double, double) lineTo,
|
||||
Function(double, double, double, double) quadTo,
|
||||
Function(double, double, double, double, double, double) cubicTo,
|
||||
Function close) {
|
||||
int i = 0;
|
||||
for (final int verb in verbs) {
|
||||
switch (verb) {
|
||||
case kMove_PathVerb:
|
||||
moveTo(pts[i + 0], pts[i + 1]);
|
||||
i += 2;
|
||||
break;
|
||||
case kLine_PathVerb:
|
||||
lineTo(pts[i + 0], pts[i + 1]);
|
||||
i += 2;
|
||||
break;
|
||||
case kQuad_PathVerb:
|
||||
quadTo(pts[i + 0], pts[i + 1], pts[i + 2], pts[i + 3]);
|
||||
i += 4;
|
||||
break;
|
||||
case kConic_PathVerb: // not supported
|
||||
assert(false);
|
||||
break;
|
||||
case kCubic_PathVerb:
|
||||
cubicTo(pts[i + 0], pts[i + 1], pts[i + 2], pts[i + 3], pts[i + 4],
|
||||
pts[i + 5]);
|
||||
i += 6;
|
||||
break;
|
||||
case kClose_PathVerb:
|
||||
close();
|
||||
break;
|
||||
default:
|
||||
throw 'unknown verb $verb';
|
||||
}
|
||||
}
|
||||
assert(i == pts.length);
|
||||
}
|
||||
|
||||
RawPath scalexy(double sx, double sy, double tx, double ty) {
|
||||
final newp = Float32List(pts.length);
|
||||
for (int i = 0; i < pts.length; i += 2) {
|
||||
newp[i + 0] = pts[i + 0] * sx + tx;
|
||||
newp[i + 1] = pts[i + 1] * sy + ty;
|
||||
}
|
||||
// we share verbs to save memory -- so don't modify them!
|
||||
return RawPath(Float32List.fromList(newp), verbs);
|
||||
}
|
||||
}
|
||||
|
||||
class RawPathBuilder {
|
||||
List<double> pts = [];
|
||||
List<int> vbs = [];
|
||||
|
||||
void reset() {
|
||||
pts = [];
|
||||
vbs = [];
|
||||
}
|
||||
|
||||
double get lastX {
|
||||
return pts[pts.length - 2];
|
||||
}
|
||||
|
||||
double get lastY {
|
||||
return pts[pts.length - 1];
|
||||
}
|
||||
|
||||
void moveTo(double x, double y) {
|
||||
pts.add(x);
|
||||
pts.add(y);
|
||||
vbs.add(kMove_PathVerb);
|
||||
}
|
||||
|
||||
void lineTo(double x, double y) {
|
||||
pts.add(x);
|
||||
pts.add(y);
|
||||
vbs.add(kLine_PathVerb);
|
||||
}
|
||||
|
||||
void quadTo(double x, double y, double x1, double y1) {
|
||||
pts.add(x);
|
||||
pts.add(y);
|
||||
pts.add(x1);
|
||||
pts.add(y1);
|
||||
vbs.add(kQuad_PathVerb);
|
||||
}
|
||||
|
||||
void cubicTo(double x, double y, double x1, double y1, double x2, double y2) {
|
||||
pts.add(x);
|
||||
pts.add(y);
|
||||
pts.add(x1);
|
||||
pts.add(y1);
|
||||
pts.add(x2);
|
||||
pts.add(y2);
|
||||
vbs.add(kCubic_PathVerb);
|
||||
}
|
||||
|
||||
void close() {
|
||||
vbs.add(kClose_PathVerb);
|
||||
}
|
||||
|
||||
void addLine(double x0, double y0, double x1, double y1) {
|
||||
moveTo(x0, y0);
|
||||
lineTo(x1, y1);
|
||||
}
|
||||
|
||||
void addLTRB(double l, double t, double r, double b) {
|
||||
moveTo(l, t);
|
||||
lineTo(r, t);
|
||||
lineTo(r, b);
|
||||
lineTo(l, b);
|
||||
close();
|
||||
}
|
||||
|
||||
void addRect(Rect r) {
|
||||
addLTRB(r.left, r.top, r.right, r.bottom);
|
||||
}
|
||||
|
||||
RawPath detach() {
|
||||
final rp = RawPath(Float32List.fromList(pts), Uint8List.fromList(vbs));
|
||||
reset();
|
||||
return rp;
|
||||
}
|
||||
}
|
255
lib/src/rive_core/src/text/rive_font.dart
Normal file
255
lib/src/rive_core/src/text/rive_font.dart
Normal file
@ -0,0 +1,255 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rive/src/rive_core/src/text/built_in_font.dart';
|
||||
|
||||
import 'raw_path.dart';
|
||||
|
||||
// for diagnostic/debugging purposes
|
||||
const bool enableFontDump = false;
|
||||
|
||||
class Reader {
|
||||
ByteData data;
|
||||
int offset = 0;
|
||||
|
||||
Reader(this.data);
|
||||
|
||||
int u8() {
|
||||
offset += 1;
|
||||
return data.getUint8(offset - 1);
|
||||
}
|
||||
|
||||
int s16() {
|
||||
offset += 2;
|
||||
return data.getInt16(offset - 2, Endian.little);
|
||||
}
|
||||
|
||||
int u16() {
|
||||
offset += 2;
|
||||
return data.getUint16(offset - 2, Endian.little);
|
||||
}
|
||||
|
||||
int u32() {
|
||||
offset += 4;
|
||||
return data.getUint32(offset - 4, Endian.little);
|
||||
}
|
||||
|
||||
void skipPad16() {
|
||||
offset += offset & 1;
|
||||
}
|
||||
|
||||
Uint8List u8list(int count) {
|
||||
offset += count;
|
||||
return Uint8List.sublistView(data, offset - count, offset);
|
||||
}
|
||||
|
||||
int available() {
|
||||
return data.lengthInBytes - offset;
|
||||
}
|
||||
|
||||
bool eof() {
|
||||
return offset == data.lengthInBytes;
|
||||
}
|
||||
}
|
||||
|
||||
int Tag(int a, int b, int c, int d) {
|
||||
return (a << 24) | (b << 16) | (c << 8) | d;
|
||||
}
|
||||
|
||||
String fromTag(int tag) {
|
||||
final int a = (tag >> 24) & 0xFF;
|
||||
final int b = (tag >> 16) & 0xFF;
|
||||
final int c = (tag >> 8) & 0xFF;
|
||||
final int d = (tag >> 0) & 0xFF;
|
||||
return '' +
|
||||
String.fromCharCode(a) +
|
||||
String.fromCharCode(b) +
|
||||
String.fromCharCode(c) +
|
||||
String.fromCharCode(d);
|
||||
}
|
||||
|
||||
const int kCMap_TableTag = 1668112752; // cmap
|
||||
const int kAdvances_TableTag = 1751213174; // hadv
|
||||
const int kInfo_TableTag = 1768842863; // info
|
||||
const int kPaths_TableTag = 1885434984; // path
|
||||
const int kOffsets_TableTag = 1886348902; // poff
|
||||
|
||||
ByteData find_table(List<int> dir, int tag, ByteData data) {
|
||||
for (int i = 0; i < dir.length; i += 3) {
|
||||
if (dir[i] == tag) {
|
||||
return ByteData.view(data.buffer, dir[i + 1], dir[i + 2]);
|
||||
}
|
||||
}
|
||||
throw 'missing ${fromTag(tag)} table';
|
||||
}
|
||||
|
||||
class RiveFont {
|
||||
final double _ascent = -0.9; // TODO: get from file
|
||||
final double _descent = 0.2; // TODO: get from file
|
||||
|
||||
final _cmap = <int, int>{};
|
||||
final _advances = <double>[];
|
||||
final _rawpaths = <RawPath?>[];
|
||||
|
||||
RiveFont() {
|
||||
_advances.add(0);
|
||||
_rawpaths.add(null);
|
||||
}
|
||||
|
||||
double get ascent {
|
||||
return _ascent;
|
||||
}
|
||||
|
||||
double get descent {
|
||||
return _descent;
|
||||
}
|
||||
|
||||
double get height {
|
||||
return _descent - _ascent;
|
||||
}
|
||||
|
||||
int charToGlyph(int charCode) {
|
||||
int? glyph = _cmap[charCode];
|
||||
return glyph ?? 0;
|
||||
}
|
||||
|
||||
double getAdvance(int glyph) {
|
||||
return _advances[glyph];
|
||||
}
|
||||
|
||||
Path? getPath(int glyph) {
|
||||
final rp = getRawPath(glyph);
|
||||
if (rp != null) {
|
||||
Path p = Path();
|
||||
rp.sinker(p.moveTo, p.lineTo, p.quadraticBezierTo, p.cubicTo, p.close);
|
||||
return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
RawPath? getRawPath(int glyph) {
|
||||
return _rawpaths[glyph];
|
||||
}
|
||||
|
||||
Uint16List textToGlyphs(List<int> chars, int start, int end) {
|
||||
final int n = end - start;
|
||||
Uint16List glyphs = Uint16List(n);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
glyphs[i] = charToGlyph(chars[i + start]);
|
||||
}
|
||||
return glyphs;
|
||||
}
|
||||
|
||||
Float32List getAdvances(Uint16List glyphs) {
|
||||
Float32List advances = Float32List(glyphs.length);
|
||||
for (int i = 0; i < glyphs.length; ++i) {
|
||||
advances[i] = getAdvance(glyphs[i]);
|
||||
}
|
||||
return advances;
|
||||
}
|
||||
|
||||
void _build_cmap(int glyphCount, ByteData cmapD) {
|
||||
final reader = Reader(cmapD);
|
||||
final int count = reader.u16();
|
||||
if (enableFontDump) {
|
||||
print('cmap has $count entries');
|
||||
}
|
||||
for (int i = 0; i < count; ++i) {
|
||||
final int charCode = reader.u16();
|
||||
final int glyphID = reader.u16();
|
||||
assert(glyphID < glyphCount);
|
||||
_cmap.putIfAbsent(charCode, () => glyphID);
|
||||
}
|
||||
}
|
||||
|
||||
void _build_advances(int glyphCount, double scale, ByteData advD) {
|
||||
assert(advD.lengthInBytes == glyphCount * 2);
|
||||
final advances = Reader(advD);
|
||||
for (int i = 0; i < glyphCount; ++i) {
|
||||
_advances.add(advances.u16() * scale);
|
||||
}
|
||||
}
|
||||
|
||||
RawPath _build_rawpath(double scale, ByteData pathD) {
|
||||
final reader = Reader(pathD);
|
||||
|
||||
final int verbCount = reader.u16();
|
||||
assert(verbCount > 0);
|
||||
final int pointCount = reader.u16();
|
||||
final Float32List pts = Float32List(pointCount * 2);
|
||||
|
||||
final verbs = reader.u8list(verbCount);
|
||||
reader.skipPad16();
|
||||
for (int i = 0; i < pointCount * 2; ++i) {
|
||||
pts[i] = reader.s16() * scale;
|
||||
}
|
||||
assert(reader.eof());
|
||||
return RawPath(pts, verbs);
|
||||
}
|
||||
|
||||
void _build_rawpaths(
|
||||
int glyphCount, double scale, ByteData offD, ByteData pathD) {
|
||||
final offsets = Reader(offD);
|
||||
int start = offsets.u32();
|
||||
for (int i = 0; i < glyphCount; ++i) {
|
||||
int end = offsets.u32();
|
||||
assert(start <= end);
|
||||
|
||||
RawPath? path;
|
||||
if (start < end) {
|
||||
path = _build_rawpath(scale, ByteData.sublistView(pathD, start, end));
|
||||
}
|
||||
_rawpaths.add(path);
|
||||
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
|
||||
RiveFont.fromBinary(ByteData data) {
|
||||
final Reader reader = Reader(data);
|
||||
|
||||
{
|
||||
int signature = reader.u32();
|
||||
int version = reader.u32();
|
||||
assert(signature == 0x23581321);
|
||||
assert(version == 1);
|
||||
}
|
||||
final int tableCount = reader.u32();
|
||||
if (enableFontDump) {
|
||||
print('tables $tableCount');
|
||||
}
|
||||
|
||||
final List<int> dir = [];
|
||||
for (int i = 0; i < tableCount; ++i) {
|
||||
int tag = reader.u32();
|
||||
int off = reader.u32();
|
||||
int len = reader.u32();
|
||||
if (enableFontDump) {
|
||||
print('tag: ${fromTag(tag)} offset:$off length:$len');
|
||||
}
|
||||
dir.add(tag); // tag
|
||||
dir.add(off); // offset
|
||||
dir.add(len); // length
|
||||
}
|
||||
|
||||
final infoReader = Reader(find_table(dir, kInfo_TableTag, data));
|
||||
final int glyphCount = infoReader.u16();
|
||||
final int upem = infoReader.u16();
|
||||
final double scale = 1.0 / upem;
|
||||
if (enableFontDump) {
|
||||
print('glyphs $glyphCount, upem $upem');
|
||||
}
|
||||
|
||||
_build_cmap(glyphCount, find_table(dir, kCMap_TableTag, data));
|
||||
|
||||
_build_advances(
|
||||
glyphCount, scale, find_table(dir, kAdvances_TableTag, data));
|
||||
|
||||
_build_rawpaths(glyphCount, scale, find_table(dir, kOffsets_TableTag, data),
|
||||
find_table(dir, kPaths_TableTag, data));
|
||||
}
|
||||
|
||||
static final builtIn = RiveFont.fromBinary(
|
||||
ByteData.view(const Base64Decoder().convert(builtInFontBase64).buffer));
|
||||
}
|
252
lib/src/rive_core/src/text/shape_text.dart
Normal file
252
lib/src/rive_core/src/text/shape_text.dart
Normal file
@ -0,0 +1,252 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'rive_font.dart';
|
||||
|
||||
abstract class TextRun {
|
||||
RiveFont get font;
|
||||
double get pointSize;
|
||||
int get textLength;
|
||||
set textLength(int value);
|
||||
|
||||
TextRun cloneRun();
|
||||
}
|
||||
|
||||
class GlyphRun {
|
||||
TextRun textRun; // the run this glyph run was generated from
|
||||
RiveFont font; // possibly different from TextRun (substitution)
|
||||
double pointSize; // possibly different from TextRun
|
||||
|
||||
int textOffset;
|
||||
Uint16List glyphs;
|
||||
Float32List xpos; // #glyphs + 1
|
||||
|
||||
GlyphRun(
|
||||
this.textRun,
|
||||
this.font,
|
||||
this.pointSize,
|
||||
this.textOffset,
|
||||
this.glyphs,
|
||||
this.xpos,
|
||||
) {
|
||||
assert(glyphs.length + 1 == xpos.length);
|
||||
}
|
||||
|
||||
double get left {
|
||||
return xpos.first;
|
||||
}
|
||||
|
||||
double get right {
|
||||
return xpos.last;
|
||||
}
|
||||
|
||||
double get width {
|
||||
return right - left;
|
||||
}
|
||||
|
||||
int get textStart {
|
||||
return textOffset;
|
||||
}
|
||||
|
||||
int get textEnd {
|
||||
return textOffset + glyphs.length;
|
||||
}
|
||||
|
||||
static bool ws(int code) {
|
||||
return code <= 32;
|
||||
}
|
||||
|
||||
static List<GlyphRun> shapeText(List<int> chars, List<TextRun> textRuns) {
|
||||
final glyphRuns = <GlyphRun>[];
|
||||
int offset = 0;
|
||||
double x = 0;
|
||||
|
||||
for (final TextRun run in textRuns) {
|
||||
if (run.textLength > 0) {
|
||||
final glyphs =
|
||||
run.font.textToGlyphs(chars, offset, offset + run.textLength);
|
||||
final advances = run.font.getAdvances(glyphs);
|
||||
final int n = glyphs.length;
|
||||
final xpos = Float32List(n + 1);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
xpos[i] = x;
|
||||
x += advances[i] * run.pointSize;
|
||||
}
|
||||
xpos[n] = x;
|
||||
glyphRuns
|
||||
.add(GlyphRun(run, run.font, run.pointSize, offset, glyphs, xpos));
|
||||
offset += run.textLength;
|
||||
}
|
||||
}
|
||||
return glyphRuns;
|
||||
}
|
||||
|
||||
static List<int> findWordBreaks(List<int> chars) {
|
||||
List<int> breaks = [];
|
||||
for (int i = 0; i < chars.length;) {
|
||||
// skip ws
|
||||
while (i < chars.length && ws(chars[i])) {
|
||||
++i;
|
||||
}
|
||||
breaks.add(i); // word start
|
||||
// skip non-ws
|
||||
while (i < chars.length && !ws(chars[i])) {
|
||||
++i;
|
||||
}
|
||||
breaks.add(i); // word end
|
||||
}
|
||||
assert(breaks.last == chars.length);
|
||||
return breaks;
|
||||
}
|
||||
|
||||
int offsetToIndex(int offset) {
|
||||
assert(textOffset <= offset);
|
||||
assert(offset - textOffset <= glyphs.length);
|
||||
return offset - textOffset;
|
||||
}
|
||||
}
|
||||
|
||||
class GlyphLine {
|
||||
List<GlyphRun> runs;
|
||||
int startRun;
|
||||
int startIndex;
|
||||
int endRun;
|
||||
int endIndex;
|
||||
int wsRun;
|
||||
int wsIndex;
|
||||
double startX;
|
||||
double top = 0, baseline = 0, bottom = 0;
|
||||
|
||||
void validate() {
|
||||
void val(int r, int i) {
|
||||
final run = runs[r];
|
||||
assert(i >= 0 && i <= run.glyphs.length);
|
||||
}
|
||||
|
||||
val(startRun, startIndex);
|
||||
val(endRun, endIndex);
|
||||
val(wsRun, wsIndex);
|
||||
assert(startRun < endRun || (startRun == endRun && startIndex <= endIndex));
|
||||
assert(endRun < wsRun || (endRun == wsRun && endIndex <= wsIndex));
|
||||
}
|
||||
|
||||
GlyphLine(this.runs, this.startRun, this.startIndex, this.endRun,
|
||||
this.endIndex, this.wsRun, this.wsIndex, this.startX);
|
||||
|
||||
int get textStart {
|
||||
return runs[startRun].textStart + startIndex;
|
||||
}
|
||||
|
||||
int get textEnd {
|
||||
return runs[wsRun].textStart + wsIndex;
|
||||
}
|
||||
|
||||
double offsetToX(int textOffset) {
|
||||
assert(textOffset <= runs[endRun].textEnd);
|
||||
for (int i = startRun; i <= endRun; ++i) {
|
||||
final run = runs[i];
|
||||
if (textOffset <= run.textEnd) {
|
||||
return run.xpos[textOffset - run.textStart] - startX;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _offsetToRunIndex(
|
||||
List<GlyphRun> runs, int offset, int charLength) {
|
||||
assert(offset >= 0);
|
||||
for (int i = 1; i < runs.length; ++i) {
|
||||
final run = runs[i];
|
||||
if (run.textOffset >= offset) {
|
||||
return i - 1;
|
||||
}
|
||||
}
|
||||
return runs.length - 1;
|
||||
}
|
||||
|
||||
static List<GlyphLine> lineBreak(
|
||||
List<int> chars, List<int> breaks, List<GlyphRun> runs, double width) {
|
||||
List<GlyphLine> lines = [];
|
||||
int startRun = 0;
|
||||
int startIndex = 0;
|
||||
double xlimit = width;
|
||||
|
||||
int prevRun = 0;
|
||||
int prevIndex = 0;
|
||||
|
||||
int wordStart = breaks[0];
|
||||
int wordEnd = breaks[1];
|
||||
int nextBreakIndex = 2;
|
||||
int lineStartTextOffset = wordStart;
|
||||
|
||||
for (;;) {
|
||||
assert(wordStart <= wordEnd); // == means trailing spaces?
|
||||
|
||||
int endRun = _offsetToRunIndex(runs, wordEnd, chars.length);
|
||||
int endIndex = runs[endRun].offsetToIndex(wordEnd);
|
||||
double pos = runs[endRun].xpos[endIndex];
|
||||
bool bumpBreakIndex = true;
|
||||
if (pos > xlimit) {
|
||||
int wsRun = _offsetToRunIndex(runs, wordStart, chars.length);
|
||||
int wsIndex = runs[wsRun].offsetToIndex(wordStart);
|
||||
|
||||
bumpBreakIndex = false;
|
||||
// does just one word not fit?
|
||||
if (lineStartTextOffset == wordStart) {
|
||||
// walk backwards a letter at a time until we fit, stopping at
|
||||
// 1 letter.
|
||||
int wend = wordEnd;
|
||||
while (pos > xlimit && wend - 1 > wordStart) {
|
||||
wend -= 1;
|
||||
prevRun = _offsetToRunIndex(runs, wend, chars.length);
|
||||
prevIndex = runs[prevRun].offsetToIndex(wend);
|
||||
pos = runs[prevRun].xpos[prevIndex];
|
||||
}
|
||||
assert(wend < wordEnd || wend == wordEnd && wordStart + 1 == wordEnd);
|
||||
if (wend == wordEnd) {
|
||||
bumpBreakIndex = true;
|
||||
}
|
||||
|
||||
// now reset our "whitespace" marker to just be prev, since
|
||||
// by defintion we have no extra whitespace on this line
|
||||
wsRun = prevRun;
|
||||
wsIndex = prevIndex;
|
||||
wordStart = wend;
|
||||
}
|
||||
|
||||
// bulid the line
|
||||
final lineStartX = runs[startRun].xpos[startIndex];
|
||||
lines.add(GlyphLine(runs, startRun, startIndex, prevRun, prevIndex,
|
||||
wsRun, wsIndex, lineStartX));
|
||||
|
||||
// update for the next line
|
||||
xlimit = runs[wsRun].xpos[wsIndex] + width;
|
||||
startRun = prevRun = wsRun;
|
||||
startIndex = prevIndex = wsIndex;
|
||||
lineStartTextOffset = wordStart;
|
||||
} else {
|
||||
// we didn't go too far, so remember this word-end boundary
|
||||
prevRun = endRun;
|
||||
prevIndex = endIndex;
|
||||
}
|
||||
|
||||
if (bumpBreakIndex) {
|
||||
if (nextBreakIndex < breaks.length) {
|
||||
wordStart = breaks[nextBreakIndex++];
|
||||
wordEnd = breaks[nextBreakIndex++];
|
||||
} else {
|
||||
break; // bust out of the loop
|
||||
}
|
||||
}
|
||||
}
|
||||
// scoop up the last line (if present)
|
||||
final int tailRun = runs.length - 1;
|
||||
final int tailIndex = runs[tailRun].glyphs.length;
|
||||
if (startRun != tailRun || startIndex != tailIndex) {
|
||||
final double startX = runs[startRun].xpos[startIndex];
|
||||
lines.add(GlyphLine(runs, startRun, startIndex, tailRun, tailIndex,
|
||||
tailRun, tailIndex, startX));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/rive_core/animation/animation_state.dart';
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
|
Reference in New Issue
Block a user