diff --git a/lib/src/editor/block_component/base_component/text_style_configuration.dart b/lib/src/editor/block_component/base_component/text_style_configuration.dart index 29736f287..3d9b553b1 100644 --- a/lib/src/editor/block_component/base_component/text_style_configuration.dart +++ b/lib/src/editor/block_component/base_component/text_style_configuration.dart @@ -25,6 +25,8 @@ class TextStyleConfiguration { ), this.applyHeightToFirstAscent = false, this.applyHeightToLastDescent = false, + this.lineHeight = 1.5, + this.leadingDistribution = TextLeadingDistribution.even, }); /// default text style @@ -55,6 +57,9 @@ class TextStyleConfiguration { final bool applyHeightToFirstAscent; final bool applyHeightToLastDescent; + final double lineHeight; + final TextLeadingDistribution leadingDistribution; + TextStyleConfiguration copyWith({ TextStyle? text, TextStyle? bold, @@ -66,6 +71,8 @@ class TextStyleConfiguration { TextStyle? autoComplete, bool? applyHeightToFirstAscent, bool? applyHeightToLastDescent, + double? lineHeight, + TextLeadingDistribution? leadingDistribution, }) { return TextStyleConfiguration( text: text ?? this.text, @@ -80,6 +87,8 @@ class TextStyleConfiguration { applyHeightToFirstAscent ?? this.applyHeightToFirstAscent, applyHeightToLastDescent: applyHeightToLastDescent ?? this.applyHeightToLastDescent, + lineHeight: lineHeight ?? this.lineHeight, + leadingDistribution: leadingDistribution ?? this.leadingDistribution, ); } } diff --git a/lib/src/editor/block_component/rich_text/appflowy_rich_text.dart b/lib/src/editor/block_component/rich_text/appflowy_rich_text.dart index b52e4df52..10405701a 100644 --- a/lib/src/editor/block_component/rich_text/appflowy_rich_text.dart +++ b/lib/src/editor/block_component/rich_text/appflowy_rich_text.dart @@ -183,42 +183,61 @@ class _AppFlowyRichTextState extends State return null; } - final textPosition = TextPosition(offset: position.offset); - var cursorHeight = _renderParagraph?.getFullHeightForCaret(textPosition); - var cursorOffset = - _renderParagraph?.getOffsetForCaret(textPosition, Rect.zero) ?? - Offset.zero; - if (cursorHeight == null) { - cursorHeight = - _placeholderRenderParagraph?.getFullHeightForCaret(textPosition); - cursorOffset = _placeholderRenderParagraph?.getOffsetForCaret( - textPosition, - Rect.zero, - ) ?? - Offset.zero; - if (textDirection() == TextDirection.rtl) { - if (widget.placeholderText.trim().isNotEmpty) { - cursorOffset = cursorOffset.translate( - _placeholderRenderParagraph?.size.width ?? 0, - 0, - ); - } - } + final selection = Selection( + start: position.copyWith( + offset: position.offset == 0 ? 0 : position.offset - 1, + ), + end: position.copyWith( + offset: position.offset == 0 ? 1 : position.offset, + ), + ); + + final rects = getRectsInSelection(selection); + var selectionRect = rects.firstOrNull; + var cursorHeight = selectionRect?.height; + var cursorOffset = _getCursorOffset(position, selectionRect); + if (cursorOffset == null || + cursorHeight == null || + selectionRect?.width == 0) { + selectionRect = + getRectsInSelection(selection, paragraph: _placeholderRenderParagraph) + .firstOrNull; + cursorHeight = selectionRect?.height; + cursorOffset = _getCursorOffset(position, selectionRect); } - if (widget.cursorHeight != null && cursorHeight != null) { - cursorOffset = Offset( - cursorOffset.dx, - cursorOffset.dy + (cursorHeight - widget.cursorHeight!) / 2, - ); + if (cursorOffset != null && + cursorHeight != null && + widget.cursorHeight != null) { + cursorOffset = _adjustCursorOffset(cursorOffset, cursorHeight); cursorHeight = widget.cursorHeight; } - final rect = Rect.fromLTWH( + + return _getCursorRect(cursorOffset, cursorHeight); + } + + Offset? _getCursorOffset(Position position, Rect? selectionRect) { + return position.offset == 0 + ? selectionRect?.topLeft + : selectionRect?.topRight; + } + + Offset _adjustCursorOffset(Offset cursorOffset, double cursorHeight) { + return Offset( + cursorOffset.dx, + cursorOffset.dy + (cursorHeight - widget.cursorHeight!) / 2, + ); + } + + Rect _getCursorRect(Offset? cursorOffset, double? cursorHeight) { + if (cursorOffset == null || cursorHeight == null) { + return Rect.zero; + } + return Rect.fromLTWH( max(0, cursorOffset.dx - (widget.cursorWidth / 2.0)), cursorOffset.dy, widget.cursorWidth, - cursorHeight ?? 16.0, + cursorHeight, ); - return rect; } @override @@ -271,15 +290,17 @@ class _AppFlowyRichTextState extends State List getRectsInSelection( Selection selection, { bool shiftWithBaseOffset = false, + RenderParagraph? paragraph, }) { - if (kDebugMode && _renderParagraph?.debugNeedsLayout == true) { + paragraph ??= _renderParagraph; + if (kDebugMode && paragraph?.debugNeedsLayout == true) { return []; } final textSelection = textSelectionFromEditorSelection(selection); if (textSelection == null) { return []; } - final rects = _renderParagraph + final rects = paragraph ?.getBoxesForSelection( textSelection, boxHeightStyle: BoxHeightStyle.max, @@ -291,7 +312,7 @@ class _AppFlowyRichTextState extends State // If the rich text widget does not contain any text, // there will be no selection boxes, // so we need to return to the default selection. - return [Rect.fromLTWH(0, 0, 0, _renderParagraph?.size.height ?? 0)]; + return [Rect.fromLTWH(0, 0, 0, paragraph?.size.height ?? 0)]; } return rects; } @@ -341,6 +362,7 @@ class _AppFlowyRichTextState extends State textStyleConfiguration.applyHeightToFirstAscent, applyHeightToLastDescent: textStyleConfiguration.applyHeightToLastDescent, + leadingDistribution: textStyleConfiguration.leadingDistribution, ), text: widget.placeholderTextSpanDecorator != null ? widget.placeholderTextSpanDecorator!(textSpan) @@ -348,10 +370,6 @@ class _AppFlowyRichTextState extends State textDirection: textDirection(), textScaler: TextScaler.linear(widget.editorState.editorStyle.textScaleFactor), - strutStyle: StrutStyle( - height: widget.lineHeight, - forceStrutHeight: true, - ), ); } @@ -366,6 +384,7 @@ class _AppFlowyRichTextState extends State textStyleConfiguration.applyHeightToFirstAscent, applyHeightToLastDescent: textStyleConfiguration.applyHeightToLastDescent, + leadingDistribution: textStyleConfiguration.leadingDistribution, ), text: widget.textSpanDecorator != null ? widget.textSpanDecorator!(textSpan) @@ -373,10 +392,6 @@ class _AppFlowyRichTextState extends State textDirection: textDirection(), textScaler: TextScaler.linear(widget.editorState.editorStyle.textScaleFactor), - strutStyle: StrutStyle( - height: widget.lineHeight, - forceStrutHeight: true, - ), ); } @@ -419,6 +434,7 @@ class _AppFlowyRichTextState extends State textStyleConfiguration.applyHeightToFirstAscent, applyHeightToLastDescent: textStyleConfiguration.applyHeightToLastDescent, + leadingDistribution: textStyleConfiguration.leadingDistribution, ), text: widget.textSpanDecorator != null ? widget.textSpanDecorator!(textSpan) @@ -426,10 +442,6 @@ class _AppFlowyRichTextState extends State textDirection: textDirection(), textScaler: TextScaler.linear(widget.editorState.editorStyle.textScaleFactor), - strutStyle: StrutStyle( - height: widget.lineHeight, - forceStrutHeight: true, - ), ); }, ); @@ -454,8 +466,9 @@ class _AppFlowyRichTextState extends State int offset = 0; List textSpans = []; for (final textInsert in textInserts) { - TextStyle textStyle = - textStyleConfiguration.text.copyWith(height: widget.lineHeight); + TextStyle textStyle = textStyleConfiguration.text.copyWith( + height: textStyleConfiguration.lineHeight, + ); final attributes = textInsert.attributes; if (attributes != null) { if (attributes.bold == true) { diff --git a/lib/src/editor_state.dart b/lib/src/editor_state.dart index 90292c517..af683b75c 100644 --- a/lib/src/editor_state.dart +++ b/lib/src/editor_state.dart @@ -258,7 +258,10 @@ class EditorState { // the value of the notifier is meaningless, just for triggering the callbacks. final ValueNotifier onDispose = ValueNotifier(0); + bool isDisposed = false; + void dispose() { + isDisposed = true; _observer.close(); _debouncedSealHistoryItemTimer?.cancel(); onDispose.value += 1; @@ -282,7 +285,7 @@ class EditorState { ApplyOptions options = const ApplyOptions(recordUndo: true), bool withUpdateSelection = true, }) async { - if (!editable) { + if (!editable || isDisposed) { return; }