mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-08-06 00:21:17 +08:00
Adding run targeting to text value ranges!
Ability to target an individual run with the modifier range: <img width="1577" alt="CleanShot 2023-07-24 at 16 25 12@2x" src="https://github.com/rive-app/rive/assets/454182/48bca39b-057c-4ed5-b04e-cada62c1f190"> Diffs= 215d11c86 Adding run targeting to text value ranges! (#5660) Co-authored-by: Alex Gibson <agibson.uk@gmail.com> Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
This commit is contained in:
@ -1 +1 @@
|
||||
24aaadf9aeb0d6beb3faa1dec9f2d490d5d67afa
|
||||
215d11c864cc550adbcfc9a47691cf9583fa7f25
|
||||
|
@ -1391,6 +1391,11 @@ class RiveCoreContext {
|
||||
object.offset = value;
|
||||
}
|
||||
break;
|
||||
case TextModifierRangeBase.runIdPropertyKey:
|
||||
if (object is TextModifierRangeBase && value is int) {
|
||||
object.runId = value;
|
||||
}
|
||||
break;
|
||||
case TextStyleFeatureBase.tagPropertyKey:
|
||||
if (object is TextStyleFeatureBase && value is int) {
|
||||
object.tag = value;
|
||||
@ -1656,6 +1661,7 @@ class RiveCoreContext {
|
||||
case TextModifierRangeBase.unitsValuePropertyKey:
|
||||
case TextModifierRangeBase.typeValuePropertyKey:
|
||||
case TextModifierRangeBase.modeValuePropertyKey:
|
||||
case TextModifierRangeBase.runIdPropertyKey:
|
||||
case TextStyleFeatureBase.tagPropertyKey:
|
||||
case TextStyleFeatureBase.featureValuePropertyKey:
|
||||
case TextVariationModifierBase.axisTagPropertyKey:
|
||||
@ -1996,6 +2002,8 @@ class RiveCoreContext {
|
||||
return (object as TextModifierRangeBase).typeValue;
|
||||
case TextModifierRangeBase.modeValuePropertyKey:
|
||||
return (object as TextModifierRangeBase).modeValue;
|
||||
case TextModifierRangeBase.runIdPropertyKey:
|
||||
return (object as TextModifierRangeBase).runId;
|
||||
case TextStyleFeatureBase.tagPropertyKey:
|
||||
return (object as TextStyleFeatureBase).tag;
|
||||
case TextStyleFeatureBase.featureValuePropertyKey:
|
||||
@ -2753,6 +2761,11 @@ class RiveCoreContext {
|
||||
object.modeValue = value;
|
||||
}
|
||||
break;
|
||||
case TextModifierRangeBase.runIdPropertyKey:
|
||||
if (object is TextModifierRangeBase) {
|
||||
object.runId = value;
|
||||
}
|
||||
break;
|
||||
case TextStyleFeatureBase.tagPropertyKey:
|
||||
if (object is TextStyleFeatureBase) {
|
||||
object.tag = value;
|
||||
|
@ -238,6 +238,30 @@ abstract class TextModifierRangeBase extends ContainerComponent {
|
||||
|
||||
void offsetChanged(double from, double to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// RunId field with key 378.
|
||||
static const int runIdInitialValue = -1;
|
||||
int _runId = runIdInitialValue;
|
||||
static const int runIdPropertyKey = 378;
|
||||
|
||||
/// Identifier used to which run should be targeted.
|
||||
int get runId => _runId;
|
||||
|
||||
/// Change the [_runId] field value.
|
||||
/// [runIdChanged] will be invoked only if the field's value has changed.
|
||||
set runId(int value) {
|
||||
if (_runId == value) {
|
||||
return;
|
||||
}
|
||||
int from = _runId;
|
||||
_runId = value;
|
||||
if (hasValidated) {
|
||||
runIdChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void runIdChanged(int from, int to);
|
||||
|
||||
@override
|
||||
void copy(covariant TextModifierRangeBase source) {
|
||||
super.copy(source);
|
||||
@ -251,5 +275,6 @@ abstract class TextModifierRangeBase extends ContainerComponent {
|
||||
_falloffFrom = source._falloffFrom;
|
||||
_falloffTo = source._falloffTo;
|
||||
_offset = source._offset;
|
||||
_runId = source._runId;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/text/text_modifier_range_base.dart';
|
||||
import 'package:rive/src/rive_core/animation/cubic_interpolator_component.dart';
|
||||
import 'package:rive/src/rive_core/animation/interpolator.dart';
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
import 'package:rive/src/rive_core/component_dirt.dart';
|
||||
import 'package:rive/src/rive_core/text/text_modifier_group.dart';
|
||||
import 'package:rive/src/rive_core/text/text_value_run.dart';
|
||||
import 'package:rive_common/rive_text.dart';
|
||||
|
||||
export 'package:rive/src/generated/text/text_modifier_range_base.dart';
|
||||
@ -110,25 +111,36 @@ class TextModifierRange extends TextModifierRangeBase {
|
||||
if (_rangeMapper != null) {
|
||||
return;
|
||||
}
|
||||
int start = 0;
|
||||
int end = text.length;
|
||||
if (run != null) {
|
||||
start = run!.offset;
|
||||
end = start + run!.text.length;
|
||||
}
|
||||
switch (units) {
|
||||
case TextRangeUnits.charactersExcludingSpaces:
|
||||
_rangeMapper = RangeMapper.fromCharacters(
|
||||
text,
|
||||
start,
|
||||
end,
|
||||
glyphLookup,
|
||||
withoutSpaces: true,
|
||||
);
|
||||
break;
|
||||
case TextRangeUnits.words:
|
||||
_rangeMapper = RangeMapper.fromWords(text);
|
||||
_rangeMapper = RangeMapper.fromWords(text, start, end);
|
||||
break;
|
||||
case TextRangeUnits.lines:
|
||||
if (shape != null && lines != null) {
|
||||
_rangeMapper = RangeMapper.fromLines(text, shape, lines, glyphLookup);
|
||||
_rangeMapper = RangeMapper.fromLines(
|
||||
text, start, end, shape, lines, glyphLookup);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
_rangeMapper = RangeMapper.fromCharacters(
|
||||
text,
|
||||
start,
|
||||
end,
|
||||
glyphLookup,
|
||||
);
|
||||
break;
|
||||
@ -247,6 +259,30 @@ class TextModifierRange extends TextModifierRangeBase {
|
||||
|
||||
@override
|
||||
void strengthChanged(double from, double to) => modifierGroup?.rangeChanged();
|
||||
|
||||
@override
|
||||
void runIdChanged(int from, int to) {
|
||||
run = to == Core.missingId ? null : context.resolve(to);
|
||||
modifierGroup?.rangeTypeChanged();
|
||||
}
|
||||
|
||||
@override
|
||||
void onAddedDirty() {
|
||||
run = context.resolve(runId);
|
||||
super.onAddedDirty();
|
||||
}
|
||||
|
||||
TextValueRun? _run;
|
||||
|
||||
TextValueRun? get run => _run;
|
||||
|
||||
set run(TextValueRun? value) {
|
||||
if (_run == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
_run = value;
|
||||
}
|
||||
}
|
||||
|
||||
// See word indices and word lengths implementation above, we basically want the
|
||||
@ -270,7 +306,7 @@ class RangeMapper {
|
||||
unitLengths = Uint32List.fromList(lengths);
|
||||
|
||||
/// Build a RangeMapper from the words in [text].
|
||||
static RangeMapper? fromWords(String text) {
|
||||
static RangeMapper? fromWords(String text, int start, int end) {
|
||||
if (text.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
@ -280,12 +316,21 @@ class RangeMapper {
|
||||
|
||||
int characterCount = 0;
|
||||
int index = 0;
|
||||
int indexFrom = 0;
|
||||
for (final unit in text.codeUnits) {
|
||||
if (wantWhiteSpace == isWhiteSpace(unit)) {
|
||||
if (!wantWhiteSpace) {
|
||||
indices.add(index);
|
||||
indexFrom = index;
|
||||
} else {
|
||||
lengths.add(characterCount);
|
||||
var indexTo = indexFrom + characterCount;
|
||||
if (indexTo > start && end > indexFrom) {
|
||||
var actualStart = max(start, indexFrom);
|
||||
int selected = min(end, indexTo) - actualStart;
|
||||
if (selected > 0) {
|
||||
indices.add(actualStart);
|
||||
lengths.add(selected);
|
||||
}
|
||||
}
|
||||
|
||||
characterCount = 0;
|
||||
}
|
||||
@ -296,23 +341,20 @@ class RangeMapper {
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
if (characterCount != 0) {
|
||||
lengths.add(characterCount);
|
||||
indices.add(index);
|
||||
}
|
||||
indices.add(end);
|
||||
return RangeMapper(indices, lengths);
|
||||
}
|
||||
|
||||
/// Build a RangeMapper from the words in [text].
|
||||
static RangeMapper? fromCharacters(String text, GlyphLookup glyphLookup,
|
||||
static RangeMapper? fromCharacters(
|
||||
String text, int start, int end, GlyphLookup glyphLookup,
|
||||
{bool withoutSpaces = false}) {
|
||||
if (text.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
List<int> indices = [];
|
||||
List<int> lengths = [];
|
||||
for (int i = 0; i < text.length;) {
|
||||
for (int i = start; i < end;) {
|
||||
var unit = text.codeUnits[i];
|
||||
if (withoutSpaces && isWhiteSpace(unit)) {
|
||||
i++;
|
||||
@ -325,12 +367,12 @@ class RangeMapper {
|
||||
i += codePoints;
|
||||
}
|
||||
|
||||
indices.add(text.length);
|
||||
indices.add(end);
|
||||
return RangeMapper(indices, lengths);
|
||||
}
|
||||
|
||||
static RangeMapper? fromLines(String text, TextShapeResult shape,
|
||||
BreakLinesResult lines, GlyphLookup glyphLookup) {
|
||||
static RangeMapper? fromLines(String text, int start, int end,
|
||||
TextShapeResult shape, BreakLinesResult lines, GlyphLookup glyphLookup) {
|
||||
if (text.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
@ -342,23 +384,25 @@ class RangeMapper {
|
||||
final paragraph = shape.paragraphs[paragraphIndex++];
|
||||
var glyphRuns = paragraph.runs;
|
||||
for (final line in paragraphLines) {
|
||||
// if (line.endIndex == 0) {
|
||||
// // Empty line.
|
||||
// continue;
|
||||
// }
|
||||
var rf = glyphRuns[line.startRun];
|
||||
var indexFrom = rf.textIndexAt(line.startIndex);
|
||||
|
||||
var rt = glyphRuns[line.endRun];
|
||||
var endGlyphIndex = max(
|
||||
0, line.endIndex - 1); //min(rt.glyphCount - 1, line.endIndex - 1);
|
||||
var endGlyphIndex = max(0, line.endIndex - 1);
|
||||
var indexTo = rt.textIndexAt(endGlyphIndex);
|
||||
indexTo += glyphLookup.count(indexTo);
|
||||
indices.add(indexFrom);
|
||||
lengths.add(indexTo - indexFrom);
|
||||
|
||||
if (indexTo > start && end > indexFrom) {
|
||||
var actualStart = max(start, indexFrom);
|
||||
int selected = min(end, indexTo) - actualStart;
|
||||
if (selected > 0) {
|
||||
indices.add(actualStart);
|
||||
lengths.add(selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
indices.add(text.length);
|
||||
indices.add(end);
|
||||
return RangeMapper(indices, lengths);
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,22 @@ class TextValueRun extends TextValueRunBase {
|
||||
_style?.ref(this);
|
||||
}
|
||||
|
||||
/// Returns the offset of this run within the text.
|
||||
int get offset {
|
||||
var text = textComponent;
|
||||
if (text == null) {
|
||||
return 0;
|
||||
}
|
||||
int value = 0;
|
||||
for (final run in text.runs) {
|
||||
if (run == this) {
|
||||
break;
|
||||
}
|
||||
value += run.text.length;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void markShapeDirty() => textComponent?.markShapeDirty();
|
||||
|
||||
@override
|
||||
|
Reference in New Issue
Block a user