Merge pull request #142 from singerdmx/fix2

Add tap handles, scrollBottomInset, toolbar button fillColor as param, and fix replace length
pull/145/head
hyouuu 4 years ago committed by GitHub
commit 48d176d864
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      lib/models/documents/document.dart
  2. 23
      lib/models/rules/insert.dart
  3. 91
      lib/widgets/editor.dart
  4. 8
      lib/widgets/raw_editor.dart
  5. 11
      lib/widgets/text_block.dart
  6. 1
      lib/widgets/text_selection.dart
  7. 24
      lib/widgets/toolbar.dart

@ -47,7 +47,7 @@ class Document {
Stream<Tuple3<Delta, Delta, ChangeSource>> get changes => _observer.stream;
Delta insert(int index, Object? data) {
Delta insert(int index, Object? data, {int replaceLength = 0}) {
assert(index >= 0);
assert(data is String || data is Embeddable);
if (data is Embeddable) {
@ -56,7 +56,7 @@ class Document {
return Delta();
}
final delta = _rules.apply(RuleType.INSERT, this, index, data: data);
final delta = _rules.apply(RuleType.INSERT, this, index, data: data, len: replaceLength);
compose(delta, ChangeSource.LOCAL);
return delta;
}
@ -80,8 +80,10 @@ class Document {
var delta = Delta();
// We have to insert before applying delete rules
// Otherwise delete would be operating on stale document snapshot.
if (dataIsNotEmpty) {
delta = insert(index + len, data);
delta = insert(index, data, replaceLength: len);
}
if (len > 0) {

@ -13,7 +13,6 @@ abstract class InsertRule extends Rule {
@override
void validateArgs(int? len, Object? data, Attribute? attribute) {
assert(len == null);
assert(data != null);
assert(attribute == null);
}
@ -43,7 +42,7 @@ class PreserveLineStyleOnSplitRule extends InsertRule {
final text = after.data as String;
final delta = Delta()..retain(index);
final delta = Delta()..retain(index + (len ?? 0));
if (text.contains('\n')) {
assert(after.isPlain);
delta.insert('\n');
@ -86,7 +85,7 @@ class PreserveBlockStyleOnInsertRule extends InsertRule {
}
final lines = data.split('\n');
final delta = Delta()..retain(index);
final delta = Delta()..retain(index + (len ?? 0));
for (var i = 0; i < lines.length; i++) {
final line = lines[i];
if (line.isNotEmpty) {
@ -157,7 +156,7 @@ class AutoExitBlockRule extends InsertRule {
.firstWhere((k) => Attribute.blockKeysExceptHeader.contains(k));
attributes[k] = null;
// retain(1) should be '\n', set it with no attribute
return Delta()..retain(index)..retain(1, attributes);
return Delta()..retain(index + (len ?? 0))..retain(1, attributes);
}
}
@ -183,7 +182,7 @@ class ResetLineFormatOnNewLineRule extends InsertRule {
resetStyle = Attribute.header.toJson();
}
return Delta()
..retain(index)
..retain(index + (len ?? 0))
..insert('\n', cur.attributes)
..retain(1, resetStyle)
..trim();
@ -200,7 +199,7 @@ class InsertEmbedsRule extends InsertRule {
return null;
}
final delta = Delta()..retain(index);
final delta = Delta()..retain(index + (len ?? 0));
final itr = DeltaIterator(document);
final prev = itr.skip(index), cur = itr.next();
@ -258,7 +257,7 @@ class ForceNewlineForInsertsAroundEmbedRule extends InsertRule {
if (!cursorBeforeEmbed && !cursorAfterEmbed) {
return null;
}
final delta = Delta()..retain(index);
final delta = Delta()..retain(index + (len ?? 0));
if (cursorBeforeEmbed && !text.endsWith('\n')) {
return delta..insert(text)..insert('\n');
}
@ -299,7 +298,7 @@ class AutoFormatLinksRule extends InsertRule {
attributes.addAll(LinkAttribute(link.toString()).toJson());
return Delta()
..retain(index - cand.length)
..retain(index + (len ?? 0) - cand.length)
..retain(cand.length, attributes)
..insert(data, prev.attributes);
} on FormatException {
@ -330,13 +329,13 @@ class PreserveInlineStylesRule extends InsertRule {
final text = data;
if (attributes == null || !attributes.containsKey(Attribute.link.key)) {
return Delta()
..retain(index)
..retain(index + (len ?? 0))
..insert(text, attributes);
}
attributes.remove(Attribute.link.key);
final delta = Delta()
..retain(index)
..retain(index + (len ?? 0))
..insert(text, attributes.isEmpty ? null : attributes);
final next = itr.next();
@ -346,7 +345,7 @@ class PreserveInlineStylesRule extends InsertRule {
}
if (attributes[Attribute.link.key] == nextAttributes[Attribute.link.key]) {
return Delta()
..retain(index)
..retain(index + (len ?? 0))
..insert(text, attributes);
}
return delta;
@ -360,7 +359,7 @@ class CatchAllInsertRule extends InsertRule {
Delta applyRule(Delta document, int index,
{int? len, Object? data, Attribute? attribute}) {
return Delta()
..retain(index)
..retain(index + (len ?? 0))
..insert(data);
}
}

@ -119,6 +119,7 @@ class QuillEditor extends StatefulWidget {
required this.focusNode,
required this.scrollController,
required this.scrollable,
required this.scrollBottomInset,
required this.padding,
required this.autoFocus,
required this.readOnly,
@ -133,7 +134,12 @@ class QuillEditor extends StatefulWidget {
this.keyboardAppearance = Brightness.light,
this.scrollPhysics,
this.onLaunchUrl,
this.embedBuilder = _defaultEmbedBuilder,
this.onTapDown,
this.onTapUp,
this.onSingleLongTapStart,
this.onSingleLongTapMoveUpdate,
this.onSingleLongTapEnd,
this.embedBuilder = _defaultEmbedBuilder
});
factory QuillEditor.basic({
@ -148,6 +154,7 @@ class QuillEditor extends StatefulWidget {
autoFocus: true,
readOnly: readOnly,
expands: false,
scrollBottomInset: 0,
padding: EdgeInsets.zero);
}
@ -155,6 +162,7 @@ class QuillEditor extends StatefulWidget {
final FocusNode focusNode;
final ScrollController scrollController;
final bool scrollable;
final double scrollBottomInset;
final EdgeInsetsGeometry padding;
final bool autoFocus;
final bool? showCursor;
@ -169,6 +177,16 @@ class QuillEditor extends StatefulWidget {
final Brightness keyboardAppearance;
final ScrollPhysics? scrollPhysics;
final ValueChanged<String>? onLaunchUrl;
// Returns whether gesture is handled
final bool Function(TapDownDetails details, TextPosition textPosition)? onTapDown;
// Returns whether gesture is handled
final bool Function(TapUpDetails details, TextPosition textPosition)? onTapUp;
// Returns whether gesture is handled
final bool Function(LongPressStartDetails details, TextPosition textPosition)? onSingleLongTapStart;
// Returns whether gesture is handled
final bool Function(LongPressMoveUpdateDetails details, TextPosition textPosition)? onSingleLongTapMoveUpdate;
// Returns whether gesture is handled
final bool Function(LongPressEndDetails details, TextPosition textPosition)? onSingleLongTapEnd;
final EmbedBuilder embedBuilder;
@override
@ -239,6 +257,7 @@ class _QuillEditorState extends State<QuillEditor>
widget.focusNode,
widget.scrollController,
widget.scrollable,
widget.scrollBottomInset,
widget.padding,
widget.readOnly,
widget.placeholder,
@ -315,6 +334,15 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override
void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) {
if (_state.widget.onSingleLongTapMoveUpdate != null) {
final renderEditor = getRenderEditor();
if (renderEditor != null) {
if (_state.widget.onSingleLongTapMoveUpdate!(details, renderEditor.getPositionForOffset(details.globalPosition)
)) {
return;
}
}
}
if (!delegate.getSelectionEnabled()) {
return;
}
@ -434,8 +462,30 @@ class _QuillEditorSelectionGestureDetectorBuilder
await launch(url);
}
@override
void onTapDown(TapDownDetails details) {
if (_state.widget.onTapDown != null) {
final renderEditor = getRenderEditor();
if (renderEditor != null) {
if (_state.widget.onTapDown!(details, renderEditor.getPositionForOffset(details.globalPosition))) {
return;
}
}
}
super.onTapDown(details);
}
@override
void onSingleTapUp(TapUpDetails details) {
if (_state.widget.onTapUp != null) {
final renderEditor = getRenderEditor();
if (renderEditor != null) {
if (_state.widget.onTapUp!(details, renderEditor.getPositionForOffset(details.globalPosition))) {
return;
}
}
}
getEditor()!.hideToolbar();
final positionSelected = _onTapping(details);
@ -469,6 +519,15 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override
void onSingleLongTapStart(LongPressStartDetails details) {
if (_state.widget.onSingleLongTapStart != null) {
final renderEditor = getRenderEditor();
if (renderEditor != null) {
if (_state.widget.onSingleLongTapStart!(details, renderEditor.getPositionForOffset(details.globalPosition))) {
return;
}
}
}
if (delegate.getSelectionEnabled()) {
switch (Theme.of(_state.context).platform) {
case TargetPlatform.iOS:
@ -491,6 +550,19 @@ class _QuillEditorSelectionGestureDetectorBuilder
}
}
}
@override
void onSingleLongTapEnd(LongPressEndDetails details) {
if (_state.widget.onSingleLongTapEnd != null) {
final renderEditor = getRenderEditor();
if (renderEditor != null) {
if (_state.widget.onSingleLongTapEnd!(details, renderEditor.getPositionForOffset(details.globalPosition))) {
return;
}
}
}
super.onSingleLongTapEnd(details);
}
}
typedef TextSelectionChangedHandler = void Function(
@ -501,6 +573,7 @@ class RenderEditor extends RenderEditableContainerBox
RenderEditor(
List<RenderEditableBox>? children,
TextDirection textDirection,
double scrollBottomInset,
EdgeInsetsGeometry padding,
this.document,
this.selection,
@ -513,6 +586,7 @@ class RenderEditor extends RenderEditableContainerBox
children,
document.root,
textDirection,
scrollBottomInset,
padding,
);
@ -571,6 +645,14 @@ class RenderEditor extends RenderEditableContainerBox
markNeedsPaint();
}
void setScrollBottomInset(double value) {
if (scrollBottomInset == value) {
return;
}
scrollBottomInset = value;
markNeedsPaint();
}
@override
List<TextSelectionPoint> getEndpointsForSelection(
TextSelection textSelection) {
@ -843,8 +925,9 @@ class RenderEditor extends RenderEditableContainerBox
child.preferredLineHeight(TextPosition(
offset: selection.extentOffset - child.getContainer().offset)) -
kMargin +
offsetInViewport;
final caretBottom = endpoint.point.dy + kMargin + offsetInViewport;
offsetInViewport +
scrollBottomInset;
final caretBottom = endpoint.point.dy + kMargin + offsetInViewport + scrollBottomInset;
double? dy;
if (caretTop < scrollOffset) {
dy = caretTop;
@ -871,6 +954,7 @@ class RenderEditableContainerBox extends RenderBox
List<RenderEditableBox>? children,
this._container,
this.textDirection,
this.scrollBottomInset,
this._padding,
) : assert(_padding.isNonNegative) {
addAll(children);
@ -879,6 +963,7 @@ class RenderEditableContainerBox extends RenderBox
container_node.Container _container;
TextDirection textDirection;
EdgeInsetsGeometry _padding;
double scrollBottomInset;
EdgeInsets? _resolvedPadding;
container_node.Container getContainer() {

@ -34,6 +34,7 @@ class RawEditor extends StatefulWidget {
this.focusNode,
this.scrollController,
this.scrollable,
this.scrollBottomInset,
this.padding,
this.readOnly,
this.placeholder,
@ -65,6 +66,7 @@ class RawEditor extends StatefulWidget {
final FocusNode focusNode;
final ScrollController scrollController;
final bool scrollable;
final double scrollBottomInset;
final EdgeInsetsGeometry padding;
final bool readOnly;
final String? placeholder;
@ -527,6 +529,7 @@ class RawEditorState extends EditorState
startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink,
onSelectionChanged: _handleSelectionChanged,
scrollBottomInset: widget.scrollBottomInset,
padding: widget.padding,
children: _buildChildren(_doc, context),
),
@ -588,6 +591,7 @@ class RawEditorState extends EditorState
final editableTextBlock = EditableTextBlock(
node,
_textDirection,
widget.scrollBottomInset,
_getVerticalSpacingForBlock(node, _styles),
widget.controller.selection,
widget.selectionColor,
@ -1137,6 +1141,7 @@ class _Editor extends MultiChildRenderObjectWidget {
required this.startHandleLayerLink,
required this.endHandleLayerLink,
required this.onSelectionChanged,
required this.scrollBottomInset,
this.padding = EdgeInsets.zero,
}) : super(key: key, children: children);
@ -1147,6 +1152,7 @@ class _Editor extends MultiChildRenderObjectWidget {
final LayerLink startHandleLayerLink;
final LayerLink endHandleLayerLink;
final TextSelectionChangedHandler onSelectionChanged;
final double scrollBottomInset;
final EdgeInsetsGeometry padding;
@override
@ -1154,6 +1160,7 @@ class _Editor extends MultiChildRenderObjectWidget {
return RenderEditor(
null,
textDirection,
scrollBottomInset,
padding,
document,
selection,
@ -1177,6 +1184,7 @@ class _Editor extends MultiChildRenderObjectWidget {
..setStartHandleLayerLink(startHandleLayerLink)
..setEndHandleLayerLink(endHandleLayerLink)
..onSelectionChanged = onSelectionChanged
..setScrollBottomInset(scrollBottomInset)
..setPadding(padding);
}
}

@ -50,6 +50,7 @@ class EditableTextBlock extends StatelessWidget {
const EditableTextBlock(
this.block,
this.textDirection,
this.scrollBottomInset,
this.verticalSpacing,
this.textSelection,
this.color,
@ -64,6 +65,7 @@ class EditableTextBlock extends StatelessWidget {
final Block block;
final TextDirection textDirection;
final double scrollBottomInset;
final Tuple2 verticalSpacing;
final TextSelection textSelection;
final Color color;
@ -84,6 +86,7 @@ class EditableTextBlock extends StatelessWidget {
block,
textDirection,
verticalSpacing as Tuple2<double, double>,
scrollBottomInset,
_getDecorationForBlock(block, defaultStyles) ?? const BoxDecoration(),
contentPadding,
_buildChildren(context, indentLevelCounts));
@ -256,6 +259,7 @@ class RenderEditableTextBlock extends RenderEditableContainerBox
required Block block,
required TextDirection textDirection,
required EdgeInsetsGeometry padding,
required double scrollBottomInset,
required Decoration decoration,
List<RenderEditableBox>? children,
ImageConfiguration configuration = ImageConfiguration.empty,
@ -268,6 +272,7 @@ class RenderEditableTextBlock extends RenderEditableContainerBox
children,
block,
textDirection,
scrollBottomInset,
padding.add(contentPadding),
);
@ -512,14 +517,16 @@ class _EditableBlock extends MultiChildRenderObjectWidget {
this.block,
this.textDirection,
this.padding,
this.scrollBottomInset,
this.decoration,
this.contentPadding,
List<Widget> children,
List<Widget> children
) : super(children: children);
final Block block;
final TextDirection textDirection;
final Tuple2<double, double> padding;
final double scrollBottomInset;
final Decoration decoration;
final EdgeInsets? contentPadding;
@ -534,6 +541,7 @@ class _EditableBlock extends MultiChildRenderObjectWidget {
block: block,
textDirection: textDirection,
padding: _padding,
scrollBottomInset: scrollBottomInset,
decoration: decoration,
contentPadding: _contentPadding,
);
@ -545,6 +553,7 @@ class _EditableBlock extends MultiChildRenderObjectWidget {
renderObject
..setContainer(block)
..textDirection = textDirection
..scrollBottomInset = scrollBottomInset
..setPadding(_padding)
..decoration = decoration
..contentPadding = _contentPadding;

@ -533,6 +533,7 @@ class _EditorTextSelectionGestureDetectorState
}
void _handleTapDown(TapDownDetails details) {
// renderObject.resetTapDownStatus();
if (widget.onTapDown != null) {
widget.onTapDown!(details);
}

@ -24,11 +24,13 @@ class InsertEmbedButton extends StatelessWidget {
const InsertEmbedButton({
required this.controller,
required this.icon,
this.fillColor,
Key? key,
}) : super(key: key);
final QuillController controller;
final IconData icon;
final Color? fillColor;
@override
Widget build(BuildContext context) {
@ -41,7 +43,7 @@ class InsertEmbedButton extends StatelessWidget {
size: iconSize,
color: Theme.of(context).iconTheme.color,
),
fillColor: Theme.of(context).canvasColor,
fillColor: fillColor ?? Theme.of(context).canvasColor,
onPressed: () {
final index = controller.selection.baseOffset;
final length = controller.selection.extentOffset - index;
@ -169,6 +171,7 @@ typedef ToggleStyleButtonBuilder = Widget Function(
BuildContext context,
Attribute attribute,
IconData icon,
Color? fillColor,
bool? isToggled,
VoidCallback? onPressed,
);
@ -178,6 +181,7 @@ class ToggleStyleButton extends StatefulWidget {
required this.attribute,
required this.icon,
required this.controller,
this.fillColor,
this.childBuilder = defaultToggleStyleButtonBuilder,
Key? key,
}) : super(key: key);
@ -186,6 +190,8 @@ class ToggleStyleButton extends StatefulWidget {
final IconData icon;
final Color? fillColor;
final QuillController controller;
final ToggleStyleButtonBuilder childBuilder;
@ -246,8 +252,7 @@ class _ToggleStyleButtonState extends State<ToggleStyleButton> {
_selectionStyle.attributes.containsKey(Attribute.codeBlock.key);
final isEnabled =
!isInCodeBlock || widget.attribute.key == Attribute.codeBlock.key;
return widget.childBuilder(context, widget.attribute, widget.icon,
_isToggled, isEnabled ? _toggleAttribute : null);
return widget.childBuilder(context, widget.attribute, widget.icon, widget.fillColor, _isToggled, isEnabled ? _toggleAttribute : null);
}
void _toggleAttribute() {
@ -262,12 +267,15 @@ class ToggleCheckListButton extends StatefulWidget {
required this.icon,
required this.controller,
required this.attribute,
this.fillColor,
this.childBuilder = defaultToggleStyleButtonBuilder,
Key? key,
}) : super(key: key);
final IconData icon;
final Color? fillColor;
final QuillController controller;
final ToggleStyleButtonBuilder childBuilder;
@ -331,8 +339,7 @@ class _ToggleCheckListButtonState extends State<ToggleCheckListButton> {
_selectionStyle.attributes.containsKey(Attribute.codeBlock.key);
final isEnabled =
!isInCodeBlock || Attribute.list.key == Attribute.codeBlock.key;
return widget.childBuilder(context, Attribute.unchecked, widget.icon,
_isToggled, isEnabled ? _toggleAttribute : null);
return widget.childBuilder(context, Attribute.unchecked, widget.icon, widget.fillColor, _isToggled, isEnabled ? _toggleAttribute : null);
}
void _toggleAttribute() {
@ -346,6 +353,7 @@ Widget defaultToggleStyleButtonBuilder(
BuildContext context,
Attribute attribute,
IconData icon,
Color? fillColor,
bool? isToggled,
VoidCallback? onPressed,
) {
@ -356,14 +364,14 @@ Widget defaultToggleStyleButtonBuilder(
? theme.primaryIconTheme.color
: theme.iconTheme.color
: theme.disabledColor;
final fillColor =
isToggled == true ? theme.toggleableActiveColor : theme.canvasColor;
final fill =
isToggled == true ? theme.toggleableActiveColor : fillColor ?? theme.canvasColor;
return QuillIconButton(
highlightElevation: 0,
hoverElevation: 0,
size: iconSize * 1.77,
icon: Icon(icon, size: iconSize, color: iconColor),
fillColor: fillColor,
fillColor: fill,
onPressed: onPressed,
);
}

Loading…
Cancel
Save