Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix cascading and fallback propagation of text styles #3129

Merged
merged 3 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/flame/lib/src/text/elements/group_text_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:math';
import 'dart:ui';

import 'package:flame/text.dart';
import 'package:meta/meta.dart';

class GroupTextElement extends InlineTextElement {
GroupTextElement(List<InlineTextElement> children)
Expand All @@ -15,6 +16,9 @@ class GroupTextElement extends InlineTextElement {
@override
LineMetrics get metrics => _metrics;

@visibleForTesting
List<InlineTextElement> get children => List.unmodifiable(_children);

@override
void draw(Canvas canvas) {
for (final child in _children) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:ui';

import 'package:flame/text.dart';
import 'package:flutter/rendering.dart' as flutter;
import 'package:meta/meta.dart';

class TextPainterTextElement extends InlineTextElement {
TextPainterTextElement(this._textPainter)
Expand All @@ -16,6 +17,9 @@ class TextPainterTextElement extends InlineTextElement {
final flutter.TextPainter _textPainter;
final LineMetrics _box;

@visibleForTesting
flutter.TextPainter get textPainter => _textPainter;

@override
LineMetrics get metrics => _box;

Expand Down
42 changes: 24 additions & 18 deletions packages/flame/lib/src/text/styles/document_style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,18 @@ class DocumentStyle extends FlameTextStyle {
BlockStyle? header4,
BlockStyle? header5,
BlockStyle? header6,
}) : _text = FlameTextStyle.merge(text, DocumentStyle.defaultTextStyle),
_boldText = FlameTextStyle.merge(boldText, BoldTextNode.defaultStyle),
}) : _text = FlameTextStyle.merge(DocumentStyle.defaultTextStyle, text),
_boldText = FlameTextStyle.merge(BoldTextNode.defaultStyle, boldText),
_italicText =
FlameTextStyle.merge(italicText, ItalicTextNode.defaultStyle),
FlameTextStyle.merge(ItalicTextNode.defaultStyle, italicText),
_paragraph =
FlameTextStyle.merge(paragraph, ParagraphNode.defaultStyle),
_header1 = FlameTextStyle.merge(header1, HeaderNode.defaultStyleH1),
_header2 = FlameTextStyle.merge(header2, HeaderNode.defaultStyleH2),
_header3 = FlameTextStyle.merge(header3, HeaderNode.defaultStyleH3),
_header4 = FlameTextStyle.merge(header4, HeaderNode.defaultStyleH4),
_header5 = FlameTextStyle.merge(header5, HeaderNode.defaultStyleH5),
_header6 = FlameTextStyle.merge(header6, HeaderNode.defaultStyleH6);
FlameTextStyle.merge(ParagraphNode.defaultStyle, paragraph),
_header1 = FlameTextStyle.merge(HeaderNode.defaultStyleH1, header1),
_header2 = FlameTextStyle.merge(HeaderNode.defaultStyleH2, header2),
_header3 = FlameTextStyle.merge(HeaderNode.defaultStyleH3, header3),
_header4 = FlameTextStyle.merge(HeaderNode.defaultStyleH4, header4),
_header5 = FlameTextStyle.merge(HeaderNode.defaultStyleH5, header5),
_header6 = FlameTextStyle.merge(HeaderNode.defaultStyleH6, header6);

final InlineTextStyle? _text;
final InlineTextStyle? _boldText;
Expand Down Expand Up @@ -114,19 +114,25 @@ class DocumentStyle extends FlameTextStyle {
width: other.width ?? width,
height: other.height ?? height,
padding: other.padding,
background: merge(other.background, background)! as BackgroundStyle,
paragraph: merge(other.paragraph, paragraph)! as BlockStyle,
header1: merge(other.header1, header1)! as BlockStyle,
header2: merge(other.header2, header2)! as BlockStyle,
header3: merge(other.header3, header3)! as BlockStyle,
header4: merge(other.header4, header4)! as BlockStyle,
header5: merge(other.header5, header5)! as BlockStyle,
header6: merge(other.header6, header6)! as BlockStyle,
text: FlameTextStyle.merge(_text, other.text),
boldText: FlameTextStyle.merge(_boldText, other.boldText),
italicText: FlameTextStyle.merge(_italicText, other.italicText),
background: merge(background, other.background) as BackgroundStyle?,
paragraph: merge(paragraph, other.paragraph) as BlockStyle?,
header1: merge(header1, other.header1) as BlockStyle?,
header2: merge(header2, other.header2) as BlockStyle?,
header3: merge(header3, other.header3) as BlockStyle?,
header4: merge(header4, other.header4) as BlockStyle?,
header5: merge(header5, other.header5) as BlockStyle?,
header6: merge(header6, other.header6) as BlockStyle?,
);
}

final Map<FlameTextStyle, Map<FlameTextStyle, FlameTextStyle>>
_mergedStylesCache = {};

/// Merges two [FlameTextStyle]s together, preferring the properties of
/// [style2] if present, falling back to the properties of [style1].
FlameTextStyle? merge(FlameTextStyle? style1, FlameTextStyle? style2) {
if (style1 == null) {
return style2;
Expand Down
4 changes: 4 additions & 0 deletions packages/flame/lib/src/text/styles/flame_text_style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@ import 'package:flame/text.dart';
abstract class FlameTextStyle {
const FlameTextStyle();

/// Creates a new [FlameTextStyle], preferring the properties of [other]
/// if present, falling back to the properties of `this`.
FlameTextStyle copyWith(covariant FlameTextStyle other);

/// Merges two [FlameTextStyle]s together, preferring the properties of
/// [style2] if present, falling back to the properties of [style1].
static T? merge<T extends FlameTextStyle>(T? style1, T? style2) {
if (style1 == null) {
return style2;
Expand Down
14 changes: 7 additions & 7 deletions packages/flame/lib/src/text/styles/inline_text_style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ class InlineTextStyle extends FlameTextStyle {
@override
InlineTextStyle copyWith(InlineTextStyle other) {
return InlineTextStyle(
color: color ?? other.color,
fontFamily: fontFamily ?? other.fontFamily,
fontSize: fontSize ?? other.fontSize,
fontScale: fontScale ?? other.fontScale,
fontWeight: fontWeight ?? other.fontWeight,
fontStyle: fontStyle ?? other.fontStyle,
letterSpacing: letterSpacing ?? other.letterSpacing,
color: other.color ?? color,
fontFamily: other.fontFamily ?? fontFamily,
fontSize: other.fontSize ?? fontSize,
fontScale: other.fontScale ?? fontScale,
fontWeight: other.fontWeight ?? fontWeight,
fontStyle: other.fontStyle ?? fontStyle,
letterSpacing: other.letterSpacing ?? letterSpacing,
);
}

Expand Down
146 changes: 146 additions & 0 deletions packages/flame/test/text/text_style_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import 'package:flame/src/text/elements/group_element.dart';
import 'package:flame/src/text/elements/group_text_element.dart';
import 'package:flame/text.dart';
import 'package:flutter/rendering.dart';
import 'package:test/test.dart';

void main() {
group('text styles', () {
test('document style defaults are applied', () {
final style = DocumentStyle();
expect(style.text.fontSize, 16);
expect(style.boldText.fontWeight, FontWeight.bold);
expect(style.italicText.fontStyle, FontStyle.italic);
expect(style.paragraph.margin, const EdgeInsets.all(6));
expect(style.paragraph.padding, EdgeInsets.zero);
});

test('document style parameters are respected', () {
final style = DocumentStyle(
text: InlineTextStyle(
fontSize: 8,
),
boldText: InlineTextStyle(
fontWeight: FontWeight.w900,
),
italicText: InlineTextStyle(
fontStyle: FontStyle.normal,
),
paragraph: const BlockStyle(
margin: EdgeInsets.zero,
padding: EdgeInsets.zero,
),
);
expect(style.text.fontSize, 8);
expect(style.boldText.fontWeight, FontWeight.w900);
expect(style.italicText.fontStyle, FontStyle.normal);
expect(style.paragraph.margin, EdgeInsets.zero);
expect(style.paragraph.padding, EdgeInsets.zero);
});

test('document style can be copied', () {
final style = DocumentStyle(
text: InlineTextStyle(
fontSize: 8,
),
boldText: InlineTextStyle(
fontWeight: FontWeight.w900,
),
paragraph: const BlockStyle(
margin: EdgeInsets.zero,
padding: EdgeInsets.zero,
),
background: BackgroundStyle(
color: const Color(0xFFFF00FF),
),
);
final newStyle = style.copyWith(
DocumentStyle(
text: InlineTextStyle(
fontSize: 10,
),
boldText: InlineTextStyle(
fontWeight: FontWeight.w900,
),
italicText: InlineTextStyle(
fontStyle: FontStyle.italic,
),
paragraph: const BlockStyle(
margin: EdgeInsets.all(10),
padding: EdgeInsets.zero,
),
),
);
expect(newStyle.text.fontSize, 10);
expect(newStyle.boldText.fontWeight, FontWeight.w900);
expect(newStyle.italicText.fontStyle, FontStyle.italic);
expect(newStyle.paragraph.margin, const EdgeInsets.all(10));
expect(newStyle.paragraph.padding, EdgeInsets.zero);
expect(
newStyle.background!.backgroundPaint!.color,
const Color(0xFFFF00FF),
);
});

test('styles are cascading', () {
final style = DocumentStyle(
width: 600.0,
height: 400.0,
text: InlineTextStyle(
fontFamily: 'Helvetica',
fontSize: 8,
),
boldText: InlineTextStyle(
fontFamily: 'Arial',
fontSize: 10,
fontWeight: FontWeight.w900,
),
italicText: InlineTextStyle(
fontFamily: 'Arial',
fontSize: 6,
fontStyle: FontStyle.italic,
),
paragraph: BlockStyle(
text: InlineTextStyle(
fontSize: 12,
),
),
);
final document = DocumentRoot([
ParagraphNode.group([
PlainTextNode('This is '),
BoldTextNode.simple('my'),
PlainTextNode(' town - '),
ItalicTextNode.simple('The Sheriff'),
]),
]);

final element = document.format(style);
final groupElement = element.children.first as GroupElement;
final groupTextElement = groupElement.children.first as GroupTextElement;
final styles = groupTextElement.children.map((e) {
return (e as TextPainterTextElement).textPainter.text!.style!;
}).toList();

expect(styles[0].fontSize, 12);
expect(styles[0].fontWeight, isNull);
expect(styles[0].fontStyle, isNull);
expect(styles[0].fontFamily, 'Helvetica');

expect(styles[1].fontSize, 10);
expect(styles[1].fontWeight, FontWeight.w900);
expect(styles[1].fontStyle, isNull);
expect(styles[1].fontFamily, 'Arial');

expect(styles[2].fontSize, 12);
expect(styles[2].fontWeight, isNull);
expect(styles[2].fontStyle, isNull);
expect(styles[2].fontFamily, 'Helvetica');

expect(styles[3].fontSize, 6);
expect(styles[3].fontWeight, isNull);
expect(styles[3].fontStyle, FontStyle.italic);
expect(styles[3].fontFamily, 'Arial');
});
});
}
Loading