mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-08-06 08:29:35 +08:00
Text fixes
Bunch of text fixes: - gradients respect origin on text - changing font size updates the text editor selection - apply @avivian's transform constraint fix from https://github.com/rive-app/rive/pull/5689 to cpp Diffs= 6af562d4c Text fixes (#5696) fcccdeccd Add originX and originY support to images (#5624) 5b2a52f44 Fix text alignment in cpp based runtimes (#5691) Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
This commit is contained in:
@ -1 +1 @@
|
||||
dd69830342af74b8510729a90a380656bd9ff850
|
||||
6af562d4cc2b8e309169c754a64cbc0211b9d68b
|
||||
|
@ -1,3 +1,7 @@
|
||||
## 0.11.7
|
||||
|
||||
- Fix for gradients on text.
|
||||
|
||||
## 0.11.6
|
||||
|
||||
- Follow path constraint.
|
||||
|
@ -1132,6 +1132,16 @@ class RiveCoreContext {
|
||||
object.assetId = value;
|
||||
}
|
||||
break;
|
||||
case ImageBase.originXPropertyKey:
|
||||
if (object is ImageBase && value is double) {
|
||||
object.originX = value;
|
||||
}
|
||||
break;
|
||||
case ImageBase.originYPropertyKey:
|
||||
if (object is ImageBase && value is double) {
|
||||
object.originY = value;
|
||||
}
|
||||
break;
|
||||
case CubicDetachedVertexBase.inRotationPropertyKey:
|
||||
if (object is CubicDetachedVertexBase && value is double) {
|
||||
object.inRotation = value;
|
||||
@ -1754,6 +1764,8 @@ class RiveCoreContext {
|
||||
case CubicMirroredVertexBase.distancePropertyKey:
|
||||
case PolygonBase.cornerRadiusPropertyKey:
|
||||
case StarBase.innerRadiusPropertyKey:
|
||||
case ImageBase.originXPropertyKey:
|
||||
case ImageBase.originYPropertyKey:
|
||||
case CubicDetachedVertexBase.inRotationPropertyKey:
|
||||
case CubicDetachedVertexBase.inDistancePropertyKey:
|
||||
case CubicDetachedVertexBase.outRotationPropertyKey:
|
||||
@ -2183,6 +2195,10 @@ class RiveCoreContext {
|
||||
return (object as PolygonBase).cornerRadius;
|
||||
case StarBase.innerRadiusPropertyKey:
|
||||
return (object as StarBase).innerRadius;
|
||||
case ImageBase.originXPropertyKey:
|
||||
return (object as ImageBase).originX;
|
||||
case ImageBase.originYPropertyKey:
|
||||
return (object as ImageBase).originY;
|
||||
case CubicDetachedVertexBase.inRotationPropertyKey:
|
||||
return (object as CubicDetachedVertexBase).inRotation;
|
||||
case CubicDetachedVertexBase.inDistancePropertyKey:
|
||||
@ -3188,6 +3204,16 @@ class RiveCoreContext {
|
||||
object.innerRadius = value;
|
||||
}
|
||||
break;
|
||||
case ImageBase.originXPropertyKey:
|
||||
if (object is ImageBase) {
|
||||
object.originX = value;
|
||||
}
|
||||
break;
|
||||
case ImageBase.originYPropertyKey:
|
||||
if (object is ImageBase) {
|
||||
object.originY = value;
|
||||
}
|
||||
break;
|
||||
case CubicDetachedVertexBase.inRotationPropertyKey:
|
||||
if (object is CubicDetachedVertexBase) {
|
||||
object.inRotation = value;
|
||||
|
@ -47,9 +47,59 @@ abstract class ImageBase extends Drawable {
|
||||
|
||||
void assetIdChanged(int from, int to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// OriginX field with key 380.
|
||||
static const double originXInitialValue = 0.5;
|
||||
double _originX = originXInitialValue;
|
||||
static const int originXPropertyKey = 380;
|
||||
|
||||
/// Origin x in normalized coordinates (0.5 = center, 0 = left, 1 = right).
|
||||
double get originX => _originX;
|
||||
|
||||
/// Change the [_originX] field value.
|
||||
/// [originXChanged] will be invoked only if the field's value has changed.
|
||||
set originX(double value) {
|
||||
if (_originX == value) {
|
||||
return;
|
||||
}
|
||||
double from = _originX;
|
||||
_originX = value;
|
||||
if (hasValidated) {
|
||||
originXChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void originXChanged(double from, double to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// OriginY field with key 381.
|
||||
static const double originYInitialValue = 0.5;
|
||||
double _originY = originYInitialValue;
|
||||
static const int originYPropertyKey = 381;
|
||||
|
||||
/// Origin y in normalized coordinates (0.5 = center, 0 = top, 1 = bottom).
|
||||
double get originY => _originY;
|
||||
|
||||
/// Change the [_originY] field value.
|
||||
/// [originYChanged] will be invoked only if the field's value has changed.
|
||||
set originY(double value) {
|
||||
if (_originY == value) {
|
||||
return;
|
||||
}
|
||||
double from = _originY;
|
||||
_originY = value;
|
||||
if (hasValidated) {
|
||||
originYChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void originYChanged(double from, double to);
|
||||
|
||||
@override
|
||||
void copy(covariant ImageBase source) {
|
||||
super.copy(source);
|
||||
_assetId = source._assetId;
|
||||
_originX = source._originX;
|
||||
_originY = source._originY;
|
||||
}
|
||||
}
|
||||
|
@ -106,17 +106,6 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
|
||||
|
||||
void _sortAndValidateKeyFrames() {
|
||||
sort();
|
||||
|
||||
for (int i = 0; i < _keyframes.length - 1; i++) {
|
||||
var a = _keyframes[i];
|
||||
var b = _keyframes[i + 1];
|
||||
if (a.frame == b.frame) {
|
||||
// N.B. this removes it from the list too.
|
||||
context.removeObject(a);
|
||||
// Repeat current.
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of keyframes for this keyed property.
|
||||
|
@ -12,18 +12,14 @@ export 'package:rive/src/generated/constraints/transform_constraint_base.dart';
|
||||
/// constrained component in world or local space.
|
||||
class TransformConstraint extends TransformConstraintBase {
|
||||
Mat2D get targetTransform {
|
||||
var transform = target!.worldTransform;
|
||||
if (originX != 0 || originY != 0) {
|
||||
var bounds = target!.localBounds;
|
||||
var local = Mat2D.fromTranslation(
|
||||
Vec2D.fromValues(
|
||||
bounds.left + bounds.width * originX,
|
||||
bounds.top + bounds.height * originY,
|
||||
),
|
||||
);
|
||||
transform = Mat2D.multiply(Mat2D(), transform, local);
|
||||
}
|
||||
return transform;
|
||||
var bounds = target!.localBounds;
|
||||
var local = Mat2D.fromTranslation(
|
||||
Vec2D.fromValues(
|
||||
bounds.left + bounds.width * originX,
|
||||
bounds.top + bounds.height * originY,
|
||||
),
|
||||
);
|
||||
return Mat2D.multiply(Mat2D(), target!.worldTransform, local);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -30,9 +30,12 @@ class Image extends ImageBase
|
||||
if (asset == null) {
|
||||
return AABB.empty();
|
||||
}
|
||||
final halfWidth = width / 2;
|
||||
final halfHeight = height / 2;
|
||||
return AABB.fromValues(-halfWidth, -halfHeight, halfWidth, halfHeight);
|
||||
return AABB.fromValues(
|
||||
-width * originX,
|
||||
-height * originY,
|
||||
-width * originX + width,
|
||||
-height * originY + height,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -54,7 +57,8 @@ class Image extends ImageBase
|
||||
canvas.save();
|
||||
canvas.transform(renderTransform.mat4);
|
||||
if (_mesh == null || !_mesh!.draws) {
|
||||
canvas.drawImage(uiImage, ui.Offset(-width / 2, -height / 2), paint);
|
||||
canvas.drawImage(
|
||||
uiImage, ui.Offset(-width * originX, -height * originY), paint);
|
||||
} else {
|
||||
paint.shader = ui.ImageShader(
|
||||
uiImage,
|
||||
@ -124,6 +128,12 @@ class Image extends ImageBase
|
||||
@override
|
||||
Skinnable<MeshVertex>? get skinnable => _mesh;
|
||||
|
||||
@override
|
||||
void originXChanged(double from, double to) => markTransformDirty();
|
||||
|
||||
@override
|
||||
void originYChanged(double from, double to) => markTransformDirty();
|
||||
|
||||
Mat2D get renderTransform {
|
||||
var mesh = _mesh;
|
||||
if (mesh != null && mesh.draws) {
|
||||
|
@ -252,36 +252,42 @@ class Text extends TextBase with TextStyleContainer {
|
||||
|
||||
double y = 0;
|
||||
double minY = 0;
|
||||
double maxX = 0;
|
||||
var paragraphIndex = 0;
|
||||
int ellipsisLine = -1;
|
||||
bool isEllipsisLineLast = false;
|
||||
double maxWidth = 0;
|
||||
|
||||
// Find the line to put the ellipsis on (line before the one that
|
||||
// overflows).
|
||||
if (overflow == TextOverflow.ellipsis && sizing == TextSizing.fixed) {
|
||||
int lastLineIndex = -1;
|
||||
for (final paragraphLines in lines) {
|
||||
for (final line in paragraphLines) {
|
||||
lastLineIndex++;
|
||||
if (y + line.bottom <= height) {
|
||||
ellipsisLine++;
|
||||
}
|
||||
// If we want an ellipsis we need to find the line to put the
|
||||
// ellipsis on (line before the one that overflows).
|
||||
var wantEllipsis =
|
||||
overflow == TextOverflow.ellipsis && sizing == TextSizing.fixed;
|
||||
|
||||
// We iterate in a pre-path building pass to compute dimensions.
|
||||
int lastLineIndex = -1;
|
||||
for (final paragraphLines in lines) {
|
||||
final paragraph = shape.paragraphs[paragraphIndex++];
|
||||
for (final line in paragraphLines) {
|
||||
var width = line.width(paragraph);
|
||||
if (width > maxWidth) {
|
||||
maxWidth = width;
|
||||
}
|
||||
|
||||
if (paragraphLines.isNotEmpty) {
|
||||
y += paragraphLines.last.bottom;
|
||||
lastLineIndex++;
|
||||
if (wantEllipsis && y + line.bottom <= height) {
|
||||
ellipsisLine++;
|
||||
}
|
||||
}
|
||||
|
||||
y += paragraphSpacing;
|
||||
if (paragraphLines.isNotEmpty) {
|
||||
y += paragraphLines.last.bottom;
|
||||
}
|
||||
if (ellipsisLine == -1) {
|
||||
// Nothing fits, just show the first line and ellipse it.
|
||||
ellipsisLine = 0;
|
||||
}
|
||||
isEllipsisLineLast = lastLineIndex == ellipsisLine;
|
||||
y = 0;
|
||||
|
||||
y += paragraphSpacing;
|
||||
}
|
||||
if (wantEllipsis && ellipsisLine == -1) {
|
||||
// Nothing fits, just show the first line and ellipse it.
|
||||
ellipsisLine = 0;
|
||||
}
|
||||
isEllipsisLineLast = lastLineIndex == ellipsisLine;
|
||||
|
||||
bool haveModifiers = _modifierGroups.isNotEmpty;
|
||||
if (haveModifiers) {
|
||||
@ -298,6 +304,37 @@ class Text extends TextBase with TextStyleContainer {
|
||||
y -= lines.first.first.baseline;
|
||||
minY = y;
|
||||
}
|
||||
|
||||
switch (sizing) {
|
||||
case TextSizing.autoWidth:
|
||||
_bounds = AABB.fromValues(
|
||||
0.0,
|
||||
minY,
|
||||
maxWidth,
|
||||
max(minY, y - paragraphSpacing),
|
||||
);
|
||||
break;
|
||||
case TextSizing.autoHeight:
|
||||
_bounds = AABB.fromValues(
|
||||
0.0,
|
||||
minY,
|
||||
width,
|
||||
max(minY, y - paragraphSpacing),
|
||||
);
|
||||
break;
|
||||
case TextSizing.fixed:
|
||||
_bounds = AABB.fromValues(
|
||||
0.0,
|
||||
minY,
|
||||
width,
|
||||
minY + height,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
y = -_bounds.height * originY;
|
||||
paragraphIndex = 0;
|
||||
|
||||
outer:
|
||||
for (final paragraphLines in lines) {
|
||||
final paragraph = shape.paragraphs[paragraphIndex++];
|
||||
@ -317,7 +354,7 @@ class Text extends TextBase with TextStyleContainer {
|
||||
break;
|
||||
}
|
||||
|
||||
double x = line.startX;
|
||||
double x = -_bounds.width * originX + line.startX;
|
||||
for (final glyphInfo in lineIndex == ellipsisLine
|
||||
? line.glyphsWithEllipsis(
|
||||
width,
|
||||
@ -369,9 +406,6 @@ class Text extends TextBase with TextStyleContainer {
|
||||
|
||||
x += run.advanceAt(glyphInfo.index);
|
||||
}
|
||||
if (x > maxX) {
|
||||
maxX = x;
|
||||
}
|
||||
if (lineIndex == ellipsisLine) {
|
||||
break outer;
|
||||
}
|
||||
@ -382,32 +416,6 @@ class Text extends TextBase with TextStyleContainer {
|
||||
}
|
||||
y += paragraphSpacing;
|
||||
}
|
||||
switch (sizing) {
|
||||
case TextSizing.autoWidth:
|
||||
_bounds = AABB.fromValues(
|
||||
0.0,
|
||||
minY,
|
||||
maxX,
|
||||
max(minY, y - paragraphSpacing),
|
||||
);
|
||||
break;
|
||||
case TextSizing.autoHeight:
|
||||
_bounds = AABB.fromValues(
|
||||
0.0,
|
||||
minY,
|
||||
width,
|
||||
max(minY, y - paragraphSpacing),
|
||||
);
|
||||
break;
|
||||
case TextSizing.fixed:
|
||||
_bounds = AABB.fromValues(
|
||||
0.0,
|
||||
minY,
|
||||
width,
|
||||
minY + height,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@ -422,7 +430,6 @@ class Text extends TextBase with TextStyleContainer {
|
||||
canvas.save();
|
||||
}
|
||||
canvas.transform(worldTransform.mat4);
|
||||
canvas.translate(-_bounds.width * originX, -_bounds.height * originY);
|
||||
if (overflow == TextOverflow.clipped) {
|
||||
canvas.clipRect(Offset.zero & size);
|
||||
}
|
||||
|
@ -96,7 +96,9 @@ class TextStyle extends TextStyleBase
|
||||
int shaperId = -1;
|
||||
|
||||
@override
|
||||
void fontSizeChanged(double from, double to) => _markShapeDirty();
|
||||
void fontSizeChanged(double from, double to) {
|
||||
_markShapeDirty();
|
||||
}
|
||||
|
||||
void _markShapeDirty() {
|
||||
for (final run in _referencers) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: rive
|
||||
version: 0.11.6
|
||||
version: 0.11.7
|
||||
homepage: https://rive.app
|
||||
description: Rive 2 Flutter Runtime. This package provides runtime functionality for playing back and interacting with animations built with the Rive editor available at https://rive.app.
|
||||
repository: https://github.com/rive-app/rive-flutter
|
||||
@ -15,7 +15,7 @@ dependencies:
|
||||
http: ^1.1.0
|
||||
meta: ^1.3.0
|
||||
plugin_platform_interface: ^2.0.2
|
||||
rive_common: 0.2.2
|
||||
rive_common: 0.2.3
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
Reference in New Issue
Block a user