From 4f9ee27f7dc8baf64d36784d4d74c6feb1e18ece Mon Sep 17 00:00:00 2001 From: X Code Date: Sun, 20 Feb 2022 08:23:10 -0800 Subject: [PATCH] Support text direction rtl --- lib/src/models/documents/attribute.dart | 12 ++++++++++++ lib/src/utils/delta.dart | 11 +++++++++++ lib/src/widgets/raw_editor.dart | 7 +++++-- lib/src/widgets/text_block.dart | 5 ++++- lib/src/widgets/toolbar.dart | 11 ++++++++++- 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/lib/src/models/documents/attribute.dart b/lib/src/models/documents/attribute.dart index ed848d79..fe2832da 100644 --- a/lib/src/models/documents/attribute.dart +++ b/lib/src/models/documents/attribute.dart @@ -79,6 +79,8 @@ class Attribute { static final BlockQuoteAttribute blockQuote = BlockQuoteAttribute(); + static final DirectionAttribute direction = DirectionAttribute(null); + static final WidthAttribute width = WidthAttribute(null); static final HeightAttribute height = HeightAttribute(null); @@ -116,6 +118,7 @@ class Attribute { Attribute.codeBlock.key, Attribute.blockQuote.key, Attribute.indent.key, + Attribute.direction.key, }); static final Set blockKeysExceptHeader = LinkedHashSet.of({ @@ -124,6 +127,7 @@ class Attribute { Attribute.codeBlock.key, Attribute.blockQuote.key, Attribute.indent.key, + Attribute.direction.key, }); static final Set exclusiveBlockKeys = LinkedHashSet.of({ @@ -163,6 +167,9 @@ class Attribute { // "attributes":{"list":"unchecked"} static Attribute get unchecked => ListAttribute('unchecked'); + // "attributes":{"direction":"rtl"} + static Attribute get rtl => DirectionAttribute('rtl'); + // "attributes":{"indent":1"} static Attribute get indentL1 => IndentAttribute(level: 1); @@ -309,6 +316,11 @@ class BlockQuoteAttribute extends Attribute { BlockQuoteAttribute() : super('blockquote', AttributeScope.BLOCK, true); } +class DirectionAttribute extends Attribute { + DirectionAttribute(String? val) + : super('direction', AttributeScope.BLOCK, val); +} + class WidthAttribute extends Attribute { WidthAttribute(String? val) : super('width', AttributeScope.IGNORE, val); } diff --git a/lib/src/utils/delta.dart b/lib/src/utils/delta.dart index cf0e5c63..c737e1ac 100644 --- a/lib/src/utils/delta.dart +++ b/lib/src/utils/delta.dart @@ -1,5 +1,8 @@ import 'dart:math' as math; +import 'dart:ui'; +import '../models/documents/attribute.dart'; +import '../models/documents/nodes/node.dart'; import '../models/quill_delta.dart'; // Diff between two texts - old text and new text @@ -72,3 +75,11 @@ int getPositionDelta(Delta user, Delta actual) { } return diff; } + +TextDirection getDirectionOfNode(Node node) { + final direction = node.style.attributes[Attribute.direction.key]; + if (direction == Attribute.rtl) { + return TextDirection.rtl; + } + return TextDirection.ltr; +} diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index b15bc247..05cd99ef 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -19,6 +19,7 @@ import '../models/documents/nodes/embeddable.dart'; import '../models/documents/nodes/line.dart'; import '../models/documents/nodes/node.dart'; import '../models/documents/style.dart'; +import '../utils/delta.dart'; import '../utils/platform.dart'; import 'controller.dart'; import 'cursor.dart'; @@ -436,7 +437,8 @@ class RawEditorState extends EditorState for (final node in doc.root.children) { if (node is Line) { final editableTextLine = _getEditableTextLineFromNode(node, context); - result.add(editableTextLine); + result.add(Directionality( + textDirection: getDirectionOfNode(node), child: editableTextLine)); } else if (node is Block) { final attrs = node.style.attributes; final editableTextBlock = EditableTextBlock( @@ -461,7 +463,8 @@ class RawEditorState extends EditorState onCheckboxTap: _handleCheckboxTap, readOnly: widget.readOnly, customStyleBuilder: widget.customStyleBuilder); - result.add(editableTextBlock); + result.add(Directionality( + textDirection: getDirectionOfNode(node), child: editableTextBlock)); } else { throw StateError('Unreachable.'); } diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index 3ab0f439..26305179 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -5,6 +5,7 @@ import 'package:tuple/tuple.dart'; import '../../flutter_quill.dart'; import '../models/documents/nodes/block.dart'; import '../models/documents/nodes/line.dart'; +import '../utils/delta.dart'; import 'box.dart'; import 'cursor.dart'; import 'delegate.dart'; @@ -146,7 +147,9 @@ class EditableTextBlock extends StatelessWidget { hasFocus, MediaQuery.of(context).devicePixelRatio, cursorCont); - children.add(editableTextLine); + final nodeTextDirection = getDirectionOfNode(line); + children.add(Directionality( + textDirection: nodeTextDirection, child: editableTextLine)); } return children.toList(growable: false); } diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index ad350c00..e001c9d9 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -100,6 +100,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { bool showImageButton = true, bool showVideoButton = true, bool showCameraButton = true, + bool showDirection = false, OnImagePickCallback? onImagePickCallback, OnVideoPickCallback? onVideoPickCallback, MediaPickSettingSelector? mediaPickSettingSelector, @@ -131,7 +132,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { showClearFormat || onImagePickCallback != null || onVideoPickCallback != null, - showAlignmentButtons, + showAlignmentButtons || showDirection, showLeftAlignment, showCenterAlignment, showRightAlignment, @@ -296,6 +297,14 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { showRightAlignment: showRightAlignment, showJustifyAlignment: showJustifyAlignment, ), + if (showDirection) + ToggleStyleButton( + attribute: Attribute.rtl, + controller: controller, + icon: Icons.format_textdirection_r_to_l, + iconSize: toolbarIconSize, + iconTheme: iconTheme, + ), if (showDividers && isButtonGroupShown[1] && (isButtonGroupShown[2] ||