Skip to content

Commit

Permalink
feat: support customizing line height (#806)
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasXu0 committed May 22, 2024
1 parent 7fab6ff commit 0c79b87
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class TextStyleConfiguration {
),
this.applyHeightToFirstAscent = false,
this.applyHeightToLastDescent = false,
this.lineHeight = 1.5,
this.leadingDistribution = TextLeadingDistribution.even,
});

/// default text style
Expand Down Expand Up @@ -55,6 +57,9 @@ class TextStyleConfiguration {
final bool applyHeightToFirstAscent;
final bool applyHeightToLastDescent;

final double lineHeight;
final TextLeadingDistribution leadingDistribution;

TextStyleConfiguration copyWith({
TextStyle? text,
TextStyle? bold,
Expand All @@ -66,6 +71,8 @@ class TextStyleConfiguration {
TextStyle? autoComplete,
bool? applyHeightToFirstAscent,
bool? applyHeightToLastDescent,
double? lineHeight,
TextLeadingDistribution? leadingDistribution,
}) {
return TextStyleConfiguration(
text: text ?? this.text,
Expand All @@ -80,6 +87,8 @@ class TextStyleConfiguration {
applyHeightToFirstAscent ?? this.applyHeightToFirstAscent,
applyHeightToLastDescent:
applyHeightToLastDescent ?? this.applyHeightToLastDescent,
lineHeight: lineHeight ?? this.lineHeight,
leadingDistribution: leadingDistribution ?? this.leadingDistribution,
);
}
}
105 changes: 59 additions & 46 deletions lib/src/editor/block_component/rich_text/appflowy_rich_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -183,42 +183,61 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
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
Expand Down Expand Up @@ -271,15 +290,17 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
List<Rect> 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,
Expand All @@ -291,7 +312,7 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
// 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;
}
Expand Down Expand Up @@ -341,17 +362,14 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
textStyleConfiguration.applyHeightToFirstAscent,
applyHeightToLastDescent:
textStyleConfiguration.applyHeightToLastDescent,
leadingDistribution: textStyleConfiguration.leadingDistribution,
),
text: widget.placeholderTextSpanDecorator != null
? widget.placeholderTextSpanDecorator!(textSpan)
: textSpan,
textDirection: textDirection(),
textScaler:
TextScaler.linear(widget.editorState.editorStyle.textScaleFactor),
strutStyle: StrutStyle(
height: widget.lineHeight,
forceStrutHeight: true,
),
);
}

Expand All @@ -366,17 +384,14 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
textStyleConfiguration.applyHeightToFirstAscent,
applyHeightToLastDescent:
textStyleConfiguration.applyHeightToLastDescent,
leadingDistribution: textStyleConfiguration.leadingDistribution,
),
text: widget.textSpanDecorator != null
? widget.textSpanDecorator!(textSpan)
: textSpan,
textDirection: textDirection(),
textScaler:
TextScaler.linear(widget.editorState.editorStyle.textScaleFactor),
strutStyle: StrutStyle(
height: widget.lineHeight,
forceStrutHeight: true,
),
);
}

Expand Down Expand Up @@ -419,17 +434,14 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
textStyleConfiguration.applyHeightToFirstAscent,
applyHeightToLastDescent:
textStyleConfiguration.applyHeightToLastDescent,
leadingDistribution: textStyleConfiguration.leadingDistribution,
),
text: widget.textSpanDecorator != null
? widget.textSpanDecorator!(textSpan)
: textSpan,
textDirection: textDirection(),
textScaler:
TextScaler.linear(widget.editorState.editorStyle.textScaleFactor),
strutStyle: StrutStyle(
height: widget.lineHeight,
forceStrutHeight: true,
),
);
},
);
Expand All @@ -454,8 +466,9 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
int offset = 0;
List<InlineSpan> 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) {
Expand Down
5 changes: 4 additions & 1 deletion lib/src/editor_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,10 @@ class EditorState {
// the value of the notifier is meaningless, just for triggering the callbacks.
final ValueNotifier<int> onDispose = ValueNotifier(0);

bool isDisposed = false;

void dispose() {
isDisposed = true;
_observer.close();
_debouncedSealHistoryItemTimer?.cancel();
onDispose.value += 1;
Expand All @@ -282,7 +285,7 @@ class EditorState {
ApplyOptions options = const ApplyOptions(recordUndo: true),
bool withUpdateSelection = true,
}) async {
if (!editable) {
if (!editable || isDisposed) {
return;
}

Expand Down

0 comments on commit 0c79b87

Please sign in to comment.