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:
luigi-rosso
2023-07-25 22:26:38 +00:00
parent 810730f00c
commit 8dfb05c64d
5 changed files with 124 additions and 26 deletions

View File

@ -1 +1 @@
24aaadf9aeb0d6beb3faa1dec9f2d490d5d67afa 215d11c864cc550adbcfc9a47691cf9583fa7f25

View File

@ -1391,6 +1391,11 @@ class RiveCoreContext {
object.offset = value; object.offset = value;
} }
break; break;
case TextModifierRangeBase.runIdPropertyKey:
if (object is TextModifierRangeBase && value is int) {
object.runId = value;
}
break;
case TextStyleFeatureBase.tagPropertyKey: case TextStyleFeatureBase.tagPropertyKey:
if (object is TextStyleFeatureBase && value is int) { if (object is TextStyleFeatureBase && value is int) {
object.tag = value; object.tag = value;
@ -1656,6 +1661,7 @@ class RiveCoreContext {
case TextModifierRangeBase.unitsValuePropertyKey: case TextModifierRangeBase.unitsValuePropertyKey:
case TextModifierRangeBase.typeValuePropertyKey: case TextModifierRangeBase.typeValuePropertyKey:
case TextModifierRangeBase.modeValuePropertyKey: case TextModifierRangeBase.modeValuePropertyKey:
case TextModifierRangeBase.runIdPropertyKey:
case TextStyleFeatureBase.tagPropertyKey: case TextStyleFeatureBase.tagPropertyKey:
case TextStyleFeatureBase.featureValuePropertyKey: case TextStyleFeatureBase.featureValuePropertyKey:
case TextVariationModifierBase.axisTagPropertyKey: case TextVariationModifierBase.axisTagPropertyKey:
@ -1996,6 +2002,8 @@ class RiveCoreContext {
return (object as TextModifierRangeBase).typeValue; return (object as TextModifierRangeBase).typeValue;
case TextModifierRangeBase.modeValuePropertyKey: case TextModifierRangeBase.modeValuePropertyKey:
return (object as TextModifierRangeBase).modeValue; return (object as TextModifierRangeBase).modeValue;
case TextModifierRangeBase.runIdPropertyKey:
return (object as TextModifierRangeBase).runId;
case TextStyleFeatureBase.tagPropertyKey: case TextStyleFeatureBase.tagPropertyKey:
return (object as TextStyleFeatureBase).tag; return (object as TextStyleFeatureBase).tag;
case TextStyleFeatureBase.featureValuePropertyKey: case TextStyleFeatureBase.featureValuePropertyKey:
@ -2753,6 +2761,11 @@ class RiveCoreContext {
object.modeValue = value; object.modeValue = value;
} }
break; break;
case TextModifierRangeBase.runIdPropertyKey:
if (object is TextModifierRangeBase) {
object.runId = value;
}
break;
case TextStyleFeatureBase.tagPropertyKey: case TextStyleFeatureBase.tagPropertyKey:
if (object is TextStyleFeatureBase) { if (object is TextStyleFeatureBase) {
object.tag = value; object.tag = value;

View File

@ -238,6 +238,30 @@ abstract class TextModifierRangeBase extends ContainerComponent {
void offsetChanged(double from, double to); 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 @override
void copy(covariant TextModifierRangeBase source) { void copy(covariant TextModifierRangeBase source) {
super.copy(source); super.copy(source);
@ -251,5 +275,6 @@ abstract class TextModifierRangeBase extends ContainerComponent {
_falloffFrom = source._falloffFrom; _falloffFrom = source._falloffFrom;
_falloffTo = source._falloffTo; _falloffTo = source._falloffTo;
_offset = source._offset; _offset = source._offset;
_runId = source._runId;
} }
} }

View File

@ -1,13 +1,14 @@
import 'dart:math'; import 'dart:math';
import 'dart:typed_data';
import 'package:collection/collection.dart'; 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/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/cubic_interpolator_component.dart';
import 'package:rive/src/rive_core/animation/interpolator.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.dart';
import 'package:rive/src/rive_core/component_dirt.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_modifier_group.dart';
import 'package:rive/src/rive_core/text/text_value_run.dart';
import 'package:rive_common/rive_text.dart'; import 'package:rive_common/rive_text.dart';
export 'package:rive/src/generated/text/text_modifier_range_base.dart'; export 'package:rive/src/generated/text/text_modifier_range_base.dart';
@ -110,25 +111,36 @@ class TextModifierRange extends TextModifierRangeBase {
if (_rangeMapper != null) { if (_rangeMapper != null) {
return; return;
} }
int start = 0;
int end = text.length;
if (run != null) {
start = run!.offset;
end = start + run!.text.length;
}
switch (units) { switch (units) {
case TextRangeUnits.charactersExcludingSpaces: case TextRangeUnits.charactersExcludingSpaces:
_rangeMapper = RangeMapper.fromCharacters( _rangeMapper = RangeMapper.fromCharacters(
text, text,
start,
end,
glyphLookup, glyphLookup,
withoutSpaces: true, withoutSpaces: true,
); );
break; break;
case TextRangeUnits.words: case TextRangeUnits.words:
_rangeMapper = RangeMapper.fromWords(text); _rangeMapper = RangeMapper.fromWords(text, start, end);
break; break;
case TextRangeUnits.lines: case TextRangeUnits.lines:
if (shape != null && lines != null) { if (shape != null && lines != null) {
_rangeMapper = RangeMapper.fromLines(text, shape, lines, glyphLookup); _rangeMapper = RangeMapper.fromLines(
text, start, end, shape, lines, glyphLookup);
} }
break; break;
default: default:
_rangeMapper = RangeMapper.fromCharacters( _rangeMapper = RangeMapper.fromCharacters(
text, text,
start,
end,
glyphLookup, glyphLookup,
); );
break; break;
@ -247,6 +259,30 @@ class TextModifierRange extends TextModifierRangeBase {
@override @override
void strengthChanged(double from, double to) => modifierGroup?.rangeChanged(); 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 // See word indices and word lengths implementation above, we basically want the
@ -270,7 +306,7 @@ class RangeMapper {
unitLengths = Uint32List.fromList(lengths); unitLengths = Uint32List.fromList(lengths);
/// Build a RangeMapper from the words in [text]. /// 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) { if (text.isEmpty) {
return null; return null;
} }
@ -280,12 +316,21 @@ class RangeMapper {
int characterCount = 0; int characterCount = 0;
int index = 0; int index = 0;
int indexFrom = 0;
for (final unit in text.codeUnits) { for (final unit in text.codeUnits) {
if (wantWhiteSpace == isWhiteSpace(unit)) { if (wantWhiteSpace == isWhiteSpace(unit)) {
if (!wantWhiteSpace) { if (!wantWhiteSpace) {
indices.add(index); indexFrom = index;
} else { } 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; characterCount = 0;
} }
@ -296,23 +341,20 @@ class RangeMapper {
} }
index++; index++;
} }
indices.add(end);
if (characterCount != 0) {
lengths.add(characterCount);
indices.add(index);
}
return RangeMapper(indices, lengths); return RangeMapper(indices, lengths);
} }
/// Build a RangeMapper from the words in [text]. /// 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}) { {bool withoutSpaces = false}) {
if (text.isEmpty) { if (text.isEmpty) {
return null; return null;
} }
List<int> indices = []; List<int> indices = [];
List<int> lengths = []; List<int> lengths = [];
for (int i = 0; i < text.length;) { for (int i = start; i < end;) {
var unit = text.codeUnits[i]; var unit = text.codeUnits[i];
if (withoutSpaces && isWhiteSpace(unit)) { if (withoutSpaces && isWhiteSpace(unit)) {
i++; i++;
@ -325,12 +367,12 @@ class RangeMapper {
i += codePoints; i += codePoints;
} }
indices.add(text.length); indices.add(end);
return RangeMapper(indices, lengths); return RangeMapper(indices, lengths);
} }
static RangeMapper? fromLines(String text, TextShapeResult shape, static RangeMapper? fromLines(String text, int start, int end,
BreakLinesResult lines, GlyphLookup glyphLookup) { TextShapeResult shape, BreakLinesResult lines, GlyphLookup glyphLookup) {
if (text.isEmpty) { if (text.isEmpty) {
return null; return null;
} }
@ -342,23 +384,25 @@ class RangeMapper {
final paragraph = shape.paragraphs[paragraphIndex++]; final paragraph = shape.paragraphs[paragraphIndex++];
var glyphRuns = paragraph.runs; var glyphRuns = paragraph.runs;
for (final line in paragraphLines) { for (final line in paragraphLines) {
// if (line.endIndex == 0) {
// // Empty line.
// continue;
// }
var rf = glyphRuns[line.startRun]; var rf = glyphRuns[line.startRun];
var indexFrom = rf.textIndexAt(line.startIndex); var indexFrom = rf.textIndexAt(line.startIndex);
var rt = glyphRuns[line.endRun]; var rt = glyphRuns[line.endRun];
var endGlyphIndex = max( var endGlyphIndex = max(0, line.endIndex - 1);
0, line.endIndex - 1); //min(rt.glyphCount - 1, line.endIndex - 1);
var indexTo = rt.textIndexAt(endGlyphIndex); var indexTo = rt.textIndexAt(endGlyphIndex);
indexTo += glyphLookup.count(indexTo); 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); return RangeMapper(indices, lengths);
} }

View File

@ -21,6 +21,22 @@ class TextValueRun extends TextValueRunBase {
_style?.ref(this); _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(); void markShapeDirty() => textComponent?.markShapeDirty();
@override @override