selection.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. import 'dart:async';
  2. import 'dart:math' as math;
  3. import 'package:flutter/foundation.dart';
  4. import 'package:flutter/gestures.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/rendering.dart';
  7. import 'package:flutter/scheduler.dart';
  8. import '../model/document/node/node.dart';
  9. import '../rendering/editor.dart';
  10. enum _TextSelectionHandlePosition {
  11. START,
  12. END,
  13. }
  14. class EditorTextSelectionOverlay {
  15. EditorTextSelectionOverlay(
  16. this.value,
  17. this._handlesVisible,
  18. this.context,
  19. this.debugRequiredFor,
  20. this.toolbarLayerLink,
  21. this.startHandlerLayerLink,
  22. this.endHandlerLayerLink,
  23. this.renderObject,
  24. this.selectionControls,
  25. this.selectionDelegate,
  26. this.dragStartBehavior,
  27. this.onSelectionHandleTapped,
  28. this.clipboardStatus,
  29. ) {
  30. final overlay = Overlay.of(context, rootOverlay: true)!;
  31. _toolbarController = AnimationController(
  32. vsync: overlay,
  33. duration: const Duration(milliseconds: 150),
  34. );
  35. }
  36. TextEditingValue value;
  37. final BuildContext context;
  38. final Widget debugRequiredFor;
  39. final LayerLink toolbarLayerLink;
  40. final LayerLink startHandlerLayerLink;
  41. final LayerLink endHandlerLayerLink;
  42. final RenderEditor? renderObject;
  43. final TextSelectionControls selectionControls;
  44. final TextSelectionDelegate selectionDelegate;
  45. final DragStartBehavior dragStartBehavior;
  46. final VoidCallback? onSelectionHandleTapped;
  47. final ClipboardStatusNotifier clipboardStatus;
  48. late AnimationController _toolbarController;
  49. List<OverlayEntry>? _handles;
  50. final bool _handlesVisible;
  51. OverlayEntry? toolbar;
  52. bool get handlesVisible => _handlesVisible;
  53. TextSelection get _selection => value.selection;
  54. Animation<double> get _toolbarOpacity => _toolbarController.view;
  55. set handlesVisible(bool value) {
  56. if (_handlesVisible == value) {
  57. return;
  58. }
  59. handlesVisible = value;
  60. if (SchedulerBinding.instance!.schedulerPhase ==
  61. SchedulerPhase.persistentCallbacks) {
  62. SchedulerBinding.instance!.addPostFrameCallback(markNeedsBuild);
  63. } else {
  64. markNeedsBuild();
  65. }
  66. }
  67. void dispose() {
  68. hide();
  69. _toolbarController.dispose();
  70. }
  71. void hide() {
  72. if (_handles != null) {
  73. _handles![0].remove();
  74. _handles![1].remove();
  75. _handles = null;
  76. }
  77. if (toolbar != null) {
  78. hideToolbar();
  79. }
  80. }
  81. void hideHandles() {
  82. if (_handles == null) {
  83. return;
  84. }
  85. _handles![0].remove();
  86. _handles![1].remove();
  87. _handles = null;
  88. }
  89. void showHandles() {
  90. assert(_handles == null);
  91. _handles = <OverlayEntry>[
  92. OverlayEntry(builder: (context) {
  93. return _buildHandle(context, _TextSelectionHandlePosition.START);
  94. }),
  95. OverlayEntry(builder: (context) {
  96. return _buildHandle(context, _TextSelectionHandlePosition.END);
  97. }),
  98. ];
  99. Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)!
  100. .insertAll(_handles!);
  101. }
  102. void hideToolbar() {
  103. assert(toolbar != null);
  104. _toolbarController.stop();
  105. toolbar!.remove();
  106. toolbar = null;
  107. }
  108. void showToolbar() {
  109. assert(toolbar == null);
  110. toolbar = OverlayEntry(builder: _buildToolbar);
  111. Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)!
  112. .insert(toolbar!);
  113. _toolbarController.forward(from: 0);
  114. }
  115. Widget _buildToolbar(BuildContext context) {
  116. final endpoints = renderObject!.getEndpointsForSelection(_selection);
  117. final editingRegion = Rect.fromPoints(
  118. renderObject!.localToGlobal(Offset.zero),
  119. renderObject!.localToGlobal(renderObject!.size.bottomRight(Offset.zero)),
  120. );
  121. final baseLineHeight = renderObject!.preferredLineHeight(_selection.base);
  122. final extentLineHeight =
  123. renderObject!.preferredLineHeight(_selection.extent);
  124. final smallestLineHeight = math.min(baseLineHeight, extentLineHeight);
  125. final isMultiLine = endpoints.last.point.dy - endpoints.first.point.dy >
  126. smallestLineHeight / 2;
  127. final midX = isMultiLine
  128. ? editingRegion.width / 2
  129. : (endpoints.first.point.dx + endpoints.last.point.dx) / 2;
  130. final midpoint = Offset(midX, endpoints[0].point.dy - baseLineHeight);
  131. return FadeTransition(
  132. opacity: _toolbarOpacity,
  133. child: CompositedTransformFollower(
  134. link: toolbarLayerLink,
  135. showWhenUnlinked: false,
  136. offset: -editingRegion.topLeft,
  137. child: selectionControls.buildToolbar(
  138. context,
  139. editingRegion,
  140. baseLineHeight,
  141. midpoint,
  142. endpoints,
  143. selectionDelegate,
  144. clipboardStatus,
  145. const Offset(0, 0),
  146. ),
  147. ),
  148. );
  149. }
  150. Widget _buildHandle(
  151. BuildContext context, _TextSelectionHandlePosition position) {
  152. if (_selection.isCollapsed &&
  153. _TextSelectionHandlePosition.END == position) {
  154. return Container();
  155. }
  156. return Visibility(
  157. visible: handlesVisible,
  158. child: _TextSelectionHandleOverlay(
  159. onSelectionHandleChanged: (newSelection) =>
  160. _handleSelectionHandleChanged(newSelection, position),
  161. onSelectionhandleTapped: onSelectionHandleTapped,
  162. startHandleLayerLink: startHandlerLayerLink,
  163. endHandleLayerLink: endHandlerLayerLink,
  164. renderObject: renderObject,
  165. selection: _selection,
  166. selectionControls: selectionControls,
  167. position: position,
  168. dragStartBehavior: dragStartBehavior,
  169. ),
  170. );
  171. }
  172. void update(TextEditingValue newValue) {
  173. if (value == newValue) {
  174. return;
  175. }
  176. value = newValue;
  177. if (SchedulerBinding.instance!.schedulerPhase ==
  178. SchedulerPhase.persistentCallbacks) {
  179. SchedulerBinding.instance!.addPostFrameCallback(markNeedsBuild);
  180. } else {
  181. markNeedsBuild();
  182. }
  183. }
  184. void markNeedsBuild([Duration? duration]) {
  185. if (_handles != null) {
  186. _handles![0].markNeedsBuild();
  187. _handles![1].markNeedsBuild();
  188. }
  189. toolbar?.markNeedsBuild();
  190. }
  191. void _handleSelectionHandleChanged(
  192. TextSelection? newSelection, _TextSelectionHandlePosition position) {
  193. TextPosition textPosition;
  194. switch (position) {
  195. case _TextSelectionHandlePosition.START:
  196. textPosition = newSelection != null
  197. ? newSelection.base
  198. : const TextPosition(offset: 0);
  199. break;
  200. case _TextSelectionHandlePosition.END:
  201. textPosition = newSelection != null
  202. ? newSelection.extent
  203. : const TextPosition(offset: 0);
  204. break;
  205. default:
  206. throw 'Invalid position';
  207. }
  208. // TODO: Update this deprecated API
  209. selectionDelegate
  210. // ignore: deprecated_member_use
  211. ..textEditingValue =
  212. value.copyWith(selection: newSelection, composing: TextRange.empty)
  213. ..bringIntoView(textPosition);
  214. }
  215. }
  216. /* --------------------------------- Overlay -------------------------------- */
  217. class _TextSelectionHandleOverlay extends StatefulWidget {
  218. const _TextSelectionHandleOverlay({
  219. Key? key,
  220. required this.selection,
  221. required this.position,
  222. required this.startHandleLayerLink,
  223. required this.endHandleLayerLink,
  224. required this.renderObject,
  225. required this.onSelectionHandleChanged,
  226. required this.onSelectionhandleTapped,
  227. required this.selectionControls,
  228. this.dragStartBehavior = DragStartBehavior.start,
  229. }) : super(key: key);
  230. final TextSelection selection;
  231. final _TextSelectionHandlePosition position;
  232. final LayerLink startHandleLayerLink;
  233. final LayerLink endHandleLayerLink;
  234. final RenderEditor? renderObject;
  235. final ValueChanged<TextSelection?> onSelectionHandleChanged;
  236. final VoidCallback? onSelectionhandleTapped;
  237. final TextSelectionControls selectionControls;
  238. final DragStartBehavior dragStartBehavior;
  239. ValueListenable<bool>? get _visibility {
  240. switch (position) {
  241. case _TextSelectionHandlePosition.START:
  242. return renderObject!.selectionStartInViewport;
  243. case _TextSelectionHandlePosition.END:
  244. return renderObject!.selectionEndInViewport;
  245. }
  246. }
  247. @override
  248. __TextSelectionHandleOverlayState createState() =>
  249. __TextSelectionHandleOverlayState();
  250. }
  251. class __TextSelectionHandleOverlayState
  252. extends State<_TextSelectionHandleOverlay>
  253. with SingleTickerProviderStateMixin {
  254. late AnimationController _controller;
  255. Animation<double> get _opacity => _controller.view;
  256. @override
  257. void initState() {
  258. super.initState();
  259. _controller = AnimationController(
  260. duration: const Duration(milliseconds: 150),
  261. vsync: this,
  262. );
  263. _handleVisibilityChanged();
  264. widget._visibility!.addListener(_handleVisibilityChanged);
  265. }
  266. @override
  267. void didUpdateWidget(covariant _TextSelectionHandleOverlay oldWidget) {
  268. super.didUpdateWidget(oldWidget);
  269. oldWidget._visibility!.removeListener(_handleVisibilityChanged);
  270. _handleVisibilityChanged();
  271. widget._visibility!.addListener(_handleVisibilityChanged);
  272. }
  273. @override
  274. void dispose() {
  275. widget._visibility!.removeListener(_handleVisibilityChanged);
  276. _controller.dispose();
  277. super.dispose();
  278. }
  279. @override
  280. Widget build(BuildContext context) {
  281. late LayerLink layerLink;
  282. TextSelectionHandleType? type;
  283. switch (widget.position) {
  284. case _TextSelectionHandlePosition.START:
  285. layerLink = widget.startHandleLayerLink;
  286. type = _chooseType(
  287. widget.renderObject!.textDirection,
  288. TextSelectionHandleType.left,
  289. TextSelectionHandleType.right,
  290. );
  291. break;
  292. case _TextSelectionHandlePosition.END:
  293. assert(!widget.selection.isCollapsed);
  294. layerLink = widget.endHandleLayerLink;
  295. type = _chooseType(
  296. widget.renderObject!.textDirection,
  297. TextSelectionHandleType.right,
  298. TextSelectionHandleType.left,
  299. );
  300. break;
  301. }
  302. final textPosition = widget.position == _TextSelectionHandlePosition.START
  303. ? widget.selection.base
  304. : widget.selection.extent;
  305. final lineHeight = widget.renderObject!.preferredLineHeight(textPosition);
  306. final handleAnchor =
  307. widget.selectionControls.getHandleAnchor(type!, lineHeight);
  308. final handleSize = widget.selectionControls.getHandleSize(lineHeight);
  309. final handleRect = Rect.fromLTWH(
  310. -handleAnchor.dx,
  311. -handleAnchor.dy,
  312. handleSize.width,
  313. handleSize.height,
  314. );
  315. final interactiveRect = handleRect.expandToInclude(Rect.fromCircle(
  316. center: handleRect.center,
  317. radius: kMinInteractiveDimension / 2,
  318. ));
  319. final padding = RelativeRect.fromLTRB(
  320. math.max((interactiveRect.width - handleRect.width) / 2, 0),
  321. math.max((interactiveRect.height - handleRect.height) / 2, 0),
  322. math.max((interactiveRect.width - handleRect.width) / 2, 0),
  323. math.max((interactiveRect.height - handleRect.height) / 2, 0),
  324. );
  325. return CompositedTransformFollower(
  326. link: layerLink,
  327. offset: interactiveRect.topLeft,
  328. showWhenUnlinked: false,
  329. child: FadeTransition(
  330. opacity: _opacity,
  331. child: Container(
  332. alignment: Alignment.topLeft,
  333. width: interactiveRect.width,
  334. height: interactiveRect.height,
  335. child: GestureDetector(
  336. behavior: HitTestBehavior.translucent,
  337. dragStartBehavior: widget.dragStartBehavior,
  338. onPanStart: _handleDragStart,
  339. onPanUpdate: _handleDragUpdate,
  340. onTap: _handleTap,
  341. child: Padding(
  342. padding: EdgeInsets.only(
  343. left: padding.left,
  344. top: padding.top,
  345. right: padding.right,
  346. bottom: padding.bottom,
  347. ),
  348. child: widget.selectionControls.buildHandle(
  349. context,
  350. type,
  351. lineHeight,
  352. ),
  353. ),
  354. ),
  355. ),
  356. ),
  357. );
  358. }
  359. // Utils
  360. void _handleVisibilityChanged() {
  361. if (widget._visibility!.value) {
  362. _controller.forward();
  363. } else {
  364. _controller.reverse();
  365. }
  366. }
  367. void _handleDragStart(DragStartDetails details) {}
  368. void _handleDragUpdate(DragUpdateDetails details) {
  369. final position =
  370. widget.renderObject!.getPositionForOffset(details.globalPosition);
  371. if (widget.selection.isCollapsed) {
  372. widget.onSelectionHandleChanged(TextSelection.fromPosition(position));
  373. return;
  374. }
  375. final isNormalized =
  376. widget.selection.extentOffset >= widget.selection.baseOffset;
  377. TextSelection? newSelection;
  378. switch (widget.position) {
  379. case _TextSelectionHandlePosition.START:
  380. newSelection = TextSelection(
  381. baseOffset:
  382. isNormalized ? position.offset : widget.selection.baseOffset,
  383. extentOffset:
  384. isNormalized ? widget.selection.extentOffset : position.offset,
  385. );
  386. break;
  387. case _TextSelectionHandlePosition.END:
  388. newSelection = TextSelection(
  389. baseOffset:
  390. isNormalized ? widget.selection.baseOffset : position.offset,
  391. extentOffset:
  392. isNormalized ? position.offset : widget.selection.extentOffset,
  393. );
  394. break;
  395. }
  396. widget.onSelectionHandleChanged(newSelection);
  397. }
  398. void _handleTap() {
  399. if (widget.onSelectionhandleTapped != null) {
  400. widget.onSelectionhandleTapped!();
  401. }
  402. }
  403. TextSelectionHandleType? _chooseType(
  404. TextDirection textDirection,
  405. TextSelectionHandleType ltrType,
  406. TextSelectionHandleType rtlType,
  407. ) {
  408. if (widget.selection.isCollapsed) {
  409. return TextSelectionHandleType.collapsed;
  410. }
  411. switch (textDirection) {
  412. case TextDirection.ltr:
  413. return ltrType;
  414. case TextDirection.rtl:
  415. return rtlType;
  416. }
  417. }
  418. }
  419. /* --------------------------------- Gesture -------------------------------- */
  420. class EditorTextSelectionGestureDetector extends StatefulWidget {
  421. const EditorTextSelectionGestureDetector({
  422. required this.child,
  423. this.onTapDown,
  424. this.onTapUp,
  425. this.onTapCancel,
  426. this.onForcePressStart,
  427. this.onForcePressEnd,
  428. this.onLongPressStart,
  429. this.onLongPressMoveUpdate,
  430. this.onLongPressEnd,
  431. this.onDoubleTapDown,
  432. this.onDragSelectionStart,
  433. this.onDragSelectionUpdate,
  434. this.onDragSelectionEnd,
  435. this.behavior,
  436. Key? key,
  437. }) : super(key: key);
  438. final GestureTapDownCallback? onTapDown;
  439. final GestureTapUpCallback? onTapUp;
  440. final GestureTapCancelCallback? onTapCancel;
  441. final GestureForcePressStartCallback? onForcePressStart;
  442. final GestureForcePressEndCallback? onForcePressEnd;
  443. final GestureLongPressStartCallback? onLongPressStart;
  444. final GestureLongPressMoveUpdateCallback? onLongPressMoveUpdate;
  445. final GestureLongPressEndCallback? onLongPressEnd;
  446. final GestureTapDownCallback? onDoubleTapDown;
  447. final GestureDragStartCallback? onDragSelectionStart;
  448. final DragSelectionUpdateCallback? onDragSelectionUpdate;
  449. final GestureDragEndCallback? onDragSelectionEnd;
  450. final HitTestBehavior? behavior;
  451. final Widget child;
  452. @override
  453. _EditorTextSelectionGestureDetectorState createState() =>
  454. _EditorTextSelectionGestureDetectorState();
  455. }
  456. class _EditorTextSelectionGestureDetectorState
  457. extends State<EditorTextSelectionGestureDetector> {
  458. // Double Tap
  459. Timer? _doubleTapTimer;
  460. Offset? _lastTapOffset;
  461. bool _isDoubleTap = false;
  462. // Drag
  463. Timer? _dragUpdateThrottleTimer;
  464. DragStartDetails? _lastDragStartDetails;
  465. DragUpdateDetails? _lastDragUpdateDetails;
  466. @override
  467. void dispose() {
  468. _doubleTapTimer?.cancel();
  469. _dragUpdateThrottleTimer?.cancel();
  470. super.dispose();
  471. }
  472. // Gesture Handler
  473. void _handleTapDown(TapDownDetails details) {
  474. if (widget.onTapDown != null) {
  475. widget.onTapDown!(details);
  476. }
  477. if (_doubleTapTimer != null &&
  478. _isWithinDoubleTapTolerance(details.globalPosition)) {
  479. if (widget.onDoubleTapDown != null) {
  480. widget.onDoubleTapDown!(details);
  481. }
  482. _doubleTapTimer!.cancel();
  483. _doubleTapTimeout();
  484. _isDoubleTap = true;
  485. }
  486. }
  487. void _handleTapUp(TapUpDetails details) {
  488. if (!_isDoubleTap) {
  489. if (widget.onTapUp != null) {
  490. widget.onTapUp!(details);
  491. }
  492. _lastTapOffset = details.globalPosition;
  493. _doubleTapTimer = Timer(kDoubleTapTimeout, _doubleTapTimeout);
  494. }
  495. _isDoubleTap = false;
  496. }
  497. void _handleTapCancel() {
  498. if (widget.onTapCancel != null) {
  499. widget.onTapCancel!();
  500. }
  501. }
  502. void _handleDragStart(DragStartDetails details) {
  503. assert(_lastDragStartDetails == null);
  504. _lastDragStartDetails = details;
  505. if (widget.onDragSelectionStart != null) {
  506. widget.onDragSelectionStart!(details);
  507. }
  508. }
  509. void _handleDragUpdate(DragUpdateDetails details) {
  510. _lastDragUpdateDetails = details;
  511. _dragUpdateThrottleTimer ??= Timer(
  512. const Duration(milliseconds: 50),
  513. _handleDragUpdateThrottled,
  514. );
  515. }
  516. void _handleDragUpdateThrottled() {
  517. assert(_lastDragStartDetails != null);
  518. assert(_lastDragUpdateDetails != null);
  519. if (widget.onDragSelectionUpdate != null) {
  520. widget.onDragSelectionUpdate!(
  521. _lastDragStartDetails!, _lastDragUpdateDetails!);
  522. }
  523. _dragUpdateThrottleTimer = null;
  524. _lastDragUpdateDetails = null;
  525. }
  526. void _handleDragEnd(DragEndDetails details) {
  527. assert(_lastDragStartDetails != null);
  528. if (_dragUpdateThrottleTimer != null) {
  529. _dragUpdateThrottleTimer!.cancel();
  530. _handleDragUpdateThrottled();
  531. }
  532. if (widget.onDragSelectionEnd != null) {
  533. widget.onDragSelectionEnd!(details);
  534. }
  535. _dragUpdateThrottleTimer = null;
  536. _lastDragStartDetails = null;
  537. _lastDragUpdateDetails = null;
  538. }
  539. void _forcePressStarted(ForcePressDetails details) {
  540. _doubleTapTimer?.cancel();
  541. _doubleTapTimer = null;
  542. if (widget.onForcePressStart != null) {
  543. widget.onForcePressStart!(details);
  544. }
  545. }
  546. void _forcePressEnded(ForcePressDetails details) {
  547. if (widget.onForcePressEnd != null) {
  548. widget.onForcePressEnd!(details);
  549. }
  550. }
  551. void _handleLongPressStart(LongPressStartDetails details) {
  552. if (!_isDoubleTap && widget.onLongPressStart != null) {
  553. widget.onLongPressStart!(details);
  554. }
  555. }
  556. void _handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) {
  557. if (!_isDoubleTap && widget.onLongPressMoveUpdate != null) {
  558. widget.onLongPressMoveUpdate!(details);
  559. }
  560. }
  561. void _handleLongPressEnd(LongPressEndDetails details) {
  562. if (!_isDoubleTap && widget.onLongPressEnd != null) {
  563. widget.onLongPressEnd!(details);
  564. }
  565. _isDoubleTap = false;
  566. }
  567. @override
  568. Widget build(BuildContext context) {
  569. return RawGestureDetector(
  570. gestures: _buildGestureRecognizers(),
  571. excludeFromSemantics: true,
  572. behavior: widget.behavior,
  573. child: widget.child,
  574. );
  575. }
  576. // Util
  577. bool _isWithinDoubleTapTolerance(Offset secondTapOffset) {
  578. if (_lastTapOffset == null) {
  579. return false;
  580. }
  581. return (secondTapOffset - _lastTapOffset!).distance <= kDoubleTapSlop;
  582. }
  583. void _doubleTapTimeout() {
  584. _doubleTapTimer = null;
  585. _lastTapOffset = null;
  586. }
  587. Map<Type, GestureRecognizerFactory> _buildGestureRecognizers() {
  588. final recognizers = <Type, GestureRecognizerFactory>{};
  589. // Tap
  590. recognizers[TapGestureRecognizer] =
  591. GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
  592. () => TapGestureRecognizer(debugOwner: this),
  593. (gestureRecognizer) {
  594. gestureRecognizer
  595. ..onTapDown = _handleTapDown
  596. ..onTapUp = _handleTapUp
  597. ..onTapCancel = _handleTapCancel;
  598. },
  599. );
  600. // Long Press
  601. if (widget.onLongPressStart != null ||
  602. widget.onLongPressMoveUpdate != null ||
  603. widget.onLongPressEnd != null) {
  604. recognizers[LongPressGestureRecognizer] =
  605. GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
  606. () => LongPressGestureRecognizer(
  607. debugOwner: this, supportedDevices: {PointerDeviceKind.touch}),
  608. (gestureRecognizer) {
  609. gestureRecognizer
  610. ..onLongPressStart = _handleLongPressStart
  611. ..onLongPressMoveUpdate = _handleLongPressMoveUpdate
  612. ..onLongPressEnd = _handleLongPressEnd;
  613. },
  614. );
  615. }
  616. // Drag
  617. if (widget.onDragSelectionStart != null ||
  618. widget.onDragSelectionUpdate != null ||
  619. widget.onDragSelectionEnd != null) {
  620. recognizers[HorizontalDragGestureRecognizer] =
  621. GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
  622. () => HorizontalDragGestureRecognizer(
  623. debugOwner: this, supportedDevices: {PointerDeviceKind.mouse}),
  624. (gestureRecognizer) {
  625. gestureRecognizer
  626. ..dragStartBehavior = DragStartBehavior.down
  627. ..onStart = _handleDragStart
  628. ..onUpdate = _handleDragUpdate
  629. ..onEnd = _handleDragEnd;
  630. },
  631. );
  632. // Force Press
  633. if (widget.onForcePressStart != null || widget.onForcePressEnd != null) {
  634. recognizers[ForcePressGestureRecognizer] =
  635. GestureRecognizerFactoryWithHandlers<ForcePressGestureRecognizer>(
  636. () => ForcePressGestureRecognizer(debugOwner: this),
  637. (gestureRecognizer) {
  638. gestureRecognizer
  639. ..onStart =
  640. widget.onForcePressStart != null ? _forcePressStarted : null
  641. ..onEnd =
  642. widget.onForcePressEnd != null ? _forcePressEnded : null;
  643. },
  644. );
  645. }
  646. }
  647. return recognizers;
  648. }
  649. }
  650. /* ---------------------------------- Util ---------------------------------- */
  651. TextSelection localSelection(Node node, TextSelection selection, fromParent) {
  652. final base = fromParent ? node.offset : node.documentOffset;
  653. assert(base <= selection.end && selection.start <= base + node.length - 1);
  654. final offset = fromParent ? node.offset : node.documentOffset;
  655. return selection.copyWith(
  656. baseOffset: math.max(selection.start - offset, 0),
  657. extentOffset: math.min(selection.end - offset, node.length - 1),
  658. );
  659. }