parent
37fc173dde
commit
f5a1a61938
8 changed files with 664 additions and 0 deletions
@ -0,0 +1,124 @@ |
|||||||
|
import 'dart:math' as math; |
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart'; |
||||||
|
import 'package:flutter_quill/models/documents/attribute.dart'; |
||||||
|
import 'package:flutter_quill/models/documents/document.dart'; |
||||||
|
import 'package:flutter_quill/models/documents/nodes/embed.dart'; |
||||||
|
import 'package:flutter_quill/models/documents/style.dart'; |
||||||
|
import 'package:flutter_quill/utils/diff_delta.dart'; |
||||||
|
import 'package:quill_delta/quill_delta.dart'; |
||||||
|
|
||||||
|
class QuillController extends ChangeNotifier { |
||||||
|
final Document document; |
||||||
|
TextSelection selection; |
||||||
|
Style toggledStyle = Style(); |
||||||
|
|
||||||
|
QuillController(this.document, this.selection, this.toggledStyle) |
||||||
|
: assert(document != null), |
||||||
|
assert(selection != null); |
||||||
|
|
||||||
|
Style getSelectionStyle() { |
||||||
|
return document |
||||||
|
.collectStyle(selection.start, selection.end - selection.start) |
||||||
|
.mergeAll(toggledStyle); |
||||||
|
} |
||||||
|
|
||||||
|
replaceText(int index, int len, Object data, TextSelection textSelection) { |
||||||
|
assert(data is String || data is Embeddable); |
||||||
|
|
||||||
|
Delta delta; |
||||||
|
if (len > 0 || data is! String || (data as String).isNotEmpty) { |
||||||
|
delta = document.replace(index, len, data); |
||||||
|
if (delta != null && |
||||||
|
toggledStyle.isNotEmpty && |
||||||
|
delta.isNotEmpty && |
||||||
|
delta.length <= 2 && |
||||||
|
delta.last.isInsert) { |
||||||
|
Delta retainDelta = Delta() |
||||||
|
..retain(index) |
||||||
|
..retain(data is String ? data.length : 1, toggledStyle.toJson()); |
||||||
|
document.compose(retainDelta, ChangeSource.LOCAL); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
toggledStyle = Style(); |
||||||
|
if (textSelection != null) { |
||||||
|
if (delta == null) { |
||||||
|
_updateSelection(textSelection, ChangeSource.LOCAL); |
||||||
|
} else { |
||||||
|
Delta user = Delta() |
||||||
|
..retain(index) |
||||||
|
..insert(data) |
||||||
|
..delete(len); |
||||||
|
int positionDelta = getPositionDelta(user, delta); |
||||||
|
_updateSelection( |
||||||
|
textSelection.copyWith( |
||||||
|
baseOffset: textSelection.baseOffset + positionDelta, |
||||||
|
extentOffset: textSelection.extentOffset + positionDelta, |
||||||
|
), |
||||||
|
ChangeSource.LOCAL, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
notifyListeners(); |
||||||
|
} |
||||||
|
|
||||||
|
formatText(int index, int len, Attribute attribute) { |
||||||
|
if (len == 0 && attribute.isInline && attribute.key != Attribute.link.key) { |
||||||
|
toggledStyle = toggledStyle.put(attribute); |
||||||
|
} |
||||||
|
|
||||||
|
Delta change = document.format(index, len, attribute); |
||||||
|
TextSelection adjustedSelection = selection.copyWith( |
||||||
|
baseOffset: change.transformPosition(selection.baseOffset), |
||||||
|
extentOffset: change.transformPosition(selection.extentOffset)); |
||||||
|
if (selection != adjustedSelection) { |
||||||
|
_updateSelection(adjustedSelection, ChangeSource.LOCAL); |
||||||
|
} |
||||||
|
notifyListeners(); |
||||||
|
} |
||||||
|
|
||||||
|
formatSelection(Attribute attribute) { |
||||||
|
formatText(selection.start, selection.end - selection.start, attribute); |
||||||
|
} |
||||||
|
|
||||||
|
updateSelection(TextSelection textSelection, ChangeSource source) { |
||||||
|
_updateSelection(textSelection, source); |
||||||
|
notifyListeners(); |
||||||
|
} |
||||||
|
|
||||||
|
compose(Delta delta, TextSelection textSelection, ChangeSource source) { |
||||||
|
if (delta.isNotEmpty) { |
||||||
|
document.compose(delta, source); |
||||||
|
} |
||||||
|
if (textSelection != null) { |
||||||
|
_updateSelection(textSelection, source); |
||||||
|
} else { |
||||||
|
textSelection = selection.copyWith( |
||||||
|
baseOffset: |
||||||
|
delta.transformPosition(selection.baseOffset, force: false), |
||||||
|
extentOffset: |
||||||
|
delta.transformPosition(selection.extentOffset, force: false)); |
||||||
|
if (selection != textSelection) { |
||||||
|
_updateSelection(textSelection, source); |
||||||
|
} |
||||||
|
} |
||||||
|
notifyListeners(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
document.close(); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
_updateSelection(TextSelection textSelection, ChangeSource source) { |
||||||
|
assert(textSelection != null); |
||||||
|
assert(source != null); |
||||||
|
selection = textSelection; |
||||||
|
int end = document.length - 1; |
||||||
|
selection = selection.copyWith( |
||||||
|
baseOffset: math.min(selection.baseOffset, end), |
||||||
|
extentOffset: math.min(selection.extentOffset, end)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
import 'package:flutter/cupertino.dart'; |
||||||
|
|
||||||
|
import 'editor.dart'; |
||||||
|
|
||||||
|
abstract class EditorTextSelectionGestureDetectorBuilderDelegate { |
||||||
|
GlobalKey<EditorState> getEditableTextKey(); |
||||||
|
|
||||||
|
bool getForcePressEnabled(); |
||||||
|
|
||||||
|
bool getSelectionEnabled(); |
||||||
|
} |
||||||
|
|
||||||
|
class EditorTextSelectionGestureDetectorBuilder { |
||||||
|
final EditorTextSelectionGestureDetectorBuilderDelegate delegate; |
||||||
|
bool shouldShowSelectionToolbar = true; |
||||||
|
|
||||||
|
EditorTextSelectionGestureDetectorBuilder(this.delegate) |
||||||
|
: assert(delegate != null); |
||||||
|
|
||||||
|
EditorState getEditor() { |
||||||
|
return delegate.getEditableTextKey().currentState; |
||||||
|
} |
||||||
|
|
||||||
|
RenderEditor getRenderEditor() { |
||||||
|
return this.getEditor().getRenderEditor(); |
||||||
|
} |
||||||
|
|
||||||
|
onTapDown(TapDownDetails details) { |
||||||
|
// getRenderEditor().handleTapDown(details); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,247 @@ |
|||||||
|
import 'package:flutter/cupertino.dart'; |
||||||
|
import 'package:flutter/rendering.dart'; |
||||||
|
import 'package:flutter_quill/models/documents/document.dart'; |
||||||
|
import 'package:flutter_quill/widgets/text_selection.dart'; |
||||||
|
|
||||||
|
import 'controller.dart'; |
||||||
|
import 'delegate.dart'; |
||||||
|
|
||||||
|
abstract class RenderAbstractEditor { |
||||||
|
TextSelection selectWordAtPosition(TextPosition position); |
||||||
|
|
||||||
|
TextSelection selectLineAtPosition(TextPosition position); |
||||||
|
|
||||||
|
double preferredLineHeight(TextPosition position); |
||||||
|
|
||||||
|
TextPosition getPositionForOffset(Offset offset); |
||||||
|
|
||||||
|
List<TextSelectionPoint> getEndpointsForSelection( |
||||||
|
TextSelection textSelection); |
||||||
|
|
||||||
|
void handleTapDown(TapDownDetails details); |
||||||
|
|
||||||
|
void selectWordsInRange( |
||||||
|
Offset from, |
||||||
|
Offset to, |
||||||
|
SelectionChangedCause cause, |
||||||
|
); |
||||||
|
|
||||||
|
void selectWordEdge(SelectionChangedCause cause); |
||||||
|
|
||||||
|
void selectPositionAt(Offset from, Offset to, SelectionChangedCause cause); |
||||||
|
|
||||||
|
void selectWord(SelectionChangedCause cause); |
||||||
|
|
||||||
|
void selectPosition(SelectionChangedCause cause); |
||||||
|
} |
||||||
|
|
||||||
|
class QuillEditor extends StatefulWidget { |
||||||
|
final QuillController controller; |
||||||
|
final FocusNode focusNode; |
||||||
|
final ScrollController scrollController; |
||||||
|
final bool scrollable; |
||||||
|
final EdgeInsetsGeometry padding; |
||||||
|
final bool autoFocus; |
||||||
|
final bool showCursor; |
||||||
|
final bool readOnly; |
||||||
|
final bool enableInteractiveSelection; |
||||||
|
final double minHeight; |
||||||
|
final double maxHeight; |
||||||
|
final bool expands; |
||||||
|
final TextCapitalization textCapitalization; |
||||||
|
final Brightness keyboardAppearance; |
||||||
|
final ScrollPhysics scrollPhysics; |
||||||
|
final ValueChanged<String> onLaunchUrl; |
||||||
|
|
||||||
|
QuillEditor( |
||||||
|
this.controller, |
||||||
|
this.focusNode, |
||||||
|
this.scrollController, |
||||||
|
this.scrollable, |
||||||
|
this.padding, |
||||||
|
this.autoFocus, |
||||||
|
this.showCursor, |
||||||
|
this.readOnly, |
||||||
|
this.enableInteractiveSelection, |
||||||
|
this.minHeight, |
||||||
|
this.maxHeight, |
||||||
|
this.expands, |
||||||
|
this.textCapitalization, |
||||||
|
this.keyboardAppearance, |
||||||
|
this.scrollPhysics, |
||||||
|
this.onLaunchUrl) |
||||||
|
: assert(controller != null), |
||||||
|
assert(scrollController != null), |
||||||
|
assert(scrollable != null), |
||||||
|
assert(autoFocus != null), |
||||||
|
assert(readOnly != null); |
||||||
|
|
||||||
|
@override |
||||||
|
_QuillEditorState createState() => _QuillEditorState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _QuillEditorState extends State<QuillEditor> |
||||||
|
implements EditorTextSelectionGestureDetectorBuilderDelegate { |
||||||
|
final GlobalKey<EditorState> _editorKey = GlobalKey<EditorState>(); |
||||||
|
EditorTextSelectionGestureDetectorBuilder _selectionGestureDetectorBuilder; |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
// TODO: implement build |
||||||
|
throw UnimplementedError(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
GlobalKey<EditorState> getEditableTextKey() { |
||||||
|
return _editorKey; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool getForcePressEnabled() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool getSelectionEnabled() { |
||||||
|
return widget.enableInteractiveSelection; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class RawEditor extends StatefulWidget { |
||||||
|
@override |
||||||
|
State<StatefulWidget> createState() { |
||||||
|
// TODO: implement createState |
||||||
|
throw UnimplementedError(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class RenderEditor extends RenderEditableContainerBox |
||||||
|
implements RenderAbstractEditor { |
||||||
|
Document document; |
||||||
|
TextSelection selection; |
||||||
|
bool _hasFocus = false; |
||||||
|
|
||||||
|
setDocument(Document doc) { |
||||||
|
assert(doc != null); |
||||||
|
if (document == doc) { |
||||||
|
return; |
||||||
|
} |
||||||
|
document = doc; |
||||||
|
markNeedsLayout(); |
||||||
|
} |
||||||
|
|
||||||
|
setHasFocus(bool h) { |
||||||
|
assert(h != null); |
||||||
|
if (_hasFocus == h) { |
||||||
|
return; |
||||||
|
} |
||||||
|
_hasFocus = h; |
||||||
|
markNeedsSemanticsUpdate(); |
||||||
|
} |
||||||
|
|
||||||
|
setSelection(TextSelection t) { |
||||||
|
if (selection == t) { |
||||||
|
return; |
||||||
|
} |
||||||
|
selection = t; |
||||||
|
markNeedsPaint(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
List<TextSelectionPoint> getEndpointsForSelection( |
||||||
|
TextSelection textSelection) { |
||||||
|
// TODO: implement getEndpointsForSelection |
||||||
|
throw UnimplementedError(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextPosition getPositionForOffset(Offset offset) { |
||||||
|
// TODO: implement getPositionForOffset |
||||||
|
throw UnimplementedError(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void handleTapDown(TapDownDetails details) { |
||||||
|
// TODO: implement handleTapDown |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
double preferredLineHeight(TextPosition position) { |
||||||
|
// TODO: implement preferredLineHeight |
||||||
|
throw UnimplementedError(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextSelection selectLineAtPosition(TextPosition position) { |
||||||
|
// TODO: implement selectLineAtPosition |
||||||
|
throw UnimplementedError(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void selectPosition(SelectionChangedCause cause) { |
||||||
|
// TODO: implement selectPosition |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void selectPositionAt(Offset from, Offset to, SelectionChangedCause cause) { |
||||||
|
// TODO: implement selectPositionAt |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void selectWord(SelectionChangedCause cause) { |
||||||
|
// TODO: implement selectWord |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
TextSelection selectWordAtPosition(TextPosition position) { |
||||||
|
// TODO: implement selectWordAtPosition |
||||||
|
throw UnimplementedError(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void selectWordEdge(SelectionChangedCause cause) { |
||||||
|
// TODO: implement selectWordEdge |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void selectWordsInRange(Offset from, Offset to, SelectionChangedCause cause) { |
||||||
|
// TODO: implement selectWordsInRange |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class RenderEditableContainerBox extends RenderBox { |
||||||
|
Container _container; |
||||||
|
TextDirection _textDirection; |
||||||
|
|
||||||
|
setContainer(Container c) { |
||||||
|
assert(c != null); |
||||||
|
if (_container == c) { |
||||||
|
return; |
||||||
|
} |
||||||
|
_container = c; |
||||||
|
markNeedsLayout(); |
||||||
|
} |
||||||
|
|
||||||
|
setTextDirection(TextDirection t) { |
||||||
|
if (_textDirection == t) { |
||||||
|
return; |
||||||
|
} |
||||||
|
_textDirection = t; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
abstract class EditorState extends State<RawEditor> { |
||||||
|
TextEditingValue getTextEditingValue(); |
||||||
|
|
||||||
|
void setTextEditingValue(TextEditingValue value); |
||||||
|
|
||||||
|
RenderEditor getRenderEditor(); |
||||||
|
|
||||||
|
EditorTextSelectionOverlay getSelectionOverlay(); |
||||||
|
|
||||||
|
bool showToolbar(); |
||||||
|
|
||||||
|
void hideToolbar(); |
||||||
|
|
||||||
|
void requestKeyboard(); |
||||||
|
} |
@ -0,0 +1,100 @@ |
|||||||
|
import 'package:flutter/services.dart'; |
||||||
|
|
||||||
|
enum InputShortcut { CUT, COPY, PAST, SELECT_ALL } |
||||||
|
|
||||||
|
typedef CursorMoveCallback = void Function(LogicalKeyboardKey key, |
||||||
|
{bool wordModifier, bool lineModifier, bool shift}); |
||||||
|
typedef InputShortcutCallback = void Function(InputShortcut shortcut); |
||||||
|
typedef OnDeleteCallback = void Function(bool forward); |
||||||
|
|
||||||
|
class KeyboardListener { |
||||||
|
final CursorMoveCallback onCursorMove; |
||||||
|
final InputShortcutCallback onShortcut; |
||||||
|
final OnDeleteCallback onDelete; |
||||||
|
static final Set<LogicalKeyboardKey> _moveKeys = <LogicalKeyboardKey>{ |
||||||
|
LogicalKeyboardKey.arrowRight, |
||||||
|
LogicalKeyboardKey.arrowLeft, |
||||||
|
LogicalKeyboardKey.arrowUp, |
||||||
|
LogicalKeyboardKey.arrowDown, |
||||||
|
}; |
||||||
|
|
||||||
|
static final Set<LogicalKeyboardKey> _shortcutKeys = <LogicalKeyboardKey>{ |
||||||
|
LogicalKeyboardKey.keyA, |
||||||
|
LogicalKeyboardKey.keyC, |
||||||
|
LogicalKeyboardKey.keyV, |
||||||
|
LogicalKeyboardKey.keyX, |
||||||
|
LogicalKeyboardKey.delete, |
||||||
|
LogicalKeyboardKey.backspace, |
||||||
|
}; |
||||||
|
|
||||||
|
static final Set<LogicalKeyboardKey> _nonModifierKeys = <LogicalKeyboardKey>{ |
||||||
|
..._shortcutKeys, |
||||||
|
..._moveKeys, |
||||||
|
}; |
||||||
|
|
||||||
|
static final Set<LogicalKeyboardKey> _modifierKeys = <LogicalKeyboardKey>{ |
||||||
|
LogicalKeyboardKey.shift, |
||||||
|
LogicalKeyboardKey.control, |
||||||
|
LogicalKeyboardKey.alt, |
||||||
|
}; |
||||||
|
|
||||||
|
static final Set<LogicalKeyboardKey> _macOsModifierKeys = |
||||||
|
<LogicalKeyboardKey>{ |
||||||
|
LogicalKeyboardKey.shift, |
||||||
|
LogicalKeyboardKey.meta, |
||||||
|
LogicalKeyboardKey.alt, |
||||||
|
}; |
||||||
|
|
||||||
|
static final Set<LogicalKeyboardKey> _interestingKeys = <LogicalKeyboardKey>{ |
||||||
|
..._modifierKeys, |
||||||
|
..._macOsModifierKeys, |
||||||
|
..._nonModifierKeys, |
||||||
|
}; |
||||||
|
|
||||||
|
static final Map<LogicalKeyboardKey, InputShortcut> _keyToShortcut = { |
||||||
|
LogicalKeyboardKey.keyX: InputShortcut.CUT, |
||||||
|
LogicalKeyboardKey.keyC: InputShortcut.COPY, |
||||||
|
LogicalKeyboardKey.keyV: InputShortcut.PAST, |
||||||
|
LogicalKeyboardKey.keyA: InputShortcut.SELECT_ALL, |
||||||
|
}; |
||||||
|
|
||||||
|
KeyboardListener(this.onCursorMove, this.onShortcut, this.onDelete) |
||||||
|
: assert(onCursorMove != null), |
||||||
|
assert(onShortcut != null), |
||||||
|
assert(onDelete != null); |
||||||
|
|
||||||
|
bool handleRawKeyEvent(RawKeyEvent event) { |
||||||
|
if (event is! RawKeyDownEvent) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
Set<LogicalKeyboardKey> keysPressed = |
||||||
|
LogicalKeyboardKey.collapseSynonyms(RawKeyboard.instance.keysPressed); |
||||||
|
LogicalKeyboardKey key = event.logicalKey; |
||||||
|
bool isMacOS = event.data is RawKeyEventDataMacOs; |
||||||
|
if (!_nonModifierKeys.contains(key) || |
||||||
|
keysPressed |
||||||
|
.difference(isMacOS ? _macOsModifierKeys : _modifierKeys) |
||||||
|
.length > |
||||||
|
1 || |
||||||
|
keysPressed.difference(_interestingKeys).isNotEmpty) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
if (_moveKeys.contains(key)) { |
||||||
|
onCursorMove(key, |
||||||
|
wordModifier: isMacOS ? event.isAltPressed : event.isControlPressed, |
||||||
|
lineModifier: isMacOS ? event.isMetaPressed : event.isAltPressed, |
||||||
|
shift: event.isShiftPressed); |
||||||
|
} else if (isMacOS |
||||||
|
? event.isMetaPressed |
||||||
|
: event.isControlPressed && _shortcutKeys.contains(key)) { |
||||||
|
onShortcut(_keyToShortcut[key]); |
||||||
|
} else if (key == LogicalKeyboardKey.delete) { |
||||||
|
onDelete(true); |
||||||
|
} else if (key == LogicalKeyboardKey.backspace) { |
||||||
|
onDelete(false); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,83 @@ |
|||||||
|
import 'package:flutter/cupertino.dart'; |
||||||
|
import 'package:flutter/gestures.dart'; |
||||||
|
import 'package:flutter/rendering.dart'; |
||||||
|
|
||||||
|
import 'editor.dart'; |
||||||
|
|
||||||
|
class EditorTextSelectionOverlay { |
||||||
|
TextEditingValue value; |
||||||
|
bool handlesVisible = false; |
||||||
|
final BuildContext context; |
||||||
|
final Widget debugRequiredFor; |
||||||
|
final LayerLink toolbarLayerLink; |
||||||
|
final LayerLink startHandleLayerLink; |
||||||
|
final LayerLink endHandleLayerLink; |
||||||
|
final RenderEditor renderObject; |
||||||
|
final TextSelectionControls selectionCtrls; |
||||||
|
final TextSelectionDelegate selectionDelegate; |
||||||
|
final DragStartBehavior dragStartBehavior; |
||||||
|
final VoidCallback onSelectionHandleTapped; |
||||||
|
final ClipboardStatusNotifier clipboardStatus; |
||||||
|
AnimationController _toolbarController; |
||||||
|
List<OverlayEntry> _handles; |
||||||
|
OverlayEntry _toolbar; |
||||||
|
|
||||||
|
EditorTextSelectionOverlay( |
||||||
|
this.value, |
||||||
|
this.handlesVisible, |
||||||
|
this.context, |
||||||
|
this.debugRequiredFor, |
||||||
|
this.toolbarLayerLink, |
||||||
|
this.startHandleLayerLink, |
||||||
|
this.endHandleLayerLink, |
||||||
|
this.renderObject, |
||||||
|
this.selectionCtrls, |
||||||
|
this.selectionDelegate, |
||||||
|
this.dragStartBehavior, |
||||||
|
this.onSelectionHandleTapped, |
||||||
|
this.clipboardStatus) |
||||||
|
: assert(value != null), |
||||||
|
assert(context != null), |
||||||
|
assert(handlesVisible != null) { |
||||||
|
OverlayState overlay = Overlay.of(context, rootOverlay: true); |
||||||
|
assert( |
||||||
|
overlay != null, |
||||||
|
); |
||||||
|
_toolbarController = AnimationController( |
||||||
|
duration: Duration(milliseconds: 150), vsync: overlay); |
||||||
|
} |
||||||
|
|
||||||
|
setHandlesVisible(bool visible) {} |
||||||
|
|
||||||
|
hideHandles() { |
||||||
|
if (_handles == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
_handles[0].remove(); |
||||||
|
_handles[1].remove(); |
||||||
|
_handles = null; |
||||||
|
} |
||||||
|
|
||||||
|
/// Shows the toolbar by inserting it into the [context]'s overlay. |
||||||
|
showToolbar() { |
||||||
|
assert(_toolbar == null); |
||||||
|
_toolbar = OverlayEntry(builder: _buildToolbar); |
||||||
|
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor) |
||||||
|
.insert(_toolbar); |
||||||
|
_toolbarController.forward(from: 0.0); |
||||||
|
} |
||||||
|
|
||||||
|
Widget _buildToolbar(BuildContext context) { |
||||||
|
if (selectionCtrls == null) { |
||||||
|
return Container(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_markNeedsBuild() { |
||||||
|
if (_handles != null) { |
||||||
|
_handles[0].markNeedsBuild(); |
||||||
|
_handles[1].markNeedsBuild(); |
||||||
|
} |
||||||
|
_toolbar?.markNeedsBuild(); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue