Browse Source

Fixed caret and selection handling in horizontal rule

Anatoly Pulyaevskiy 6 years ago
parent
commit
5d4a8df758

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

198
   final CaretPainter _caretPainter = new CaretPainter();
198
   final CaretPainter _caretPainter = new CaretPainter();
199
 
199
 
200
   void _paintCaret(PaintingContext context, Offset offset) {
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
     _caretPainter.paint(context.canvas, caretOffset + offset);
203
     _caretPainter.paint(context.canvas, caretOffset + offset);
206
   }
204
   }
207
 
205
 

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

90
     return documentSelection.copyWith(baseOffset: base, extentOffset: extent);
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
   // This method works around some issues in getBoxesForSelection and handles
124
   // This method works around some issues in getBoxesForSelection and handles
94
   // edge-case with our TextSpan objects not having last line-break character.
125
   // edge-case with our TextSpan objects not having last line-break character.
95
   @override
126
   @override

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

60
     @required this.selection,
60
     @required this.selection,
61
     @required this.showCursor,
61
     @required this.showCursor,
62
     @required this.renderContext,
62
     @required this.renderContext,
63
-  })  : _activeParagraphs = new Set.from(renderContext.active),
63
+  })  : _activeBoxes = new Set.from(renderContext.active),
64
         super(key: key, child: child);
64
         super(key: key, child: child);
65
 
65
 
66
   final TextSelection selection;
66
   final TextSelection selection;
67
   final ValueNotifier<bool> showCursor;
67
   final ValueNotifier<bool> showCursor;
68
   final ZefyrRenderContext renderContext;
68
   final ZefyrRenderContext renderContext;
69
-  final Set<RenderEditableBox> _activeParagraphs;
69
+  final Set<RenderEditableBox> _activeBoxes;
70
 
70
 
71
   @override
71
   @override
72
   bool updateShouldNotify(ZefyrEditableTextScope oldWidget) {
72
   bool updateShouldNotify(ZefyrEditableTextScope oldWidget) {
73
     return selection != oldWidget.selection ||
73
     return selection != oldWidget.selection ||
74
         showCursor != oldWidget.showCursor ||
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 View File

53
 
53
 
54
   @override
54
   @override
55
   List<ui.TextBox> getEndpointsForSelection(TextSelection selection) {
55
   List<ui.TextBox> getEndpointsForSelection(TextSelection selection) {
56
-    TextSelection local =  getLocalSelection(selection);
56
+    TextSelection local = getLocalSelection(selection);
57
     if (local.isCollapsed) {
57
     if (local.isCollapsed) {
58
+      final dx = local.extentOffset == 0 ? 0.0 : size.width;
58
       return [
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
   }
82
   }
82
 
83
 
83
   @override
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
   @override
94
   @override
89
   TextRange getWordBoundary(TextPosition position) {
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
   @override
100
   @override
94
   void paintSelection(PaintingContext context, Offset offset,
101
   void paintSelection(PaintingContext context, Offset offset,
95
       TextSelection selection, Color selectionColor) {
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
   @override
112
   @override

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

13
 import 'editable_text.dart';
13
 import 'editable_text.dart';
14
 import 'editor.dart';
14
 import 'editor.dart';
15
 
15
 
16
-RenderEditableBox _getRenderParagraph(HitTestResult result) {
16
+RenderEditableBox _getEditableBox(HitTestResult result) {
17
   for (var entry in result.path) {
17
   for (var entry in result.path) {
18
     if (entry.target is RenderEditableBox) {
18
     if (entry.target is RenderEditableBox) {
19
       return entry.target;
19
       return entry.target;
213
     HitTestResult result = new HitTestResult();
213
     HitTestResult result = new HitTestResult();
214
     WidgetsBinding.instance.hitTest(result, globalPoint);
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
     if (_didCaretTap && _selection == selection) {
222
     if (_didCaretTap && _selection == selection) {
224
       _didCaretTap = false;
223
       _didCaretTap = false;
225
       hideToolbar();
224
       hideToolbar();
235
     _longPressPosition = null;
234
     _longPressPosition = null;
236
     HitTestResult result = new HitTestResult();
235
     HitTestResult result = new HitTestResult();
237
     WidgetsBinding.instance.hitTest(result, globalPoint);
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
       return;
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
     final selection = new TextSelection(
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
     widget.controller.updateSelection(selection, source: ChangeSource.local);
248
     widget.controller.updateSelection(selection, source: ChangeSource.local);
250
   }
249
   }
290
     assert(localSelection != null);
289
     assert(localSelection != null);
291
 
290
 
292
     final boxes = block.getEndpointsForSelection(selection);
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
     final box = isBaseHandle ? boxes.first : boxes.last;
294
     final box = isBaseHandle ? boxes.first : boxes.last;
298
     final dx = isBaseHandle ? box.start : box.end;
295
     final dx = isBaseHandle ? box.start : box.end;
299
     return new Offset(dx, box.bottom);
296
     return new Offset(dx, box.bottom);
369
     final globalPoint = _dragPosition;
366
     final globalPoint = _dragPosition;
370
     final editor = ZefyrEditor.of(context);
367
     final editor = ZefyrEditor.of(context);
371
     final editable = ZefyrEditableText.of(context);
368
     final editable = ZefyrEditableText.of(context);
372
-    final paragraph =
373
-        editable.renderContext.boxForGlobalPoint(globalPoint);
369
+    final paragraph = editable.renderContext.boxForGlobalPoint(globalPoint);
374
     if (paragraph == null) {
370
     if (paragraph == null) {
375
       return;
371
       return;
376
     }
372
     }
377
 
373
 
378
     final localPoint = paragraph.globalToLocal(globalPoint);
374
     final localPoint = paragraph.globalToLocal(globalPoint);
379
     final position = paragraph.getPositionForOffset(localPoint);
375
     final position = paragraph.getPositionForOffset(localPoint);
380
-    final documentOffset = paragraph.node.documentOffset + position.offset;
381
     final newSelection = selection.copyWith(
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
     if (newSelection.baseOffset >= newSelection.extentOffset) {
380
     if (newSelection.baseOffset >= newSelection.extentOffset) {
386
       // Don't allow reversed or collapsed selection.
381
       // Don't allow reversed or collapsed selection.
413
 
408
 
414
 class _SelectionToolbarState extends State<_SelectionToolbar> {
409
 class _SelectionToolbarState extends State<_SelectionToolbar> {
415
   ZefyrEditableTextScope get editable => widget.editable;
410
   ZefyrEditableTextScope get editable => widget.editable;
411
+  TextSelection get selection => widget.delegate.textEditingValue.selection;
416
 
412
 
417
   @override
413
   @override
418
   Widget build(BuildContext context) {
414
   Widget build(BuildContext context) {
420
   }
416
   }
421
 
417
 
422
   Widget _buildToolbar(BuildContext context) {
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
     final block = editable.renderContext.boxForTextOffset(base);
421
     final block = editable.renderContext.boxForTextOffset(base);
425
     if (block == null) {
422
     if (block == null) {
426
       return Container();
423
       return Container();
427
     }
424
     }
428
-
429
-    final boxes = block.getEndpointsForSelection(editable.selection);
425
+    final boxes = block.getEndpointsForSelection(selection);
430
 
426
 
431
     // Find the horizontal midpoint, just above the selected text.
427
     // Find the horizontal midpoint, just above the selected text.
432
     final Offset midpoint = new Offset(
428
     final Offset midpoint = new Offset(