Skip to content

Commit 5bd8084

Browse files
committed
Add configuration option to override default text rendering with a textSpanBuilder
1 parent 3d32e9f commit 5bd8084

File tree

7 files changed

+97
-29
lines changed

7 files changed

+97
-29
lines changed

lib/src/editor/config/editor_config.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import '../raw_editor/raw_editor.dart';
1414
import '../widgets/default_styles.dart';
1515
import '../widgets/delegate.dart';
1616
import '../widgets/link.dart';
17+
import '../widgets/text/utils/text_block_utils.dart';
1718
import 'search_config.dart';
1819

1920
// IMPORTANT For project authors: The QuillEditorConfig.copyWith()
@@ -54,6 +55,7 @@ class QuillEditorConfig {
5455
@experimental this.onKeyPressed,
5556
this.enableAlwaysIndentOnTab = false,
5657
this.embedBuilders,
58+
this.textSpanBuilder = defaultSpanBuilder,
5759
this.unknownEmbedBuilder,
5860
@experimental this.searchConfig = const QuillSearchConfig(),
5961
this.linkActionPickerDelegate = defaultLinkActionPickerDelegate,
@@ -361,6 +363,8 @@ class QuillEditorConfig {
361363
final CustomStyleBuilder? customStyleBuilder;
362364
final CustomRecognizerBuilder? customRecognizerBuilder;
363365

366+
final TextSpanBuilder textSpanBuilder;
367+
364368
/// See [search](https://github.com/singerdmx/flutter-quill/blob/master/doc/configurations/search.md)
365369
/// page for docs.
366370
@experimental

lib/src/editor/editor.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ class QuillEditorState extends State<QuillEditor>
303303
enableInteractiveSelection: configurations.enableInteractiveSelection,
304304
scrollPhysics: configurations.scrollPhysics,
305305
embedBuilder: _getEmbedBuilder,
306+
textSpanBuilder: configurations.textSpanBuilder,
306307
linkActionPickerDelegate: configurations.linkActionPickerDelegate,
307308
customStyleBuilder: configurations.customStyleBuilder,
308309
customRecognizerBuilder: configurations.customRecognizerBuilder,

lib/src/editor/raw_editor/config/raw_editor_config.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import '../../../editor/widgets/default_styles.dart';
1212
import '../../../editor/widgets/delegate.dart';
1313
import '../../../editor/widgets/link.dart';
1414
import '../../../toolbar/theme/quill_dialog_theme.dart';
15+
import '../../widgets/text/utils/text_block_utils.dart';
1516
import '../builders/leading_block_builder.dart';
1617
import 'events/events.dart';
1718

@@ -25,6 +26,7 @@ class QuillRawEditorConfig {
2526
required this.selectionColor,
2627
required this.selectionCtrls,
2728
required this.embedBuilder,
29+
required this.textSpanBuilder,
2830
required this.autoFocus,
2931
required this.characterShortcutEvents,
3032
required this.spaceShortcutEvents,
@@ -360,6 +362,9 @@ class QuillRawEditorConfig {
360362
final bool floatingCursorDisabled;
361363
final List<String> customLinkPrefixes;
362364

365+
/// Used to build the [InlineSpan]s containing text content.
366+
final TextSpanBuilder textSpanBuilder;
367+
363368
/// Configures the dialog theme.
364369
final QuillDialogTheme? dialogTheme;
365370

lib/src/editor/raw_editor/raw_editor_state.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ class QuillRawEditorState extends EditorState
607607
? const EdgeInsets.all(16)
608608
: null,
609609
embedBuilder: widget.config.embedBuilder,
610+
textSpanBuilder: widget.config.textSpanBuilder,
610611
linkActionPicker: _linkActionPicker,
611612
onLaunchUrl: widget.config.onLaunchUrl,
612613
cursorCont: _cursorCont,
@@ -643,6 +644,7 @@ class QuillRawEditorState extends EditorState
643644
line: node,
644645
textDirection: _textDirection,
645646
embedBuilder: widget.config.embedBuilder,
647+
textSpanBuilder: widget.config.textSpanBuilder,
646648
customStyleBuilder: widget.config.customStyleBuilder,
647649
customRecognizerBuilder: widget.config.customRecognizerBuilder,
648650
styles: _styles!,

lib/src/editor/widgets/text/text_block.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class EditableTextBlock extends StatelessWidget {
7070
required this.hasFocus,
7171
required this.contentPadding,
7272
required this.embedBuilder,
73+
required this.textSpanBuilder,
7374
required this.linkActionPicker,
7475
required this.cursorCont,
7576
required this.indentLevelCounts,
@@ -100,6 +101,7 @@ class EditableTextBlock extends StatelessWidget {
100101
final bool hasFocus;
101102
final EdgeInsets? contentPadding;
102103
final EmbedsBuilder embedBuilder;
104+
final TextSpanBuilder textSpanBuilder;
103105
final LinkActionPicker linkActionPicker;
104106
final ValueChanged<String>? onLaunchUrl;
105107
final CustomRecognizerBuilder? customRecognizerBuilder;
@@ -186,6 +188,7 @@ class EditableTextBlock extends StatelessWidget {
186188
line: line,
187189
textDirection: textDirection,
188190
embedBuilder: embedBuilder,
191+
textSpanBuilder: textSpanBuilder,
189192
customStyleBuilder: customStyleBuilder,
190193
styles: styles!,
191194
readOnly: readOnly,

lib/src/editor/widgets/text/text_line.dart

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class TextLine extends StatefulWidget {
2424
const TextLine({
2525
required this.line,
2626
required this.embedBuilder,
27+
required this.textSpanBuilder,
2728
required this.styles,
2829
required this.readOnly,
2930
required this.controller,
@@ -40,6 +41,7 @@ class TextLine extends StatefulWidget {
4041
final Line line;
4142
final TextDirection? textDirection;
4243
final EmbedsBuilder embedBuilder;
44+
final TextSpanBuilder textSpanBuilder;
4345
final DefaultStyles styles;
4446
final bool readOnly;
4547
final QuillController controller;
@@ -192,7 +194,12 @@ class _TextLineState extends State<TextLine> {
192194
InlineSpan _getTextSpanForWholeLine() {
193195
var lineStyle = _getLineStyle(widget.styles);
194196
if (!widget.line.hasEmbed) {
195-
return _buildTextSpan(widget.styles, widget.line.children, lineStyle);
197+
return _buildTextSpan(
198+
widget.styles,
199+
widget.line.children,
200+
lineStyle,
201+
widget.textSpanBuilder,
202+
);
196203
}
197204

198205
// The line could contain more than one Embed & more than one Text
@@ -201,8 +208,12 @@ class _TextLineState extends State<TextLine> {
201208
for (var child in widget.line.children) {
202209
if (child is Embed) {
203210
if (textNodes.isNotEmpty) {
204-
textSpanChildren
205-
.add(_buildTextSpan(widget.styles, textNodes, lineStyle));
211+
textSpanChildren.add(_buildTextSpan(
212+
widget.styles,
213+
textNodes,
214+
lineStyle,
215+
widget.textSpanBuilder,
216+
));
206217
textNodes = LinkedList<Node>();
207218
}
208219
// Creates correct node for custom embed
@@ -243,7 +254,12 @@ class _TextLineState extends State<TextLine> {
243254
}
244255

245256
if (textNodes.isNotEmpty) {
246-
textSpanChildren.add(_buildTextSpan(widget.styles, textNodes, lineStyle));
257+
textSpanChildren.add(_buildTextSpan(
258+
widget.styles,
259+
textNodes,
260+
lineStyle,
261+
widget.textSpanBuilder,
262+
));
247263
}
248264

249265
return TextSpan(style: lineStyle, children: textSpanChildren);
@@ -263,10 +279,11 @@ class _TextLineState extends State<TextLine> {
263279
return TextAlign.start;
264280
}
265281

266-
TextSpan _buildTextSpan(
282+
InlineSpan _buildTextSpan(
267283
DefaultStyles defaultStyles,
268284
LinkedList<Node> nodes,
269285
TextStyle lineStyle,
286+
TextSpanBuilder textSpanBuilder,
270287
) {
271288
if (nodes.isEmpty && kIsWeb) {
272289
nodes = LinkedList<Node>()..add(leaf.QuillText('\u{200B}'));
@@ -280,20 +297,28 @@ class _TextLineState extends State<TextLine> {
280297

281298
if (isComposingRangeOutOfLine) {
282299
final children = nodes
283-
.map((node) =>
284-
_getTextSpanFromNode(defaultStyles, node, widget.line.style))
300+
.map((node) => _getTextSpanFromNode(
301+
defaultStyles,
302+
node,
303+
widget.line.style,
304+
textSpanBuilder,
305+
))
285306
.toList(growable: false);
286307
return TextSpan(children: children, style: lineStyle);
287308
}
288309

289310
final children = nodes.expand((node) {
290-
final child =
291-
_getTextSpanFromNode(defaultStyles, node, widget.line.style);
311+
final child = _getTextSpanFromNode(
312+
defaultStyles,
313+
node,
314+
widget.line.style,
315+
textSpanBuilder,
316+
);
292317
final isNodeInComposingRange =
293318
node.documentOffset <= widget.composingRange.start &&
294319
widget.composingRange.end <= node.documentOffset + node.length;
295320
if (isNodeInComposingRange) {
296-
return _splitAndApplyComposingStyle(node, child);
321+
return _splitAndApplyComposingStyle(node, child, textSpanBuilder);
297322
} else {
298323
return [child];
299324
}
@@ -304,7 +329,11 @@ class _TextLineState extends State<TextLine> {
304329

305330
// split the text nodes into composing and non-composing nodes
306331
// and apply the composing style to the composing nodes
307-
List<InlineSpan> _splitAndApplyComposingStyle(Node node, InlineSpan child) {
332+
List<InlineSpan> _splitAndApplyComposingStyle(
333+
Node node,
334+
InlineSpan child,
335+
TextSpanBuilder textSpanBuilder,
336+
) {
308337
assert(widget.composingRange.isValid && !widget.composingRange.isCollapsed);
309338

310339
final composingStart = widget.composingRange.start - node.documentOffset;
@@ -319,19 +348,13 @@ class _TextLineState extends State<TextLine> {
319348
?.merge(const TextStyle(decoration: TextDecoration.underline)) ??
320349
const TextStyle(decoration: TextDecoration.underline);
321350

351+
final isLink = node.style.attributes[Attribute.link.key]?.value != null;
352+
final recognizer = _getRecognizer(node, isLink);
353+
322354
return [
323-
TextSpan(
324-
text: textBefore,
325-
style: child.style,
326-
),
327-
TextSpan(
328-
text: textComposing,
329-
style: composingStyle,
330-
),
331-
TextSpan(
332-
text: textAfter,
333-
style: child.style,
334-
),
355+
textSpanBuilder(context, textBefore, node, child.style, recognizer),
356+
textSpanBuilder(context, textComposing, node, composingStyle, recognizer),
357+
textSpanBuilder(context, textAfter, node, child.style, recognizer),
335358
];
336359
}
337360

@@ -461,7 +484,11 @@ class _TextLineState extends State<TextLine> {
461484
}
462485

463486
InlineSpan _getTextSpanFromNode(
464-
DefaultStyles defaultStyles, Node node, Style lineStyle) {
487+
DefaultStyles defaultStyles,
488+
Node node,
489+
Style lineStyle,
490+
TextSpanBuilder textSpanBuilder,
491+
) {
465492
final textNode = node as leaf.QuillText;
466493
final nodeStyle = textNode.style;
467494
final isLink = nodeStyle.containsKey(Attribute.link.key) &&
@@ -480,11 +507,12 @@ class _TextLineState extends State<TextLine> {
480507
}
481508

482509
final recognizer = _getRecognizer(node, isLink);
483-
return TextSpan(
484-
text: textNode.value,
485-
style: style,
486-
recognizer: recognizer,
487-
mouseCursor: (recognizer != null) ? SystemMouseCursors.click : null,
510+
return textSpanBuilder(
511+
context,
512+
textNode.value,
513+
textNode,
514+
style,
515+
recognizer,
488516
);
489517
}
490518

lib/src/editor/widgets/text/utils/text_block_utils.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import 'package:flutter/gestures.dart';
12
import 'package:flutter/material.dart';
3+
24
import '../../../../common/structs/horizontal_spacing.dart';
35
import '../../../../document/attribute.dart';
46
import '../../../../document/nodes/block.dart';
7+
import '../../../../document/nodes/node.dart';
58
import '../../default_styles.dart';
69

710
typedef LeadingBlockIndentWidth = HorizontalSpacing Function(
@@ -13,6 +16,28 @@ typedef LeadingBlockIndentWidth = HorizontalSpacing Function(
1316
typedef LeadingBlockNumberPointWidth = double Function(
1417
double fontSize, int count);
1518

19+
typedef TextSpanBuilder = InlineSpan Function(
20+
BuildContext context,
21+
String text,
22+
Node node,
23+
TextStyle? style,
24+
GestureRecognizer? recognizer,
25+
);
26+
27+
TextSpan defaultSpanBuilder(
28+
BuildContext context,
29+
String text,
30+
Node node,
31+
TextStyle? style,
32+
GestureRecognizer? recognizer,
33+
) =>
34+
TextSpan(
35+
text: text,
36+
style: style,
37+
recognizer: recognizer,
38+
mouseCursor: (recognizer != null) ? SystemMouseCursors.click : null,
39+
);
40+
1641
abstract final class TextBlockUtils {
1742
/// Get the horizontalSpacing using the default
1843
/// implementation provided by [Flutter Quill]

0 commit comments

Comments
 (0)