Sfoglia il codice sorgente

Paint selection and caret, plus some cleanups

Anatoly Pulyaevskiy 6 anni fa
parent
commit
43e7a73cf9

+ 6
- 2
packages/zefyr/lib/src/widgets/caret.dart Vedi File

9
   static const double _kCaretHeightOffset = 2.0; // pixels
9
   static const double _kCaretHeightOffset = 2.0; // pixels
10
   static const double _kCaretWidth = 1.0; // pixels
10
   static const double _kCaretWidth = 1.0; // pixels
11
 
11
 
12
+  static Rect buildPrototype(double lineHeight) {
13
+    return new Rect.fromLTWH(
14
+        0.0, 0.0, _kCaretWidth, lineHeight - _kCaretHeightOffset);
15
+  }
16
+
12
   Rect _prototype;
17
   Rect _prototype;
13
 
18
 
14
   Rect get prototype => _prototype;
19
   Rect get prototype => _prototype;
15
 
20
 
16
   void layout(double lineHeight) {
21
   void layout(double lineHeight) {
17
-    _prototype = new Rect.fromLTWH(
18
-        0.0, 0.0, _kCaretWidth, lineHeight - _kCaretHeightOffset);
22
+    _prototype = buildPrototype(lineHeight);
19
   }
23
   }
20
 
24
 
21
   void paint(Canvas canvas, Offset offset) {
25
   void paint(Canvas canvas, Offset offset) {

+ 77
- 10
packages/zefyr/lib/src/widgets/editable_box.dart Vedi File

8
 import 'package:flutter/widgets.dart';
8
 import 'package:flutter/widgets.dart';
9
 import 'package:notus/notus.dart';
9
 import 'package:notus/notus.dart';
10
 
10
 
11
+import 'caret.dart';
11
 import 'render_context.dart';
12
 import 'render_context.dart';
12
 
13
 
13
 class EditableBox extends SingleChildRenderObjectWidget {
14
 class EditableBox extends SingleChildRenderObjectWidget {
29
   final Color selectionColor;
30
   final Color selectionColor;
30
 
31
 
31
   @override
32
   @override
32
-  RenderObject createRenderObject(BuildContext context) {
33
+  RenderEditableProxyBox createRenderObject(BuildContext context) {
33
     return new RenderEditableProxyBox(
34
     return new RenderEditableProxyBox(
34
       node: node,
35
       node: node,
35
       layerLink: layerLink,
36
       layerLink: layerLink,
39
       selectionColor: selectionColor,
40
       selectionColor: selectionColor,
40
     );
41
     );
41
   }
42
   }
43
+
44
+  @override
45
+  void updateRenderObject(
46
+      BuildContext context, RenderEditableProxyBox renderObject) {
47
+    renderObject
48
+      ..node = node
49
+      ..layerLink = layerLink
50
+      ..renderContext = renderContext
51
+      ..showCursor = showCursor
52
+      ..selection = selection
53
+      ..selectionColor = selectionColor;
54
+  }
42
 }
55
 }
43
 
56
 
44
 class RenderEditableProxyBox extends RenderBox
57
 class RenderEditableProxyBox extends RenderBox
114
     markNeedsPaint();
127
     markNeedsPaint();
115
   }
128
   }
116
 
129
 
117
-  double get preferredLineHeight => child.preferredLineHeight;
118
-
119
   /// Returns `true` if current selection is collapsed, located within
130
   /// Returns `true` if current selection is collapsed, located within
120
   /// this paragraph and is visible according to tick timer.
131
   /// this paragraph and is visible according to tick timer.
121
   bool get isCaretVisible {
132
   bool get isCaretVisible {
153
     super.detach();
164
     super.detach();
154
   }
165
   }
155
 
166
 
156
-  /// Subclasses must call super as the last statement when overriding this
157
-  /// method.
158
   @override
167
   @override
159
   @mustCallSuper
168
   @mustCallSuper
160
   void performLayout() {
169
   void performLayout() {
161
     super.performLayout();
170
     super.performLayout();
171
+    _caretPainter.layout(preferredLineHeight);
162
     // Indicate to render context that this object can be used by other
172
     // Indicate to render context that this object can be used by other
163
     // layers (selection overlay, for instance).
173
     // layers (selection overlay, for instance).
164
     _renderContext.markDirty(this, false);
174
     _renderContext.markDirty(this, false);
171
     super.markNeedsLayout();
181
     super.markNeedsLayout();
172
   }
182
   }
173
 
183
 
184
+  @override
185
+  void paint(PaintingContext context, Offset offset) {
186
+    if (selectionOrder == SelectionOrder.background && isSelectionVisible) {
187
+      paintSelection(context, offset, selection, selectionColor);
188
+    }
189
+    super.paint(context, offset);
190
+    if (selectionOrder == SelectionOrder.foreground && isSelectionVisible) {
191
+      paintSelection(context, offset, selection, selectionColor);
192
+    }
193
+    if (isCaretVisible) {
194
+      _paintCaret(context, offset);
195
+    }
196
+  }
197
+
198
+  final CaretPainter _caretPainter = new CaretPainter();
199
+
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);
205
+    _caretPainter.paint(context.canvas, caretOffset + offset);
206
+  }
207
+
174
   @override
208
   @override
175
   bool hitTestSelf(Offset position) => true;
209
   bool hitTestSelf(Offset position) => true;
176
 
210
 
183
     return false;
217
     return false;
184
   }
218
   }
185
 
219
 
220
+  //
221
+  // Proxy methods
222
+  //
223
+
224
+  @override
225
+  double get preferredLineHeight => child.preferredLineHeight;
226
+
227
+  @override
228
+  SelectionOrder get selectionOrder => child.selectionOrder;
229
+
230
+  @override
231
+  void paintSelection(PaintingContext context, Offset offset,
232
+          TextSelection selection, Color selectionColor) =>
233
+      child.paintSelection(context, offset, selection, selectionColor);
234
+
235
+  @override
236
+  Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) =>
237
+      child.getOffsetForCaret(position, caretPrototype);
238
+
186
   @override
239
   @override
187
   TextSelection getLocalSelection(TextSelection documentSelection) =>
240
   TextSelection getLocalSelection(TextSelection documentSelection) =>
188
       child.getLocalSelection(documentSelection);
241
       child.getLocalSelection(documentSelection);
191
       child.intersectsWithSelection(selection);
244
       child.intersectsWithSelection(selection);
192
 
245
 
193
   @override
246
   @override
194
-  List<ui.TextBox> getEndpointsForSelection(TextSelection selection,
195
-          {bool isLocal: false}) =>
196
-      child.getEndpointsForSelection(selection, isLocal: isLocal);
247
+  List<ui.TextBox> getEndpointsForSelection(TextSelection selection) =>
248
+      child.getEndpointsForSelection(selection);
197
 
249
 
198
   @override
250
   @override
199
   ui.TextPosition getPositionForOffset(ui.Offset offset) =>
251
   ui.TextPosition getPositionForOffset(ui.Offset offset) =>
204
       child.getWordBoundary(position);
256
       child.getWordBoundary(position);
205
 }
257
 }
206
 
258
 
259
+enum SelectionOrder {
260
+  /// Background selection is painted before primary content of editable box.
261
+  background,
262
+
263
+  /// Foreground selection is painted after primary content of editable box.
264
+  foreground,
265
+}
266
+
207
 abstract class RenderEditableBox extends RenderBox {
267
 abstract class RenderEditableBox extends RenderBox {
208
   Node get node;
268
   Node get node;
209
   double get preferredLineHeight;
269
   double get preferredLineHeight;
210
 
270
 
211
   TextPosition getPositionForOffset(Offset offset);
271
   TextPosition getPositionForOffset(Offset offset);
212
-  List<ui.TextBox> getEndpointsForSelection(TextSelection selection,
213
-      {bool isLocal: false});
272
+  List<ui.TextBox> getEndpointsForSelection(TextSelection selection);
214
 
273
 
215
   /// Returns the text range of the word at the given offset. Characters not
274
   /// Returns the text range of the word at the given offset. Characters not
216
   /// part of a word, such as spaces, symbols, and punctuation, have word breaks
275
   /// part of a word, such as spaces, symbols, and punctuation, have word breaks
223
   /// Valid only after [layout].
282
   /// Valid only after [layout].
224
   TextRange getWordBoundary(TextPosition position);
283
   TextRange getWordBoundary(TextPosition position);
225
 
284
 
285
+  /// Paint order of selection in this editable box.
286
+  SelectionOrder get selectionOrder;
287
+
288
+  void paintSelection(PaintingContext context, Offset offset,
289
+      TextSelection selection, Color selectionColor);
290
+
291
+  Offset getOffsetForCaret(TextPosition position, Rect caretPrototype);
292
+
226
   /// Returns part of [documentSelection] local to this box. May return
293
   /// Returns part of [documentSelection] local to this box. May return
227
   /// `null`.
294
   /// `null`.
228
   ///
295
   ///

+ 29
- 23
packages/zefyr/lib/src/widgets/editable_image.dart Vedi File

46
     _node = value;
46
     _node = value;
47
   }
47
   }
48
 
48
 
49
+  @override
49
   double get preferredLineHeight => size.height;
50
   double get preferredLineHeight => size.height;
50
 
51
 
52
+  @override
53
+  SelectionOrder get selectionOrder => SelectionOrder.foreground;
54
+
51
   @override
55
   @override
52
   TextSelection getLocalSelection(TextSelection documentSelection) {
56
   TextSelection getLocalSelection(TextSelection documentSelection) {
53
     if (!intersectsWithSelection(documentSelection)) return null;
57
     if (!intersectsWithSelection(documentSelection)) return null;
60
     return documentSelection.copyWith(baseOffset: base, extentOffset: extent);
64
     return documentSelection.copyWith(baseOffset: base, extentOffset: extent);
61
   }
65
   }
62
 
66
 
63
-  List<ui.TextBox> getEndpointsForSelection(TextSelection selection,
64
-      {bool isLocal: false}) {
65
-    TextSelection local = isLocal ? selection : getLocalSelection(selection);
67
+  @override
68
+  List<TextBox> getEndpointsForSelection(TextSelection selection) {
69
+    TextSelection local = getLocalSelection(selection);
66
     if (local.isCollapsed) {
70
     if (local.isCollapsed) {
67
       return [
71
       return [
68
-        new ui.TextBox.fromLTRBD(0.0, 0.0, 0.0, size.height, TextDirection.ltr),
72
+        new TextBox.fromLTRBD(0.0, 0.0, 0.0, size.height, TextDirection.ltr),
69
       ];
73
       ];
70
     }
74
     }
71
 
75
 
72
     return [
76
     return [
73
-      new ui.TextBox.fromLTRBD(0.0, 0.0, 0.0, size.height, TextDirection.ltr),
74
-      new ui.TextBox.fromLTRBD(
77
+      new TextBox.fromLTRBD(0.0, 0.0, 0.0, size.height, TextDirection.ltr),
78
+      new TextBox.fromLTRBD(
75
           size.width, 0.0, size.width, size.height, TextDirection.ltr),
79
           size.width, 0.0, size.width, size.height, TextDirection.ltr),
76
     ];
80
     ];
77
   }
81
   }
78
 
82
 
79
   @override
83
   @override
80
-  void performLayout() {
81
-    super.performLayout();
82
-//    _prototypePainter.layout(
83
-//        minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
84
-//    _caretPainter.layout(_prototypePainter.height);
84
+  TextPosition getPositionForOffset(Offset offset) {
85
+    return new TextPosition(offset: node.documentOffset);
85
   }
86
   }
86
 
87
 
87
   @override
88
   @override
88
-  void paint(PaintingContext context, Offset offset) {
89
-//    if (_isSelectionVisible) _paintSelection(context, offset);
90
-    super.paint(context, offset);
91
-//    if (_isCaretVisible) _paintCaret(context, offset);
92
-  }
93
-
94
-  @override
95
-  ui.TextPosition getPositionForOffset(ui.Offset offset) {
96
-    return new ui.TextPosition(offset: node.documentOffset);
97
-  }
98
-
99
-  /// Returns `true` if this paragraph intersects with document [selection].
100
   bool intersectsWithSelection(TextSelection selection) {
89
   bool intersectsWithSelection(TextSelection selection) {
101
     final int base = node.documentOffset;
90
     final int base = node.documentOffset;
102
     final int extent = base + node.length;
91
     final int extent = base + node.length;
107
   TextRange getWordBoundary(ui.TextPosition position) {
96
   TextRange getWordBoundary(ui.TextPosition position) {
108
     return new TextRange(start: position.offset, end: position.offset + 1);
97
     return new TextRange(start: position.offset, end: position.offset + 1);
109
   }
98
   }
99
+
100
+  @override
101
+  ui.Offset getOffsetForCaret(
102
+      ui.TextPosition position, ui.Rect caretPrototype) {
103
+    final pos = position.offset - node.documentOffset;
104
+    Offset caretOffset = Offset.zero;
105
+    if (pos == 1) {
106
+      caretOffset = caretOffset + new Offset(size.width - 1.0, 0.0);
107
+    }
108
+    return caretOffset;
109
+  }
110
+
111
+  @override
112
+  void paintSelection(PaintingContext context, ui.Offset offset,
113
+      TextSelection selection, ui.Color selectionColor) {
114
+    // TODO: implement paintSelection
115
+  }
110
 }
116
 }

+ 17
- 25
packages/zefyr/lib/src/widgets/editable_rich_text.dart Vedi File

75
   @override
75
   @override
76
   double get preferredLineHeight => _prototypePainter.height;
76
   double get preferredLineHeight => _prototypePainter.height;
77
 
77
 
78
+  @override
79
+  SelectionOrder get selectionOrder => SelectionOrder.background;
80
+
78
   @override
81
   @override
79
   TextSelection getLocalSelection(TextSelection documentSelection) {
82
   TextSelection getLocalSelection(TextSelection documentSelection) {
80
     if (!intersectsWithSelection(documentSelection)) return null;
83
     if (!intersectsWithSelection(documentSelection)) return null;
89
 
92
 
90
   // This method works around some issues in getBoxesForSelection and handles
93
   // This method works around some issues in getBoxesForSelection and handles
91
   // edge-case with our TextSpan objects not having last line-break character.
94
   // edge-case with our TextSpan objects not having last line-break character.
92
-  // Wait for https://github.com/flutter/flutter/issues/16418 to be resolved.
93
   @override
95
   @override
94
-  List<ui.TextBox> getEndpointsForSelection(TextSelection selection,
95
-      {bool isLocal: false}) {
96
-    TextSelection local = isLocal ? selection : getLocalSelection(selection);
96
+  List<ui.TextBox> getEndpointsForSelection(TextSelection selection) {
97
+    TextSelection local = getLocalSelection(selection);
97
     if (local.isCollapsed) {
98
     if (local.isCollapsed) {
98
-      final offset = getOffsetForCaret(local.extent, _caretPainter.prototype);
99
+      final caret = CaretPainter.buildPrototype(preferredLineHeight);
100
+      final offset = getOffsetForCaret(local.extent, caret);
99
       return [
101
       return [
100
         new ui.TextBox.fromLTRBD(
102
         new ui.TextBox.fromLTRBD(
101
           offset.dx,
103
           offset.dx,
102
           offset.dy,
104
           offset.dy,
103
           offset.dx,
105
           offset.dx,
104
-          offset.dy + _caretPainter.prototype.height,
106
+          offset.dy + caret.height,
105
           TextDirection.ltr,
107
           TextDirection.ltr,
106
         )
108
         )
107
       ];
109
       ];
158
     super.performLayout();
160
     super.performLayout();
159
     _prototypePainter.layout(
161
     _prototypePainter.layout(
160
         minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
162
         minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
161
-    _caretPainter.layout(_prototypePainter.height);
162
   }
163
   }
163
 
164
 
164
   @override
165
   @override
165
   void paint(PaintingContext context, Offset offset) {
166
   void paint(PaintingContext context, Offset offset) {
166
-//    if (isSelectionVisible) _paintSelection(context, offset);
167
     super.paint(context, offset);
167
     super.paint(context, offset);
168
-//    if (isCaretVisible) _paintCaret(context, offset);
169
   }
168
   }
170
 
169
 
171
-  //
172
-  // Private members
173
-  //
174
-
175
   final TextPainter _prototypePainter;
170
   final TextPainter _prototypePainter;
176
-  final CaretPainter _caretPainter = new CaretPainter();
177
   List<ui.TextBox> _selectionRects;
171
   List<ui.TextBox> _selectionRects;
178
 
172
 
179
   /// Returns `true` if this paragraph intersects with document [selection].
173
   /// Returns `true` if this paragraph intersects with document [selection].
174
+  @override
180
   bool intersectsWithSelection(TextSelection selection) {
175
   bool intersectsWithSelection(TextSelection selection) {
181
     final int base = node.documentOffset;
176
     final int base = node.documentOffset;
182
     final int extent = base + node.length;
177
     final int extent = base + node.length;
183
     return base <= selection.extentOffset && selection.baseOffset <= extent;
178
     return base <= selection.extentOffset && selection.baseOffset <= extent;
184
   }
179
   }
185
 
180
 
186
-//
187
-//  void _paintCaret(PaintingContext context, Offset offset) {
188
-//    final TextPosition caret = new TextPosition(
189
-//      offset: _selection.extentOffset - node.documentOffset,
190
-//    );
191
-//    Offset caretOffset = getOffsetForCaret(caret, _caretPainter.prototype);
192
-//    _caretPainter.paint(context.canvas, caretOffset + offset);
193
-//  }
194
-//
181
+  TextSelection _lastPaintedSelection;
182
+  @override
195
   void paintSelection(PaintingContext context, Offset offset,
183
   void paintSelection(PaintingContext context, Offset offset,
196
       TextSelection selection, Color selectionColor) {
184
       TextSelection selection, Color selectionColor) {
197
-    // TODO: this could be improved by painting additional box for line-break characters.
185
+    if (_lastPaintedSelection != selection) {
186
+      _selectionRects = null;
187
+    }
198
     _selectionRects ??= getBoxesForSelection(getLocalSelection(selection));
188
     _selectionRects ??= getBoxesForSelection(getLocalSelection(selection));
199
     final Paint paint = new Paint()..color = selectionColor;
189
     final Paint paint = new Paint()..color = selectionColor;
200
-    for (ui.TextBox box in _selectionRects)
190
+    for (ui.TextBox box in _selectionRects) {
201
       context.canvas.drawRect(box.toRect().shift(offset), paint);
191
       context.canvas.drawRect(box.toRect().shift(offset), paint);
192
+    }
193
+    _lastPaintedSelection = selection;
202
   }
194
   }
203
 }
195
 }

+ 24
- 28
packages/zefyr/lib/src/widgets/horizontal_rule.dart Vedi File

48
   @override
48
   @override
49
   double get preferredLineHeight => size.height;
49
   double get preferredLineHeight => size.height;
50
 
50
 
51
-  List<ui.TextBox> getEndpointsForSelection(TextSelection selection,
52
-      {bool isLocal: false}) {
53
-    TextSelection local = isLocal ? selection : getLocalSelection(selection);
51
+  @override
52
+  SelectionOrder get selectionOrder => SelectionOrder.background;
53
+
54
+  @override
55
+  List<ui.TextBox> getEndpointsForSelection(TextSelection selection) {
56
+    TextSelection local =  getLocalSelection(selection);
54
     if (local.isCollapsed) {
57
     if (local.isCollapsed) {
55
       return [
58
       return [
56
         new ui.TextBox.fromLTRBD(0.0, 0.0, 0.0, size.height, TextDirection.ltr),
59
         new ui.TextBox.fromLTRBD(0.0, 0.0, 0.0, size.height, TextDirection.ltr),
68
   void performLayout() {
71
   void performLayout() {
69
     assert(constraints.hasBoundedWidth);
72
     assert(constraints.hasBoundedWidth);
70
     size = new Size(constraints.maxWidth, _kHeight);
73
     size = new Size(constraints.maxWidth, _kHeight);
71
-//    _caretPainter.layout(height);
72
   }
74
   }
73
 
75
 
74
   @override
76
   @override
75
   void paint(PaintingContext context, Offset offset) {
77
   void paint(PaintingContext context, Offset offset) {
76
-//    if (isSelectionVisible) _paintSelection(context, offset);
77
     final rect = new Rect.fromLTWH(0.0, 0.0, size.width, _kThickness);
78
     final rect = new Rect.fromLTWH(0.0, 0.0, size.width, _kThickness);
78
     final paint = new ui.Paint()..color = Colors.grey.shade200;
79
     final paint = new ui.Paint()..color = Colors.grey.shade200;
79
     context.canvas.drawRect(rect.shift(offset), paint);
80
     context.canvas.drawRect(rect.shift(offset), paint);
80
-//    if (isCaretVisible) _paintCaret(context, offset);
81
   }
81
   }
82
 
82
 
83
   @override
83
   @override
86
   }
86
   }
87
 
87
 
88
   @override
88
   @override
89
-  TextRange getWordBoundary(ui.TextPosition position) {
89
+  TextRange getWordBoundary(TextPosition position) {
90
     return new TextRange(start: position.offset, end: position.offset + 1);
90
     return new TextRange(start: position.offset, end: position.offset + 1);
91
   }
91
   }
92
 
92
 
93
-  //
94
-  // Private members
95
-  //
96
-//
97
-//  final CaretPainter _caretPainter = new CaretPainter();
98
-//
99
-//  void _paintCaret(PaintingContext context, Offset offset) {
100
-//    final pos = selection.extentOffset - node.documentOffset;
101
-//    Offset caretOffset = Offset.zero;
102
-//    if (pos == 1) {
103
-//      caretOffset = caretOffset + new Offset(size.width - 1.0, 0.0);
104
-//    }
105
-//    _caretPainter.paint(context.canvas, caretOffset + offset);
106
-//  }
107
-//
108
-//  void _paintSelection(PaintingContext context, Offset offset) {
109
-//    assert(isSelectionVisible);
110
-//    final Paint paint = new Paint()..color = selectionColor;
111
-//    final rect = new Rect.fromLTWH(0.0, 0.0, size.width, _kHeight);
112
-//    context.canvas.drawRect(rect.shift(offset), paint);
113
-//  }
93
+  @override
94
+  void paintSelection(PaintingContext context, Offset offset,
95
+      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);
99
+  }
100
+
101
+  @override
102
+  Offset getOffsetForCaret(ui.TextPosition position, ui.Rect caretPrototype) {
103
+    final pos = position.offset - node.documentOffset;
104
+    Offset caretOffset = Offset.zero;
105
+    if (pos == 1) {
106
+      caretOffset = caretOffset + new Offset(size.width - 1.0, 0.0);
107
+    }
108
+    return caretOffset;
109
+  }
114
 }
110
 }