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

feat: Structured text and text styles #1830

Merged
merged 44 commits into from
Aug 10, 2022
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
54db1b1
text structure nodes
st-pasha Jul 5, 2022
e6ca7ed
+ few more nodes
st-pasha Jul 5, 2022
7538623
styles and elements classes
st-pasha Jul 5, 2022
6fe3ccc
DocumentElement wip
st-pasha Jul 5, 2022
710a6dd
move DocumentElement
st-pasha Jul 6, 2022
4e6a269
background elements
st-pasha Jul 7, 2022
f595720
rename into Element
st-pasha Jul 7, 2022
f1edf94
Element class
st-pasha Jul 8, 2022
35656d2
wip
st-pasha Jul 8, 2022
ca5ea67
remove DocumentElement class
st-pasha Jul 8, 2022
3ece7d7
move format() method
st-pasha Jul 8, 2022
9b773d4
format for ParagraphNode
st-pasha Jul 8, 2022
79432e4
an example
st-pasha Jul 8, 2022
32c27ed
Move DocumentNode
st-pasha Jul 9, 2022
e9e0efc
Remove Element.layout()
st-pasha Jul 9, 2022
e486b7a
Created BlockElement base class
st-pasha Jul 9, 2022
38e61b2
Moved ParagraphNode into a separate file;
st-pasha Jul 9, 2022
6fda6e9
update Paragraphs formatter
st-pasha Jul 9, 2022
5bc50e2
wip
st-pasha Jul 9, 2022
a3dfac2
docs
st-pasha Jul 9, 2022
e74babc
render background for paragraphs
st-pasha Jul 9, 2022
f5c2cde
add padding for paragraphs
st-pasha Jul 9, 2022
be55e57
RectElement is no longer a block
st-pasha Jul 10, 2022
a99291c
same for RRectElement
st-pasha Jul 10, 2022
6f0625d
Moved BlockNode into separate file
st-pasha Jul 11, 2022
c08d60a
clean up BackgroundStyle
st-pasha Jul 12, 2022
df16df5
copyWith() methods for styles
st-pasha Jul 12, 2022
7b93eb3
add root Style class
st-pasha Jul 13, 2022
8621bef
added header support
st-pasha Jul 13, 2022
49b8f7d
fix imports
st-pasha Jul 13, 2022
97d3958
documentation for Style
st-pasha Jul 13, 2022
c82a1b9
acquire backgroundStyle too
st-pasha Jul 13, 2022
17fe082
Merge branch 'main' into ps.document-style
st-pasha Aug 1, 2022
549609d
fix accidental renames
st-pasha Aug 1, 2022
a7e6eff
format
st-pasha Aug 1, 2022
1726543
analyze
st-pasha Aug 1, 2022
f625d3e
remove comment
st-pasha Aug 1, 2022
2a2e1a5
move TextElement into elements/
st-pasha Aug 1, 2022
3b667c2
analysis issues
st-pasha Aug 1, 2022
fe2d339
remove dead code
st-pasha Aug 3, 2022
339e7d5
Headers of levels 4-6
st-pasha Aug 10, 2022
22b3856
Merge branch 'main' into ps.document-style
st-pasha Aug 10, 2022
d86770e
Merge branch 'main' into ps.document-style
spydon Aug 10, 2022
e6a1794
Merge branch 'main' into ps.document-style
spydon Aug 10, 2022
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
7 changes: 7 additions & 0 deletions examples/lib/stories/rendering/rendering.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:examples/stories/rendering/layers_example.dart';
import 'package:examples/stories/rendering/nine_tile_box_example.dart';
import 'package:examples/stories/rendering/particles_example.dart';
import 'package:examples/stories/rendering/particles_interactive_example.dart';
import 'package:examples/stories/rendering/rich_text_example.dart';
import 'package:examples/stories/rendering/text_example.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -59,5 +60,11 @@ void addRenderingStories(Dashbook dashbook) {
),
codeLink: baseLink('rendering/particles_interactive_example.dart'),
info: ParticlesInteractiveExample.description,
)
..add(
'Rich Text',
(_) => GameWidget(game: RichTextExample()),
codeLink: baseLink('rendering/rich_text_example.dart'),
info: RichTextExample.description,
);
}
72 changes: 72 additions & 0 deletions examples/lib/stories/rendering/rich_text_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/text.dart';
import 'package:flutter/painting.dart';

class RichTextExample extends FlameGame {
static String description = '';

@override
Color backgroundColor() => const Color(0xFF888888);

@override
Future<void> onLoad() async {
add(MyTextComponent()..position = Vector2.all(100));
}
}

class MyTextComponent extends PositionComponent {
late final Element element;

@override
Future<void> onLoad() async {
final style = DocumentStyle(
width: 400,
height: 200,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14),
background: BackgroundStyle(
color: const Color(0xFFFFFFEE),
borderColor: const Color(0xFF000000),
borderWidth: 2.0,
),
paragraphStyle: BlockStyle(
margin: const EdgeInsets.symmetric(vertical: 6),
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 6),
background: BackgroundStyle(
color: const Color(0xFFFFF0CB),
borderColor: const Color(0xFFAAAAAA),
),
),
);
final document = DocumentNode([
ParagraphNode.simple(
'Anything could be true. The so-called laws of nature were nonsense.',
),
ParagraphNode.simple(
'The law of gravity was nonsense. "If I wished," O\'Brien had said, '
'"I could float off this floor like a soap bubble." Winston worked it '
'out. "If he thinks he floats off the floor, and I simultaneously '
'think I can see him do it, then the thing happens."',
),
ParagraphNode.simple(
'Suddenly, like a lump of submerged wreckage breaking the surface of '
'water, the thought burst into his mind: "It doesn\'t really happen. '
'We imagine it. It is hallucination."',
),
ParagraphNode.simple(
'He pushed the thought under instantly. The fallacy was obvious. It '
'presupposed that somewhere or other, outside oneself, there was a '
'"real" world where "real" things happened. But how could there be '
'such a world? What knowledge have we of anything, save through our '
'own minds? All happenings are in the mind. Whatever happens in all '
'minds, truly happens.',
),
]);
element = document.format(style);
}

@override
void render(Canvas canvas) {
element.render(canvas);
}
}
2 changes: 1 addition & 1 deletion packages/flame/lib/src/components/text_component.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'dart:ui';

import 'package:flame/components.dart';
import 'package:flame/src/text/elements/text_element.dart';
import 'package:flame/src/text/formatter_text_renderer.dart';
import 'package:flame/src/text/inline/text_element.dart';
import 'package:flame/src/text/text_renderer.dart';
import 'package:flutter/painting.dart';
import 'package:meta/meta.dart';
Expand Down
2 changes: 1 addition & 1 deletion packages/flame/lib/src/text/common/text_line.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:flame/src/text/common/line_metrics.dart';
import 'package:flame/src/text/inline/text_element.dart';
import 'package:flame/src/text/elements/text_element.dart';

/// [TextLine] is an abstract class describing a single line (or a fragment of
/// a line) of a laid-out text.
Expand Down
12 changes: 12 additions & 0 deletions packages/flame/lib/src/text/common/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'dart:math';

import 'package:meta/meta.dart';

@internal
double collapseMargin(double margin1, double margin2) {
if (margin1 >= 0) {
return (margin2 < 0) ? margin1 + margin2 : max(margin1, margin2);
} else {
return (margin2 < 0) ? min(margin1, margin2) : margin1 + margin2;
}
}
12 changes: 12 additions & 0 deletions packages/flame/lib/src/text/elements/block_element.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:flame/src/text/elements/element.dart';

/// [BlockElement] is the base class for [Element]s with rectangular shape and
/// "block" placement rules.
///
/// Within HTML, this corresponds to elements with `display: block` property,
/// such as `<div>` or `<blockquote>`.
abstract class BlockElement extends Element {
BlockElement(this.width, this.height);
double width;
double height;
}
22 changes: 22 additions & 0 deletions packages/flame/lib/src/text/elements/element.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'dart:ui';

/// An [Element] is a basic building block of a rich-text document.
///
/// Elements are concrete and "physical": they are objects that are ready to be
/// rendered on a canvas. This property distinguishes them from Nodes (which are
/// structured pieces of text), and from Styles (which are descriptors for how
/// arbitrary pieces of text ought to be rendered).
///
/// Elements are at the final stage of the text rendering pipeline, they are
/// created during the layout step.
abstract class Element {
void translate(double dx, double dy);

/// Renders the element on the [canvas], at coordinates determined during the
/// layout.
///
/// In order to render the element at a different location, consider either
/// calling the [translate] method, or applying a translation transform to the
/// canvas beforehand.
void render(Canvas canvas);
}
20 changes: 20 additions & 0 deletions packages/flame/lib/src/text/elements/group_element.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'dart:ui';

import 'package:flame/src/text/elements/block_element.dart';
import 'package:flame/src/text/elements/element.dart';

class GroupElement extends BlockElement {
GroupElement(super.width, super.height, this.children);

final List<Element> children;

@override
void translate(double dx, double dy) {
children.forEach((child) => child.translate(dx, dy));
}

@override
void render(Canvas canvas) {
children.forEach((child) => child.render(canvas));
}
}
21 changes: 21 additions & 0 deletions packages/flame/lib/src/text/elements/rect_element.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'dart:ui';

import 'package:flame/src/text/elements/element.dart';

class RectElement extends Element {
RectElement(double width, double height, this._paint)
: _rect = Rect.fromLTWH(0, 0, width, height);

Rect _rect;
final Paint _paint;

@override
void translate(double dx, double dy) {
_rect = _rect.translate(dx, dy);
}

@override
void render(Canvas canvas) {
canvas.drawRect(_rect, _paint);
}
}
25 changes: 25 additions & 0 deletions packages/flame/lib/src/text/elements/rrect_element.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'dart:ui';

import 'package:flame/src/text/elements/element.dart';

class RRectElement extends Element {
RRectElement(
double width,
double height,
double radius,
this._paint,
) : _rrect = RRect.fromLTRBR(0, 0, width, height, Radius.circular(radius));

RRect _rrect;
final Paint _paint;

@override
void translate(double dx, double dy) {
_rrect = _rrect.shift(Offset(dx, dy));
}

@override
void render(Canvas canvas) {
canvas.drawRRect(_rrect, _paint);
}
}
12 changes: 12 additions & 0 deletions packages/flame/lib/src/text/elements/text_element.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:flame/src/text/common/text_line.dart';
import 'package:flame/src/text/elements/element.dart';

/// [TextElement] is the base class describing a span of text that has *inline*
/// placement rules.
///
/// Concrete implementations of this class must know how to perform own layout
/// (i.e. determine the exact placement and size of each internal piece), and
/// then render on a canvas afterwards.
abstract class TextElement extends Element {
TextLine get lastLine;
}
2 changes: 1 addition & 1 deletion packages/flame/lib/src/text/formatters/text_formatter.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:flame/src/text/inline/text_element.dart';
import 'package:flame/src/text/elements/text_element.dart';

/// [TextFormatter] is an abstract interface for a class that can convert an
/// arbitrary string of text into a renderable [TextElement].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'dart:ui';

import 'package:flame/src/text/common/line_metrics.dart';
import 'package:flame/src/text/common/text_line.dart';
import 'package:flame/src/text/inline/text_element.dart';
import 'package:flame/src/text/elements/text_element.dart';

class SpriteFontTextElement extends TextElement implements TextLine {
SpriteFontTextElement({
Expand Down
22 changes: 0 additions & 22 deletions packages/flame/lib/src/text/inline/text_element.dart

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import 'dart:ui';

import 'package:flame/src/text/common/line_metrics.dart';
import 'package:flame/src/text/common/text_line.dart';
import 'package:flame/src/text/inline/text_element.dart';
import 'package:flame/src/text/elements/element.dart';
import 'package:flame/src/text/elements/text_element.dart';
import 'package:flutter/rendering.dart' show TextBaseline, TextPainter;

class TextPainterTextElement extends TextElement implements TextLine {
class TextPainterTextElement extends TextElement implements TextLine, Element {
TextPainterTextElement(this._textPainter)
: _box = LineMetrics(
ascent: _textPainter
Expand Down
41 changes: 41 additions & 0 deletions packages/flame/lib/src/text/nodes.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'package:flame/src/text/nodes/block_node.dart';

class GroupBlockNode extends BlockNode {
GroupBlockNode(this.children);

final List<BlockNode> children;
}

class BlockquoteNode extends GroupBlockNode {
BlockquoteNode(super.children);
}

abstract class TextNode {}

class PlainTextNode extends TextNode {
PlainTextNode(this.text);

final String text;
}

class GroupTextNode extends TextNode {
GroupTextNode(this.children);

final List<TextNode> children;
}

class BoldTextNode extends GroupTextNode {
BoldTextNode(super.children);
}

class ItalicTextNode extends GroupTextNode {
ItalicTextNode(super.children);
}

class StrikethroughTextNode extends GroupTextNode {
StrikethroughTextNode(super.children);
}

class HighlightedTextNode extends GroupTextNode {
HighlightedTextNode(super.children);
}
55 changes: 55 additions & 0 deletions packages/flame/lib/src/text/nodes/block_node.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'package:flame/src/text/elements/element.dart';
import 'package:flame/src/text/elements/group_element.dart';
import 'package:flame/src/text/elements/rect_element.dart';
import 'package:flame/src/text/elements/rrect_element.dart';
import 'package:flame/src/text/styles/background_style.dart';

/// An abstract base class for all entities with "block" placement rules.
abstract class BlockNode {
Element? makeBackground(BackgroundStyle? style, double width, double height) {
if (style == null) {
return null;
}
final out = <Element>[];
final backgroundPaint = style.backgroundPaint;
final borderPaint = style.borderPaint;
final borders = style.borderWidths;
final radius = style.borderRadius;

if (backgroundPaint != null) {
if (radius == 0) {
out.add(RectElement(width, height, backgroundPaint));
} else {
out.add(RRectElement(width, height, radius, backgroundPaint));
}
}
if (borderPaint != null) {
if (radius == 0) {
out.add(
RectElement(
width - borders.horizontal / 2,
height - borders.vertical / 2,
borderPaint,
)..translate(borders.left / 2, borders.top / 2),
);
} else {
out.add(
RRectElement(
width - borders.horizontal / 2,
height - borders.vertical / 2,
radius,
borderPaint,
)..translate(borders.left / 2, borders.top / 2),
);
}
}
if (out.isEmpty) {
return null;
}
if (out.length == 1) {
return out.first;
} else {
return GroupElement(width, height, out);
}
}
}
Loading