mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 11:43:19 +08:00
refactor!: Rename (Text) Elements, Nodes and Styles for clarity, add docs (#2700)
This occurred to me after a discussion on the [new FCS component PR](https://github.com/flame-engine/flame/pull/2694#discussion_r1312450113). As per usual, @spydon has opened my eyes to the ultimate truth: We should rename loads of files, and it shall affect almost no one. The idea is to (1) add a "Text" prefix to all text-rendering-related classes and (2) rename the existing `Text*` to `InlineText*` (which is what they are). This PR is a bit big, but the changes should hopefully be simple to review, and can be broken down into: * Add a proper base class for the node inheritance chain, call it TextNode (while working on Flame Markdown I realized the value this will have to me) * Rename the old TextNode to InlineTextNode * Rename DocumentNode to DocumentRoot because it is not a node * Rename Element to TextElement * Rename the old TextElement to InlineTextElement * Rename Style to FlameTextStyle (note: we could consider dropping the Flame here) * Rename the old FlameTextStyle to InlineTextStyle * Update the docs accordingly * Add some more diagrams and explanations to the docs, following the new nomenclature * I also updated our "internal" imports to use the text module to make life so much easier (this could arguably be done in a separate PR, but I honestly think it's easier to review together, please lmk if you prefer me to split). These are all breaking changes but likely won't actually affect most users (see below). While this is breaking, it should hopefully not affect most users, because these are all infrastructure classes that most people aren't using directly. If you are using the FCS components, or the renderers `TextPaint` or `SpriteFontRenderer` directly, this should have zero effect to you. If you are using the Nodes, Stlyes or Elements directly, or have a custom TextRenderer, see below. Migrating should be a simple matter of renaming your type references: * from TextNode to InlineTextNode * from TextElement to InlineTextElement * from Element to TextElement * from FlameTextStyle to InlineTextStyle * from Style to FlameTextStyle Make sure to do it in the appropriate order not to cause any double-replace issues. If you are importing via the module `package:flame/text.dart`, which we highly encourage, you should not have to change any import statements whatsoever.
This commit is contained in:
@ -127,6 +127,7 @@ this section is for you.
|
||||
The following diagram showcases the class and inheritance structure of the text rendering pipeline:
|
||||
|
||||
```mermaid
|
||||
%%{init: { 'theme': 'dark' } }%%
|
||||
classDiagram
|
||||
%% renderers
|
||||
note for TextRenderer "This just the style (how).
|
||||
@ -265,21 +266,21 @@ it possible to test the layout, positioning and sizing of the elements without h
|
||||
font-based rendering.
|
||||
|
||||
|
||||
## Text Elements
|
||||
## Inline Text Elements
|
||||
|
||||
Text Elements are "pre-compiled", formatted and laid-out pieces of text with a specific styling
|
||||
A `TextElement` is a "pre-compiled", formatted and laid-out piece of text with a specific styling
|
||||
applied, ready to be rendered at any given position.
|
||||
|
||||
`TextElement` implements the `Element` interface and must implement their two methods, one that
|
||||
teaches how to translate it around and another on how to draw it to the canvas:
|
||||
A `InlineTextElement` implements the `TextElement` interface and must implement their two methods,
|
||||
one that teaches how to translate it around and another on how to draw it to the canvas:
|
||||
|
||||
```dart
|
||||
void translate(double dx, double dy);
|
||||
void draw(Canvas canvas);
|
||||
```
|
||||
|
||||
These methods are intended to be overwritten by the implementations of `TextElement` but probably
|
||||
will not be called directly by users; because a convenient `render` method is provided:
|
||||
These methods are intended to be overwritten by the implementations of `InlineTextElement`, and
|
||||
probably will not be called directly by users; because a convenient `render` method is provided:
|
||||
|
||||
```dart
|
||||
void render(
|
||||
@ -291,37 +292,212 @@ will not be called directly by users; because a convenient `render` method is pr
|
||||
|
||||
That allows the element to be rendered at a specific position, using a given anchor.
|
||||
|
||||
The interface also mandates (and provides) a getter for the LineMetrics object associated with that
|
||||
`TextElement`, which allows you (and the `render` implementation) to access sizing information
|
||||
related to the element (width, height, ascend, etc).
|
||||
The interface also mandates (and provides) a getter for the `LineMetrics` object associated with
|
||||
that `InlineTextElement`, which allows you (and the `render` implementation) to access sizing
|
||||
information related to the element (width, height, ascend, etc).
|
||||
|
||||
```dart
|
||||
LineMetrics get metrics;
|
||||
```
|
||||
|
||||
|
||||
## Elements, Nodes, and Styles
|
||||
## Text Elements, Text Nodes, and Text Styles
|
||||
|
||||
While normal renderers always work with TextElements directly, there is a bigger underlying
|
||||
While normal renderers always work with a `InlineTextElement` directly, there is a bigger underlying
|
||||
infrastructure that can be used to render more rich or formatter text.
|
||||
|
||||
Elements are a superset of TextElements that represent an arbitrary rendering block within a
|
||||
rich-text document. Essentially, they are concrete and "physical": they are objects that are ready
|
||||
to be rendered on a canvas.
|
||||
Text Elements are a superset of Inline Text Elements that represent an arbitrary rendering block
|
||||
within a rich-text document. Essentially, they 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,
|
||||
This property distinguishes them from Text Nodes, which are structured pieces of text, and from Text
|
||||
Styles (called `FlameTextStyle` in code to make it easier to work alongside Flutter's `TextStyle`),
|
||||
which are descriptors for how arbitrary pieces of text ought to be rendered.
|
||||
|
||||
So a user would use Node to describe a desired document of rich text; define Styles to apply to it;
|
||||
and use that to generate an Element. Depending on the type of rendering, the Element generated will
|
||||
be a TextElement, which brings us back to the normal flow of the rendering pipeline. The unique
|
||||
property of the Text-type element is that it exposes a LineMetrics that can be used for advanced
|
||||
rendering; while the other elements only expose a simpler `draw` method which is unaware of sizing
|
||||
and positioning.
|
||||
So, in the most general case, a user would use a `TextNode` to describe a desired piece of rich
|
||||
text; define a `FlameTextStyle` to apply to it; and use that to generate a `TextElement`. Depending
|
||||
on the type of rendering, the `TextElement` generated will be an `InlineTextElement`, which brings
|
||||
us back to the normal flow of the rendering pipeline. The unique property of the Inline-Text-type
|
||||
element is that it exposes a LineMetrics that can be used for advanced rendering; while the other
|
||||
elements only expose a simpler `draw` method which is unaware of sizing and positioning.
|
||||
|
||||
However the other types of Elements, Nodes and Style must be used if the intent is to create an
|
||||
entire Document, enriched with formatted text. Currently these extra features of the system are not
|
||||
exposed through FCS, but can be used directly.
|
||||
However, the other types of Text Elements, Text Nodes, and Text Styles must be used if the intent is
|
||||
to create an entire document (multiple blocks or paragraphs), enriched with formatted text.
|
||||
Currently, these extra features of the system are not exposed through FCS; but can be used directly.
|
||||
|
||||
An example of such usages can be seen in [this
|
||||
example](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/rendering/rich_text_example.dart).
|
||||
|
||||
|
||||
### Text Nodes and the Document Root
|
||||
|
||||
A `DocumentRoot` is not a `TextNode` (inheritance-wise) in itself but represents a grouping of
|
||||
`BlockNodes` that layout a "page" or "document" of rich text laid out in multiple blocks or
|
||||
paragraphs. It represents the entire document and can receive a global Style.
|
||||
|
||||
The first step to define your rich-text document is to create a Node, which will likely be a
|
||||
`DocumentRoot`.
|
||||
|
||||
It will first contain the top-most list of Block Nodes that can define headers, paragraphs or
|
||||
columns.
|
||||
|
||||
Then each of those blocks can contain other blocks or the Inline Text Nodes, either Plain Text Nodes
|
||||
or some rich-text with specific formatting.
|
||||
|
||||
Note that the hierarchy defined by the node structure is also used for styling purposes as per
|
||||
defined in the `FlameTextStyle` class.
|
||||
|
||||
The actual nodes all inherit from `TextNode` and are broken down by the following diagram:
|
||||
|
||||
```mermaid
|
||||
%%{init: { 'theme': 'dark' } }%%
|
||||
graph TD
|
||||
%% Config %%
|
||||
classDef default fill:#282828,stroke:#F6BE00;
|
||||
|
||||
%% Nodes %%
|
||||
TextNode("
|
||||
<big><strong>TextNode</strong></big>
|
||||
Can be thought of as an HTML DOM node;
|
||||
each subclass can be thought of as a specific tag.
|
||||
")
|
||||
BlockNode("
|
||||
<big><strong>BlockNode</strong></big>
|
||||
#quot;div#quot;
|
||||
")
|
||||
InlineTextNode("
|
||||
<big><strong>InlineTextNode</strong></big>
|
||||
#quot;span#quot;
|
||||
")
|
||||
ColumnNode("
|
||||
<big><strong>ColumnNode</strong></big>
|
||||
column-arranged group of other Block Nodes
|
||||
")
|
||||
TextBlockNode("
|
||||
<big><strong>TextBlockNode</strong></big>
|
||||
a #quot;div#quot; with an InlineTextNode as a direct child
|
||||
")
|
||||
HeaderNode("
|
||||
<big><strong>HeaderNode</strong></big>
|
||||
#quot;h1#quot; / #quot;h2#quot; / etc
|
||||
")
|
||||
ParagraphNode("
|
||||
<big><strong>ParagraphNode</strong></big>
|
||||
#quot;p#quot;
|
||||
")
|
||||
GroupTextNode("
|
||||
<big><strong>GroupTextNode</strong></big>
|
||||
groups other TextNodes in a single line
|
||||
")
|
||||
PlainTextNode("
|
||||
<big><strong>PlainTextNode</strong></big>
|
||||
just plain text, unformatted
|
||||
")
|
||||
ItalicTextNode("
|
||||
<big><strong>ItalicTextNode</strong></big>
|
||||
#quot;i#quot; / #quot;em#quot;
|
||||
")
|
||||
BoldTextNode("
|
||||
<big><strong>BoldTextNode</strong></big>
|
||||
#quot;b#quot; / #quot;strong#quot;
|
||||
")
|
||||
TextNode ----> BlockNode
|
||||
TextNode --------> InlineTextNode
|
||||
BlockNode --> ColumnNode
|
||||
BlockNode --> TextBlockNode
|
||||
TextBlockNode --> HeaderNode
|
||||
TextBlockNode --> ParagraphNode
|
||||
InlineTextNode --> GroupTextNode
|
||||
InlineTextNode --> PlainTextNode
|
||||
InlineTextNode --> BoldTextNode
|
||||
InlineTextNode --> ItalicTextNode
|
||||
```
|
||||
|
||||
|
||||
### (Flame) Text Styles
|
||||
|
||||
Text Styles can be applied to nodes to generate elements. They all inherit from `FlameTextStyle`
|
||||
abstract class (which is named as is to avoid confusion with Flutter's `TextStyle`).
|
||||
|
||||
They follow a tree-like structure, always having `DocumentStyle` as the root; this structure is
|
||||
leveraged to apply cascading style to the analogous Node structure. In fact, they are pretty similar
|
||||
to, and can be thought of as, CSS definitions.
|
||||
|
||||
The full inheritance chain can be seen on the following diagram:
|
||||
|
||||
```mermaid
|
||||
%%{init: { 'theme': 'dark' } }%%
|
||||
classDiagram
|
||||
%% Nodes %%
|
||||
class FlameTextStyle {
|
||||
copyWith()
|
||||
merge()
|
||||
}
|
||||
|
||||
note for FlameTextStyle "Root for all styles.
|
||||
Not to be confused with Flutter's TextStyle."
|
||||
|
||||
class DocumentStyle {
|
||||
<<for the entire Document Root>>
|
||||
size
|
||||
padding
|
||||
background [BackgroundStyle]
|
||||
specific styles [for blocks & inline]
|
||||
}
|
||||
|
||||
class BlockStyle {
|
||||
<<for Block Nodes>>
|
||||
margin, padding
|
||||
background [BackgroundStyle]
|
||||
text [InlineTextStyle]
|
||||
}
|
||||
|
||||
class BackgroundStyle {
|
||||
<<for Block or Document>>
|
||||
color
|
||||
border
|
||||
}
|
||||
|
||||
class InlineTextStyle {
|
||||
<<for any nodes>>
|
||||
font, color
|
||||
}
|
||||
|
||||
FlameTextStyle <|-- DocumentStyle
|
||||
FlameTextStyle <|-- BlockStyle
|
||||
FlameTextStyle <|-- BackgroundStyle
|
||||
FlameTextStyle <|-- InlineTextStyle
|
||||
```
|
||||
|
||||
|
||||
### Text Elements
|
||||
|
||||
Finally, we have the elements, that represent a combination of a node ("what") with a style ("how"),
|
||||
and therefore represent a pre-compiled, laid-out piece of rich text to be rendered on the Canvas.
|
||||
|
||||
Inline Text Elements specifically can alternatively be thought of as a combination of a
|
||||
`TextRenderer` (simplified "how") and a string (single line of "what").
|
||||
|
||||
That is because an `InlineTextStyle` can be converted to a specific `TextRenderer` via the
|
||||
`asTextRenderer` method, which is then used to lay out each line of text into a unique
|
||||
`InlineTextElement`.
|
||||
|
||||
When using the renderer directly, the entire layout process is skipped, and a single
|
||||
`TextPainterTextElement` or `SpriteFontTextElement` is returned.
|
||||
|
||||
As you can see, both definitions of an Element are, essentially, equivalent, all things considered.
|
||||
But it still leaves us with two paths for rendering text. Which one to pick? How to solve this
|
||||
conundrum?
|
||||
|
||||
When in doubt, the following guidelines can help you picking the best path for you:
|
||||
|
||||
- for the simplest way to render text, use `TextPaint` (basic renderer implementation)
|
||||
- you can use the FCS provided component `TextComponent` for that.
|
||||
- for rendering Sprite Fonts, you must use `SpriteFontRenderer` (a renderer implementation that
|
||||
accepts a `SpriteFont`);
|
||||
- for rendering multiple lines of text, with automatic line breaks, you have two options:
|
||||
- use the FCS `TextBoxComponent`, which uses any text renderer to draw each line of text as an
|
||||
Element, and does its own layout and line breaking;
|
||||
- use the Text Node & Style system to create your pre-laid-out Elements. Note: there is no current
|
||||
FCS component for it.
|
||||
- finally, in order to have formatted (or rich) text, you must use Text Nodes & Styles.
|
||||
|
||||
@ -219,7 +219,7 @@ class KeyboardKey extends PositionComponent {
|
||||
}
|
||||
|
||||
final String text;
|
||||
late final TextElement textElement;
|
||||
late final InlineTextElement textElement;
|
||||
late final RRect rect;
|
||||
|
||||
/// The RawKeyEvents may occur very fast, and out of sync with the game loop.
|
||||
|
||||
@ -17,7 +17,7 @@ class RichTextExample extends FlameGame {
|
||||
}
|
||||
|
||||
class MyTextComponent extends PositionComponent {
|
||||
late final Element element;
|
||||
late final TextElement element;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
@ -38,7 +38,7 @@ class MyTextComponent extends PositionComponent {
|
||||
),
|
||||
),
|
||||
);
|
||||
final document = DocumentNode([
|
||||
final document = DocumentRoot([
|
||||
HeaderNode.simple('1984', level: 1),
|
||||
ParagraphNode.simple(
|
||||
'Anything could be true. The so-called laws of nature were nonsense.',
|
||||
|
||||
@ -38,7 +38,7 @@ class TextComponent<T extends TextRenderer> extends PositionComponent {
|
||||
updateBounds();
|
||||
}
|
||||
|
||||
late TextElement _textElement;
|
||||
late InlineTextElement _textElement;
|
||||
|
||||
@internal
|
||||
void updateBounds() {
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
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';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@internal
|
||||
@ -17,11 +14,15 @@ double collapseMargin(double margin1, double margin2) {
|
||||
}
|
||||
|
||||
@internal
|
||||
Element? makeBackground(BackgroundStyle? style, double width, double height) {
|
||||
TextElement? makeBackground(
|
||||
BackgroundStyle? style,
|
||||
double width,
|
||||
double height,
|
||||
) {
|
||||
if (style == null) {
|
||||
return null;
|
||||
}
|
||||
final out = <Element>[];
|
||||
final out = <TextElement>[];
|
||||
final backgroundPaint = style.backgroundPaint;
|
||||
final borderPaint = style.borderPaint;
|
||||
final borders = style.borderWidths;
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import 'package:flame/src/text/elements/element.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
/// [BlockElement] is the base class for [Element]s with rectangular shape and
|
||||
/// "block" placement rules.
|
||||
/// [BlockElement] is the base class for [TextElement]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 {
|
||||
abstract class BlockElement extends TextElement {
|
||||
BlockElement(this.width, this.height);
|
||||
|
||||
final double width;
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flame/src/text/styles/style.dart';
|
||||
|
||||
/// An [Element] is a basic rendering 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 [Style]s (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 {
|
||||
/// Moves the element by ([dx], [dy]) relative to its current location.
|
||||
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 itself.
|
||||
void draw(Canvas canvas);
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
import 'package:flame/src/text/elements/block_element.dart';
|
||||
import 'package:flame/src/text/elements/element.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:flutter/rendering.dart' hide TextStyle;
|
||||
|
||||
class GroupElement extends BlockElement {
|
||||
@ -9,7 +8,7 @@ class GroupElement extends BlockElement {
|
||||
required this.children,
|
||||
}) : super(width, height);
|
||||
|
||||
final List<Element> children;
|
||||
final List<TextElement> children;
|
||||
|
||||
@override
|
||||
void translate(double dx, double dy) {
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/src/text/common/line_metrics.dart';
|
||||
import 'package:flame/src/text/elements/text_element.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
class GroupTextElement extends TextElement {
|
||||
GroupTextElement(List<TextElement> children)
|
||||
class GroupTextElement extends InlineTextElement {
|
||||
GroupTextElement(List<InlineTextElement> children)
|
||||
: assert(children.isNotEmpty, 'The children list cannot be empty'),
|
||||
_children = children,
|
||||
_metrics = _computeMetrics(children);
|
||||
|
||||
final List<TextElement> _children;
|
||||
final List<InlineTextElement> _children;
|
||||
final LineMetrics _metrics;
|
||||
|
||||
@override
|
||||
@ -31,7 +30,7 @@ class GroupTextElement extends TextElement {
|
||||
}
|
||||
}
|
||||
|
||||
static LineMetrics _computeMetrics(List<TextElement> elements) {
|
||||
static LineMetrics _computeMetrics(List<InlineTextElement> elements) {
|
||||
var width = 0.0;
|
||||
var ascent = 0.0;
|
||||
var descent = 0.0;
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
import 'package:flame/extensions.dart';
|
||||
import 'package:flame/src/anchor.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
/// [InlineTextElement] is the base class that represents a single line of text,
|
||||
/// laid out and prepared for rendering.
|
||||
abstract class InlineTextElement extends TextElement {
|
||||
/// The dimensions of this line.
|
||||
LineMetrics get metrics;
|
||||
|
||||
void render(
|
||||
Canvas canvas,
|
||||
Vector2 position, {
|
||||
Anchor anchor = Anchor.topLeft,
|
||||
}) {
|
||||
final box = metrics;
|
||||
translate(
|
||||
position.x - box.width * anchor.x,
|
||||
position.y - box.height * anchor.y - box.top,
|
||||
);
|
||||
draw(canvas);
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/src/text/elements/element.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
class RectElement extends Element {
|
||||
class RectElement extends TextElement {
|
||||
RectElement(double width, double height, this._paint)
|
||||
: _rect = Rect.fromLTWH(0, 0, width, height);
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/src/text/elements/element.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
class RRectElement extends Element {
|
||||
class RRectElement extends TextElement {
|
||||
RRectElement(
|
||||
double width,
|
||||
double height,
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/src/text/common/line_metrics.dart';
|
||||
import 'package:flame/src/text/elements/text_element.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
class SpriteFontTextElement extends TextElement {
|
||||
class SpriteFontTextElement extends InlineTextElement {
|
||||
SpriteFontTextElement({
|
||||
required this.source,
|
||||
required this.transforms,
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
import 'package:flame/extensions.dart';
|
||||
import 'package:flame/src/anchor.dart';
|
||||
import 'package:flame/src/text/common/line_metrics.dart';
|
||||
import 'package:flame/src/text/elements/element.dart';
|
||||
import 'dart:ui';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
/// [TextElement] is the base class that represents a single line of text, laid
|
||||
/// out and prepared for rendering.
|
||||
abstract class TextElement extends Element {
|
||||
/// The dimensions of this line.
|
||||
LineMetrics get metrics;
|
||||
/// An [TextElement] is a basic rendering 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 [FlameTextStyle]s (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 TextElement {
|
||||
/// Moves the element by ([dx], [dy]) relative to its current location.
|
||||
void translate(double dx, double dy);
|
||||
|
||||
void render(
|
||||
Canvas canvas,
|
||||
Vector2 position, {
|
||||
Anchor anchor = Anchor.topLeft,
|
||||
}) {
|
||||
final box = metrics;
|
||||
translate(
|
||||
position.x - box.width * anchor.x,
|
||||
position.y - box.height * anchor.y - box.top,
|
||||
);
|
||||
draw(canvas);
|
||||
}
|
||||
/// 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 itself.
|
||||
void draw(Canvas canvas);
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/src/text/common/line_metrics.dart';
|
||||
import 'package:flame/src/text/elements/text_element.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:flutter/rendering.dart' as flutter;
|
||||
|
||||
class TextPainterTextElement extends TextElement {
|
||||
class TextPainterTextElement extends InlineTextElement {
|
||||
TextPainterTextElement(this._textPainter)
|
||||
: _box = LineMetrics(
|
||||
ascent: _textPainter.computeDistanceToActualBaseline(
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
import 'package:flame/src/text/elements/block_element.dart';
|
||||
import 'package:flame/src/text/styles/block_style.dart';
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
/// [BlockNode] is a base class for all nodes with "block" placement rules; it
|
||||
/// roughly corresponds to `<div/>` in HTML.
|
||||
@ -14,11 +11,13 @@ import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
/// Implementations include:
|
||||
/// * ColumnNode
|
||||
/// * TextBlockNode (which itself can be a HeaderNode or ParagraphNode)
|
||||
abstract class BlockNode {
|
||||
abstract class BlockNode implements TextNode<BlockStyle> {
|
||||
/// The runtime style applied to this node, this will be set by [fillStyles].
|
||||
@override
|
||||
late BlockStyle style;
|
||||
|
||||
BlockElement format(double availableWidth);
|
||||
|
||||
void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle);
|
||||
@override
|
||||
void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle);
|
||||
}
|
||||
|
||||
@ -1,27 +1,24 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/src/text/nodes/group_text_node.dart';
|
||||
import 'package:flame/src/text/nodes/plain_text_node.dart';
|
||||
import 'package:flame/src/text/nodes/text_node.dart';
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/src/text/styles/style.dart';
|
||||
import 'package:flame/src/text/nodes/inline_text_node.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
class BoldTextNode extends TextNode {
|
||||
class BoldTextNode extends InlineTextNode {
|
||||
BoldTextNode(this.child);
|
||||
|
||||
BoldTextNode.simple(String text) : child = PlainTextNode(text);
|
||||
|
||||
BoldTextNode.group(List<TextNode> children) : child = GroupTextNode(children);
|
||||
BoldTextNode.group(List<InlineTextNode> children)
|
||||
: child = GroupTextNode(children);
|
||||
|
||||
final TextNode child;
|
||||
final InlineTextNode child;
|
||||
|
||||
static final defaultStyle = FlameTextStyle(fontWeight: FontWeight.bold);
|
||||
static final defaultStyle = InlineTextStyle(fontWeight: FontWeight.bold);
|
||||
|
||||
@override
|
||||
void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) {
|
||||
textStyle = Style.merge(parentTextStyle, stylesheet.boldText)!;
|
||||
child.fillStyles(stylesheet, textStyle);
|
||||
void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle) {
|
||||
style = FlameTextStyle.merge(parentTextStyle, stylesheet.boldText)!;
|
||||
child.fillStyles(stylesheet, style);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
import 'package:flame/src/text/common/utils.dart';
|
||||
import 'package:flame/src/text/elements/element.dart';
|
||||
import 'package:flame/src/text/elements/group_element.dart';
|
||||
import 'package:flame/src/text/nodes/block_node.dart';
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// [ColumnNode] is a block node containing other block nodes arranged as a
|
||||
@ -18,7 +15,7 @@ abstract class ColumnNode extends BlockNode {
|
||||
|
||||
@override
|
||||
GroupElement format(double availableWidth) {
|
||||
final out = <Element>[];
|
||||
final out = <TextElement>[];
|
||||
final blockWidth = availableWidth;
|
||||
final padding = style.padding;
|
||||
|
||||
@ -55,7 +52,7 @@ abstract class ColumnNode extends BlockNode {
|
||||
|
||||
@mustCallSuper
|
||||
@override
|
||||
void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) {
|
||||
void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle) {
|
||||
for (final node in children) {
|
||||
node.fillStyles(stylesheet, parentTextStyle);
|
||||
}
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flame/src/text/common/utils.dart';
|
||||
import 'package:flame/src/text/elements/element.dart';
|
||||
import 'package:flame/src/text/elements/group_element.dart';
|
||||
import 'package:flame/src/text/nodes/block_node.dart';
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
|
||||
class DocumentNode {
|
||||
DocumentNode(this.children);
|
||||
class DocumentRoot {
|
||||
DocumentRoot(this.children);
|
||||
|
||||
final List<BlockNode> children;
|
||||
|
||||
@ -22,7 +20,7 @@ class DocumentNode {
|
||||
style.width != null || width != null,
|
||||
'Width must be either provided explicitly or set in the stylesheet',
|
||||
);
|
||||
final out = <Element>[];
|
||||
final out = <TextElement>[];
|
||||
final border = style.background?.borderWidths ?? EdgeInsets.zero;
|
||||
final padding = style.padding;
|
||||
|
||||
@ -1,19 +1,17 @@
|
||||
import 'package:flame/src/text/elements/group_text_element.dart';
|
||||
import 'package:flame/src/text/elements/text_element.dart';
|
||||
import 'package:flame/src/text/nodes/text_node.dart';
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/src/text/nodes/inline_text_node.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
class GroupTextNode extends TextNode {
|
||||
class GroupTextNode extends InlineTextNode {
|
||||
GroupTextNode(this.children);
|
||||
|
||||
final List<TextNode> children;
|
||||
final List<InlineTextNode> children;
|
||||
|
||||
@override
|
||||
void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) {
|
||||
textStyle = parentTextStyle;
|
||||
void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle) {
|
||||
style = parentTextStyle;
|
||||
for (final node in children) {
|
||||
node.fillStyles(stylesheet, textStyle);
|
||||
node.fillStyles(stylesheet, style);
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,9 +30,9 @@ class _GroupTextLayoutBuilder extends TextNodeLayoutBuilder {
|
||||
bool get isDone => _currentChildIndex == node.children.length;
|
||||
|
||||
@override
|
||||
TextElement? layOutNextLine(double availableWidth) {
|
||||
InlineTextElement? layOutNextLine(double availableWidth) {
|
||||
assert(!isDone);
|
||||
final out = <TextElement>[];
|
||||
final out = <InlineTextElement>[];
|
||||
var usedWidth = 0.0;
|
||||
while (true) {
|
||||
if (_currentChildBuilder?.isDone ?? false) {
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/src/text/nodes/plain_text_node.dart';
|
||||
import 'package:flame/src/text/nodes/text_block_node.dart';
|
||||
import 'package:flame/src/text/styles/block_style.dart';
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/src/text/styles/style.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:flutter/rendering.dart' show EdgeInsets;
|
||||
|
||||
class HeaderNode extends TextBlockNode {
|
||||
@ -17,28 +12,28 @@ class HeaderNode extends TextBlockNode {
|
||||
final int level;
|
||||
|
||||
static BlockStyle defaultStyleH1 = BlockStyle(
|
||||
text: FlameTextStyle(fontScale: 2.0, fontWeight: FontWeight.bold),
|
||||
text: InlineTextStyle(fontScale: 2.0, fontWeight: FontWeight.bold),
|
||||
margin: const EdgeInsets.fromLTRB(0, 24, 0, 12),
|
||||
);
|
||||
static BlockStyle defaultStyleH2 = BlockStyle(
|
||||
text: FlameTextStyle(fontScale: 1.5, fontWeight: FontWeight.bold),
|
||||
text: InlineTextStyle(fontScale: 1.5, fontWeight: FontWeight.bold),
|
||||
margin: const EdgeInsets.fromLTRB(0, 24, 0, 8),
|
||||
);
|
||||
static BlockStyle defaultStyleH3 = BlockStyle(
|
||||
text: FlameTextStyle(fontScale: 1.25, fontWeight: FontWeight.bold),
|
||||
text: InlineTextStyle(fontScale: 1.25, fontWeight: FontWeight.bold),
|
||||
);
|
||||
static BlockStyle defaultStyleH4 = BlockStyle(
|
||||
text: FlameTextStyle(fontScale: 1.0, fontWeight: FontWeight.bold),
|
||||
text: InlineTextStyle(fontScale: 1.0, fontWeight: FontWeight.bold),
|
||||
);
|
||||
static BlockStyle defaultStyleH5 = BlockStyle(
|
||||
text: FlameTextStyle(fontScale: 0.875, fontWeight: FontWeight.bold),
|
||||
text: InlineTextStyle(fontScale: 0.875, fontWeight: FontWeight.bold),
|
||||
);
|
||||
static BlockStyle defaultStyleH6 = BlockStyle(
|
||||
text: FlameTextStyle(fontScale: 0.85, fontWeight: FontWeight.bold),
|
||||
text: InlineTextStyle(fontScale: 0.85, fontWeight: FontWeight.bold),
|
||||
);
|
||||
|
||||
@override
|
||||
void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) {
|
||||
void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle) {
|
||||
style = switch (level) {
|
||||
1 => stylesheet.header1,
|
||||
2 => stylesheet.header2,
|
||||
@ -47,7 +42,7 @@ class HeaderNode extends TextBlockNode {
|
||||
5 => stylesheet.header5,
|
||||
_ => stylesheet.header6,
|
||||
};
|
||||
final textStyle = Style.merge(parentTextStyle, style.text)!;
|
||||
final textStyle = FlameTextStyle.merge(parentTextStyle, style.text)!;
|
||||
super.fillStyles(stylesheet, textStyle);
|
||||
}
|
||||
}
|
||||
|
||||
25
packages/flame/lib/src/text/nodes/inline_text_node.dart
Normal file
25
packages/flame/lib/src/text/nodes/inline_text_node.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
/// [InlineTextNode] is a base class for all nodes with "inline" placement
|
||||
/// rules; it roughly corresponds to `<span/>` in HTML.
|
||||
///
|
||||
/// Implementations include:
|
||||
/// * PlainTextNode - just a string of plain text, no special formatting.
|
||||
/// * BoldTextNode - bolded string
|
||||
/// * ItalicTextNode - italic string
|
||||
/// * GroupTextNode - collection of multiple [InlineTextNode]'s to be joined one
|
||||
/// after the other.
|
||||
abstract class InlineTextNode extends TextNode<InlineTextStyle> {
|
||||
@override
|
||||
late InlineTextStyle style;
|
||||
|
||||
@override
|
||||
void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle);
|
||||
|
||||
TextNodeLayoutBuilder get layoutBuilder;
|
||||
}
|
||||
|
||||
abstract class TextNodeLayoutBuilder {
|
||||
InlineTextElement? layOutNextLine(double availableWidth);
|
||||
bool get isDone;
|
||||
}
|
||||
@ -1,28 +1,24 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/src/text/nodes/group_text_node.dart';
|
||||
import 'package:flame/src/text/nodes/plain_text_node.dart';
|
||||
import 'package:flame/src/text/nodes/text_node.dart';
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/src/text/styles/style.dart';
|
||||
import 'package:flame/src/text/nodes/inline_text_node.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
class ItalicTextNode extends TextNode {
|
||||
class ItalicTextNode extends InlineTextNode {
|
||||
ItalicTextNode(this.child);
|
||||
|
||||
ItalicTextNode.simple(String text) : child = PlainTextNode(text);
|
||||
|
||||
ItalicTextNode.group(List<TextNode> children)
|
||||
ItalicTextNode.group(List<InlineTextNode> children)
|
||||
: child = GroupTextNode(children);
|
||||
|
||||
final TextNode child;
|
||||
final InlineTextNode child;
|
||||
|
||||
static final defaultStyle = FlameTextStyle(fontStyle: FontStyle.italic);
|
||||
static final defaultStyle = InlineTextStyle(fontStyle: FontStyle.italic);
|
||||
|
||||
@override
|
||||
void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) {
|
||||
textStyle = Style.merge(parentTextStyle, stylesheet.italicText)!;
|
||||
child.fillStyles(stylesheet, textStyle);
|
||||
void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle) {
|
||||
style = FlameTextStyle.merge(parentTextStyle, stylesheet.italicText)!;
|
||||
child.fillStyles(stylesheet, style);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -1,11 +1,4 @@
|
||||
import 'package:flame/src/text/nodes/group_text_node.dart';
|
||||
import 'package:flame/src/text/nodes/plain_text_node.dart';
|
||||
import 'package:flame/src/text/nodes/text_block_node.dart';
|
||||
import 'package:flame/src/text/nodes/text_node.dart';
|
||||
import 'package:flame/src/text/styles/block_style.dart';
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/src/text/styles/style.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:flutter/rendering.dart' show EdgeInsets;
|
||||
|
||||
class ParagraphNode extends TextBlockNode {
|
||||
@ -13,7 +6,7 @@ class ParagraphNode extends TextBlockNode {
|
||||
|
||||
ParagraphNode.simple(String text) : super(PlainTextNode(text));
|
||||
|
||||
ParagraphNode.group(List<TextNode> fragments)
|
||||
ParagraphNode.group(List<InlineTextNode> fragments)
|
||||
: super(GroupTextNode(fragments));
|
||||
|
||||
static const defaultStyle = BlockStyle(
|
||||
@ -21,9 +14,9 @@ class ParagraphNode extends TextBlockNode {
|
||||
);
|
||||
|
||||
@override
|
||||
void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) {
|
||||
void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle) {
|
||||
style = stylesheet.paragraph;
|
||||
final textStyle = Style.merge(parentTextStyle, style.text)!;
|
||||
final textStyle = FlameTextStyle.merge(parentTextStyle, style.text)!;
|
||||
super.fillStyles(stylesheet, textStyle);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,14 @@
|
||||
import 'package:flame/src/text/elements/text_element.dart';
|
||||
import 'package:flame/src/text/nodes/text_node.dart';
|
||||
import 'package:flame/src/text/renderers/text_renderer.dart';
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/src/text/nodes/inline_text_node.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
class PlainTextNode extends TextNode {
|
||||
class PlainTextNode extends InlineTextNode {
|
||||
PlainTextNode(this.text);
|
||||
|
||||
final String text;
|
||||
|
||||
@override
|
||||
void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) {
|
||||
textStyle = parentTextStyle;
|
||||
void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle) {
|
||||
style = parentTextStyle;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -20,7 +17,7 @@ class PlainTextNode extends TextNode {
|
||||
|
||||
class _PlainTextLayoutBuilder extends TextNodeLayoutBuilder {
|
||||
_PlainTextLayoutBuilder(this.node)
|
||||
: renderer = node.textStyle.asTextRenderer(),
|
||||
: renderer = node.style.asTextRenderer(),
|
||||
words = node.text.split(' ');
|
||||
|
||||
final PlainTextNode node;
|
||||
@ -33,8 +30,8 @@ class _PlainTextLayoutBuilder extends TextNodeLayoutBuilder {
|
||||
bool get isDone => index1 > words.length;
|
||||
|
||||
@override
|
||||
TextElement? layOutNextLine(double availableWidth) {
|
||||
TextElement? tentativeLine;
|
||||
InlineTextElement? layOutNextLine(double availableWidth) {
|
||||
InlineTextElement? tentativeLine;
|
||||
int? tentativeIndex0;
|
||||
while (index1 <= words.length) {
|
||||
final textPiece = words.sublist(index0, index1).join(' ');
|
||||
|
||||
@ -1,21 +1,16 @@
|
||||
import 'package:flame/src/text/common/utils.dart';
|
||||
import 'package:flame/src/text/elements/block_element.dart';
|
||||
import 'package:flame/src/text/elements/group_element.dart';
|
||||
import 'package:flame/src/text/elements/text_element.dart';
|
||||
import 'package:flame/src/text/nodes/block_node.dart';
|
||||
import 'package:flame/src/text/nodes/text_node.dart';
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
abstract class TextBlockNode extends BlockNode {
|
||||
TextBlockNode(this.child);
|
||||
|
||||
final TextNode child;
|
||||
final InlineTextNode child;
|
||||
|
||||
@mustCallSuper
|
||||
@override
|
||||
void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle) {
|
||||
void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle) {
|
||||
child.fillStyles(stylesheet, parentTextStyle);
|
||||
}
|
||||
|
||||
@ -28,7 +23,7 @@ abstract class TextBlockNode extends BlockNode {
|
||||
final blockWidth = availableWidth;
|
||||
final contentWidth = blockWidth - style.padding.horizontal;
|
||||
|
||||
final lines = <TextElement>[];
|
||||
final lines = <InlineTextElement>[];
|
||||
final horizontalOffset = style.padding.left;
|
||||
var verticalOffset = style.padding.top;
|
||||
while (!layoutBuilder.isDone) {
|
||||
|
||||
@ -1,25 +1,7 @@
|
||||
import 'package:flame/src/text/elements/text_element.dart';
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
/// [TextNode] is a base class for all nodes with "inline" placement rules; it
|
||||
/// roughly corresponds to `<span/>` in HTML.
|
||||
///
|
||||
/// Implementations include:
|
||||
/// * PlainTextNode - just a string of plain text, no special formatting.
|
||||
/// * BoldTextNode - bolded string
|
||||
/// * ItalicTextNode - italic string
|
||||
/// * GroupTextNode - collection of multiple [TextNode]'s to be joined one
|
||||
/// after the other.
|
||||
abstract class TextNode {
|
||||
late FlameTextStyle textStyle;
|
||||
abstract class TextNode<T extends FlameTextStyle> {
|
||||
T get style;
|
||||
|
||||
void fillStyles(DocumentStyle stylesheet, FlameTextStyle parentTextStyle);
|
||||
|
||||
TextNodeLayoutBuilder get layoutBuilder;
|
||||
}
|
||||
|
||||
abstract class TextNodeLayoutBuilder {
|
||||
TextElement? layOutNextLine(double availableWidth);
|
||||
bool get isDone;
|
||||
void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle);
|
||||
}
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' hide LineMetrics;
|
||||
|
||||
import 'package:flame/src/text/common/line_metrics.dart';
|
||||
import 'package:flame/src/text/common/sprite_font.dart';
|
||||
import 'package:flame/src/text/elements/sprite_font_text_element.dart';
|
||||
import 'package:flame/src/text/renderers/text_renderer.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
/// [SpriteFontRenderer] will render text using a [SpriteFont] font,
|
||||
/// creating a [SpriteFontTextElement].
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import 'package:flame/cache.dart';
|
||||
import 'package:flame/src/text/elements/text_painter_text_element.dart';
|
||||
import 'package:flame/src/text/renderers/text_renderer.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
/// [TextPaint] applies a Flutter [TextStyle] to a string of
|
||||
|
||||
@ -3,9 +3,9 @@ import 'package:flame/src/anchor.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
/// [TextRenderer] is an abstract interface for a class that can convert an
|
||||
/// arbitrary string of text into a renderable [TextElement].
|
||||
/// arbitrary string of text into a renderable [InlineTextElement].
|
||||
abstract class TextRenderer {
|
||||
TextElement format(String text);
|
||||
InlineTextElement format(String text);
|
||||
|
||||
LineMetrics getLineMetrics(String text) {
|
||||
return format(text).metrics;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import 'package:flame/src/text/styles/style.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@immutable
|
||||
class BackgroundStyle extends Style {
|
||||
class BackgroundStyle extends FlameTextStyle {
|
||||
BackgroundStyle({
|
||||
Color? color,
|
||||
Paint? paint,
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import 'package:flame/src/text/styles/background_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/src/text/styles/style.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:flutter/painting.dart' hide TextStyle;
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// [BlockStyle] is a generic descriptor for a visual appearance of a block-
|
||||
/// level element.
|
||||
@immutable
|
||||
class BlockStyle extends Style {
|
||||
class BlockStyle extends FlameTextStyle {
|
||||
const BlockStyle({
|
||||
EdgeInsets? margin,
|
||||
EdgeInsets? padding,
|
||||
@ -19,7 +17,7 @@ class BlockStyle extends Style {
|
||||
final EdgeInsets? _margin;
|
||||
final EdgeInsets? _padding;
|
||||
final BackgroundStyle? background;
|
||||
final FlameTextStyle? text;
|
||||
final InlineTextStyle? text;
|
||||
|
||||
EdgeInsets get margin => _margin ?? EdgeInsets.zero;
|
||||
EdgeInsets get padding => _padding ?? EdgeInsets.zero;
|
||||
@ -30,7 +28,7 @@ class BlockStyle extends Style {
|
||||
margin: other._margin ?? _margin,
|
||||
padding: other._padding ?? _padding,
|
||||
background: other.background ?? background,
|
||||
text: Style.merge(text, other.text),
|
||||
text: FlameTextStyle.merge(text, other.text),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,5 @@
|
||||
import 'package:flame/src/text/nodes/bold_text_node.dart';
|
||||
import 'package:flame/src/text/nodes/header_node.dart';
|
||||
import 'package:flame/src/text/nodes/italic_text_node.dart';
|
||||
import 'package:flame/src/text/nodes/paragraph_node.dart';
|
||||
import 'package:flame/src/text/styles/background_style.dart';
|
||||
import 'package:flame/src/text/styles/block_style.dart';
|
||||
import 'package:flame/src/text/styles/flame_text_style.dart';
|
||||
import 'package:flame/src/text/styles/overflow.dart';
|
||||
import 'package:flame/src/text/styles/style.dart';
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:flutter/painting.dart' show EdgeInsets;
|
||||
|
||||
/// [DocumentStyle] is a user-facing description of how to render an entire
|
||||
@ -18,15 +11,15 @@ import 'package:flutter/painting.dart' show EdgeInsets;
|
||||
///
|
||||
/// All styles that collectively describe how to render text are organized into
|
||||
/// a tree, with [DocumentStyle] at the root.
|
||||
class DocumentStyle extends Style {
|
||||
class DocumentStyle extends FlameTextStyle {
|
||||
DocumentStyle({
|
||||
this.width,
|
||||
this.height,
|
||||
this.padding = EdgeInsets.zero,
|
||||
this.background,
|
||||
FlameTextStyle? text,
|
||||
FlameTextStyle? boldText,
|
||||
FlameTextStyle? italicText,
|
||||
InlineTextStyle? text,
|
||||
InlineTextStyle? boldText,
|
||||
InlineTextStyle? italicText,
|
||||
BlockStyle? paragraph,
|
||||
BlockStyle? header1,
|
||||
BlockStyle? header2,
|
||||
@ -34,20 +27,22 @@ class DocumentStyle extends Style {
|
||||
BlockStyle? header4,
|
||||
BlockStyle? header5,
|
||||
BlockStyle? header6,
|
||||
}) : _text = Style.merge(text, DocumentStyle.defaultTextStyle),
|
||||
_boldText = Style.merge(boldText, BoldTextNode.defaultStyle),
|
||||
_italicText = Style.merge(italicText, ItalicTextNode.defaultStyle),
|
||||
_paragraph = Style.merge(paragraph, ParagraphNode.defaultStyle),
|
||||
_header1 = Style.merge(header1, HeaderNode.defaultStyleH1),
|
||||
_header2 = Style.merge(header2, HeaderNode.defaultStyleH2),
|
||||
_header3 = Style.merge(header3, HeaderNode.defaultStyleH3),
|
||||
_header4 = Style.merge(header4, HeaderNode.defaultStyleH4),
|
||||
_header5 = Style.merge(header5, HeaderNode.defaultStyleH5),
|
||||
_header6 = Style.merge(header6, HeaderNode.defaultStyleH6);
|
||||
}) : _text = FlameTextStyle.merge(text, DocumentStyle.defaultTextStyle),
|
||||
_boldText = FlameTextStyle.merge(boldText, BoldTextNode.defaultStyle),
|
||||
_italicText =
|
||||
FlameTextStyle.merge(italicText, ItalicTextNode.defaultStyle),
|
||||
_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);
|
||||
|
||||
final FlameTextStyle? _text;
|
||||
final FlameTextStyle? _boldText;
|
||||
final FlameTextStyle? _italicText;
|
||||
final InlineTextStyle? _text;
|
||||
final InlineTextStyle? _boldText;
|
||||
final InlineTextStyle? _italicText;
|
||||
final BlockStyle? _paragraph;
|
||||
final BlockStyle? _header1;
|
||||
final BlockStyle? _header2;
|
||||
@ -96,9 +91,9 @@ class DocumentStyle extends Style {
|
||||
/// document page(s).
|
||||
final BackgroundStyle? background;
|
||||
|
||||
FlameTextStyle get text => _text!;
|
||||
FlameTextStyle get boldText => _boldText!;
|
||||
FlameTextStyle get italicText => _italicText!;
|
||||
InlineTextStyle get text => _text!;
|
||||
InlineTextStyle get boldText => _boldText!;
|
||||
InlineTextStyle get italicText => _italicText!;
|
||||
|
||||
/// Style for [ParagraphNode]s.
|
||||
BlockStyle get paragraph => _paragraph!;
|
||||
@ -111,7 +106,7 @@ class DocumentStyle extends Style {
|
||||
BlockStyle get header5 => _header5!;
|
||||
BlockStyle get header6 => _header6!;
|
||||
|
||||
static FlameTextStyle defaultTextStyle = FlameTextStyle(fontSize: 16.0);
|
||||
static InlineTextStyle defaultTextStyle = InlineTextStyle(fontSize: 16.0);
|
||||
|
||||
@override
|
||||
DocumentStyle copyWith(DocumentStyle other) {
|
||||
@ -130,8 +125,9 @@ class DocumentStyle extends Style {
|
||||
);
|
||||
}
|
||||
|
||||
final Map<Style, Map<Style, Style>> _mergedStylesCache = {};
|
||||
Style? merge(Style? style1, Style? style2) {
|
||||
final Map<FlameTextStyle, Map<FlameTextStyle, FlameTextStyle>>
|
||||
_mergedStylesCache = {};
|
||||
FlameTextStyle? merge(FlameTextStyle? style1, FlameTextStyle? style2) {
|
||||
if (style1 == null) {
|
||||
return style2;
|
||||
} else if (style2 == null) {
|
||||
|
||||
@ -1,55 +1,30 @@
|
||||
import 'package:flame/src/text/renderers/text_paint.dart';
|
||||
import 'package:flame/src/text/renderers/text_renderer.dart';
|
||||
import 'package:flame/src/text/styles/style.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:flame/text.dart';
|
||||
|
||||
@immutable
|
||||
class FlameTextStyle extends Style {
|
||||
FlameTextStyle({
|
||||
this.color,
|
||||
this.fontFamily,
|
||||
this.fontSize,
|
||||
this.fontScale,
|
||||
this.fontWeight,
|
||||
this.fontStyle,
|
||||
this.letterSpacing,
|
||||
});
|
||||
/// A [FlameTextStyle] is a base class for several classes that collectively
|
||||
/// describe the desired visual appearance of a "rich-text" document.
|
||||
///
|
||||
/// The style classes mostly are collections of properties that describe how a
|
||||
/// potential document should be formatted. However, they have little logic
|
||||
/// beyond that. The style classes are then passed to document `Node`s so that
|
||||
/// the content of a document can be formatted.
|
||||
///
|
||||
/// Various [FlameTextStyle] classes are organized into a tree, with
|
||||
/// [DocumentStyle] at the root.
|
||||
///
|
||||
/// The tree of [FlameTextStyle]s is roughly equivalent to a CSS stylesheet.
|
||||
abstract class FlameTextStyle {
|
||||
const FlameTextStyle();
|
||||
|
||||
final Color? color;
|
||||
final String? fontFamily;
|
||||
final double? fontSize;
|
||||
final double? fontScale;
|
||||
final FontWeight? fontWeight;
|
||||
final FontStyle? fontStyle;
|
||||
final double? letterSpacing;
|
||||
FlameTextStyle copyWith(covariant FlameTextStyle other);
|
||||
|
||||
late final TextRenderer renderer = asTextRenderer();
|
||||
|
||||
@override
|
||||
FlameTextStyle copyWith(FlameTextStyle other) {
|
||||
return FlameTextStyle(
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
@internal
|
||||
TextPaint asTextRenderer() {
|
||||
return TextPaint(
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize! * (fontScale ?? 1.0),
|
||||
fontWeight: fontWeight,
|
||||
fontStyle: fontStyle,
|
||||
letterSpacing: letterSpacing,
|
||||
),
|
||||
);
|
||||
static T? merge<T extends FlameTextStyle>(T? style1, T? style2) {
|
||||
if (style1 == null) {
|
||||
return style2;
|
||||
} else if (style2 == null) {
|
||||
return style1;
|
||||
} else {
|
||||
assert(style1.runtimeType == style2.runtimeType);
|
||||
return style1.copyWith(style2) as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
53
packages/flame/lib/src/text/styles/inline_text_style.dart
Normal file
53
packages/flame/lib/src/text/styles/inline_text_style.dart
Normal file
@ -0,0 +1,53 @@
|
||||
import 'package:flame/text.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@immutable
|
||||
class InlineTextStyle extends FlameTextStyle {
|
||||
InlineTextStyle({
|
||||
this.color,
|
||||
this.fontFamily,
|
||||
this.fontSize,
|
||||
this.fontScale,
|
||||
this.fontWeight,
|
||||
this.fontStyle,
|
||||
this.letterSpacing,
|
||||
});
|
||||
|
||||
final Color? color;
|
||||
final String? fontFamily;
|
||||
final double? fontSize;
|
||||
final double? fontScale;
|
||||
final FontWeight? fontWeight;
|
||||
final FontStyle? fontStyle;
|
||||
final double? letterSpacing;
|
||||
|
||||
late final TextRenderer renderer = asTextRenderer();
|
||||
|
||||
@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,
|
||||
);
|
||||
}
|
||||
|
||||
@internal
|
||||
TextPaint asTextRenderer() {
|
||||
return TextPaint(
|
||||
style: TextStyle(
|
||||
color: color,
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize! * (fontScale ?? 1.0),
|
||||
fontWeight: fontWeight,
|
||||
fontStyle: fontStyle,
|
||||
letterSpacing: letterSpacing,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
import 'package:flame/src/text/styles/document_style.dart';
|
||||
|
||||
/// A [Style] is a base class for several classes that collectively describe
|
||||
/// the desired visual appearance of a "rich-text" document.
|
||||
///
|
||||
/// The style classes mostly are collections of properties that describe how a
|
||||
/// potential document should be formatted. However, they have little logic
|
||||
/// beyond that. The style classes are then passed to document `Node`s so that
|
||||
/// the content of a document can be formatted.
|
||||
///
|
||||
/// Various [Style] classes are organized into a tree, with [DocumentStyle] at
|
||||
/// the root.
|
||||
///
|
||||
/// The tree of [Style]s is roughly equivalent to a CSS stylesheet.
|
||||
abstract class Style {
|
||||
const Style();
|
||||
|
||||
Style copyWith(covariant Style other);
|
||||
|
||||
static T? merge<T extends Style>(T? style1, T? style2) {
|
||||
if (style1 == null) {
|
||||
return style2;
|
||||
} else if (style2 == null) {
|
||||
return style1;
|
||||
} else {
|
||||
assert(style1.runtimeType == style2.runtimeType);
|
||||
return style1.copyWith(style2) as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ export 'src/text/common/glyph.dart' show Glyph;
|
||||
export 'src/text/common/line_metrics.dart' show LineMetrics;
|
||||
export 'src/text/common/sprite_font.dart' show SpriteFont;
|
||||
export 'src/text/elements/block_element.dart' show BlockElement;
|
||||
export 'src/text/elements/element.dart' show Element;
|
||||
export 'src/text/elements/inline_text_element.dart' show InlineTextElement;
|
||||
export 'src/text/elements/rect_element.dart' show RectElement;
|
||||
export 'src/text/elements/rrect_element.dart' show RRectElement;
|
||||
export 'src/text/elements/sprite_font_text_element.dart'
|
||||
@ -15,9 +15,10 @@ export 'src/text/elements/text_painter_text_element.dart'
|
||||
export 'src/text/nodes/block_node.dart' show BlockNode;
|
||||
export 'src/text/nodes/bold_text_node.dart' show BoldTextNode;
|
||||
export 'src/text/nodes/column_node.dart' show ColumnNode;
|
||||
export 'src/text/nodes/document_node.dart' show DocumentNode;
|
||||
export 'src/text/nodes/document_root.dart' show DocumentRoot;
|
||||
export 'src/text/nodes/group_text_node.dart' show GroupTextNode;
|
||||
export 'src/text/nodes/header_node.dart' show HeaderNode;
|
||||
export 'src/text/nodes/inline_text_node.dart' show InlineTextNode;
|
||||
export 'src/text/nodes/italic_text_node.dart' show ItalicTextNode;
|
||||
export 'src/text/nodes/paragraph_node.dart' show ParagraphNode;
|
||||
export 'src/text/nodes/plain_text_node.dart' show PlainTextNode;
|
||||
@ -31,4 +32,4 @@ export 'src/text/styles/background_style.dart' show BackgroundStyle;
|
||||
export 'src/text/styles/block_style.dart' show BlockStyle;
|
||||
export 'src/text/styles/document_style.dart' show DocumentStyle;
|
||||
export 'src/text/styles/flame_text_style.dart' show FlameTextStyle;
|
||||
export 'src/text/styles/style.dart' show Style;
|
||||
export 'src/text/styles/inline_text_style.dart' show InlineTextStyle;
|
||||
|
||||
@ -34,12 +34,12 @@ void main() {
|
||||
|
||||
class _CustomTextRenderer extends TextRenderer {
|
||||
@override
|
||||
TextElement format(String text) {
|
||||
InlineTextElement format(String text) {
|
||||
return CustomTextElement();
|
||||
}
|
||||
}
|
||||
|
||||
class CustomTextElement extends TextElement {
|
||||
class CustomTextElement extends InlineTextElement {
|
||||
@override
|
||||
LineMetrics get metrics => LineMetrics();
|
||||
|
||||
|
||||
@ -24,10 +24,10 @@ class DebugTextRenderer extends TextRenderer {
|
||||
final FontStyle fontStyle;
|
||||
|
||||
@override
|
||||
TextElement format(String text) => _DebugTextElement(this, text);
|
||||
InlineTextElement format(String text) => _DebugTextElement(this, text);
|
||||
}
|
||||
|
||||
class _DebugTextElement extends TextElement {
|
||||
class _DebugTextElement extends InlineTextElement {
|
||||
_DebugTextElement(this.style, this.text) {
|
||||
final charWidth = style.fontSize * 1.0;
|
||||
final charHeight = style.fontSize;
|
||||
|
||||
@ -45,7 +45,7 @@ void main() {
|
||||
class TextElementsComponent extends PositionComponent {
|
||||
TextElementsComponent(this.elements);
|
||||
|
||||
final List<TextElement> elements;
|
||||
final List<InlineTextElement> elements;
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
|
||||
Reference in New Issue
Block a user