Sfoglia il codice sorgente

Fixed caret and selection handling in horizontal rule

Anatoly Pulyaevskiy 6 anni fa
parent
commit
5d4a8df758

+ 2
- 4
packages/zefyr/lib/src/widgets/editable_box.dart Vedi File

@@ -198,10 +198,8 @@ class RenderEditableProxyBox extends RenderBox
198 198
   final CaretPainter _caretPainter = new CaretPainter();
199 199
 
200 200
   void _paintCaret(PaintingContext context, Offset offset) {
201
-    final TextPosition caret = new TextPosition(
202
-      offset: _selection.extentOffset - node.documentOffset,
203
-    );
204
-    Offset caretOffset = getOffsetForCaret(caret, _caretPainter.prototype);
201
+    Offset caretOffset =
202
+        getOffsetForCaret(_selection.extent, _caretPainter.prototype);
205 203
     _caretPainter.paint(context.canvas, caretOffset + offset);
206 204
   }
207 205
 

+ 31
- 0
packages/zefyr/lib/src/widgets/editable_rich_text.dart Vedi File

@@ -90,6 +90,37 @@ class RenderEditableParagraph extends RenderParagraph
90 90
     return documentSelection.copyWith(baseOffset: base, extentOffset: extent);
91 91
   }
92 92
 
93
+  @override
94
+  TextPosition getPositionForOffset(Offset offset) {
95
+    final position = super.getPositionForOffset(offset);
96
+    return new TextPosition(
97
+      offset: _node.documentOffset + position.offset,
98
+      affinity: position.affinity,
99
+    );
100
+  }
101
+
102
+  @override
103
+  TextRange getWordBoundary(TextPosition position) {
104
+    final localPosition = new TextPosition(
105
+      offset: position.offset - _node.offset,
106
+      affinity: position.affinity,
107
+    );
108
+    final localRange = super.getWordBoundary(localPosition);
109
+    return new TextRange(
110
+      start: _node.documentOffset + localRange.start,
111
+      end: _node.documentOffset + localRange.end,
112
+    );
113
+  }
114
+
115
+  @override
116
+  Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) {
117
+    final localPosition = new TextPosition(
118
+      offset: position.offset - _node.documentOffset,
119
+      affinity: position.affinity,
120
+    );
121
+    return super.getOffsetForCaret(localPosition, caretPrototype);
122
+  }
123
+
93 124
   // This method works around some issues in getBoxesForSelection and handles
94 125
   // edge-case with our TextSpan objects not having last line-break character.
95 126
   @override

+ 3
- 3
packages/zefyr/lib/src/widgets/editable_text.dart Vedi File

@@ -60,19 +60,19 @@ class ZefyrEditableTextScope extends InheritedWidget {
60 60
     @required this.selection,
61 61
     @required this.showCursor,
62 62
     @required this.renderContext,
63
-  })  : _activeParagraphs = new Set.from(renderContext.active),
63
+  })  : _activeBoxes = new Set.from(renderContext.active),
64 64
         super(key: key, child: child);
65 65
 
66 66
   final TextSelection selection;
67 67
   final ValueNotifier<bool> showCursor;
68 68
   final ZefyrRenderContext renderContext;
69
-  final Set<RenderEditableBox> _activeParagraphs;
69
+  final Set<RenderEditableBox> _activeBoxes;
70 70
 
71 71
   @override
72 72
   bool updateShouldNotify(ZefyrEditableTextScope oldWidget) {
73 73
     return selection != oldWidget.selection ||
74 74
         showCursor != oldWidget.showCursor ||
75
-        !_kEquality.equals(_activeParagraphs, oldWidget._activeParagraphs);
75
+        !_kEquality.equals(_activeBoxes, oldWidget._activeBoxes);
76 76
   }
77 77
 }
78 78
 

+ 19
- 8
packages/zefyr/lib/src/widgets/horizontal_rule.dart Vedi File

@@ -53,10 +53,11 @@ class RenderHorizontalRule extends RenderEditableBox {
53 53
 
54 54
   @override
55 55
   List<ui.TextBox> getEndpointsForSelection(TextSelection selection) {
56
-    TextSelection local =  getLocalSelection(selection);
56
+    TextSelection local = getLocalSelection(selection);
57 57
     if (local.isCollapsed) {
58
+      final dx = local.extentOffset == 0 ? 0.0 : size.width;
58 59
       return [
59
-        new ui.TextBox.fromLTRBD(0.0, 0.0, 0.0, size.height, TextDirection.ltr),
60
+        new ui.TextBox.fromLTRBD(dx, 0.0, dx, size.height, TextDirection.ltr),
60 61
       ];
61 62
     }
62 63
 
@@ -81,21 +82,31 @@ class RenderHorizontalRule extends RenderEditableBox {
81 82
   }
82 83
 
83 84
   @override
84
-  ui.TextPosition getPositionForOffset(ui.Offset offset) {
85
-    return new ui.TextPosition(offset: _node.documentOffset);
85
+  TextPosition getPositionForOffset(Offset offset) {
86
+    int position = _node.documentOffset;
87
+
88
+    if (offset.dx > size.width / 2) {
89
+      position++;
90
+    }
91
+    return new TextPosition(offset: position);
86 92
   }
87 93
 
88 94
   @override
89 95
   TextRange getWordBoundary(TextPosition position) {
90
-    return new TextRange(start: position.offset, end: position.offset + 1);
96
+    final start = _node.documentOffset;
97
+    return new TextRange(start: start, end: start + 1);
91 98
   }
92 99
 
93 100
   @override
94 101
   void paintSelection(PaintingContext context, Offset offset,
95 102
       TextSelection selection, Color selectionColor) {
96
-    final Paint paint = new Paint()..color = selectionColor;
97
-    final rect = new Rect.fromLTWH(0.0, 0.0, size.width, _kHeight);
98
-    context.canvas.drawRect(rect.shift(offset), paint);
103
+    final localSelection = getLocalSelection(selection);
104
+    assert(localSelection != null);
105
+    if (!localSelection.isCollapsed) {
106
+      final Paint paint = new Paint()..color = selectionColor;
107
+      final rect = new Rect.fromLTWH(0.0, 0.0, size.width, _kHeight);
108
+      context.canvas.drawRect(rect.shift(offset), paint);
109
+    }
99 110
   }
100 111
 
101 112
   @override

+ 22
- 26
packages/zefyr/lib/src/widgets/selection.dart Vedi File

@@ -13,7 +13,7 @@ import 'editable_box.dart';
13 13
 import 'editable_text.dart';
14 14
 import 'editor.dart';
15 15
 
16
-RenderEditableBox _getRenderParagraph(HitTestResult result) {
16
+RenderEditableBox _getEditableBox(HitTestResult result) {
17 17
   for (var entry in result.path) {
18 18
     if (entry.target is RenderEditableBox) {
19 19
       return entry.target;
@@ -213,13 +213,12 @@ class _ZefyrSelectionOverlayState extends State<ZefyrSelectionOverlay>
213 213
     HitTestResult result = new HitTestResult();
214 214
     WidgetsBinding.instance.hitTest(result, globalPoint);
215 215
 
216
-    final paragraph = _getRenderParagraph(result);
217
-    if (paragraph == null) return;
216
+    final box = _getEditableBox(result);
217
+    if (box == null) return;
218 218
 
219
-    final localPoint = paragraph.globalToLocal(globalPoint);
220
-    final position = paragraph.getPositionForOffset(localPoint);
221
-    final selection = new TextSelection.collapsed(
222
-        offset: paragraph.node.documentOffset + position.offset);
219
+    final localPoint = box.globalToLocal(globalPoint);
220
+    final position = box.getPositionForOffset(localPoint);
221
+    final selection = new TextSelection.collapsed(offset: position.offset);
223 222
     if (_didCaretTap && _selection == selection) {
224 223
       _didCaretTap = false;
225 224
       hideToolbar();
@@ -235,16 +234,16 @@ class _ZefyrSelectionOverlayState extends State<ZefyrSelectionOverlay>
235 234
     _longPressPosition = null;
236 235
     HitTestResult result = new HitTestResult();
237 236
     WidgetsBinding.instance.hitTest(result, globalPoint);
238
-    final paragraph = _getRenderParagraph(result);
239
-    if (paragraph == null) {
237
+    final box = _getEditableBox(result);
238
+    if (box == null) {
240 239
       return;
241 240
     }
242
-    final localPoint = paragraph.globalToLocal(globalPoint);
243
-    final position = paragraph.getPositionForOffset(localPoint);
244
-    final word = paragraph.getWordBoundary(position);
241
+    final localPoint = box.globalToLocal(globalPoint);
242
+    final position = box.getPositionForOffset(localPoint);
243
+    final word = box.getWordBoundary(position);
245 244
     final selection = new TextSelection(
246
-      baseOffset: paragraph.node.documentOffset + word.start,
247
-      extentOffset: paragraph.node.documentOffset + word.end,
245
+      baseOffset: word.start,
246
+      extentOffset: word.end,
248 247
     );
249 248
     widget.controller.updateSelection(selection, source: ChangeSource.local);
250 249
   }
@@ -290,10 +289,8 @@ class _SelectionHandleDriverState extends State<SelectionHandleDriver> {
290 289
     assert(localSelection != null);
291 290
 
292 291
     final boxes = block.getEndpointsForSelection(selection);
293
-    if (boxes.isEmpty) {
294
-      print('Got empty boxes for selection ${selection}');
295
-      return null;
296
-    }
292
+    assert(boxes.isNotEmpty, 'Got empty boxes for selection ${selection}');
293
+
297 294
     final box = isBaseHandle ? boxes.first : boxes.last;
298 295
     final dx = isBaseHandle ? box.start : box.end;
299 296
     return new Offset(dx, box.bottom);
@@ -369,18 +366,16 @@ class _SelectionHandleDriverState extends State<SelectionHandleDriver> {
369 366
     final globalPoint = _dragPosition;
370 367
     final editor = ZefyrEditor.of(context);
371 368
     final editable = ZefyrEditableText.of(context);
372
-    final paragraph =
373
-        editable.renderContext.boxForGlobalPoint(globalPoint);
369
+    final paragraph = editable.renderContext.boxForGlobalPoint(globalPoint);
374 370
     if (paragraph == null) {
375 371
       return;
376 372
     }
377 373
 
378 374
     final localPoint = paragraph.globalToLocal(globalPoint);
379 375
     final position = paragraph.getPositionForOffset(localPoint);
380
-    final documentOffset = paragraph.node.documentOffset + position.offset;
381 376
     final newSelection = selection.copyWith(
382
-      baseOffset: isBaseHandle ? documentOffset : selection.baseOffset,
383
-      extentOffset: isBaseHandle ? selection.extentOffset : documentOffset,
377
+      baseOffset: isBaseHandle ? position.offset : selection.baseOffset,
378
+      extentOffset: isBaseHandle ? selection.extentOffset : position.offset,
384 379
     );
385 380
     if (newSelection.baseOffset >= newSelection.extentOffset) {
386 381
       // Don't allow reversed or collapsed selection.
@@ -413,6 +408,7 @@ class _SelectionToolbar extends StatefulWidget {
413 408
 
414 409
 class _SelectionToolbarState extends State<_SelectionToolbar> {
415 410
   ZefyrEditableTextScope get editable => widget.editable;
411
+  TextSelection get selection => widget.delegate.textEditingValue.selection;
416 412
 
417 413
   @override
418 414
   Widget build(BuildContext context) {
@@ -420,13 +416,13 @@ class _SelectionToolbarState extends State<_SelectionToolbar> {
420 416
   }
421 417
 
422 418
   Widget _buildToolbar(BuildContext context) {
423
-    final base = editable.selection.baseOffset;
419
+    final base = selection.baseOffset;
420
+    // TODO: Editable is not refreshed and may contain stale renderContext instance.
424 421
     final block = editable.renderContext.boxForTextOffset(base);
425 422
     if (block == null) {
426 423
       return Container();
427 424
     }
428
-
429
-    final boxes = block.getEndpointsForSelection(editable.selection);
425
+    final boxes = block.getEndpointsForSelection(selection);
430 426
 
431 427
     // Find the horizontal midpoint, just above the selected text.
432 428
     final Offset midpoint = new Offset(