diff --git a/packages/fleather/example/lib/main.dart b/packages/fleather/example/lib/main.dart index 9dca5aef..2bbee2e2 100644 --- a/packages/fleather/example/lib/main.dart +++ b/packages/fleather/example/lib/main.dart @@ -143,14 +143,17 @@ class _HomePageState extends State { ); } - Widget _embedBuilder(BuildContext context, EmbedNode node) { + FleatherEmbed _embedBuilder(BuildContext context, EmbedNode node) { if (node.value.type == 'icon') { final data = node.value.data; // Icons.rocket_launch_outlined - return Icon( - IconData(int.parse(data['codePoint']), fontFamily: data['fontFamily']), - color: Color(int.parse(data['color'])), - size: 18, + return FleatherSpanEmbed( + child: Icon( + IconData(int.parse(data['codePoint']), + fontFamily: data['fontFamily']), + color: Color(int.parse(data['color'])), + size: 18, + ), ); } @@ -176,14 +179,17 @@ class _HomePageState extends State { } } if (image != null) { - return Padding( - // Caret takes 2 pixels, hence not symmetric padding values. - padding: const EdgeInsets.only(left: 4, right: 2, top: 2, bottom: 2), - child: Container( - width: (node.value.data['width'] as num?)?.toDouble() ?? 300, - height: (node.value.data['height'] as num?)?.toDouble() ?? 300, - decoration: BoxDecoration( - image: DecorationImage(image: image, fit: BoxFit.cover), + return FleatherEmbed( + child: Padding( + // Caret takes 2 pixels, hence not symmetric padding values. + padding: + const EdgeInsets.only(left: 4, right: 2, top: 2, bottom: 2), + child: Container( + width: (node.value.data['width'] as num?)?.toDouble() ?? 300, + height: (node.value.data['height'] as num?)?.toDouble() ?? 300, + decoration: BoxDecoration( + image: DecorationImage(image: image, fit: BoxFit.cover), + ), ), ), ); diff --git a/packages/fleather/lib/src/widgets/editor.dart b/packages/fleather/lib/src/widgets/editor.dart index a9b82990..ba444044 100644 --- a/packages/fleather/lib/src/widgets/editor.dart +++ b/packages/fleather/lib/src/widgets/editor.dart @@ -76,22 +76,24 @@ Widget defaultSpellCheckMenuBuilder( } /// Builder function for embeddable objects in [FleatherEditor]. -typedef FleatherEmbedBuilder = Widget Function( +typedef FleatherEmbedBuilder = FleatherEmbed Function( BuildContext context, EmbedNode node); /// Default implementation of a builder function for embeddable objects in /// Fleather. /// /// Only supports "horizontal rule" embeds. -Widget defaultFleatherEmbedBuilder(BuildContext context, EmbedNode node) { +FleatherEmbed defaultFleatherEmbedBuilder( + BuildContext context, EmbedNode node) { if (node.value.type == 'hr') { final fleatherThemeData = FleatherTheme.of(context)!; - return Divider( + return FleatherEmbed( + child: Divider( height: fleatherThemeData.horizontalRule.height, thickness: fleatherThemeData.horizontalRule.thickness, color: fleatherThemeData.horizontalRule.color, - ); + )); } throw UnimplementedError( 'Embeddable type "${node.value.type}" is not supported by default embed ' @@ -99,6 +101,25 @@ Widget defaultFleatherEmbedBuilder(BuildContext context, EmbedNode node) { 'embedBuilder property of FleatherEditor or FleatherField widgets.'); } +final class FleatherEmbed { + final Widget child; + + const FleatherEmbed({required this.child}); +} + +final class FleatherSpanEmbed extends FleatherEmbed { + final PlaceholderAlignment placeholderAlignment; + final TextBaseline? textBaseline; + final TextStyle? textStyle; + + const FleatherSpanEmbed({ + required super.child, + this.placeholderAlignment = PlaceholderAlignment.bottom, + this.textBaseline, + this.textStyle, + }); +} + /// Widget for editing rich text documents. class FleatherEditor extends StatefulWidget { /// Controller object which establishes a link between a rich text document diff --git a/packages/fleather/lib/src/widgets/text_line.dart b/packages/fleather/lib/src/widgets/text_line.dart index 7cf3e2e2..31e4b718 100644 --- a/packages/fleather/lib/src/widgets/text_line.dart +++ b/packages/fleather/lib/src/widgets/text_line.dart @@ -122,8 +122,9 @@ class _TextLineState extends State { Widget build(BuildContext context) { assert(debugCheckHasMediaQuery(context)); if (widget.node.hasBlockEmbed) { - final embed = widget.node.children.single as EmbedNode; - return EmbedProxy(child: widget.embedBuilder(context, embed)); + final embedNode = widget.node.children.single as EmbedNode; + final embedObject = widget.embedBuilder(context, embedNode); + return EmbedProxy(child: embedObject.child); } final theme = FleatherTheme.of(context)!; final text = buildText(context, widget.node, theme); @@ -174,8 +175,18 @@ class _TextLineState extends State { InlineSpan _segmentToTextSpan(Node segment, FleatherThemeData theme) { if (segment is EmbedNode) { - return WidgetSpan( - child: EmbedProxy(child: widget.embedBuilder(context, segment))); + final embedObject = widget.embedBuilder(context, segment); + final embedWidget = EmbedProxy(child: embedObject.child); + if (embedObject is FleatherSpanEmbed) { + return WidgetSpan( + child: embedWidget, + alignment: embedObject.placeholderAlignment, + baseline: embedObject.textBaseline, + style: embedObject.textStyle, + ); + } else { + return WidgetSpan(child: embedWidget); + } } final text = segment as TextNode; final attrs = text.style;