Browse Source

Updated to Flutter 1.7.8 and fixed positioning of selection handles after update

Anatoly Pulyaevskiy 5 years ago
parent
commit
d37493a9cb
2 changed files with 132 additions and 54 deletions
  1. 131
    53
      packages/zefyr/lib/src/widgets/selection.dart
  2. 1
    1
      packages/zefyr/test/testing.dart

+ 131
- 53
packages/zefyr/lib/src/widgets/selection.dart View File

@@ -1,8 +1,10 @@
1 1
 // Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
2 2
 // for details. All rights reserved. Use of this source code is governed by a
3 3
 // BSD-style license that can be found in the LICENSE file.
4
+import 'dart:math' as math;
4 5
 import 'dart:ui' as ui;
5 6
 
7
+import 'package:flutter/gestures.dart';
6 8
 import 'package:flutter/material.dart';
7 9
 import 'package:flutter/rendering.dart';
8 10
 import 'package:notus/notus.dart';
@@ -75,13 +77,13 @@ class _ZefyrSelectionOverlayState extends State<ZefyrSelectionOverlay>
75 77
     final toolbarOpacity = _toolbarController.view;
76 78
     _toolbar = new OverlayEntry(
77 79
       builder: (context) => new FadeTransition(
78
-            opacity: toolbarOpacity,
79
-            child: new _SelectionToolbar(
80
-              scope: _editor,
81
-              controls: widget.controls,
82
-              delegate: this,
83
-            ),
84
-          ),
80
+        opacity: toolbarOpacity,
81
+        child: new _SelectionToolbar(
82
+          scope: _editor,
83
+          controls: widget.controls,
84
+          delegate: this,
85
+        ),
86
+      ),
85 87
     );
86 88
     widget.overlay.insert(_toolbar);
87 89
     _toolbarController.forward(from: 0.0);
@@ -303,7 +305,8 @@ class SelectionHandleDriver extends StatefulWidget {
303 305
       new _SelectionHandleDriverState();
304 306
 }
305 307
 
306
-class _SelectionHandleDriverState extends State<SelectionHandleDriver> {
308
+class _SelectionHandleDriverState extends State<SelectionHandleDriver>
309
+    with SingleTickerProviderStateMixin {
307 310
   ZefyrScope _scope;
308 311
 
309 312
   /// Current document selection.
@@ -320,19 +323,18 @@ class _SelectionHandleDriverState extends State<SelectionHandleDriver> {
320 323
   int get documentOffset =>
321 324
       isBaseHandle ? selection.baseOffset : selection.extentOffset;
322 325
 
323
-  /// Position in pixels of this selection handle within its paragraph [block].
324
-  Offset getPosition(RenderEditableBox block) {
326
+  List<TextSelectionPoint> getEndpointsForSelection(RenderEditableBox block) {
325 327
     if (block == null) return null;
326 328
 
327
-    final localSelection = block.getLocalSelection(selection);
328
-    assert(localSelection != null);
329
-
330
-    final boxes = block.getEndpointsForSelection(selection);
331
-    assert(boxes.isNotEmpty, 'Got empty boxes for selection ${selection}');
332
-
333
-    final box = isBaseHandle ? boxes.first : boxes.last;
334
-    final dx = isBaseHandle ? box.start : box.end;
335
-    return new Offset(dx, box.bottom);
329
+    final Offset paintOffset = Offset.zero;
330
+    final List<ui.TextBox> boxes = block.getEndpointsForSelection(selection);
331
+    final Offset start =
332
+        Offset(boxes.first.start, boxes.first.bottom) + paintOffset;
333
+    final Offset end = Offset(boxes.last.end, boxes.last.bottom) + paintOffset;
334
+    return <TextSelectionPoint>[
335
+      TextSelectionPoint(start, boxes.first.direction),
336
+      TextSelectionPoint(end, boxes.last.direction),
337
+    ];
336 338
   }
337 339
 
338 340
   @override
@@ -366,39 +368,97 @@ class _SelectionHandleDriverState extends State<SelectionHandleDriver> {
366 368
       return new Container();
367 369
     }
368 370
     final block = _scope.renderContext.boxForTextOffset(documentOffset);
369
-    final position = getPosition(block);
370
-    Widget handle;
371
-    if (position == null) {
372
-      handle = new Container();
373
-    } else {
374
-      final handleType = isBaseHandle
375
-          ? TextSelectionHandleType.left
376
-          : TextSelectionHandleType.right;
377
-      handle = new Positioned(
378
-        left: position.dx,
379
-        top: position.dy,
380
-        child: widget.controls.buildHandle(
381
-          context,
382
-          handleType,
383
-          block.preferredLineHeight,
384
-        ),
385
-      );
386
-      handle = new CompositedTransformFollower(
387
-        link: block.layerLink,
388
-        showWhenUnlinked: false,
389
-        child: new Stack(
390
-          overflow: Overflow.visible,
391
-          children: <Widget>[handle],
392
-        ),
393
-      );
371
+    if (block == null) {
372
+      // TODO: For some reason sometimes we get updates when render boxes
373
+      //      are in process of rebuilding so we don't have access to them here.
374
+      //      As a workaround we just return empty container. There is usually
375
+      //      another rebuild right after which "fixes" the view.
376
+      //      Example: when toolbar button is toggled changing style of current
377
+      //      selection.
378
+      return Container();
379
+    }
380
+
381
+    final List<TextSelectionPoint> endpoints = getEndpointsForSelection(block);
382
+    Offset point;
383
+    TextSelectionHandleType type;
384
+
385
+    switch (widget.position) {
386
+      case _SelectionHandlePosition.base:
387
+        point = endpoints[0].point;
388
+        type = _chooseType(endpoints[0], TextSelectionHandleType.left,
389
+            TextSelectionHandleType.right);
390
+        break;
391
+      case _SelectionHandlePosition.extent:
392
+        // [endpoints] will only contain 1 point for collapsed selections, in
393
+        // which case we shouldn't be building the [end] handle.
394
+        assert(endpoints.length == 2);
395
+        point = endpoints[1].point;
396
+        type = _chooseType(endpoints[1], TextSelectionHandleType.right,
397
+            TextSelectionHandleType.left);
398
+        break;
394 399
     }
395
-    // Always return this gesture detector even if handle is an empty container
396
-    // This way we prevent drag gesture from being canceled in case current
397
-    // position is somewhere outside of any visible paragraph block.
398
-    return new GestureDetector(
399
-      onPanStart: _handleDragStart,
400
-      onPanUpdate: _handleDragUpdate,
401
-      child: handle,
400
+
401
+    final Size viewport = block.size;
402
+    point = Offset(
403
+      point.dx.clamp(0.0, viewport.width),
404
+      point.dy.clamp(0.0, viewport.height),
405
+    );
406
+
407
+    final Offset handleAnchor = widget.controls.getHandleAnchor(
408
+      type,
409
+      block.preferredLineHeight,
410
+    );
411
+    final Size handleSize = widget.controls.getHandleSize(
412
+      block.preferredLineHeight,
413
+    );
414
+    final Rect handleRect = Rect.fromLTWH(
415
+      // Put handleAnchor on top of point
416
+      point.dx - handleAnchor.dx,
417
+      point.dy - handleAnchor.dy,
418
+      handleSize.width,
419
+      handleSize.height,
420
+    );
421
+
422
+    // Make sure the GestureDetector is big enough to be easily interactive.
423
+    final Rect interactiveRect = handleRect.expandToInclude(
424
+      Rect.fromCircle(
425
+          center: handleRect.center, radius: kMinInteractiveSize / 2),
426
+    );
427
+    final RelativeRect padding = RelativeRect.fromLTRB(
428
+      math.max((interactiveRect.width - handleRect.width) / 2, 0),
429
+      math.max((interactiveRect.height - handleRect.height) / 2, 0),
430
+      math.max((interactiveRect.width - handleRect.width) / 2, 0),
431
+      math.max((interactiveRect.height - handleRect.height) / 2, 0),
432
+    );
433
+
434
+    return CompositedTransformFollower(
435
+      link: block.layerLink,
436
+      offset: interactiveRect.topLeft,
437
+      showWhenUnlinked: false,
438
+      child: Container(
439
+        alignment: Alignment.topLeft,
440
+        width: interactiveRect.width,
441
+        height: interactiveRect.height,
442
+        child: GestureDetector(
443
+          behavior: HitTestBehavior.translucent,
444
+          dragStartBehavior: DragStartBehavior.start,
445
+          onPanStart: _handleDragStart,
446
+          onPanUpdate: _handleDragUpdate,
447
+          child: Padding(
448
+            padding: EdgeInsets.only(
449
+              left: padding.left,
450
+              top: padding.top,
451
+              right: padding.right,
452
+              bottom: padding.bottom,
453
+            ),
454
+            child: widget.controls.buildHandle(
455
+              context,
456
+              type,
457
+              block.preferredLineHeight,
458
+            ),
459
+          ),
460
+        ),
461
+      ),
402 462
     );
403 463
   }
404 464
 
@@ -406,6 +466,23 @@ class _SelectionHandleDriverState extends State<SelectionHandleDriver> {
406 466
   // Private members
407 467
   //
408 468
 
469
+  TextSelectionHandleType _chooseType(
470
+    TextSelectionPoint endpoint,
471
+    TextSelectionHandleType ltrType,
472
+    TextSelectionHandleType rtlType,
473
+  ) {
474
+    if (selection.isCollapsed) return TextSelectionHandleType.collapsed;
475
+
476
+    assert(endpoint.direction != null);
477
+    switch (endpoint.direction) {
478
+      case TextDirection.ltr:
479
+        return ltrType;
480
+      case TextDirection.rtl:
481
+        return rtlType;
482
+    }
483
+    return null;
484
+  }
485
+
409 486
   Offset _dragPosition;
410 487
 
411 488
   void _handleScopeChange() {
@@ -506,8 +583,9 @@ class _SelectionToolbarState extends State<_SelectionToolbar> {
506 583
       block.localToGlobal(Offset.zero),
507 584
       block.localToGlobal(block.size.bottomRight(Offset.zero)),
508 585
     );
509
-    final toolbar = widget.controls.buildToolbar(
510
-        context, editingRegion, midpoint, endpoints, widget.delegate);
586
+
587
+    final toolbar = widget.controls.buildToolbar(context, editingRegion,
588
+        block.preferredLineHeight, midpoint, endpoints, widget.delegate);
511 589
     return new CompositedTransformFollower(
512 590
       link: block.layerLink,
513 591
       showWhenUnlinked: false,

+ 1
- 1
packages/zefyr/test/testing.dart View File

@@ -110,7 +110,7 @@ class EditorSandBox {
110 110
   Finder findSelectionHandle() {
111 111
     return find.descendant(
112 112
         of: find.byType(SelectionHandleDriver),
113
-        matching: find.byType(Positioned));
113
+        matching: find.byType(GestureDetector));
114 114
   }
115 115
 }
116 116