Support for building custom inline styles (#351)

pull/371/head
X Code 4 years ago committed by GitHub
parent 9c40eb4e7a
commit f56bbff75f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      lib/src/models/documents/attribute.dart
  2. 3
      lib/src/models/documents/style.dart
  3. 2
      lib/src/widgets/delegate.dart
  4. 139
      lib/src/widgets/editor.dart
  5. 35
      lib/src/widgets/raw_editor.dart
  6. 33
      lib/src/widgets/simple_viewer.dart
  7. 35
      lib/src/widgets/text_block.dart
  8. 19
      lib/src/widgets/text_line.dart

@ -165,11 +165,11 @@ class Attribute<T> {
Map<String, dynamic> toJson() => <String, dynamic>{key: value};
static Attribute fromKeyValue(String key, dynamic value) {
if (!_registry.containsKey(key)) {
throw ArgumentError.value(key, 'key "$key" not found.');
static Attribute? fromKeyValue(String key, dynamic value) {
final origin = _registry[key];
if (origin == null) {
return null;
}
final origin = _registry[key]!;
final attribute = clone(origin, value);
return attribute;
}

@ -18,7 +18,8 @@ class Style {
final result = attributes.map((key, dynamic value) {
final attr = Attribute.fromKeyValue(key, value);
return MapEntry<String, Attribute>(key, attr);
return MapEntry<String, Attribute>(
key, attr ?? Attribute(key, AttributeScope.IGNORE, value));
});
return Style.attr(result);
}

@ -10,6 +10,8 @@ import 'text_selection.dart';
typedef EmbedBuilder = Widget Function(
BuildContext context, Embed node, bool readOnly);
typedef StyleBuilder = TextStyle Function(String attributeKey);
abstract class EditorTextSelectionGestureDetectorBuilderDelegate {
GlobalKey<EditorState> getEditableTextKey();

@ -225,33 +225,35 @@ Widget _defaultEmbedBuilder(
}
class QuillEditor extends StatefulWidget {
const QuillEditor(
{required this.controller,
required this.focusNode,
required this.scrollController,
required this.scrollable,
required this.padding,
required this.autoFocus,
required this.readOnly,
required this.expands,
this.showCursor,
this.paintCursorAboveText,
this.placeholder,
this.enableInteractiveSelection = true,
this.scrollBottomInset = 0,
this.minHeight,
this.maxHeight,
this.customStyles,
this.textCapitalization = TextCapitalization.sentences,
this.keyboardAppearance = Brightness.light,
this.scrollPhysics,
this.onLaunchUrl,
this.onTapDown,
this.onTapUp,
this.onSingleLongTapStart,
this.onSingleLongTapMoveUpdate,
this.onSingleLongTapEnd,
this.embedBuilder = _defaultEmbedBuilder});
const QuillEditor({
required this.controller,
required this.focusNode,
required this.scrollController,
required this.scrollable,
required this.padding,
required this.autoFocus,
required this.readOnly,
required this.expands,
this.showCursor,
this.paintCursorAboveText,
this.placeholder,
this.enableInteractiveSelection = true,
this.scrollBottomInset = 0,
this.minHeight,
this.maxHeight,
this.customStyles,
this.textCapitalization = TextCapitalization.sentences,
this.keyboardAppearance = Brightness.light,
this.scrollPhysics,
this.onLaunchUrl,
this.onTapDown,
this.onTapUp,
this.onSingleLongTapStart,
this.onSingleLongTapMoveUpdate,
this.onSingleLongTapEnd,
this.embedBuilder = _defaultEmbedBuilder,
this.styleBuilder,
});
factory QuillEditor.basic({
required QuillController controller,
@ -310,6 +312,7 @@ class QuillEditor extends StatefulWidget {
onSingleLongTapEnd;
final EmbedBuilder embedBuilder;
final StyleBuilder? styleBuilder;
@override
_QuillEditorState createState() => _QuillEditorState();
@ -374,46 +377,48 @@ class _QuillEditorState extends State<QuillEditor>
return _selectionGestureDetectorBuilder.build(
HitTestBehavior.translucent,
RawEditor(
_editorKey,
widget.controller,
widget.focusNode,
widget.scrollController,
widget.scrollable,
widget.scrollBottomInset,
widget.padding,
widget.readOnly,
widget.placeholder,
widget.onLaunchUrl,
ToolbarOptions(
copy: widget.enableInteractiveSelection,
cut: widget.enableInteractiveSelection,
paste: widget.enableInteractiveSelection,
selectAll: widget.enableInteractiveSelection,
),
theme.platform == TargetPlatform.iOS ||
theme.platform == TargetPlatform.android,
widget.showCursor,
CursorStyle(
color: cursorColor,
backgroundColor: Colors.grey,
width: 2,
radius: cursorRadius,
offset: cursorOffset,
paintAboveText: widget.paintCursorAboveText ?? paintCursorAboveText,
opacityAnimates: cursorOpacityAnimates,
),
widget.textCapitalization,
widget.maxHeight,
widget.minHeight,
widget.customStyles,
widget.expands,
widget.autoFocus,
selectionColor,
textSelectionControls,
widget.keyboardAppearance,
widget.enableInteractiveSelection,
widget.scrollPhysics,
widget.embedBuilder),
_editorKey,
widget.controller,
widget.focusNode,
widget.scrollController,
widget.scrollable,
widget.scrollBottomInset,
widget.padding,
widget.readOnly,
widget.placeholder,
widget.onLaunchUrl,
ToolbarOptions(
copy: widget.enableInteractiveSelection,
cut: widget.enableInteractiveSelection,
paste: widget.enableInteractiveSelection,
selectAll: widget.enableInteractiveSelection,
),
theme.platform == TargetPlatform.iOS ||
theme.platform == TargetPlatform.android,
widget.showCursor,
CursorStyle(
color: cursorColor,
backgroundColor: Colors.grey,
width: 2,
radius: cursorRadius,
offset: cursorOffset,
paintAboveText: widget.paintCursorAboveText ?? paintCursorAboveText,
opacityAnimates: cursorOpacityAnimates,
),
widget.textCapitalization,
widget.maxHeight,
widget.minHeight,
widget.customStyles,
widget.expands,
widget.autoFocus,
selectionColor,
textSelectionControls,
widget.keyboardAppearance,
widget.enableInteractiveSelection,
widget.scrollPhysics,
widget.embedBuilder,
widget.styleBuilder,
),
);
}

@ -57,6 +57,7 @@ class RawEditor extends StatefulWidget {
this.enableInteractiveSelection,
this.scrollPhysics,
this.embedBuilder,
this.styleBuilder,
) : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'),
assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'),
assert(maxHeight == null || minHeight == null || maxHeight >= minHeight,
@ -89,7 +90,7 @@ class RawEditor extends StatefulWidget {
final bool enableInteractiveSelection;
final ScrollPhysics? scrollPhysics;
final EmbedBuilder embedBuilder;
final StyleBuilder? styleBuilder;
@override
State<StatefulWidget> createState() => RawEditorState();
}
@ -231,23 +232,24 @@ class RawEditorState extends EditorState
} else if (node is Block) {
final attrs = node.style.attributes;
final editableTextBlock = EditableTextBlock(
node,
_textDirection,
widget.scrollBottomInset,
_getVerticalSpacingForBlock(node, _styles),
widget.controller.selection,
widget.selectionColor,
_styles,
widget.enableInteractiveSelection,
_hasFocus,
attrs.containsKey(Attribute.codeBlock.key)
block: node,
textDirection: _textDirection,
scrollBottomInset: widget.scrollBottomInset,
verticalSpacing: _getVerticalSpacingForBlock(node, _styles),
textSelection: widget.controller.selection,
color: widget.selectionColor,
styles: _styles,
enableInteractiveSelection: widget.enableInteractiveSelection,
hasFocus: _hasFocus,
contentPadding: attrs.containsKey(Attribute.codeBlock.key)
? const EdgeInsets.all(16)
: null,
widget.embedBuilder,
_cursorCont,
indentLevelCounts,
_handleCheckboxTap,
widget.readOnly);
embedBuilder: widget.embedBuilder,
cursorCont: _cursorCont,
indentLevelCounts: indentLevelCounts,
onCheckboxTap: _handleCheckboxTap,
readOnly: widget.readOnly,
styleBuilder: widget.styleBuilder);
result.add(editableTextBlock);
} else {
throw StateError('Unreachable.');
@ -262,6 +264,7 @@ class RawEditorState extends EditorState
line: node,
textDirection: _textDirection,
embedBuilder: widget.embedBuilder,
styleBuilder: widget.styleBuilder,
styles: _styles!,
readOnly: widget.readOnly,
);

@ -202,26 +202,23 @@ class _QuillSimpleViewerState extends State<QuillSimpleViewer>
} else if (node is Block) {
final attrs = node.style.attributes;
final editableTextBlock = EditableTextBlock(
node,
_textDirection,
widget.scrollBottomInset,
_getVerticalSpacingForBlock(node, _styles),
widget.controller.selection,
Colors.black,
// selectionColor,
_styles,
false,
// enableInteractiveSelection,
false,
// hasFocus,
attrs.containsKey(Attribute.codeBlock.key)
block: node,
textDirection: _textDirection,
scrollBottomInset: widget.scrollBottomInset,
verticalSpacing: _getVerticalSpacingForBlock(node, _styles),
textSelection: widget.controller.selection,
color: Colors.black,
styles: _styles,
enableInteractiveSelection: false,
hasFocus: false,
contentPadding: attrs.containsKey(Attribute.codeBlock.key)
? const EdgeInsets.all(16)
: null,
embedBuilder,
_cursorCont,
indentLevelCounts,
_handleCheckboxTap,
widget.readOnly);
embedBuilder: embedBuilder,
cursorCont: _cursorCont,
indentLevelCounts: indentLevelCounts,
onCheckboxTap: _handleCheckboxTap,
readOnly: widget.readOnly);
result.add(editableTextBlock);
} else {
throw StateError('Unreachable.');

@ -48,22 +48,23 @@ const List<String> romanNumbers = [
class EditableTextBlock extends StatelessWidget {
const EditableTextBlock(
this.block,
this.textDirection,
this.scrollBottomInset,
this.verticalSpacing,
this.textSelection,
this.color,
this.styles,
this.enableInteractiveSelection,
this.hasFocus,
this.contentPadding,
this.embedBuilder,
this.cursorCont,
this.indentLevelCounts,
this.onCheckboxTap,
this.readOnly,
);
{required this.block,
required this.textDirection,
required this.scrollBottomInset,
required this.verticalSpacing,
required this.textSelection,
required this.color,
required this.styles,
required this.enableInteractiveSelection,
required this.hasFocus,
required this.contentPadding,
required this.embedBuilder,
required this.cursorCont,
required this.indentLevelCounts,
required this.onCheckboxTap,
required this.readOnly,
this.styleBuilder,
Key? key});
final Block block;
final TextDirection textDirection;
@ -76,6 +77,7 @@ class EditableTextBlock extends StatelessWidget {
final bool hasFocus;
final EdgeInsets? contentPadding;
final EmbedBuilder embedBuilder;
final StyleBuilder? styleBuilder;
final CursorCont cursorCont;
final Map<int, int> indentLevelCounts;
final Function(int, bool) onCheckboxTap;
@ -123,6 +125,7 @@ class EditableTextBlock extends StatelessWidget {
line: line,
textDirection: textDirection,
embedBuilder: embedBuilder,
styleBuilder: styleBuilder,
styles: styles!,
readOnly: readOnly,
),

@ -27,6 +27,7 @@ class TextLine extends StatelessWidget {
required this.styles,
required this.readOnly,
this.textDirection,
this.styleBuilder,
Key? key,
}) : super(key: key);
@ -35,7 +36,7 @@ class TextLine extends StatelessWidget {
final EmbedBuilder embedBuilder;
final DefaultStyles styles;
final bool readOnly;
final StyleBuilder? styleBuilder;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
@ -149,10 +150,25 @@ class TextLine extends StatelessWidget {
}
textStyle = textStyle.merge(toMerge);
textStyle = _applyCustomAttributes(textStyle, line.style.attributes);
return textStyle;
}
TextStyle _applyCustomAttributes(
TextStyle textStyle, Map<String, Attribute> attributes) {
if (styleBuilder != null) {
attributes.keys
.where((key) => !attributes.containsKey(key))
.forEach((key) {
/// Custom Attribute
final customAttr = styleBuilder!.call(key);
textStyle = textStyle.merge(customAttr);
});
}
return textStyle;
}
TextSpan _getTextSpanFromNode(DefaultStyles defaultStyles, Node node) {
final textNode = node as leaf.Text;
final style = textNode.style;
@ -223,6 +239,7 @@ class TextLine extends StatelessWidget {
res = res.merge(TextStyle(backgroundColor: backgroundColor));
}
res = _applyCustomAttributes(res, textNode.style.attributes);
return TextSpan(text: textNode.value, style: res);
}

Loading…
Cancel
Save