Browse Source

Refactoring render objects to reduce duplication, WIP

Anatoly Pulyaevskiy 6 years ago
parent
commit
9d51662802

+ 12
- 4
packages/zefyr/lib/src/widgets/common.dart View File

@@ -5,12 +5,13 @@ import 'package:flutter/rendering.dart';
5 5
 import 'package:flutter/widgets.dart';
6 6
 import 'package:notus/notus.dart';
7 7
 
8
-import 'editable_paragraph.dart';
8
+import 'editable_box.dart';
9
+import 'editable_rich_text.dart';
9 10
 import 'editable_text.dart';
10 11
 import 'horizontal_rule.dart';
11 12
 import 'theme.dart';
12 13
 
13
-/// Raw widget representing a single line of Notus document in Zefyr editor.
14
+/// Raw widget representing a single line of rich text document in Zefyr editor.
14 15
 ///
15 16
 /// See [ZefyrParagraph] and [ZefyrHeading] which wrap this widget and
16 17
 /// integrate it with current [ZefyrTheme].
@@ -50,9 +51,14 @@ class _RawZefyrLineState extends State<RawZefyrLine> {
50 51
       content = buildEmbed(context);
51 52
     } else {
52 53
       assert(widget.style != null);
53
-      content = new EditableParagraph(
54
+
55
+      final text = new EditableRichText(
54 56
         node: widget.node,
55 57
         text: buildText(context),
58
+      );
59
+      content = new EditableBox(
60
+        child: text,
61
+        node: widget.node,
56 62
         layerLink: _link,
57 63
         renderContext: editable.renderContext,
58 64
         showCursor: editable.showCursor,
@@ -137,7 +143,9 @@ class _RawZefyrLineState extends State<RawZefyrLine> {
137 143
     EmbedAttribute embed = node.style.get(NotusAttribute.embed);
138 144
 
139 145
     if (embed.type == EmbedType.horizontalRule) {
140
-      return new HorizontalRule(
146
+      final hr = new HorizontalRule(node: node);
147
+      return new EditableBox(
148
+        child: hr,
141 149
         node: widget.node,
142 150
         layerLink: _link,
143 151
         renderContext: editable.renderContext,

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

@@ -1,18 +1,247 @@
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
 
6 7
 import 'package:flutter/rendering.dart';
8
+import 'package:flutter/widgets.dart';
7 9
 import 'package:notus/notus.dart';
8 10
 
9
-abstract class RenderEditableBox implements RenderBox {
10
-  ContainerNode get node;
11
+import 'render_context.dart';
12
+
13
+class EditableBox extends SingleChildRenderObjectWidget {
14
+  EditableBox({
15
+    @required Widget child,
16
+    @required this.node,
17
+    @required this.layerLink,
18
+    @required this.renderContext,
19
+    @required this.showCursor,
20
+    @required this.selection,
21
+    @required this.selectionColor,
22
+  }) : super(child: child);
23
+
24
+  final ContainerNode node;
25
+  final LayerLink layerLink;
26
+  final ZefyrRenderContext renderContext;
27
+  final ValueNotifier<bool> showCursor;
28
+  final TextSelection selection;
29
+  final Color selectionColor;
30
+
31
+  @override
32
+  RenderObject createRenderObject(BuildContext context) {
33
+    return new RenderEditableProxyBox(
34
+      node: node,
35
+      layerLink: layerLink,
36
+      renderContext: renderContext,
37
+      showCursor: showCursor,
38
+      selection: selection,
39
+      selectionColor: selectionColor,
40
+    );
41
+  }
42
+}
43
+
44
+class RenderEditableProxyBox extends RenderBox
45
+    with
46
+        RenderObjectWithChildMixin<RenderEditableBox>,
47
+        RenderProxyBoxMixin<RenderEditableBox>
48
+    implements RenderEditableBox {
49
+  RenderEditableProxyBox({
50
+    RenderEditableBox child,
51
+    @required ContainerNode node,
52
+    @required LayerLink layerLink,
53
+    @required ZefyrRenderContext renderContext,
54
+    @required ValueNotifier<bool> showCursor,
55
+    @required TextSelection selection,
56
+    @required Color selectionColor,
57
+  })  : _node = node,
58
+        _layerLink = layerLink,
59
+        _renderContext = renderContext,
60
+        _showCursor = showCursor,
61
+        _selection = selection,
62
+        _selectionColor = selectionColor,
63
+        super() {
64
+    this.child = child;
65
+  }
66
+
67
+  ContainerNode get node => _node;
68
+  ContainerNode _node;
69
+  void set node(ContainerNode value) {
70
+    _node = value;
71
+  }
72
+
73
+  LayerLink get layerLink => _layerLink;
74
+  LayerLink _layerLink;
75
+  void set layerLink(LayerLink value) {
76
+    if (_layerLink == value) return;
77
+    _layerLink = value;
78
+  }
79
+
80
+  ZefyrRenderContext _renderContext;
81
+  void set renderContext(ZefyrRenderContext value) {
82
+    if (_renderContext == value) return;
83
+    if (attached) _renderContext.removeBox(this);
84
+    _renderContext = value;
85
+    if (attached) _renderContext.addBox(this);
86
+  }
87
+
88
+  ValueNotifier<bool> _showCursor;
89
+  set showCursor(ValueNotifier<bool> value) {
90
+    assert(value != null);
91
+    if (_showCursor == value) return;
92
+    if (attached) _showCursor.removeListener(markNeedsPaint);
93
+    _showCursor = value;
94
+    if (attached) _showCursor.addListener(markNeedsPaint);
95
+    markNeedsPaint();
96
+  }
97
+
98
+  /// Current document selection.
99
+  TextSelection get selection => _selection;
100
+  TextSelection _selection;
101
+  set selection(TextSelection value) {
102
+    if (_selection == value) return;
103
+    // TODO: check if selection affects this block (also check previous value)
104
+    _selection = value;
105
+    markNeedsPaint();
106
+  }
107
+
108
+  /// Color of selection.
109
+  Color get selectionColor => _selectionColor;
110
+  Color _selectionColor;
111
+  set selectionColor(Color value) {
112
+    if (_selectionColor == value) return;
113
+    _selectionColor = value;
114
+    markNeedsPaint();
115
+  }
116
+
117
+  double get preferredLineHeight => child.preferredLineHeight;
118
+
119
+  /// Returns `true` if current selection is collapsed, located within
120
+  /// this paragraph and is visible according to tick timer.
121
+  bool get isCaretVisible {
122
+    if (!_selection.isCollapsed) return false;
123
+    if (!_showCursor.value) return false;
124
+
125
+    final int start = node.documentOffset;
126
+    final int end = start + node.length;
127
+    final int caretOffset = _selection.extentOffset;
128
+    return caretOffset >= start && caretOffset < end;
129
+  }
130
+
131
+  /// Returns `true` if selection is not collapsed and intersects with this
132
+  /// paragraph.
133
+  bool get isSelectionVisible {
134
+    if (_selection.isCollapsed) return false;
135
+    return intersectsWithSelection(_selection);
136
+  }
137
+
138
+  //
139
+  // Overridden members of RenderBox
140
+  //
141
+
142
+  @override
143
+  void attach(PipelineOwner owner) {
144
+    super.attach(owner);
145
+    _showCursor.addListener(markNeedsPaint);
146
+    _renderContext.addBox(this);
147
+  }
148
+
149
+  @override
150
+  void detach() {
151
+    _showCursor.removeListener(markNeedsPaint);
152
+    _renderContext.removeBox(this);
153
+    super.detach();
154
+  }
155
+
156
+  /// Subclasses must call super as the last statement when overriding this
157
+  /// method.
158
+  @override
159
+  @mustCallSuper
160
+  void performLayout() {
161
+    super.performLayout();
162
+    // Indicate to render context that this object can be used by other
163
+    // layers (selection overlay, for instance).
164
+    _renderContext.markDirty(this, false);
165
+  }
166
+
167
+  @override
168
+  void markNeedsLayout() {
169
+    // Temporarily remove this object from the render context.
170
+    _renderContext.markDirty(this, true);
171
+    super.markNeedsLayout();
172
+  }
173
+
174
+  @override
175
+  bool hitTestSelf(Offset position) => true;
176
+
177
+  @override
178
+  bool hitTest(HitTestResult result, {Offset position}) {
179
+    if (size.contains(position)) {
180
+      result.add(new BoxHitTestEntry(this, position));
181
+      return true;
182
+    }
183
+    return false;
184
+  }
185
+
186
+  @override
187
+  TextSelection getLocalSelection(TextSelection documentSelection) =>
188
+      child.getLocalSelection(documentSelection);
189
+
190
+  bool intersectsWithSelection(TextSelection selection) =>
191
+      child.intersectsWithSelection(selection);
192
+
193
+  @override
194
+  List<ui.TextBox> getEndpointsForSelection(TextSelection selection,
195
+          {bool isLocal: false}) =>
196
+      child.getEndpointsForSelection(selection, isLocal: isLocal);
197
+
198
+  @override
199
+  ui.TextPosition getPositionForOffset(ui.Offset offset) =>
200
+      child.getPositionForOffset(offset);
201
+
202
+  @override
203
+  TextRange getWordBoundary(ui.TextPosition position) =>
204
+      child.getWordBoundary(position);
205
+}
206
+
207
+abstract class RenderEditableBox extends RenderBox {
208
+  Node get node;
11 209
   double get preferredLineHeight;
12
-  LayerLink get layerLink;
210
+
13 211
   TextPosition getPositionForOffset(Offset offset);
14 212
   List<ui.TextBox> getEndpointsForSelection(TextSelection selection,
15 213
       {bool isLocal: false});
16
-  TextSelection getLocalSelection(TextSelection selection);
214
+
215
+  /// 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
217
+  /// on both sides. In such cases, this method will return a text range that
218
+  /// contains the given text position.
219
+  ///
220
+  /// Word boundaries are defined more precisely in Unicode Standard Annex #29
221
+  /// <http://www.unicode.org/reports/tr29/#Word_Boundaries>.
222
+  ///
223
+  /// Valid only after [layout].
17 224
   TextRange getWordBoundary(TextPosition position);
225
+
226
+  /// Returns part of [documentSelection] local to this box. May return
227
+  /// `null`.
228
+  ///
229
+  /// [documentSelection] must not be collapsed.
230
+  TextSelection getLocalSelection(TextSelection documentSelection) {
231
+    if (!intersectsWithSelection(documentSelection)) return null;
232
+
233
+    int nodeBase = node.documentOffset;
234
+    int nodeExtent = nodeBase + node.length;
235
+    int base = math.max(0, documentSelection.baseOffset - nodeBase);
236
+    int extent =
237
+        math.min(documentSelection.extentOffset, nodeExtent) - nodeBase;
238
+    return documentSelection.copyWith(baseOffset: base, extentOffset: extent);
239
+  }
240
+
241
+  /// Returns `true` if this box intersects with document [selection].
242
+  bool intersectsWithSelection(TextSelection selection) {
243
+    final int base = node.documentOffset;
244
+    final int extent = base + node.length;
245
+    return base <= selection.extentOffset && selection.baseOffset <= extent;
246
+  }
18 247
 }

+ 17
- 178
packages/zefyr/lib/src/widgets/editable_image.dart View File

@@ -9,65 +9,29 @@ import 'package:flutter/widgets.dart';
9 9
 import 'package:notus/notus.dart';
10 10
 
11 11
 import 'editable_box.dart';
12
-import 'render_context.dart';
13 12
 
14 13
 class EditableImage extends LeafRenderObjectWidget {
15
-  EditableImage({
16
-    @required this.node,
17
-    @required this.layerLink,
18
-    @required this.renderContext,
19
-    @required this.showCursor,
20
-    @required this.selection,
21
-    @required this.selectionColor,
22
-  }) : assert(renderContext != null);
14
+  EditableImage({@required this.node}) : assert(node != null);
23 15
 
24
-  final ContainerNode node;
25
-  final LayerLink layerLink;
26
-  final ZefyrRenderContext renderContext;
27
-  final ValueNotifier<bool> showCursor;
28
-  final TextSelection selection;
29
-  final Color selectionColor;
16
+  final EmbedNode node;
30 17
 
31 18
   @override
32 19
   RenderEditableImage createRenderObject(BuildContext context) {
33
-    return new RenderEditableImage(
34
-      node: node,
35
-      layerLink: layerLink,
36
-      renderContext: renderContext,
37
-      showCursor: showCursor,
38
-      selection: selection,
39
-      selectionColor: selectionColor,
40
-    );
20
+    return new RenderEditableImage(node: node);
41 21
   }
42 22
 
43 23
   @override
44 24
   void updateRenderObject(
45 25
       BuildContext context, RenderEditableImage renderObject) {
46
-    renderObject
47
-      ..node = node
48
-      ..layerLink = layerLink
49
-      ..renderContext = renderContext
50
-      ..showCursor = showCursor
51
-      ..selection = selection
52
-      ..selectionColor = selectionColor;
26
+    renderObject..node = node;
53 27
   }
54 28
 }
55 29
 
56 30
 class RenderEditableImage extends RenderImage implements RenderEditableBox {
57 31
   RenderEditableImage({
58 32
     ui.Image image,
59
-    @required ContainerNode node,
60
-    @required LayerLink layerLink,
61
-    @required ZefyrRenderContext renderContext,
62
-    @required ValueNotifier<bool> showCursor,
63
-    @required TextSelection selection,
64
-    @required Color selectionColor,
33
+    @required EmbedNode node,
65 34
   })  : _node = node,
66
-        _layerLink = layerLink,
67
-        _renderContext = renderContext,
68
-        _showCursor = showCursor,
69
-        _selection = selection,
70
-        _selectionColor = selectionColor,
71 35
         super(
72 36
           image: image,
73 37
         );
@@ -75,66 +39,25 @@ class RenderEditableImage extends RenderImage implements RenderEditableBox {
75 39
   // Public members
76 40
   //
77 41
 
78
-  ContainerNode get node => _node;
79
-  ContainerNode _node;
80
-  void set node(ContainerNode value) {
42
+  @override
43
+  EmbedNode get node => _node;
44
+  EmbedNode _node;
45
+  void set node(EmbedNode value) {
81 46
     _node = value;
82 47
   }
83 48
 
84
-  LayerLink get layerLink => _layerLink;
85
-  LayerLink _layerLink;
86
-  void set layerLink(LayerLink value) {
87
-    if (_layerLink == value) return;
88
-    _layerLink = value;
89
-  }
90
-
91
-  ZefyrRenderContext _renderContext;
92
-  void set renderContext(ZefyrRenderContext value) {
93
-    if (_renderContext == value) return;
94
-    if (attached) _renderContext.removeBox(this);
95
-    _renderContext = value;
96
-    if (attached) _renderContext.addBox(this);
97
-  }
98
-
99
-  ValueNotifier<bool> _showCursor;
100
-  set showCursor(ValueNotifier<bool> value) {
101
-    assert(value != null);
102
-    if (_showCursor == value) return;
103
-    if (attached) _showCursor.removeListener(markNeedsPaint);
104
-    _showCursor = value;
105
-    if (attached) _showCursor.addListener(markNeedsPaint);
106
-    markNeedsPaint();
107
-  }
108
-
109
-  TextSelection _selection;
110
-  set selection(TextSelection value) {
111
-    if (_selection == value) return;
112
-    // TODO: check if selection affects this block (also check previous value)
113
-    _selection = value;
114
-    markNeedsPaint();
115
-  }
116
-
117
-  Color _selectionColor;
118
-  set selectionColor(Color value) {
119
-    if (_selectionColor == value) return;
120
-    _selectionColor = value;
121
-    markNeedsPaint();
122
-  }
123
-
124 49
   double get preferredLineHeight => size.height;
125 50
 
126
-  /// Returns part of document [selection] local to this paragraph. May return
127
-  /// `null`.
128
-  ///
129
-  /// [selection] must not be collapsed.
130
-  TextSelection getLocalSelection(TextSelection selection) {
131
-    if (!_intersectsWithSelection(selection)) return null;
51
+  @override
52
+  TextSelection getLocalSelection(TextSelection documentSelection) {
53
+    if (!intersectsWithSelection(documentSelection)) return null;
132 54
 
133 55
     int nodeBase = node.documentOffset;
134 56
     int nodeExtent = nodeBase + node.length;
135
-    int base = math.max(0, selection.baseOffset - nodeBase);
136
-    int extent = math.min(selection.extentOffset, nodeExtent) - nodeBase;
137
-    return _selection.copyWith(baseOffset: base, extentOffset: extent);
57
+    int base = math.max(0, documentSelection.baseOffset - nodeBase);
58
+    int extent =
59
+        math.min(documentSelection.extentOffset, nodeExtent) - nodeBase;
60
+    return documentSelection.copyWith(baseOffset: base, extentOffset: extent);
138 61
   }
139 62
 
140 63
   List<ui.TextBox> getEndpointsForSelection(TextSelection selection,
@@ -153,45 +76,12 @@ class RenderEditableImage extends RenderImage implements RenderEditableBox {
153 76
     ];
154 77
   }
155 78
 
156
-  //
157
-  // Overridden members
158
-  //
159
-
160
-  @override
161
-  void attach(PipelineOwner owner) {
162
-    super.attach(owner);
163
-    _showCursor.addListener(markNeedsPaint);
164
-    _renderContext.addBox(this);
165
-  }
166
-
167
-  @override
168
-  void detach() {
169
-    _showCursor.removeListener(markNeedsPaint);
170
-    _renderContext.removeBox(this);
171
-    super.detach();
172
-  }
173
-
174
-  @override
175
-  bool hitTestSelf(Offset position) => true;
176
-
177
-  @override
178
-  bool hitTest(HitTestResult result, {Offset position}) {
179
-    if (size.contains(position)) {
180
-      result.add(new BoxHitTestEntry(this, position));
181
-      return true;
182
-    }
183
-    return false;
184
-  }
185
-
186 79
   @override
187 80
   void performLayout() {
188 81
     super.performLayout();
189 82
 //    _prototypePainter.layout(
190 83
 //        minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
191 84
 //    _caretPainter.layout(_prototypePainter.height);
192
-    // Indicate to render context that this object can be used by other
193
-    // layers (selection overlay, for instance).
194
-    _renderContext.markDirty(this, false);
195 85
   }
196 86
 
197 87
   @override
@@ -201,46 +91,13 @@ class RenderEditableImage extends RenderImage implements RenderEditableBox {
201 91
 //    if (_isCaretVisible) _paintCaret(context, offset);
202 92
   }
203 93
 
204
-  @override
205
-  void markNeedsLayout() {
206
-    // Temporarily remove this object from the render context.
207
-    _renderContext.markDirty(this, true);
208
-    super.markNeedsLayout();
209
-  }
210
-
211 94
   @override
212 95
   ui.TextPosition getPositionForOffset(ui.Offset offset) {
213 96
     return new ui.TextPosition(offset: node.documentOffset);
214 97
   }
215 98
 
216
-  //
217
-  // Private members
218
-  //
219
-
220
-//  final CaretPainter _caretPainter = new CaretPainter();
221
-//  List<ui.TextBox> _selectionRects;
222
-
223
-  /// Returns `true` if current selection is collapsed, located within
224
-  /// this paragraph and is visible according to tick timer.
225
-//  bool get _isCaretVisible {
226
-//    if (!_selection.isCollapsed) return false;
227
-//    if (!_showCursor.value) return false;
228
-//
229
-//    final int start = node.documentOffset;
230
-//    final int end = start + node.length;
231
-//    final int caretOffset = _selection.extentOffset;
232
-//    return caretOffset >= start && caretOffset < end;
233
-//  }
234
-
235
-  /// Returns `true` if selection is not collapsed and intersects with this
236
-  /// paragraph.
237
-//  bool get _isSelectionVisible {
238
-//    if (_selection.isCollapsed) return false;
239
-//    return _intersectsWithSelection(_selection);
240
-//  }
241
-
242 99
   /// Returns `true` if this paragraph intersects with document [selection].
243
-  bool _intersectsWithSelection(TextSelection selection) {
100
+  bool intersectsWithSelection(TextSelection selection) {
244 101
     final int base = node.documentOffset;
245 102
     final int extent = base + node.length;
246 103
     return base <= selection.extentOffset && selection.baseOffset <= extent;
@@ -250,22 +107,4 @@ class RenderEditableImage extends RenderImage implements RenderEditableBox {
250 107
   TextRange getWordBoundary(ui.TextPosition position) {
251 108
     return new TextRange(start: position.offset, end: position.offset + 1);
252 109
   }
253
-
254
-//
255
-//  void _paintCaret(PaintingContext context, Offset offset) {
256
-//    final TextPosition caret = new TextPosition(
257
-//      offset: _selection.extentOffset - node.documentOffset,
258
-//    );
259
-//    Offset caretOffset = getOffsetForCaret(caret, _caretPainter.prototype);
260
-//    _caretPainter.paint(context.canvas, caretOffset + offset);
261
-//  }
262
-//
263
-//  void _paintSelection(PaintingContext context, Offset offset) {
264
-//    assert(_isSelectionVisible);
265
-//    // TODO: this could be improved by painting additional box for line-break characters.
266
-//    _selectionRects ??= getBoxesForSelection(getLocalSelection(_selection));
267
-//    final Paint paint = new Paint()..color = _selectionColor;
268
-//    for (ui.TextBox box in _selectionRects)
269
-//      context.canvas.drawRect(box.toRect().shift(offset), paint);
270
-//  }
271 110
 }

+ 0
- 333
packages/zefyr/lib/src/widgets/editable_paragraph.dart View File

@@ -1,333 +0,0 @@
1
-// Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
2
-// for details. All rights reserved. Use of this source code is governed by a
3
-// BSD-style license that can be found in the LICENSE file.
4
-import 'dart:math' as math;
5
-import 'dart:ui' as ui;
6
-
7
-import 'package:flutter/rendering.dart';
8
-import 'package:flutter/widgets.dart';
9
-import 'package:notus/notus.dart';
10
-
11
-import 'caret.dart';
12
-import 'editable_box.dart';
13
-import 'render_context.dart';
14
-
15
-class EditableParagraph extends LeafRenderObjectWidget {
16
-  EditableParagraph({
17
-    @required this.node,
18
-    @required this.text,
19
-    @required this.layerLink,
20
-    @required this.renderContext,
21
-    @required this.showCursor,
22
-    @required this.selection,
23
-    @required this.selectionColor,
24
-  }) : assert(renderContext != null);
25
-
26
-  final ContainerNode node;
27
-  final TextSpan text;
28
-  final LayerLink layerLink;
29
-  final ZefyrRenderContext renderContext;
30
-  final ValueNotifier<bool> showCursor;
31
-  final TextSelection selection;
32
-  final Color selectionColor;
33
-
34
-  @override
35
-  RenderObject createRenderObject(BuildContext context) {
36
-    return new RenderEditableParagraph(
37
-      text,
38
-      node: node,
39
-      layerLink: layerLink,
40
-      renderContext: renderContext,
41
-      showCursor: showCursor,
42
-      selection: selection,
43
-      selectionColor: selectionColor,
44
-      textDirection: Directionality.of(context),
45
-    );
46
-  }
47
-
48
-  @override
49
-  void updateRenderObject(
50
-      BuildContext context, RenderEditableParagraph renderObject) {
51
-    renderObject
52
-      ..text = text
53
-      ..node = node
54
-      ..layerLink = layerLink
55
-      ..renderContext = renderContext
56
-      ..showCursor = showCursor
57
-      ..selection = selection
58
-      ..selectionColor = selectionColor;
59
-  }
60
-}
61
-
62
-class RenderEditableParagraph extends RenderParagraph
63
-    implements RenderEditableBox {
64
-  RenderEditableParagraph(
65
-    TextSpan text, {
66
-    @required ContainerNode node,
67
-    @required LayerLink layerLink,
68
-    @required ZefyrRenderContext renderContext,
69
-    @required ValueNotifier<bool> showCursor,
70
-    @required TextSelection selection,
71
-    @required Color selectionColor,
72
-    TextAlign textAlign: TextAlign.start,
73
-    @required TextDirection textDirection,
74
-    bool softWrap: true,
75
-    TextOverflow overflow: TextOverflow.clip,
76
-    double textScaleFactor: 1.0,
77
-    int maxLines,
78
-  })  : _node = node,
79
-        _layerLink = layerLink,
80
-        _renderContext = renderContext,
81
-        _showCursor = showCursor,
82
-        _selection = selection,
83
-        _selectionColor = selectionColor,
84
-        _prototypePainter = new TextPainter(
85
-          text: new TextSpan(text: '.', style: text.style),
86
-          textAlign: textAlign,
87
-          textDirection: textDirection,
88
-          textScaleFactor: textScaleFactor,
89
-        ),
90
-        super(
91
-          text,
92
-          textAlign: textAlign,
93
-          textDirection: textDirection,
94
-          softWrap: softWrap,
95
-          overflow: overflow,
96
-          textScaleFactor: textScaleFactor,
97
-          maxLines: maxLines,
98
-        );
99
-
100
-  //
101
-  // Public members
102
-  //
103
-
104
-  ContainerNode get node => _node;
105
-  ContainerNode _node;
106
-  void set node(ContainerNode value) {
107
-    _node = value;
108
-  }
109
-
110
-  LayerLink get layerLink => _layerLink;
111
-  LayerLink _layerLink;
112
-  void set layerLink(LayerLink value) {
113
-    if (_layerLink == value) return;
114
-    _layerLink = value;
115
-  }
116
-
117
-  ZefyrRenderContext _renderContext;
118
-  void set renderContext(ZefyrRenderContext value) {
119
-    if (_renderContext == value) return;
120
-    if (attached) _renderContext.removeBox(this);
121
-    _renderContext = value;
122
-    if (attached) _renderContext.addBox(this);
123
-  }
124
-
125
-  ValueNotifier<bool> _showCursor;
126
-  set showCursor(ValueNotifier<bool> value) {
127
-    assert(value != null);
128
-    if (_showCursor == value) return;
129
-    if (attached) _showCursor.removeListener(markNeedsPaint);
130
-    _showCursor = value;
131
-    if (attached) _showCursor.addListener(markNeedsPaint);
132
-    markNeedsPaint();
133
-  }
134
-
135
-  TextSelection _selection;
136
-  set selection(TextSelection value) {
137
-    if (_selection == value) return;
138
-    // TODO: check if selection affects this block (also check previous value)
139
-    _selection = value;
140
-    _selectionRects = null;
141
-    markNeedsPaint();
142
-  }
143
-
144
-  Color _selectionColor;
145
-  set selectionColor(Color value) {
146
-    if (_selectionColor == value) return;
147
-    _selectionColor = value;
148
-    markNeedsPaint();
149
-  }
150
-
151
-  double get preferredLineHeight => _prototypePainter.height;
152
-
153
-  /// Returns part of document [selection] local to this paragraph. May return
154
-  /// `null`.
155
-  ///
156
-  /// [selection] must not be collapsed.
157
-  TextSelection getLocalSelection(TextSelection selection) {
158
-    if (!_intersectsWithSelection(selection)) return null;
159
-
160
-    int nodeBase = node.documentOffset;
161
-    int nodeExtent = nodeBase + node.length;
162
-    int base = math.max(0, selection.baseOffset - nodeBase);
163
-    int extent = math.min(selection.extentOffset, nodeExtent) - nodeBase;
164
-    return _selection.copyWith(baseOffset: base, extentOffset: extent);
165
-  }
166
-
167
-  // This method works around some issues in getBoxesForSelection and handleы
168
-  // edge-case with our TextSpan objects not having last line-break character.
169
-  // Wait for https://github.com/flutter/flutter/issues/16418 to be resolved.
170
-  List<ui.TextBox> getEndpointsForSelection(TextSelection selection,
171
-      {bool isLocal: false}) {
172
-    TextSelection local = isLocal ? selection : getLocalSelection(selection);
173
-    if (local.isCollapsed) {
174
-      final offset = getOffsetForCaret(local.extent, _caretPainter.prototype);
175
-      return [
176
-        new ui.TextBox.fromLTRBD(
177
-          offset.dx,
178
-          offset.dy,
179
-          offset.dx,
180
-          offset.dy + _caretPainter.prototype.height,
181
-          TextDirection.ltr,
182
-        )
183
-      ];
184
-    }
185
-
186
-    int isBaseShifted = 0;
187
-    bool isExtentShifted = false;
188
-    if (local.baseOffset == node.length - 1 && local.baseOffset > 0) {
189
-      // Since we exclude last line-break from rendered TextSpan we have to
190
-      // handle end-of-line selection explicitly.
191
-      local = local.copyWith(baseOffset: local.baseOffset - 1);
192
-      isBaseShifted = -1;
193
-    } else if (local.baseOffset == 0 && local.isCollapsed) {
194
-      // This takes care of beginning of line position.
195
-      local = local.copyWith(baseOffset: local.baseOffset + 1);
196
-      isBaseShifted = 1;
197
-    }
198
-    if (text.codeUnitAt(local.extentOffset - 1) == 0xA) {
199
-      // This takes care of the rest end-of-line scenarios, where there are
200
-      // actually line-breaks in the TextSpan (e.g. in code blocks).
201
-      local = local.copyWith(extentOffset: local.extentOffset + 1);
202
-      isExtentShifted = true;
203
-    }
204
-    final result = getBoxesForSelection(local).toList();
205
-    if (isBaseShifted != 0) {
206
-      final box = result.first;
207
-      final dx = isBaseShifted == -1 ? box.right : box.left;
208
-      result.removeAt(0);
209
-      result.insert(0,
210
-          new ui.TextBox.fromLTRBD(dx, box.top, dx, box.bottom, box.direction));
211
-    }
212
-    if (isExtentShifted) {
213
-      final box = result.last;
214
-      result.removeLast;
215
-      result.add(new ui.TextBox.fromLTRBD(
216
-          box.left, box.top, box.left, box.bottom, box.direction));
217
-    }
218
-    return result;
219
-  }
220
-
221
-  //
222
-  // Overridden members
223
-  //
224
-
225
-  @override
226
-  void set text(TextSpan value) {
227
-    _prototypePainter.text = new TextSpan(text: '.', style: value.style);
228
-    _selectionRects = null;
229
-    super.text = value;
230
-  }
231
-
232
-  @override
233
-  void attach(PipelineOwner owner) {
234
-    super.attach(owner);
235
-    _showCursor.addListener(markNeedsPaint);
236
-    _renderContext.addBox(this);
237
-  }
238
-
239
-  @override
240
-  void detach() {
241
-    _showCursor.removeListener(markNeedsPaint);
242
-    _renderContext.removeBox(this);
243
-    super.detach();
244
-  }
245
-
246
-  @override
247
-  bool hitTestSelf(Offset position) => true;
248
-
249
-  @override
250
-  bool hitTest(HitTestResult result, {Offset position}) {
251
-    if (size.contains(position)) {
252
-      result.add(new BoxHitTestEntry(this, position));
253
-      return true;
254
-    }
255
-    return false;
256
-  }
257
-
258
-  @override
259
-  void performLayout() {
260
-    super.performLayout();
261
-    _prototypePainter.layout(
262
-        minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
263
-    _caretPainter.layout(_prototypePainter.height);
264
-    // Indicate to render context that this object can be used by other
265
-    // layers (selection overlay, for instance).
266
-    _renderContext.markDirty(this, false);
267
-  }
268
-
269
-  @override
270
-  void paint(PaintingContext context, Offset offset) {
271
-    if (_isSelectionVisible) _paintSelection(context, offset);
272
-    super.paint(context, offset);
273
-    if (_isCaretVisible) _paintCaret(context, offset);
274
-  }
275
-
276
-  @override
277
-  void markNeedsLayout() {
278
-    // Temporarily remove this object from the render context.
279
-    _renderContext.markDirty(this, true);
280
-    super.markNeedsLayout();
281
-  }
282
-
283
-  //
284
-  // Private members
285
-  //
286
-
287
-  final TextPainter _prototypePainter;
288
-  final CaretPainter _caretPainter = new CaretPainter();
289
-  List<ui.TextBox> _selectionRects;
290
-
291
-  /// Returns `true` if current selection is collapsed, located within
292
-  /// this paragraph and is visible according to tick timer.
293
-  bool get _isCaretVisible {
294
-    if (!_selection.isCollapsed) return false;
295
-    if (!_showCursor.value) return false;
296
-
297
-    final int start = node.documentOffset;
298
-    final int end = start + node.length;
299
-    final int caretOffset = _selection.extentOffset;
300
-    return caretOffset >= start && caretOffset < end;
301
-  }
302
-
303
-  /// Returns `true` if selection is not collapsed and intersects with this
304
-  /// paragraph.
305
-  bool get _isSelectionVisible {
306
-    if (_selection.isCollapsed) return false;
307
-    return _intersectsWithSelection(_selection);
308
-  }
309
-
310
-  /// Returns `true` if this paragraph intersects with document [selection].
311
-  bool _intersectsWithSelection(TextSelection selection) {
312
-    final int base = node.documentOffset;
313
-    final int extent = base + node.length;
314
-    return base <= selection.extentOffset && selection.baseOffset <= extent;
315
-  }
316
-
317
-  void _paintCaret(PaintingContext context, Offset offset) {
318
-    final TextPosition caret = new TextPosition(
319
-      offset: _selection.extentOffset - node.documentOffset,
320
-    );
321
-    Offset caretOffset = getOffsetForCaret(caret, _caretPainter.prototype);
322
-    _caretPainter.paint(context.canvas, caretOffset + offset);
323
-  }
324
-
325
-  void _paintSelection(PaintingContext context, Offset offset) {
326
-    assert(_isSelectionVisible);
327
-    // TODO: this could be improved by painting additional box for line-break characters.
328
-    _selectionRects ??= getBoxesForSelection(getLocalSelection(_selection));
329
-    final Paint paint = new Paint()..color = _selectionColor;
330
-    for (ui.TextBox box in _selectionRects)
331
-      context.canvas.drawRect(box.toRect().shift(offset), paint);
332
-  }
333
-}

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

@@ -0,0 +1,203 @@
1
+// Copyright (c) 2018, the Zefyr project authors.  Please see the AUTHORS file
2
+// for details. All rights reserved. Use of this source code is governed by a
3
+// BSD-style license that can be found in the LICENSE file.
4
+import 'dart:math' as math;
5
+import 'dart:ui' as ui;
6
+
7
+import 'package:flutter/rendering.dart';
8
+import 'package:flutter/widgets.dart';
9
+import 'package:notus/notus.dart';
10
+
11
+import 'caret.dart';
12
+import 'editable_box.dart';
13
+
14
+class EditableRichText extends LeafRenderObjectWidget {
15
+  EditableRichText({
16
+    @required this.node,
17
+    @required this.text,
18
+  }) : assert(node != null && text != null);
19
+
20
+  final LineNode node;
21
+  final TextSpan text;
22
+
23
+  @override
24
+  RenderObject createRenderObject(BuildContext context) {
25
+    return new RenderEditableParagraph(
26
+      text,
27
+      node: node,
28
+      textDirection: Directionality.of(context),
29
+    );
30
+  }
31
+
32
+  @override
33
+  void updateRenderObject(
34
+      BuildContext context, RenderEditableParagraph renderObject) {
35
+    renderObject
36
+      ..text = text
37
+      ..node = node;
38
+  }
39
+}
40
+
41
+class RenderEditableParagraph extends RenderParagraph
42
+    implements RenderEditableBox {
43
+  RenderEditableParagraph(
44
+    TextSpan text, {
45
+    @required ContainerNode node,
46
+    TextAlign textAlign: TextAlign.start,
47
+    @required TextDirection textDirection,
48
+    bool softWrap: true,
49
+    TextOverflow overflow: TextOverflow.clip,
50
+    double textScaleFactor: 1.0,
51
+    int maxLines,
52
+  })  : _node = node,
53
+        _prototypePainter = new TextPainter(
54
+          text: new TextSpan(text: '.', style: text.style),
55
+          textAlign: textAlign,
56
+          textDirection: textDirection,
57
+          textScaleFactor: textScaleFactor,
58
+        ),
59
+        super(
60
+          text,
61
+          textAlign: textAlign,
62
+          textDirection: textDirection,
63
+          softWrap: softWrap,
64
+          overflow: overflow,
65
+          textScaleFactor: textScaleFactor,
66
+          maxLines: maxLines,
67
+        );
68
+
69
+  LineNode get node => _node;
70
+  LineNode _node;
71
+  void set node(LineNode value) {
72
+    _node = value;
73
+  }
74
+
75
+  @override
76
+  double get preferredLineHeight => _prototypePainter.height;
77
+
78
+  @override
79
+  TextSelection getLocalSelection(TextSelection documentSelection) {
80
+    if (!intersectsWithSelection(documentSelection)) return null;
81
+
82
+    int nodeBase = node.documentOffset;
83
+    int nodeExtent = nodeBase + node.length;
84
+    int base = math.max(0, documentSelection.baseOffset - nodeBase);
85
+    int extent =
86
+        math.min(documentSelection.extentOffset, nodeExtent) - nodeBase;
87
+    return documentSelection.copyWith(baseOffset: base, extentOffset: extent);
88
+  }
89
+
90
+  // This method works around some issues in getBoxesForSelection and handles
91
+  // 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
94
+  List<ui.TextBox> getEndpointsForSelection(TextSelection selection,
95
+      {bool isLocal: false}) {
96
+    TextSelection local = isLocal ? selection : getLocalSelection(selection);
97
+    if (local.isCollapsed) {
98
+      final offset = getOffsetForCaret(local.extent, _caretPainter.prototype);
99
+      return [
100
+        new ui.TextBox.fromLTRBD(
101
+          offset.dx,
102
+          offset.dy,
103
+          offset.dx,
104
+          offset.dy + _caretPainter.prototype.height,
105
+          TextDirection.ltr,
106
+        )
107
+      ];
108
+    }
109
+
110
+    int isBaseShifted = 0;
111
+    bool isExtentShifted = false;
112
+    if (local.baseOffset == node.length - 1 && local.baseOffset > 0) {
113
+      // Since we exclude last line-break from rendered TextSpan we have to
114
+      // handle end-of-line selection explicitly.
115
+      local = local.copyWith(baseOffset: local.baseOffset - 1);
116
+      isBaseShifted = -1;
117
+    } else if (local.baseOffset == 0 && local.isCollapsed) {
118
+      // This takes care of beginning of line position.
119
+      local = local.copyWith(baseOffset: local.baseOffset + 1);
120
+      isBaseShifted = 1;
121
+    }
122
+    if (text.codeUnitAt(local.extentOffset - 1) == 0xA) {
123
+      // This takes care of the rest end-of-line scenarios, where there are
124
+      // actually line-breaks in the TextSpan (e.g. in code blocks).
125
+      local = local.copyWith(extentOffset: local.extentOffset + 1);
126
+      isExtentShifted = true;
127
+    }
128
+    final result = getBoxesForSelection(local).toList();
129
+    if (isBaseShifted != 0) {
130
+      final box = result.first;
131
+      final dx = isBaseShifted == -1 ? box.right : box.left;
132
+      result.removeAt(0);
133
+      result.insert(0,
134
+          new ui.TextBox.fromLTRBD(dx, box.top, dx, box.bottom, box.direction));
135
+    }
136
+    if (isExtentShifted) {
137
+      final box = result.last;
138
+      result.removeLast;
139
+      result.add(new ui.TextBox.fromLTRBD(
140
+          box.left, box.top, box.left, box.bottom, box.direction));
141
+    }
142
+    return result;
143
+  }
144
+
145
+  //
146
+  // Overridden members
147
+  //
148
+
149
+  @override
150
+  void set text(TextSpan value) {
151
+    _prototypePainter.text = new TextSpan(text: '.', style: value.style);
152
+    _selectionRects = null;
153
+    super.text = value;
154
+  }
155
+
156
+  @override
157
+  void performLayout() {
158
+    super.performLayout();
159
+    _prototypePainter.layout(
160
+        minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
161
+    _caretPainter.layout(_prototypePainter.height);
162
+  }
163
+
164
+  @override
165
+  void paint(PaintingContext context, Offset offset) {
166
+//    if (isSelectionVisible) _paintSelection(context, offset);
167
+    super.paint(context, offset);
168
+//    if (isCaretVisible) _paintCaret(context, offset);
169
+  }
170
+
171
+  //
172
+  // Private members
173
+  //
174
+
175
+  final TextPainter _prototypePainter;
176
+  final CaretPainter _caretPainter = new CaretPainter();
177
+  List<ui.TextBox> _selectionRects;
178
+
179
+  /// Returns `true` if this paragraph intersects with document [selection].
180
+  bool intersectsWithSelection(TextSelection selection) {
181
+    final int base = node.documentOffset;
182
+    final int extent = base + node.length;
183
+    return base <= selection.extentOffset && selection.baseOffset <= extent;
184
+  }
185
+
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
+//
195
+  void paintSelection(PaintingContext context, Offset offset,
196
+      TextSelection selection, Color selectionColor) {
197
+    // TODO: this could be improved by painting additional box for line-break characters.
198
+    _selectionRects ??= getBoxesForSelection(getLocalSelection(selection));
199
+    final Paint paint = new Paint()..color = selectionColor;
200
+    for (ui.TextBox box in _selectionRects)
201
+      context.canvas.drawRect(box.toRect().shift(offset), paint);
202
+  }
203
+}

+ 36
- 199
packages/zefyr/lib/src/widgets/horizontal_rule.dart View File

@@ -1,7 +1,6 @@
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;
5 4
 import 'dart:ui' as ui;
6 5
 
7 6
 import 'package:flutter/material.dart';
@@ -9,137 +8,46 @@ import 'package:flutter/rendering.dart';
9 8
 import 'package:flutter/widgets.dart';
10 9
 import 'package:notus/notus.dart';
11 10
 
12
-import 'caret.dart';
13 11
 import 'editable_box.dart';
14
-import 'render_context.dart';
15 12
 
16 13
 class HorizontalRule extends LeafRenderObjectWidget {
17
-  HorizontalRule({
18
-    @required this.node,
19
-    @required this.layerLink,
20
-    @required this.renderContext,
21
-    @required this.showCursor,
22
-    @required this.selection,
23
-    @required this.selectionColor,
24
-  }) : assert(renderContext != null);
14
+  HorizontalRule({@required this.node}) : assert(node != null);
25 15
 
26
-  final ContainerNode node;
27
-  final LayerLink layerLink;
28
-  final ZefyrRenderContext renderContext;
29
-  final ValueNotifier<bool> showCursor;
30
-  final TextSelection selection;
31
-  final Color selectionColor;
16
+  final EmbedNode node;
32 17
 
33 18
   @override
34 19
   RenderHorizontalRule createRenderObject(BuildContext context) {
35
-    return new RenderHorizontalRule(
36
-      node: node,
37
-      layerLink: layerLink,
38
-      renderContext: renderContext,
39
-      showCursor: showCursor,
40
-      selection: selection,
41
-      selectionColor: selectionColor,
42
-    );
20
+    return new RenderHorizontalRule(node: node);
43 21
   }
44 22
 
45 23
   @override
46 24
   void updateRenderObject(
47 25
       BuildContext context, RenderHorizontalRule renderObject) {
48
-    renderObject
49
-      ..node = node
50
-      ..layerLink = layerLink
51
-      ..renderContext = renderContext
52
-      ..showCursor = showCursor
53
-      ..selection = selection
54
-      ..selectionColor = selectionColor;
26
+    renderObject..node = node;
55 27
   }
56 28
 }
57 29
 
58
-class RenderHorizontalRule extends RenderBox implements RenderEditableBox {
59
-  static const kPaddingBottom = 24.0;
60
-  static const kWidth = 3.0;
30
+class RenderHorizontalRule extends RenderEditableBox {
31
+  static const _kPaddingBottom = 24.0;
32
+  static const _kThickness = 3.0;
33
+  static const _kHeight = _kThickness + _kPaddingBottom;
61 34
 
62 35
   RenderHorizontalRule({
63
-    @required ContainerNode node,
64
-    @required LayerLink layerLink,
65
-    @required ZefyrRenderContext renderContext,
66
-    @required ValueNotifier<bool> showCursor,
67
-    @required TextSelection selection,
68
-    @required Color selectionColor,
69
-  })  : _node = node,
70
-        _layerLink = layerLink,
71
-        _renderContext = renderContext,
72
-        _showCursor = showCursor,
73
-        _selection = selection,
74
-        _selectionColor = selectionColor,
75
-        super();
36
+    @required EmbedNode node,
37
+  }) : _node = node;
76 38
 
77
-  //
78
-  // Public members
79
-  //
80
-
81
-  ContainerNode get node => _node;
82
-  ContainerNode _node;
83
-  void set node(ContainerNode value) {
39
+  @override
40
+  EmbedNode get node => _node;
41
+  EmbedNode _node;
42
+  set node(EmbedNode value) {
43
+    if (_node == value) return;
84 44
     _node = value;
85
-  }
86
-
87
-  LayerLink get layerLink => _layerLink;
88
-  LayerLink _layerLink;
89
-  void set layerLink(LayerLink value) {
90
-    if (_layerLink == value) return;
91
-    _layerLink = value;
92
-  }
93
-
94
-  ZefyrRenderContext _renderContext;
95
-  void set renderContext(ZefyrRenderContext value) {
96
-    if (_renderContext == value) return;
97
-    if (attached) _renderContext.removeBox(this);
98
-    _renderContext = value;
99
-    if (attached) _renderContext.addBox(this);
100
-  }
101
-
102
-  ValueNotifier<bool> _showCursor;
103
-  set showCursor(ValueNotifier<bool> value) {
104
-    assert(value != null);
105
-    if (_showCursor == value) return;
106
-    if (attached) _showCursor.removeListener(markNeedsPaint);
107
-    _showCursor = value;
108
-    if (attached) _showCursor.addListener(markNeedsPaint);
109
-    markNeedsPaint();
110
-  }
111
-
112
-  TextSelection _selection;
113
-  set selection(TextSelection value) {
114
-    if (_selection == value) return;
115
-    // TODO: check if selection affects this block (also check previous value)
116
-    _selection = value;
117
-    markNeedsPaint();
118
-  }
119
-
120
-  Color _selectionColor;
121
-  set selectionColor(Color value) {
122
-    if (_selectionColor == value) return;
123
-    _selectionColor = value;
124 45
     markNeedsPaint();
125 46
   }
126 47
 
48
+  @override
127 49
   double get preferredLineHeight => size.height;
128 50
 
129
-  /// Returns part of document [selection] local to this paragraph. May return
130
-  /// `null`.
131
-  ///
132
-  /// [selection] must not be collapsed.
133
-  TextSelection getLocalSelection(TextSelection selection) {
134
-    if (!_intersectsWithSelection(selection)) return null;
135
-
136
-    int nodeBase = node.documentOffset;
137
-    int nodeExtent = nodeBase + node.length;
138
-    int base = math.max(0, selection.baseOffset - nodeBase);
139
-    int extent = math.min(selection.extentOffset, nodeExtent) - nodeBase;
140
-    return _selection.copyWith(baseOffset: base, extentOffset: extent);
141
-  }
142
-
143 51
   List<ui.TextBox> getEndpointsForSelection(TextSelection selection,
144 52
       {bool isLocal: false}) {
145 53
     TextSelection local = isLocal ? selection : getLocalSelection(selection);
@@ -156,66 +64,25 @@ class RenderHorizontalRule extends RenderBox implements RenderEditableBox {
156 64
     ];
157 65
   }
158 66
 
159
-  //
160
-  // Overridden members
161
-  //
162
-
163
-  @override
164
-  void attach(PipelineOwner owner) {
165
-    super.attach(owner);
166
-    _showCursor.addListener(markNeedsPaint);
167
-    _renderContext.addBox(this);
168
-  }
169
-
170
-  @override
171
-  void detach() {
172
-    _showCursor.removeListener(markNeedsPaint);
173
-    _renderContext.removeBox(this);
174
-    super.detach();
175
-  }
176
-
177
-  @override
178
-  bool hitTestSelf(Offset position) => true;
179
-
180
-  @override
181
-  bool hitTest(HitTestResult result, {Offset position}) {
182
-    if (size.contains(position)) {
183
-      result.add(new BoxHitTestEntry(this, position));
184
-      return true;
185
-    }
186
-    return false;
187
-  }
188
-
189 67
   @override
190 68
   void performLayout() {
191 69
     assert(constraints.hasBoundedWidth);
192
-    final height = kWidth + kPaddingBottom;
193
-    size = new Size(constraints.maxWidth, height);
194
-    _caretPainter.layout(height);
195
-    // Indicate to render context that this object can be used by other
196
-    // layers (selection overlay, for instance).
197
-    _renderContext.markDirty(this, false);
70
+    size = new Size(constraints.maxWidth, _kHeight);
71
+//    _caretPainter.layout(height);
198 72
   }
199 73
 
200 74
   @override
201 75
   void paint(PaintingContext context, Offset offset) {
202
-//    if (_isSelectionVisible) _paintSelection(context, offset);
203
-    final rect = new Rect.fromLTWH(0.0, 0.0, size.width, kWidth);
76
+//    if (isSelectionVisible) _paintSelection(context, offset);
77
+    final rect = new Rect.fromLTWH(0.0, 0.0, size.width, _kThickness);
204 78
     final paint = new ui.Paint()..color = Colors.grey.shade200;
205 79
     context.canvas.drawRect(rect.shift(offset), paint);
206
-    if (_isCaretVisible) _paintCaret(context, offset);
207
-  }
208
-
209
-  @override
210
-  void markNeedsLayout() {
211
-    // Temporarily remove this object from the render context.
212
-    _renderContext.markDirty(this, true);
213
-    super.markNeedsLayout();
80
+//    if (isCaretVisible) _paintCaret(context, offset);
214 81
   }
215 82
 
216 83
   @override
217 84
   ui.TextPosition getPositionForOffset(ui.Offset offset) {
218
-    return new ui.TextPosition(offset: node.documentOffset);
85
+    return new ui.TextPosition(offset: _node.documentOffset);
219 86
   }
220 87
 
221 88
   @override
@@ -226,52 +93,22 @@ class RenderHorizontalRule extends RenderBox implements RenderEditableBox {
226 93
   //
227 94
   // Private members
228 95
   //
229
-
230
-  final CaretPainter _caretPainter = new CaretPainter();
231
-//  List<ui.TextBox> _selectionRects;
232
-
233
-  /// Returns `true` if current selection is collapsed, located within
234
-  /// this paragraph and is visible according to tick timer.
235
-  bool get _isCaretVisible {
236
-    if (!_selection.isCollapsed) return false;
237
-    if (!_showCursor.value) return false;
238
-
239
-    final int start = node.documentOffset;
240
-    final int end = start + node.length;
241
-    final int caretOffset = _selection.extentOffset;
242
-    return caretOffset >= start && caretOffset < end;
243
-  }
244
-
245
-  /// Returns `true` if selection is not collapsed and intersects with this
246
-  /// paragraph.
247
-//  bool get _isSelectionVisible {
248
-//    if (_selection.isCollapsed) return false;
249
-//    return _intersectsWithSelection(_selection);
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);
250 106
 //  }
251
-
252
-  /// Returns `true` if this paragraph intersects with document [selection].
253
-  bool _intersectsWithSelection(TextSelection selection) {
254
-    final int base = node.documentOffset;
255
-    final int extent = base + node.length;
256
-    return base <= selection.extentOffset && selection.baseOffset <= extent;
257
-  }
258
-
259
-  void _paintCaret(PaintingContext context, Offset offset) {
260
-    final pos = _selection.extentOffset - node.documentOffset;
261
-    Offset caretOffset = Offset.zero;
262
-    if (pos == 1) {
263
-      caretOffset = caretOffset + new Offset(size.width - 1.0, 0.0);
264
-    }
265
-    _caretPainter.paint(context.canvas, caretOffset + offset);
266
-  }
267
-
268 107
 //
269 108
 //  void _paintSelection(PaintingContext context, Offset offset) {
270
-//    assert(_isSelectionVisible);
271
-//    // TODO: this could be improved by painting additional box for line-break characters.
272
-//    _selectionRects ??= getBoxesForSelection(getLocalSelection(_selection));
273
-//    final Paint paint = new Paint()..color = _selectionColor;
274
-//    for (ui.TextBox box in _selectionRects)
275
-//      context.canvas.drawRect(box.toRect().shift(offset), paint);
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);
276 113
 //  }
277 114
 }

+ 12
- 12
packages/zefyr/lib/src/widgets/render_context.dart View File

@@ -6,16 +6,16 @@ import 'package:flutter/widgets.dart';
6 6
 
7 7
 import 'editable_box.dart';
8 8
 
9
-/// Registry of all [RenderEditableBox]es inside a [ZefyrEditableText].
9
+/// Registry of all [RenderEditableProxyBox]es inside a [ZefyrEditableText].
10 10
 ///
11
-/// Provides access to all currently active [RenderEditableBox]
11
+/// Provides access to all currently active [RenderEditableProxyBox]
12 12
 /// instances of a [ZefyrEditableText].
13 13
 ///
14 14
 /// Use [boxForTextOffset] or [boxForGlobalPoint] to retrieve a
15 15
 /// specific box.
16 16
 ///
17 17
 /// The [addBox], [removeBox] and [markDirty] are intended to be
18
-/// only used by [RenderEditableBox] objects to register with a rendering
18
+/// only used by [RenderEditableProxyBox] objects to register with a rendering
19 19
 /// context.
20 20
 ///
21 21
 /// ### Life cycle details
@@ -35,31 +35,31 @@ import 'editable_box.dart';
35 35
 /// When a box is detached from rendering pipeline it unregisters
36 36
 /// itself by calling [removeBox].
37 37
 class ZefyrRenderContext extends ChangeNotifier {
38
-  final Set<RenderEditableBox> _dirtyBoxes = new Set();
39
-  final Set<RenderEditableBox> _activeBoxes = new Set();
38
+  final Set<RenderEditableProxyBox> _dirtyBoxes = new Set();
39
+  final Set<RenderEditableProxyBox> _activeBoxes = new Set();
40 40
 
41
-  Set<RenderEditableBox> get dirty => _dirtyBoxes;
42
-  Set<RenderEditableBox> get active => _activeBoxes;
41
+  Set<RenderEditableProxyBox> get dirty => _dirtyBoxes;
42
+  Set<RenderEditableProxyBox> get active => _activeBoxes;
43 43
 
44 44
   bool _disposed = false;
45 45
 
46 46
   /// Adds [box] to this context. The box is considered "dirty" at
47 47
   /// this point and is not included in query results of `boxFor*`
48 48
   /// methods.
49
-  void addBox(RenderEditableBox box) {
49
+  void addBox(RenderEditableProxyBox box) {
50 50
     assert(!_disposed);
51 51
     _dirtyBoxes.add(box);
52 52
   }
53 53
 
54 54
   /// Removes [box] from this render context.
55
-  void removeBox(RenderEditableBox box) {
55
+  void removeBox(RenderEditableProxyBox box) {
56 56
     assert(!_disposed);
57 57
     _dirtyBoxes.remove(box);
58 58
     _activeBoxes.remove(box);
59 59
     notifyListeners();
60 60
   }
61 61
 
62
-  void markDirty(RenderEditableBox box, bool isDirty) {
62
+  void markDirty(RenderEditableProxyBox box, bool isDirty) {
63 63
     assert(!_disposed);
64 64
 
65 65
     var collection = isDirty ? _dirtyBoxes : _activeBoxes;
@@ -76,7 +76,7 @@ class ZefyrRenderContext extends ChangeNotifier {
76 76
   }
77 77
 
78 78
   /// Returns box containing character at specified document [offset].
79
-  RenderEditableBox boxForTextOffset(int offset) {
79
+  RenderEditableProxyBox boxForTextOffset(int offset) {
80 80
     assert(!_disposed);
81 81
     return _activeBoxes.firstWhere(
82 82
       (p) => p.node.containsOffset(offset),
@@ -86,7 +86,7 @@ class ZefyrRenderContext extends ChangeNotifier {
86 86
 
87 87
   /// Returns box located at specified global [point] on the screen or
88 88
   /// `null`.
89
-  RenderEditableBox boxForGlobalPoint(Offset point) {
89
+  RenderEditableProxyBox boxForGlobalPoint(Offset point) {
90 90
     assert(!_disposed);
91 91
     return _activeBoxes.firstWhere((p) {
92 92
       final localPoint = p.globalToLocal(point);

+ 8
- 16
packages/zefyr/test/rendering/render_editable_paragraph_test.dart View File

@@ -6,7 +6,7 @@ import 'dart:ui';
6 6
 import 'package:flutter/material.dart';
7 7
 import 'package:flutter/rendering.dart';
8 8
 import 'package:flutter_test/flutter_test.dart';
9
-import 'package:zefyr/src/widgets/editable_paragraph.dart';
9
+import 'package:zefyr/src/widgets/editable_rich_text.dart';
10 10
 import 'package:zefyr/src/widgets/render_context.dart';
11 11
 import 'package:zefyr/zefyr.dart';
12 12
 
@@ -15,35 +15,27 @@ void main() {
15 15
     final doc = new NotusDocument();
16 16
     doc.insert(0, 'This House Is A Circus');
17 17
     final text = new TextSpan(text: 'This House Is A Circus');
18
-    final link = new LayerLink();
19
-    final showCursor = new ValueNotifier<bool>(true);
20
-    final selection = new TextSelection.collapsed(offset: 0);
21
-    final selectionColor = Colors.blue;
22
-    ZefyrRenderContext viewport;
23 18
 
19
+    ZefyrRenderContext renderContext;
24 20
     RenderEditableParagraph p;
21
+
25 22
     setUp(() {
26 23
       WidgetsFlutterBinding.ensureInitialized();
27
-      viewport = new ZefyrRenderContext();
24
+      renderContext = new ZefyrRenderContext();
28 25
       p = new RenderEditableParagraph(
29 26
         text,
30 27
         node: doc.root.children.first,
31
-        layerLink: link,
32
-        renderContext: viewport,
33
-        showCursor: showCursor,
34
-        selection: selection,
35
-        selectionColor: selectionColor,
36 28
         textDirection: TextDirection.ltr,
37 29
       );
38 30
     });
39 31
 
40 32
     test('it registers with viewport', () {
41 33
       var owner = new PipelineOwner();
42
-      expect(viewport.active, isNot(contains(p)));
34
+      expect(renderContext.active, isNot(contains(p)));
43 35
       p.attach(owner);
44
-      expect(viewport.dirty, contains(p));
36
+      expect(renderContext.dirty, contains(p));
45 37
       p.layout(new BoxConstraints());
46
-      expect(viewport.active, contains(p));
47
-    });
38
+      expect(renderContext.active, contains(p));
39
+    }, skip: 'TODO: move to RenderEditableProxyBox');
48 40
   });
49 41
 }

+ 8
- 19
packages/zefyr/test/widgets/editable_paragraph_test.dart View File

@@ -5,41 +5,30 @@ import 'dart:ui';
5 5
 
6 6
 import 'package:flutter/material.dart';
7 7
 import 'package:flutter_test/flutter_test.dart';
8
-import 'package:zefyr/src/widgets/editable_paragraph.dart';
9
-import 'package:zefyr/src/widgets/render_context.dart';
8
+import 'package:zefyr/src/widgets/editable_rich_text.dart';
10 9
 import 'package:zefyr/zefyr.dart';
11 10
 
12 11
 void main() {
13
-  group('$EditableParagraph', () {
12
+  group('$EditableRichText', () {
14 13
     final doc = new NotusDocument();
15 14
     doc.insert(0, 'This House Is A Circus');
16 15
     final text = new TextSpan(text: 'This House Is A Circus');
17
-    final link = new LayerLink();
18
-    final showCursor = new ValueNotifier<bool>(true);
19
-    final selection = new TextSelection.collapsed(offset: 0);
20
-    final selectionColor = Colors.blue;
21
-    ZefyrRenderContext viewport;
22 16
 
23 17
     Widget widget;
24 18
     setUp(() {
25
-      viewport = new ZefyrRenderContext();
26 19
       widget = new Directionality(
27 20
         textDirection: TextDirection.ltr,
28
-        child: new EditableParagraph(
29
-            node: doc.root.children.first,
30
-            text: text,
31
-            layerLink: link,
32
-            renderContext: viewport,
33
-            showCursor: showCursor,
34
-            selection: selection,
35
-            selectionColor: selectionColor),
21
+        child: new EditableRichText(
22
+          node: doc.root.children.first,
23
+          text: text,
24
+        ),
36 25
       );
37 26
     });
38 27
 
39 28
     testWidgets('initialize', (tester) async {
40 29
       await tester.pumpWidget(widget);
41
-      EditableParagraph result =
42
-          tester.firstWidget(find.byType(EditableParagraph));
30
+      EditableRichText result =
31
+          tester.firstWidget(find.byType(EditableRichText));
43 32
       expect(result, isNotNull);
44 33
       expect(result.text.text, 'This House Is A Circus');
45 34
     });

+ 3
- 7
packages/zefyr/test/widgets/editable_text_scope_test.dart View File

@@ -1,11 +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:ui';
5 4
 
6 5
 import 'package:flutter/material.dart';
7 6
 import 'package:flutter_test/flutter_test.dart';
8
-import 'package:zefyr/src/widgets/editable_paragraph.dart';
7
+import 'package:zefyr/src/widgets/editable_box.dart';
9 8
 import 'package:zefyr/src/widgets/render_context.dart';
10 9
 import 'package:zefyr/zefyr.dart';
11 10
 
@@ -74,22 +73,19 @@ ZefyrEditableTextScope createScope({
74 73
   );
75 74
 }
76 75
 
77
-RenderEditableParagraph createParagraph(ZefyrRenderContext context) {
76
+RenderEditableProxyBox createParagraph(ZefyrRenderContext context) {
78 77
   final doc = new NotusDocument();
79 78
   doc.insert(0, 'This House Is A Circus');
80
-  final text = new TextSpan(text: 'This House Is A Circus');
81 79
   final link = new LayerLink();
82 80
   final showCursor = new ValueNotifier<bool>(true);
83 81
   final selection = new TextSelection.collapsed(offset: 0);
84 82
   final selectionColor = Colors.blue;
85
-  return new RenderEditableParagraph(
86
-    text,
83
+  return new RenderEditableProxyBox(
87 84
     node: doc.root.children.first,
88 85
     layerLink: link,
89 86
     renderContext: context,
90 87
     showCursor: showCursor,
91 88
     selection: selection,
92 89
     selectionColor: selectionColor,
93
-    textDirection: TextDirection.ltr,
94 90
   );
95 91
 }

+ 2
- 2
packages/zefyr/test/widgets/editor_test.dart View File

@@ -4,7 +4,7 @@
4 4
 import 'package:flutter/material.dart';
5 5
 import 'package:flutter_test/flutter_test.dart';
6 6
 import 'package:quill_delta/quill_delta.dart';
7
-import 'package:zefyr/src/widgets/editable_paragraph.dart';
7
+import 'package:zefyr/src/widgets/editable_rich_text.dart';
8 8
 import 'package:zefyr/zefyr.dart';
9 9
 
10 10
 import '../testing.dart';
@@ -23,7 +23,7 @@ void main() {
23 23
       var editor =
24 24
           new EditorSandBox(tester: tester, document: doc, theme: theme);
25 25
       await editor.tapEditor();
26
-      EditableParagraph p = tester.widget(find.byType(EditableParagraph).first);
26
+      EditableRichText p = tester.widget(find.byType(EditableRichText).first);
27 27
       expect(p.text.children.first.style.color, Colors.red);
28 28
     });
29 29
 

+ 5
- 7
packages/zefyr/test/widgets/render_context_test.dart View File

@@ -3,7 +3,7 @@
3 3
 // BSD-style license that can be found in the LICENSE file.
4 4
 import 'package:flutter/material.dart';
5 5
 import 'package:flutter_test/flutter_test.dart';
6
-import 'package:zefyr/src/widgets/editable_paragraph.dart';
6
+import 'package:zefyr/src/widgets/editable_box.dart';
7 7
 import 'package:zefyr/src/widgets/render_context.dart';
8 8
 import 'package:zefyr/zefyr.dart';
9 9
 
@@ -68,22 +68,20 @@ void main() {
68 68
   });
69 69
 }
70 70
 
71
-RenderEditableParagraph createParagraph(ZefyrRenderContext viewport) {
71
+RenderEditableProxyBox createParagraph(ZefyrRenderContext viewport) {
72 72
   final doc = new NotusDocument();
73 73
   doc.insert(0, 'This House Is A Circus');
74
-  final text = new TextSpan(text: 'This House Is A Circus');
74
+  final LineNode node = doc.root.children.first;
75 75
   final link = new LayerLink();
76 76
   final showCursor = new ValueNotifier<bool>(true);
77 77
   final selection = new TextSelection.collapsed(offset: 0);
78 78
   final selectionColor = Colors.blue;
79
-  return new RenderEditableParagraph(
80
-    text,
81
-    node: doc.root.children.first,
79
+  return new RenderEditableProxyBox(
80
+    node: node,
82 81
     layerLink: link,
83 82
     renderContext: viewport,
84 83
     showCursor: showCursor,
85 84
     selection: selection,
86 85
     selectionColor: selectionColor,
87
-    textDirection: TextDirection.ltr,
88 86
   );
89 87
 }

+ 2
- 2
packages/zefyr/test/widgets/selection_test.dart View File

@@ -3,7 +3,7 @@
3 3
 // BSD-style license that can be found in the LICENSE file.
4 4
 import 'package:flutter/material.dart';
5 5
 import 'package:flutter_test/flutter_test.dart';
6
-import 'package:zefyr/src/widgets/editable_paragraph.dart';
6
+import 'package:zefyr/src/widgets/editable_rich_text.dart';
7 7
 import 'package:zefyr/zefyr.dart';
8 8
 
9 9
 import '../testing.dart';
@@ -15,7 +15,7 @@ void main() {
15 15
       await editor.tapEditor();
16 16
 
17 17
       RenderEditableParagraph renderObject =
18
-          tester.firstRenderObject(find.byType(EditableParagraph));
18
+          tester.firstRenderObject(find.byType(EditableRichText));
19 19
       var offset = renderObject.localToGlobal(Offset.zero);
20 20
       offset += Offset(5.0, 5.0);
21 21
       await tester.tapAt(offset);