Skip to content

Instantly share code, notes, and snippets.

@shyndman
Created November 2, 2021 00:38
Show Gist options
  • Select an option

  • Save shyndman/e1d685cfeb32f3b47d04d96af2e081d7 to your computer and use it in GitHub Desktop.

Select an option

Save shyndman/e1d685cfeb32f3b47d04d96af2e081d7 to your computer and use it in GitHub Desktop.
/// Determines the top-most model from a list of models — that is, the model
/// that draws in front of the others.
///
/// NOTE: This relies on a RenderObject's children iterating in draw order. It
/// appears this is the case most of the time, but there are no guarantees.
///
/// Algorithm:
///
/// The general idea is this: We walk the [RenderObject] ancestry of each of the
/// models, marking each RenderObject owned by their respective model. When a
/// RenderObject's ownership is contested (when we hit a branch that two models
/// share), we determine which of the two models draws last, mark it the winner,
/// then keep walking upward for the winning model only. When there's only one
/// model left, it wins.
DesignWidgetModel _determineTopMost(List<DesignWidgetModel> models) {
if (models.length == 1) return models.first;
final queue = HeapPriorityQueue<_TopModelCandidate>()
..addAll(models.map((m) => _TopModelCandidate(m)));
final renderObjectOwners = <RenderObject, _TopModelCandidate>{};
while (queue.length > 1) {
final candidate = queue.removeFirst();
if (!candidate.iterator.moveNext()) {
throw StateError('_determineTopMost should never go past the root node');
}
final renderObject = candidate.currentRenderObject;
if (renderObjectOwners.containsKey(renderObject)) {
// The top-most child will be encountered last.
_TopModelCandidate winningCandidate;
renderObject.visitChildren((child) {
if (renderObjectOwners.containsKey(child)) {
winningCandidate = renderObjectOwners[child];
}
});
// If the candidate under consideration did not win, it is now dead.
// We continue without re-enqueuing.
if (winningCandidate != candidate) {
continue;
}
}
// Assign ownership of this render object to the candidate.
renderObjectOwners[renderObject] = candidate;
// Re-enqueue the candidate. Because we moved its iterator forward, it has a
// new depth, and will be prioritized accordingly.
queue.add(candidate);
}
return queue.first.model;
}
class _TopModelCandidate implements Comparable<_TopModelCandidate> {
_TopModelCandidate(this.model)
: iterator = _renderObjectAncestry(model).iterator..moveNext();
final DesignWidgetModel model;
final Iterator<RenderObject> iterator;
RenderObject get currentRenderObject => iterator.current;
int get currentDepth => iterator.current?.depth ?? -1;
@override
int compareTo(other) => other.currentDepth.compareTo(currentDepth);
}
Iterable<RenderObject> _renderObjectAncestry(DesignWidgetModel model) sync* {
RenderObject renderObject =
model.binding.contextForModel(model).findRenderObject();
while (renderObject != null) {
yield renderObject;
renderObject = renderObject.parent as RenderObject;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment