Skip to content

Commit

Permalink
Update layers when widget is updated without invalidating the layout
Browse files Browse the repository at this point in the history
  • Loading branch information
angelosilvestre committed Dec 11, 2024
1 parent 80fe337 commit 130c078
Showing 1 changed file with 46 additions and 22 deletions.
68 changes: 46 additions & 22 deletions super_editor/lib/src/infrastructure/content_layers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -267,30 +267,45 @@ class ContentLayersElement extends RenderObjectElement {
contentLayersLog.finer("ContentLayersElement - (re)building layers");

owner!.buildScope(this, () {
final List<Element> underlays = List<Element>.filled(widget.underlays.length, _NullElement.instance);
for (int i = 0; i < underlays.length; i += 1) {
late final Element child;
if (i > _underlays.length - 1) {
child = inflateWidget(widget.underlays[i](this), _UnderlaySlot(i));
} else {
child = super.updateChild(_underlays[i], widget.underlays[i](this), _UnderlaySlot(i))!;
}
underlays[i] = child;
_buildLayersWithExistingScope();
});
}

/// Builds the underlays and overlays without establishing a new build scope.
///
/// We build the layers in two situations:
///
/// 1. When the content's layout is dirty. This happens during layout phase, when we need to
/// establish a build scope. This is done when [buildLayers] is called.
/// 2. When the content's layout is clean. This happens when [update] is called, but only
/// non-layout changes happened, like changing a color. In this case, we are already
/// inside a build scope, so we can't try to establish a new one.
///
/// See [BuildOwner.buildScope] for more information.
void _buildLayersWithExistingScope() {
final List<Element> underlays = List<Element>.filled(widget.underlays.length, _NullElement.instance);
for (int i = 0; i < underlays.length; i += 1) {
late final Element child;
if (i > _underlays.length - 1) {
child = inflateWidget(widget.underlays[i](this), _UnderlaySlot(i));
} else {
child = super.updateChild(_underlays[i], widget.underlays[i](this), _UnderlaySlot(i))!;
}
_underlays = underlays;

final List<Element> overlays = List<Element>.filled(widget.overlays.length, _NullElement.instance);
for (int i = 0; i < overlays.length; i += 1) {
late final Element child;
if (i > _overlays.length - 1) {
child = inflateWidget(widget.overlays[i](this), _OverlaySlot(i));
} else {
child = super.updateChild(_overlays[i], widget.overlays[i](this), _OverlaySlot(i))!;
}
overlays[i] = child;
underlays[i] = child;
}
_underlays = underlays;

final List<Element> overlays = List<Element>.filled(widget.overlays.length, _NullElement.instance);
for (int i = 0; i < overlays.length; i += 1) {
late final Element child;
if (i > _overlays.length - 1) {
child = inflateWidget(widget.overlays[i](this), _OverlaySlot(i));
} else {
child = super.updateChild(_overlays[i], widget.overlays[i](this), _OverlaySlot(i))!;
}
_overlays = overlays;
});
overlays[i] = child;
}
_overlays = overlays;
}

@override
Expand Down Expand Up @@ -331,6 +346,15 @@ class ContentLayersElement extends RenderObjectElement {
assert(!debugChildrenHaveDuplicateKeys(widget, [newContent]));

_content = updateChild(_content, newContent, _contentSlot);

if (!renderObject.contentNeedsLayout) {
// Layout has already run. No layout bounds changed. There might be a
// non-layout change that needs to be painted, e.g., change to theme brightness.
// Re-build all layers, which is safe to do because no layout constraints changed.
_buildLayersWithExistingScope();
}
// Else, dirty content layout will cause this whole widget to re-layout. The
// layers will be re-built during that layout pass.
}

@override
Expand Down

0 comments on commit 130c078

Please sign in to comment.