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:
luigi-rosso
2023-07-29 01:49:02 +00:00
parent 8ac04725c9
commit 2f7d8763e7
10 changed files with 167 additions and 83 deletions

View File

@ -1 +1 @@
dd69830342af74b8510729a90a380656bd9ff850
6af562d4cc2b8e309169c754a64cbc0211b9d68b

View File

@ -1,3 +1,7 @@
## 0.11.7
- Fix for gradients on text.
## 0.11.6
- Follow path constraint.

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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

View File

@ -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) {

View File

@ -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);
}

View File

@ -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) {

View File

@ -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