Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added text direction feature #438

Merged
merged 5 commits into from
Oct 30, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/notus/lib/src/document/attributes.dart
Original file line number Diff line number Diff line change
@@ -72,6 +72,7 @@ abstract class NotusAttributeBuilder<T> implements NotusAttributeKey<T> {
/// * [NotusAttribute.link]
/// * [NotusAttribute.heading]
/// * [NotusAttribute.block]
/// * [NotusAttribute.direction]
class NotusAttribute<T> implements NotusAttributeBuilder<T> {
static final Map<String, NotusAttributeBuilder> _registry = {
NotusAttribute.bold.key: NotusAttribute.bold,
@@ -82,6 +83,7 @@ class NotusAttribute<T> implements NotusAttributeBuilder<T> {
NotusAttribute.link.key: NotusAttribute.link,
NotusAttribute.heading.key: NotusAttribute.heading,
NotusAttribute.block.key: NotusAttribute.block,
NotusAttribute.direction.key: NotusAttribute.direction,
};

// Inline attributes
@@ -136,6 +138,12 @@ class NotusAttribute<T> implements NotusAttributeBuilder<T> {
/// Alias for [NotusAttribute.block.code].
static NotusAttribute<String> get code => block.code;

/// Direction attribute
static const direction = DirectionAttributeBuilder._();

/// Alias for [NotusAttribute.direction.rtl].
static NotusAttribute<String> get rtl => direction.rtl;
pulyaevskiy marked this conversation as resolved.
Show resolved Hide resolved

static NotusAttribute _fromKeyValue(String key, dynamic value) {
if (!_registry.containsKey(key)) {
throw ArgumentError.value(
@@ -408,3 +416,11 @@ class BlockAttributeBuilder extends NotusAttributeBuilder<String> {
NotusAttribute<String> get quote =>
NotusAttribute<String>._(key, scope, 'quote');
}

class DirectionAttributeBuilder extends NotusAttributeBuilder<String> {
static const _kDirection = 'direction';
const DirectionAttributeBuilder._()
: super._(_kDirection, NotusAttributeScope.line);

NotusAttribute<String> get rtl => NotusAttribute<String>._(key, scope, 'rtl');
}
3 changes: 1 addition & 2 deletions packages/zefyr/lib/src/rendering/editable_text_block.dart
Original file line number Diff line number Diff line change
@@ -17,10 +17,9 @@ class RenderEditableTextBlock extends RenderEditableContainerBox
required TextDirection textDirection,
required EdgeInsetsGeometry padding,
required Decoration decoration,
ImageConfiguration configuration = ImageConfiguration.empty,
pulyaevskiy marked this conversation as resolved.
Show resolved Hide resolved
EdgeInsets contentPadding = EdgeInsets.zero,
}) : _decoration = decoration,
_configuration = configuration,
_configuration = ImageConfiguration(textDirection: textDirection),
_savedPadding = padding,
_contentPadding = contentPadding,
super(
3 changes: 2 additions & 1 deletion packages/zefyr/lib/src/rendering/editable_text_line.dart
Original file line number Diff line number Diff line change
@@ -633,7 +633,8 @@ class RenderEditableTextLine extends RenderEditableBox {
maxHeight: body!.size.height);
leading!.layout(leadingConstraints, parentUsesSize: true);
final parentData = leading!.parentData as BoxParentData;
parentData.offset = Offset(0.0, _resolvedPadding!.top);
final dxOffset = textDirection == TextDirection.rtl ? body!.size.width : 0.0;
parentData.offset = Offset(dxOffset, _resolvedPadding!.top);
}

size = constraints.constrain(Size(
42 changes: 20 additions & 22 deletions packages/zefyr/lib/src/widgets/editable_text_block.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:notus/notus.dart';
import 'package:zefyr/util.dart';

import '../rendering/editable_text_block.dart';
import 'cursor.dart';
@@ -10,7 +11,6 @@ import 'theme.dart';

class EditableTextBlock extends StatelessWidget {
final BlockNode node;
final TextDirection textDirection;
final VerticalSpacing spacing;
final CursorController cursorController;
final TextSelection selection;
@@ -23,7 +23,6 @@ class EditableTextBlock extends StatelessWidget {
EditableTextBlock({
Key? key,
required this.node,
required this.textDirection,
required this.spacing,
required this.cursorController,
required this.selection,
@@ -41,7 +40,6 @@ class EditableTextBlock extends StatelessWidget {
final theme = ZefyrTheme.of(context)!;
return _EditableBlock(
node: node,
textDirection: textDirection,
padding: spacing,
contentPadding: contentPadding,
decoration: _getDecorationForBlock(node, theme) ?? BoxDecoration(),
@@ -56,23 +54,25 @@ class EditableTextBlock extends StatelessWidget {
var index = 0;
for (final line in node.children) {
index++;
children.add(EditableTextLine(
node: line as LineNode,
textDirection: textDirection,
spacing: _getSpacingForLine(line, index, count, theme),
leading: _buildLeading(context, line, index, count),
indentWidth: _getIndentWidth(),
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
body: TextLine(
final nodeTextDirection = getDirectionOfNode(line as LineNode);
children.add(Directionality(
textDirection: nodeTextDirection,
Comment on lines +58 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just me learning RTL again. I suspect we still need to wrap with Directionality so that everything else inside the EditableTextLine can pick it up correctly instead of relying on the default value inherited from the app?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that happening here? The EditableTextLine is wrapped with Directionality widget. I think I didn't understand what you said correctly. Can you explain more?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was more of a question to confirm my understanding here. It just seems redundant to pass direction to the line widget but also wrap it with Directionality.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm now that I checked it again I can't remember why I did that. I think there was a reason for that back then but it makes no sense now. So I removed all of them and using provider pattern instead. PTAL.

child: EditableTextLine(
node: line,
textDirection: textDirection,
embedBuilder: embedBuilder,
spacing: _getSpacingForLine(line, index, count, theme),
leading: _buildLeading(context, line, index, count),
indentWidth: _getIndentWidth(),
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
body: TextLine(
node: line,
embedBuilder: embedBuilder,
),
cursorController: cursorController,
selection: selection,
selectionColor: selectionColor,
enableInteractiveSelection: enableInteractiveSelection,
hasFocus: hasFocus,
),
cursorController: cursorController,
selection: selection,
selectionColor: selectionColor,
enableInteractiveSelection: enableInteractiveSelection,
hasFocus: hasFocus,
));
}
return children.toList(growable: false);
@@ -180,15 +180,13 @@ class EditableTextBlock extends StatelessWidget {

class _EditableBlock extends MultiChildRenderObjectWidget {
final BlockNode node;
final TextDirection textDirection;
final VerticalSpacing padding;
final Decoration decoration;
final EdgeInsets? contentPadding;

_EditableBlock({
Key? key,
required this.node,
required this.textDirection,
required this.decoration,
required List<Widget> children,
this.contentPadding,
@@ -204,7 +202,7 @@ class _EditableBlock extends MultiChildRenderObjectWidget {
RenderEditableTextBlock createRenderObject(BuildContext context) {
return RenderEditableTextBlock(
node: node,
textDirection: textDirection,
textDirection: Directionality.of(context),
padding: _padding,
decoration: decoration,
contentPadding: _contentPadding,
@@ -215,7 +213,7 @@ class _EditableBlock extends MultiChildRenderObjectWidget {
void updateRenderObject(
BuildContext context, covariant RenderEditableTextBlock renderObject) {
renderObject.node = node;
renderObject.textDirection = textDirection;
renderObject.textDirection = Directionality.of(context);
renderObject.padding = _padding;
renderObject.decoration = decoration;
renderObject.contentPadding = _contentPadding;
6 changes: 2 additions & 4 deletions packages/zefyr/lib/src/widgets/editable_text_line.dart
Original file line number Diff line number Diff line change
@@ -26,7 +26,6 @@ class EditableTextLine extends RenderObjectWidget {
/// Space above and below [body] of this text line.
final VerticalSpacing spacing;

final TextDirection textDirection;
final CursorController cursorController;
final TextSelection selection;
final Color selectionColor;
@@ -39,7 +38,6 @@ class EditableTextLine extends RenderObjectWidget {
Key? key,
required this.node,
required this.body,
required this.textDirection,
required this.cursorController,
required this.selection,
required this.selectionColor,
@@ -65,7 +63,7 @@ class EditableTextLine extends RenderObjectWidget {
return RenderEditableTextLine(
node: node,
padding: _padding,
textDirection: textDirection,
textDirection: Directionality.of(context),
cursorController: cursorController,
selection: selection,
selectionColor: selectionColor,
@@ -80,7 +78,7 @@ class EditableTextLine extends RenderObjectWidget {
BuildContext context, covariant RenderEditableTextLine renderObject) {
renderObject.node = node;
renderObject.padding = _padding;
renderObject.textDirection = textDirection;
renderObject.textDirection = Directionality.of(context);
renderObject.cursorController = cursorController;
renderObject.selection = selection;
renderObject.selectionColor = selectionColor;
58 changes: 31 additions & 27 deletions packages/zefyr/lib/src/widgets/editor.dart
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ import 'package:flutter/widgets.dart';
import 'package:notus/notus.dart';
import 'package:zefyr/src/widgets/baseline_proxy.dart';
import 'package:zefyr/src/widgets/single_child_scroll_view.dart';
import 'package:zefyr/util.dart';

import '../rendering/editor.dart';
import '../services/keyboard.dart';
@@ -1159,38 +1160,41 @@ class RawEditorState extends EditorState
final result = <Widget>[];
for (final node in widget.controller.document.root.children) {
if (node is LineNode) {
result.add(EditableTextLine(
node: node,
textDirection: _textDirection,
indentWidth: 0,
spacing: _getSpacingForLine(node, _themeData),
cursorController: _cursorController,
selection: widget.controller.selection,
selectionColor: widget.selectionColor,
enableInteractiveSelection: widget.enableInteractiveSelection,
body: TextLine(
result.add(Directionality(
textDirection: getDirectionOfNode(node),
child: EditableTextLine(
node: node,
textDirection: _textDirection,
embedBuilder: widget.embedBuilder,
indentWidth: 0,
spacing: _getSpacingForLine(node, _themeData),
cursorController: _cursorController,
selection: widget.controller.selection,
selectionColor: widget.selectionColor,
enableInteractiveSelection: widget.enableInteractiveSelection,
body: TextLine(
node: node,
embedBuilder: widget.embedBuilder,
),
hasFocus: _hasFocus,
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
),
hasFocus: _hasFocus,
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
));
} else if (node is BlockNode) {
final block = node.style.get(NotusAttribute.block);
result.add(EditableTextBlock(
node: node,
textDirection: _textDirection,
spacing: _getSpacingForBlock(node, _themeData),
cursorController: _cursorController,
selection: widget.controller.selection,
selectionColor: widget.selectionColor,
enableInteractiveSelection: widget.enableInteractiveSelection,
hasFocus: _hasFocus,
contentPadding: (block == NotusAttribute.block.code)
? EdgeInsets.all(16.0)
: null,
embedBuilder: widget.embedBuilder,
result.add(Directionality(
textDirection: getDirectionOfNode(node),
child: EditableTextBlock(
node: node,
spacing: _getSpacingForBlock(node, _themeData),
cursorController: _cursorController,
selection: widget.controller.selection,
selectionColor: widget.selectionColor,
enableInteractiveSelection: widget.enableInteractiveSelection,
hasFocus: _hasFocus,
contentPadding: (block == NotusAttribute.block.code)
? EdgeInsets.all(16.0)
: null,
embedBuilder: widget.embedBuilder,
),
));
} else {
throw StateError('Unreachable.');
13 changes: 13 additions & 0 deletions packages/zefyr/lib/src/widgets/editor_toolbar.dart
Original file line number Diff line number Diff line change
@@ -401,6 +401,7 @@ class ZefyrToolbar extends StatefulWidget implements PreferredSizeWidget {
bool hideQuote = false,
bool hideLink = false,
bool hideHorizontalRule = false,
bool hideDirection = false,
List<Widget> leading = const <Widget>[],
List<Widget> trailing = const <Widget>[],
}) {
@@ -450,6 +451,18 @@ class ZefyrToolbar extends StatefulWidget implements PreferredSizeWidget {
controller: controller,
),
),
Visibility(
visible: !hideDirection,
child: VerticalDivider(
indent: 16, endIndent: 16, color: Colors.grey.shade400)),
Visibility(
visible: !hideDirection,
child: ToggleStyleButton(
attribute: NotusAttribute.rtl,
icon: Icons.format_textdirection_r_to_l,
controller: controller,
),
),
Visibility(
visible: !hideHeadingStyle,
child: VerticalDivider(
6 changes: 2 additions & 4 deletions packages/zefyr/lib/src/widgets/rich_text_proxy.dart
Original file line number Diff line number Diff line change
@@ -7,7 +7,6 @@ class RichTextProxy extends SingleChildRenderObjectWidget {
RichTextProxy({
required RichText child,
required this.textStyle,
required this.textDirection,
required this.locale,
required this.strutStyle,
this.textScaleFactor = 1.0,
@@ -16,7 +15,6 @@ class RichTextProxy extends SingleChildRenderObjectWidget {
}) : super(child: child);

final TextStyle textStyle;
final TextDirection? textDirection;
final double textScaleFactor;
final Locale? locale;
final StrutStyle strutStyle;
@@ -27,7 +25,7 @@ class RichTextProxy extends SingleChildRenderObjectWidget {
RenderParagraphProxy createRenderObject(BuildContext context) {
return RenderParagraphProxy(
textStyle: textStyle,
textDirection: textDirection,
textDirection: Directionality.of(context),
textScaleFactor: textScaleFactor,
locale: locale,
strutStyle: strutStyle,
@@ -40,7 +38,7 @@ class RichTextProxy extends SingleChildRenderObjectWidget {
void updateRenderObject(
BuildContext context, covariant RenderParagraphProxy renderObject) {
renderObject.textStyle = textStyle;
renderObject.textDirection = textDirection!;
renderObject.textDirection = Directionality.of(context);
renderObject.textScaleFactor = textScaleFactor;
renderObject.locale = locale;
renderObject.strutStyle = strutStyle;
4 changes: 0 additions & 4 deletions packages/zefyr/lib/src/widgets/text_line.dart
Original file line number Diff line number Diff line change
@@ -14,14 +14,12 @@ import 'theme.dart';
class TextLine extends StatelessWidget {
/// Line of text represented by this widget.
final LineNode node;
final TextDirection? textDirection;
final ZefyrEmbedBuilder embedBuilder;

const TextLine({
Key? key,
required this.node,
required this.embedBuilder,
this.textDirection,
}) : super(key: key);

@override
@@ -37,12 +35,10 @@ class TextLine extends StatelessWidget {
StrutStyle.fromTextStyle(text.style!, forceStrutHeight: true);
return RichTextProxy(
textStyle: text.style!,
textDirection: textDirection,
strutStyle: strutStyle,
locale: Localizations.maybeLocaleOf(context),
child: RichText(
text: buildText(context, node),
textDirection: textDirection,
strutStyle: strutStyle,
textScaleFactor: MediaQuery.textScaleFactorOf(context),
),
4 changes: 2 additions & 2 deletions packages/zefyr/lib/src/widgets/theme.dart
Original file line number Diff line number Diff line change
@@ -192,8 +192,8 @@ class ZefyrThemeData {
spacing: baseSpacing,
lineSpacing: VerticalSpacing(top: 6, bottom: 2),
decoration: BoxDecoration(
border: Border(
left: BorderSide(width: 4, color: Colors.grey.shade300),
border: BorderDirectional(
start: BorderSide(width: 4, color: Colors.grey.shade300),
),
),
),
10 changes: 10 additions & 0 deletions packages/zefyr/lib/util.dart
Original file line number Diff line number Diff line change
@@ -6,7 +6,9 @@
library zefyr.util;

import 'dart:math' as math;
import 'dart:ui';

import 'package:notus/notus.dart';
import 'package:quill_delta/quill_delta.dart';

export 'src/fast_diff.dart';
@@ -39,3 +41,11 @@ int getPositionDelta(Delta user, Delta actual) {
}
return diff;
}

TextDirection getDirectionOfNode(StyledNode node) {
final direction = node.style.get(NotusAttribute.direction);
if (direction == NotusAttribute.rtl) {
return TextDirection.rtl;
}
return TextDirection.ltr;
}