Skip to content

Commit 41066d8

Browse files
committed
Refactored _FloatColumnElement to be based off of MultiChildRenderObjectElement and related changes.
1 parent 064b785 commit 41066d8

4 files changed

Lines changed: 109 additions & 158 deletions

File tree

lib/src/float_column.dart

Lines changed: 97 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -149,95 +149,93 @@ class _FloatColumnElement extends RenderObjectElement
149149
@override
150150
RenderFloatColumn get renderObject => super.renderObject as RenderFloatColumn;
151151

152-
// We call `updateChild` at two different times:
153-
// 1. When we ourselves are told to rebuild (see performRebuild).
154-
// 2. When our render object needs a new child (see createChild).
155-
// In both cases, we cache the results of calling into our delegate to get
156-
// the child widget, so that if we do case 2 later, we don't call the builder
157-
// again. Any time we do case 1, though, we reset the cache.
158-
159-
/// A cache of widgets so that we don't have to rebuild every time.
160-
final HashMap<int, Widget?> _childWidgets = HashMap<int, Widget?>();
152+
/// The current list of children of this element.
153+
///
154+
/// This list is filtered to hide elements that have been forgotten (using
155+
/// [forgetChild]).
156+
Iterable<Element> get children =>
157+
_children.where((child) => !_forgottenChildren.contains(child));
161158

162-
/// The map containing all active child elements. SplayTreeMap is used so that
163-
/// we have all elements ordered and iterable by their keys.
164-
final SplayTreeMap<int, Element> _childElements =
165-
SplayTreeMap<int, Element>();
159+
late List<Element> _children;
160+
// We keep a set of forgotten children to avoid O(n^2) work walking _children
161+
// repeatedly to remove children.
162+
final Set<Element> _forgottenChildren = HashSet<Element>();
166163

167164
@override
168-
void update(FloatColumn newWidget) {
169-
// dmPrint('_FloatColumnElement update');
170-
final oldWidget = widget as FloatColumn;
171-
super.update(newWidget);
172-
if (newWidget._textAndWidgets != oldWidget._textAndWidgets) {
173-
performRebuild();
174-
renderObject.markNeedsLayout();
165+
void mount(Element? parent, Object? newSlot) {
166+
super.mount(parent, newSlot);
167+
final floatColumnWidget = widget as FloatColumn;
168+
final children = List<Element>.filled(
169+
floatColumnWidget._textAndWidgets.length, _NullElement.instance);
170+
Element? previousChild;
171+
for (var i = 0; i < children.length; i += 1) {
172+
final textOrWidget = floatColumnWidget._textAndWidgets[i];
173+
final widget = textOrWidget is Widget
174+
? textOrWidget
175+
: (textOrWidget as WrappableText).toWidget();
176+
final newChild =
177+
inflateWidget(widget, IndexedSlot<Element?>(i, previousChild));
178+
children[i] = newChild;
179+
previousChild = newChild;
175180
}
181+
_children = children;
176182
}
177183

178184
@override
179-
void performRebuild() {
180-
// dmPrint('_FloatColumnElement performRebuild');
181-
_childWidgets.clear();
182-
super.performRebuild();
183-
}
184-
185-
@override
186-
Element? updateChild(Element? child, Widget? newWidget, Object? newSlot) {
187-
// dmPrint('_FloatColumnElement updateChild');
188-
final oldParentData =
189-
child?.renderObject?.parentData as FloatColumnParentData?;
190-
final newChild = super.updateChild(child, newWidget, newSlot);
191-
final newParentData =
192-
newChild?.renderObject?.parentData as FloatColumnParentData?;
193-
if (newParentData != null) {
194-
newParentData.index = newSlot! as int;
195-
if (oldParentData != null) {
196-
newParentData.offset = oldParentData.offset;
197-
}
198-
}
199-
200-
return newChild;
185+
void update(FloatColumn newWidget) {
186+
super.update(newWidget);
187+
final floatColumnWidget = widget as FloatColumn;
188+
_children = updateChildren(
189+
_children, floatColumnWidget._textAndWidgets.toWidgets(),
190+
forgottenChildren: _forgottenChildren);
191+
_forgottenChildren.clear();
201192
}
202193

203194
@override
204-
void insertRenderObjectChild(RenderObject child, int slot) {
205-
// dmPrint('_FloatColumnElement insertRenderObjectChild');
206-
final renderObject = this.renderObject;
195+
void insertRenderObjectChild(RenderObject child, IndexedSlot<Element?> slot) {
196+
final ContainerRenderObjectMixin<RenderObject,
197+
ContainerParentDataMixin<RenderObject>> renderObject =
198+
this.renderObject;
207199
assert(renderObject.debugValidateChild(child));
208-
renderObject.insert(child as RenderBox,
209-
after: _childElements[slot - 1]?.renderObject as RenderBox?);
200+
renderObject.insert(child, after: slot.value?.renderObject);
210201
assert(renderObject == this.renderObject);
211202
}
212203

213204
@override
214-
void moveRenderObjectChild(RenderObject child, int oldSlot, int newSlot) {
215-
// dmPrint('_FloatColumnElement moveRenderObjectChild');
216-
const moveChildRenderObjectErrorMessage =
217-
'Currently we maintain the list in contiguous increasing order, so '
218-
'moving children around is not allowed.';
219-
assert(false, moveChildRenderObjectErrorMessage);
205+
void moveRenderObjectChild(RenderObject child, IndexedSlot<Element?> oldSlot,
206+
IndexedSlot<Element?> newSlot) {
207+
final ContainerRenderObjectMixin<RenderObject,
208+
ContainerParentDataMixin<RenderObject>> renderObject =
209+
this.renderObject;
210+
assert(child.parent == renderObject);
211+
renderObject.move(child, after: newSlot.value?.renderObject);
212+
assert(renderObject == this.renderObject);
220213
}
221214

222215
@override
223-
void removeRenderObjectChild(RenderObject child, int slot) {
224-
// dmPrint('_FloatColumnElement removeRenderObjectChild');
216+
void removeRenderObjectChild(RenderObject child, Object? slot) {
217+
final ContainerRenderObjectMixin<RenderObject,
218+
ContainerParentDataMixin<RenderObject>> renderObject =
219+
this.renderObject;
225220
assert(child.parent == renderObject);
226-
renderObject.remove(child as RenderBox);
221+
renderObject.remove(child);
222+
assert(renderObject == this.renderObject);
227223
}
228224

229225
@override
230226
void visitChildren(ElementVisitor visitor) {
231-
// dmPrint('_FloatColumnElement visitChildren');
232-
_childElements.forEach((key, child) {
233-
visitor(child);
234-
});
227+
for (final child in _children) {
228+
if (!_forgottenChildren.contains(child)) {
229+
visitor(child);
230+
}
231+
}
235232
}
236233

237234
@override
238235
void forgetChild(Element child) {
239-
// dmPrint('_FloatColumnElement forgetChild');
240-
_childElements.remove(child.slot);
236+
assert(_children.contains(child));
237+
assert(!_forgottenChildren.contains(child));
238+
_forgottenChildren.add(child);
241239
super.forgetChild(child);
242240
}
243241

@@ -248,69 +246,37 @@ class _FloatColumnElement extends RenderObjectElement
248246
@override
249247
List<Object> get textAndWidgets => (widget as FloatColumn)._textAndWidgets;
250248

251-
@override
252-
void addOrUpdateWidgetAt(int index, Widget widget) {
253-
// dmPrint('_FloatColumnElement addOrUpdateWidgetAt');
254-
_childWidgets[index] = widget;
255-
}
256-
257249
@override
258250
RenderBox? childAt(int index) {
259-
// dmPrint('_FloatColumnElement childAt');
260-
return _childElements[index]?.renderObject as RenderBox?;
251+
return _children.maybeElementAt(index)?.renderObject as RenderBox?;
261252
}
262253

263254
@override
264-
RenderBox? addOrUpdateChild(int index, {required RenderBox? after}) {
265-
// dmPrint('_FloatColumnElement createChild');
255+
RenderBox? updateWidgetAt(int index, Widget widget) {
256+
// dmPrint('_FloatColumnElement updateWidgetAt');
266257
RenderBox? child;
267258
owner!.buildScope(this, () {
268-
final insertFirst = after == null;
269-
assert(insertFirst || _childElements[index - 1] != null);
270-
final newChild =
271-
updateChild(_childElements[index], _childWidgets[index], index);
259+
assert(index >= 0 && index < _children.length);
260+
final newChild = updateChild(_children[index], widget,
261+
IndexedSlot<Element?>(index, _children.maybeElementAt(index - 1)));
272262
if (newChild != null) {
273263
child = newChild.renderObject == null
274264
? null
275265
: newChild.renderObject! as RenderBox;
276-
_childElements[index] = newChild;
266+
_children[index] = newChild;
277267
} else {
278-
_childElements.remove(index);
268+
if (!_forgottenChildren.contains(newChild)) {
269+
forgetChild(newChild!);
270+
}
279271
}
280272
});
281273
return child;
282274
}
275+
}
283276

284-
@override
285-
void removeChild(RenderBox child) {
286-
// dmPrint('_FloatColumnElement removeChild');
287-
final index = renderObject.indexOf(child);
288-
owner!.buildScope(this, () {
289-
assert(_childElements.containsKey(index));
290-
final result = updateChild(_childElements[index], null, index);
291-
assert(result == null);
292-
_childElements.remove(index);
293-
assert(!_childElements.containsKey(index));
294-
});
295-
}
296-
297-
@override
298-
void removeAllChildren() {
299-
// dmPrint('_FloatColumnElement removeAllChildren');
300-
owner!.buildScope(this, () {
301-
_childElements
302-
..forEach((index, child) {
303-
final result = updateChild(child, null, index);
304-
assert(result == null);
305-
})
306-
..clear();
307-
// assert(_childElements.containsKey(index));
308-
// final result = updateChild(_childElements[index], null, index);
309-
// assert(result == null);
310-
// _childElements.remove(index);
311-
// assert(!_childElements.containsKey(index));
312-
});
313-
}
277+
extension _ExtOnList<T> on List<T> {
278+
/// Returns `i >= 0 && i < length ? this[i] : null`.
279+
T? maybeElementAt(int i) => i >= 0 && i < length ? this[i] : null;
314280
}
315281

316282
Iterable<Object> _expandToIncludeFloatedWidgetSpanChildren(
@@ -438,3 +404,26 @@ Key? _firstNonUniqueKey(Iterable<Object> children) {
438404
}
439405
return null;
440406
}
407+
408+
extension on List<Object> {
409+
List<Widget> toWidgets() =>
410+
map((e) => e is Widget ? e : (e as WrappableText).toWidget()).toList();
411+
}
412+
413+
/// Used as a placeholder in [List<Element>] objects when the actual
414+
/// elements are not yet determined.
415+
class _NullElement extends Element {
416+
_NullElement() : super(const _NullWidget());
417+
418+
static _NullElement instance = _NullElement();
419+
420+
@override
421+
bool get debugDoingBuild => throw UnimplementedError();
422+
}
423+
424+
class _NullWidget extends Widget {
425+
const _NullWidget();
426+
427+
@override
428+
Element createElement() => throw UnimplementedError();
429+
}

lib/src/float_column_child_manager.dart

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,9 @@ abstract class FloatColumnChildManager {
44
/// The list of WrappableText and Widget children.
55
List<Object> get textAndWidgets;
66

7-
/// Adds or updates the child widget at the given index.
8-
void addOrUpdateWidgetAt(int index, Widget widget);
7+
/// Updates the widget at the given index.
8+
RenderBox? updateWidgetAt(int index, Widget widget);
99

1010
/// Returns the child RenderBox at the given index.
1111
RenderBox? childAt(int index);
12-
13-
/// Adds or updates the child element at the given index.
14-
RenderBox? addOrUpdateChild(int index, {required RenderBox? after});
15-
16-
/// Removes the child element at the given index.
17-
void removeChild(RenderBox child);
18-
19-
/// Removes all children.
20-
void removeAllChildren();
2112
}

lib/src/render_float_column.dart

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -181,29 +181,15 @@ class RenderFloatColumn extends RenderBox
181181
return childParentData.index!;
182182
}
183183

184-
RenderBox? _addOrUpdateChild(int index, {RenderBox? after}) {
184+
RenderBox? _updateWidgetAt(int index, Widget widget) {
185185
RenderBox? child;
186186
invokeLayoutCallback<BoxConstraints>((constraints) {
187187
assert(constraints == this.constraints);
188-
child = childManager.addOrUpdateChild(index, after: after);
188+
child = childManager.updateWidgetAt(index, widget);
189189
});
190190
return child;
191191
}
192192

193-
void _removeChild(RenderBox child) {
194-
invokeLayoutCallback<BoxConstraints>((constraints) {
195-
assert(constraints == this.constraints);
196-
childManager.removeChild(child);
197-
});
198-
}
199-
200-
void _removeAllChildren() {
201-
invokeLayoutCallback<BoxConstraints>((constraints) {
202-
assert(constraints == this.constraints);
203-
childManager.removeAllChildren();
204-
});
205-
}
206-
207193
@override
208194
void performLayout() {
209195
// final layoutSize = _performLayout();

lib/src/render_float_column_ext.dart

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ extension on RenderFloatColumn {
1212
childConstraints = BoxConstraints(maxWidth: constraints.maxWidth);
1313
}
1414

15-
if (firstChild != null) {
16-
_removeAllChildren();
17-
}
18-
1915
final rc = _RenderCursor(this, childConstraints, firstChild);
2016

2117
// This gets updated to the previous non-floated child's bottom margin.
@@ -35,14 +31,6 @@ extension on RenderFloatColumn {
3531
rc.child.hasSize) {
3632
// Nothing to do here...
3733
} else {
38-
// Update the current child's widget and associated element.
39-
rc.updateCurrentChildWidget(
40-
el is Widget ? el : (el as WrappableText).toWidget());
41-
if (rc.maybeChild == null) {
42-
assert(false);
43-
continue;
44-
}
45-
4634
// If it is a Widget...
4735
if (el is Widget) {
4836
// All widgets are wrapped in a MetaData widget with FloatData.
@@ -70,6 +58,13 @@ extension on RenderFloatColumn {
7058
rc.y += topMargin;
7159
prevBottomMargin = margin.bottom;
7260

61+
// Always need to start with the original WrappableText widget.
62+
rc.updateCurrentChildWidget(el.toWidget());
63+
if (rc.maybeChild == null) {
64+
assert(false);
65+
continue;
66+
}
67+
7368
_layoutWrappableText(el, rc, childConstraints, textDirection);
7469
} else {
7570
assert(false);
@@ -79,14 +74,6 @@ extension on RenderFloatColumn {
7974
rc.moveNext();
8075
}
8176

82-
// Remove any extra children.
83-
while (rc.maybeChild != null) {
84-
final childParentData = rc.child.parentData! as FloatColumnParentData;
85-
final nextChild = childParentData.nextSibling;
86-
_removeChild(rc.child);
87-
rc.maybeChild = nextChild;
88-
}
89-
9077
rc.y += prevBottomMargin;
9178
final totalHeight =
9279
math.max(rc.floatL.maxYBelow(rc.y), rc.floatR.maxYBelow(rc.y));
@@ -537,8 +524,7 @@ class _RenderCursor {
537524

538525
/// Updates the current child's widget and associated element.
539526
void updateCurrentChildWidget(Widget widget) {
540-
rfc.childManager.addOrUpdateWidgetAt(index, widget);
541-
maybeChild = rfc._addOrUpdateChild(index, after: previousChild);
527+
maybeChild = rfc._updateWidgetAt(index, widget);
542528
}
543529

544530
/// Lays out the first floated child widget of the current WrappableText
@@ -570,7 +556,6 @@ class _RenderCursor {
570556
assert(widget is Widget);
571557
var laidOutFloatingWidget = false;
572558
if (widget is Widget) {
573-
updateCurrentChildWidget(widget);
574559
final savedY = y;
575560
final offset = (rpChild.parentData! as TextParentData).offset!;
576561
// dmPrint('wrappableTextIndex ${fd.wrappableTextIndex}, '

0 commit comments

Comments
 (0)