flutter-quill icon indicating copy to clipboard operation
flutter-quill copied to clipboard

Vertical alignment between text and embed

Open pamafe1976 opened this issue 9 months ago • 7 comments

Have you checked for an existing issue?

Flutter Quill Version

11.0.0

Steps to Reproduce

I would like to center the text vertically with embed blocks.

This question has been asked twice in #1067 [Web] [Mobile] [Desktop] Cannot vertically align custom component with the rest of the text and #1117 [Web], [Mobile] and [Desktop] Widgets vertical alignment along the text line

and in both cases has been closed as completed without explanation or solution. One of them links to a changelog entry that I could not find.

How can I achieve this?

Best Regards

Expected results

Be able to vertically align text with embeds

Actual results

text always aligned to the base of the embed

Additional Context

Screenshots / Video demonstration

[Attach media here]

Logs
[Paste logs here]

pamafe1976 avatar Feb 23 '25 19:02 pamafe1976

Can you pass to me some example code about the embed (it does not need to be equals)? I'll take a look and fix if needed.

CatHood0 avatar Feb 23 '25 23:02 CatHood0

This is a very simple example of an embed.

The text in the same line as the embed is aligned to the base of the embed, like it is shown in the images from #1067 and #1117. I could not find a way to change that vertical alignment

import 'dart:convert';
import 'package:catappadmin/extensions/quill_control_embed.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';

abstract class ControlEmbed {
  ControlEmbed();

  String get type;

  Map<String, dynamic> toJson() {
    throw UnimplementedError('toJson() has not been implemented.');
  }

  static ControlEmbed fromJson(Map<String, dynamic> json) {
    throw UnimplementedError('fromJson() has not been implemented.');
  }

  factory ControlEmbed.fromNode(String node) {
    final json = jsonDecode(node);
    return fromJson(json);
  }
}

class SimpleIconEmbed extends ControlEmbed {
  SimpleIconEmbed({required this.code, required this.size});

  final int code;
  final double size;

  @override
  String get type => 'simpleicon';

  factory SimpleIconEmbed.fromNode(String node) {
    final json = jsonDecode(node);
    return SimpleIconEmbed.fromJson(json);
  }

  @override
  factory SimpleIconEmbed.fromJson(Map<String, dynamic> json) {
    return SimpleIconEmbed(
      code: (json['code'] as num).toInt(),
      size: (json['size'] as num).toDouble(),
    );
  }

  @override
  Map<String, dynamic> toJson() {
    final json = {
      'code': code,
      'size': size,
    };
    return json;
  }
}

class SimpleIconEmbedBuilder extends EmbedBuilder {
  SimpleIconEmbedBuilder();

  @override
  String get key => 'simpleicon';

  @override
  bool get expanded => false;

  @override
  Widget build(
    BuildContext context,
    EmbedContext embedContext,
  ) {
    final iconEmbed = SimpleIconEmbed.fromNode(embedContext.node.value.data);

    final attributes = embedContext.node.style.attributes;
    final attr = attributes['color'];
    final color = attr?.value as Color?;

    return GestureDetector(
      child: Builder(builder: (context) {
        return Icon(
          IconData(iconEmbed.code, fontFamily: 'MaterialIcons'),
          size: iconEmbed.size,
          color: color,
        );
      }),
    );
  }
}

This code inserts the embed:

Future<void> addListTile(BuildContext context) async {
   final iconEmbed = SimpleIconEmbed(size: 80, code: Icons.add_box.codePoint);
   final data = jsonEncode(iconEmbed);
   final block = Embeddable('simpleicon', data);
   final index = quillController.selection.baseOffset;
   final length = quillController.selection.extentOffset - index;
   quillController
     ..skipRequestKeyboard = true
     ..replaceText(
       index,
       length,
       block,
       null,
     )
     ..moveCursorToPosition(index + 1);
 }

pamafe1976 avatar Feb 24 '25 00:02 pamafe1976

One of them links to a changelog entry that I could not find.

That's because the previous CHANGELOG has been achieved, the valid link now is: https://pub.dev/packages/flutter_quill/versions/10.8.5/changelog#702

EchoEllet avatar Feb 24 '25 19:02 EchoEllet

One of them links to a changelog entry that I could not find.

That's because the previous CHANGELOG has been achieved, the valid link now is: https://pub.dev/packages/flutter_quill/versions/10.8.5/changelog#702

I did realize of that, but the old changelog does not have any #720 entry and I found nothing related to this issue

pamafe1976 avatar Feb 24 '25 20:02 pamafe1976

This is what I found:

Allow widgets to override widget span properties.

EchoEllet avatar Feb 25 '25 04:02 EchoEllet

Hi, how to fix this then?

manudicri avatar Feb 27 '25 20:02 manudicri

For anyone looking for the fix, try overriding buildWidgetSpan of your class that extends EmbedBuilder

  @override
  WidgetSpan buildWidgetSpan(Widget widget) {
    return WidgetSpan(child: widget, alignment: PlaceholderAlignment.middle);
  }

keyur2maru avatar May 29 '25 05:05 keyur2maru

For anyone looking for the fix, try overriding buildWidgetSpan of your class that extends EmbedBuilder

  @override
  WidgetSpan buildWidgetSpan(Widget widget) {
    return WidgetSpan(child: widget, alignment: PlaceholderAlignment.middle);
  }

Hi @keyur2maru.

buildWidgetSpan looks promissing; however, how can you make the vertical alignment selectable for each embed, given the function does not pass the EmbedContext?

I would like to make the vertical alignment a property of each embed.

Regards

pamafe1976 avatar Jun 04 '25 22:06 pamafe1976

Hi @pamafe1976

I am not sure if it is possible to dynamically set the vertical alignment for each embed that’s under selection. My use case did not require dynamic vertical alignment, so I did not explore that possibility.

keyur2maru avatar Jun 04 '25 23:06 keyur2maru

Apparently there is no Attribute for vertical selection, so it may be not possible to apply to selection, but I could incorporate a variable within the Embed that holds the vertical alignment, and can be changed in the embed properties dialog. This requires a small change in buildWidgetSpan, so it passes the embedContext:

@override WidgetSpan buildWidgetSpan(Widget widget, EmbedContext embedContext) { final picture = PictureEmbed.fromNode(embedContext.node.value.data); return WidgetSpan(child: widget, alignment: VerticalAlign.values.byName(picture.valign).placeholder); }

The change is very simple, because buildWidgetSpan has only one reference inside text_line.dart, where it already uses the embedContext

I will create a PR for this soon.

Regards

pamafe1976 avatar Jun 05 '25 00:06 pamafe1976