Created
November 2, 2021 00:38
-
-
Save shyndman/e1d685cfeb32f3b47d04d96af2e081d7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// 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