From 25094903d65bc3bf63ec7a3f5e9ad171373d8d71 Mon Sep 17 00:00:00 2001 From: Luan Nico Date: Sat, 28 Dec 2024 22:02:25 -0500 Subject: [PATCH] feat: Add support for strike-through text for flame_markdown --- .github/.cspell/words_dictionary.txt | 1 + .../text/nodes/strikethrough_text_node.dart | 35 ++++++++++++++++++ .../lib/src/text/styles/document_style.dart | 11 ++++++ packages/flame/lib/text.dart | 1 + .../example/assets/fire_and_ice.md | 2 +- packages/flame_markdown/example/lib/main.dart | 11 +++++- packages/flame_markdown/example/pubspec.yaml | 1 + .../flame_markdown/lib/flame_markdown.dart | 1 + .../test/flame_markdown_test.dart | 37 ++++++++++++++++++- 9 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 packages/flame/lib/src/text/nodes/strikethrough_text_node.dart diff --git a/.github/.cspell/words_dictionary.txt b/.github/.cspell/words_dictionary.txt index 71efd07cd0f..c83a7d75a1a 100644 --- a/.github/.cspell/words_dictionary.txt +++ b/.github/.cspell/words_dictionary.txt @@ -27,6 +27,7 @@ rebalance refreshable renderable rescan +strikethrough # of a text with a horizontal line across tappable tappables toolset diff --git a/packages/flame/lib/src/text/nodes/strikethrough_text_node.dart b/packages/flame/lib/src/text/nodes/strikethrough_text_node.dart new file mode 100644 index 00000000000..5929a6b508c --- /dev/null +++ b/packages/flame/lib/src/text/nodes/strikethrough_text_node.dart @@ -0,0 +1,35 @@ +import 'dart:ui'; + +import 'package:flame/src/text/nodes/inline_text_node.dart'; +import 'package:flame/text.dart'; + +/// An [InlineTextNode] representing a text with a strikethrough line. +/// +/// The exact styling can be controlled by the `strikethroughText` property +/// on the document style. +class StrikethroughTextNode extends InlineTextNode { + StrikethroughTextNode(this.child); + + StrikethroughTextNode.simple(String text) : child = PlainTextNode(text); + + StrikethroughTextNode.group(List children) + : child = GroupTextNode(children); + + final InlineTextNode child; + + static final defaultStyle = InlineTextStyle( + decoration: TextDecoration.lineThrough, + ); + + @override + void fillStyles(DocumentStyle stylesheet, InlineTextStyle parentTextStyle) { + style = FlameTextStyle.merge( + parentTextStyle, + stylesheet.strikethroughText, + )!; + child.fillStyles(stylesheet, style); + } + + @override + TextNodeLayoutBuilder get layoutBuilder => child.layoutBuilder; +} diff --git a/packages/flame/lib/src/text/styles/document_style.dart b/packages/flame/lib/src/text/styles/document_style.dart index bf0a274f1cc..d11e8e105fd 100644 --- a/packages/flame/lib/src/text/styles/document_style.dart +++ b/packages/flame/lib/src/text/styles/document_style.dart @@ -21,6 +21,7 @@ class DocumentStyle extends FlameTextStyle { InlineTextStyle? boldText, InlineTextStyle? italicText, InlineTextStyle? codeText, + InlineTextStyle? strikethroughText, BlockStyle? paragraph, BlockStyle? header1, BlockStyle? header2, @@ -33,6 +34,10 @@ class DocumentStyle extends FlameTextStyle { _italicText = FlameTextStyle.merge(ItalicTextNode.defaultStyle, italicText), _codeText = FlameTextStyle.merge(CodeTextNode.defaultStyle, codeText), + _strikethroughText = FlameTextStyle.merge( + StrikethroughTextNode.defaultStyle, + strikethroughText, + ), _paragraph = FlameTextStyle.merge(ParagraphNode.defaultStyle, paragraph), _header1 = FlameTextStyle.merge(HeaderNode.defaultStyleH1, header1), @@ -46,6 +51,7 @@ class DocumentStyle extends FlameTextStyle { final InlineTextStyle? _boldText; final InlineTextStyle? _italicText; final InlineTextStyle? _codeText; + final InlineTextStyle? _strikethroughText; final BlockStyle? _paragraph; final BlockStyle? _header1; final BlockStyle? _header2; @@ -98,6 +104,7 @@ class DocumentStyle extends FlameTextStyle { InlineTextStyle get boldText => _boldText!; InlineTextStyle get italicText => _italicText!; InlineTextStyle get codeText => _codeText!; + InlineTextStyle get strikethroughText => _strikethroughText!; /// Style for [ParagraphNode]s. BlockStyle get paragraph => _paragraph!; @@ -122,6 +129,10 @@ class DocumentStyle extends FlameTextStyle { boldText: FlameTextStyle.merge(_boldText, other.boldText), italicText: FlameTextStyle.merge(_italicText, other.italicText), codeText: FlameTextStyle.merge(_codeText, other.codeText), + strikethroughText: FlameTextStyle.merge( + _strikethroughText, + other.strikethroughText, + ), background: merge(background, other.background) as BackgroundStyle?, paragraph: merge(paragraph, other.paragraph) as BlockStyle?, header1: merge(header1, other.header1) as BlockStyle?, diff --git a/packages/flame/lib/text.dart b/packages/flame/lib/text.dart index 60c65ccb92e..738f07ff718 100644 --- a/packages/flame/lib/text.dart +++ b/packages/flame/lib/text.dart @@ -25,6 +25,7 @@ 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; +export 'src/text/nodes/strikethrough_text_node.dart' show StrikethroughTextNode; export 'src/text/nodes/text_block_node.dart' show TextBlockNode; export 'src/text/nodes/text_node.dart' show TextNode; export 'src/text/renderers/sprite_font_renderer.dart' show SpriteFontRenderer; diff --git a/packages/flame_markdown/example/assets/fire_and_ice.md b/packages/flame_markdown/example/assets/fire_and_ice.md index 2b1cc03163b..bcf1b77199a 100644 --- a/packages/flame_markdown/example/assets/fire_and_ice.md +++ b/packages/flame_markdown/example/assets/fire_and_ice.md @@ -1,6 +1,6 @@ # Fire & Ice -Some say the world will end in **fire**, +Some say the world will ~~end~~ in **fire**, Some say in *ice*. diff --git a/packages/flame_markdown/example/lib/main.dart b/packages/flame_markdown/example/lib/main.dart index bd7381f662d..caf1c729169 100644 --- a/packages/flame_markdown/example/lib/main.dart +++ b/packages/flame_markdown/example/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flame/game.dart'; import 'package:flame/text.dart'; import 'package:flame_markdown/flame_markdown.dart'; import 'package:flutter/widgets.dart' hide Animation; +import 'package:markdown/markdown.dart'; void main() { runApp(GameWidget(game: MarkdownGame())); @@ -19,7 +20,15 @@ class MarkdownGame extends FlameGame { final markdown = await Flame.assets.readFile('fire_and_ice.md'); await add( TextElementComponent.fromDocument( - document: FlameMarkdown.toDocument(markdown), + document: FlameMarkdown.toDocument( + markdown, + document: Document( + encodeHtml: false, + inlineSyntaxes: [ + StrikethroughSyntax(), + ], + ), + ), style: DocumentStyle( padding: const EdgeInsets.all(16), ), diff --git a/packages/flame_markdown/example/pubspec.yaml b/packages/flame_markdown/example/pubspec.yaml index a2929b8f12a..e5245e865f2 100644 --- a/packages/flame_markdown/example/pubspec.yaml +++ b/packages/flame_markdown/example/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: flame_markdown: ^0.2.2+3 flutter: sdk: flutter + markdown: ^7.1.1 dev_dependencies: flame_lint: ^1.2.1 diff --git a/packages/flame_markdown/lib/flame_markdown.dart b/packages/flame_markdown/lib/flame_markdown.dart index 3996b4dc497..3176b1371bf 100644 --- a/packages/flame_markdown/lib/flame_markdown.dart +++ b/packages/flame_markdown/lib/flame_markdown.dart @@ -62,6 +62,7 @@ class FlameMarkdown { 'em' || 'i' => ItalicTextNode(child), 'strong' || 'b' => BoldTextNode(child), 'code' => CodeTextNode(child), + 'del' => StrikethroughTextNode(child), _ => throw Exception('Unknown element tag: ${element.tag}'), } as TextNode; } diff --git a/packages/flame_markdown/test/flame_markdown_test.dart b/packages/flame_markdown/test/flame_markdown_test.dart index c2cc352961a..875817a93f6 100644 --- a/packages/flame_markdown/test/flame_markdown_test.dart +++ b/packages/flame_markdown/test/flame_markdown_test.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flame/text.dart'; import 'package:flame_markdown/flame_markdown.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:markdown/markdown.dart'; void main() { group('FlameMarkdown#toDocument', () { @@ -71,7 +72,11 @@ void main() { (node) => _expectHeader(node, 1, 'Fire & Ice'), (node) => _expectParagraph(node, (p) { _expectGroup(p, [ - (node) => _expectPlain(node, 'Some say the world will end in '), + (node) => _expectPlain( + node, + // note: strike-trough is only parsed if enabled + 'Some say the world will ~~end~~ in ', + ), (node) => _expectBold(node, 'fire'), (node) => _expectPlain(node, ','), ]); @@ -97,9 +102,39 @@ void main() { }), ]); }); + + test('strikethrough can be enabled', () { + const markdown = 'Flame ~~will be~~ is a great game engine!'; + final doc = FlameMarkdown.toDocument( + markdown, + document: Document( + encodeHtml: false, + inlineSyntaxes: [ + StrikethroughSyntax(), + ], + ), + ); + + _expectDocument(doc, [ + (node) => _expectParagraph(node, (p) { + _expectGroup(p, [ + (node) => _expectPlain(node, 'Flame '), + (node) => _expectStrikethrough(node, 'will be'), + (node) => _expectPlain(node, ' is a great game engine!'), + ]); + }), + ]); + }); }); } +void _expectStrikethrough(InlineTextNode node, String text) { + expect(node, isA()); + final content = (node as StrikethroughTextNode).child; + expect(content, isA()); + expect((content as PlainTextNode).text, text); +} + void _expectBold(InlineTextNode node, String text) { expect(node, isA()); final content = (node as BoldTextNode).child;