-
Notifications
You must be signed in to change notification settings - Fork 29.7k
OverlayPortal
#105335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OverlayPortal
#105335
Conversation
118bfdb to
9497de3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's Overlay.of but uses an InheirtedWidget (Overlay.of uses context.findAncestorStateOfType<OverlayState>() because we didn't have a declarative API that requires the consumer widget to be notified when the overlay changes).
Also the widget that provides OverlayInfo lives under an OverlayEntry so it knows which OverlayEntry it is from. We can add more (private) information to OverlayInfo when needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why OverlayPortal can't do that itself? I'd expect OverlayInfo.of(context) to use the OverlayPortal's Element as context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not always I think? There're cases you want to put the child on a different Overlay (that's why Overlay.of takes a bool rootOverlay = false I guess. Unfortunately it will be a bit expensive for OverlayInfo.of to get the root overlay and updates its dependents when it changes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the time it should I think
I'd at least ewpect this parameter to be optional
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, I don't disagree with the value of this parameter.
My only argument is about making it optional and default to obtaining the closest Overlay.
There are definitely use-cases for this parameter. But I don't see value in having to pass OverlayInfo.of(contewt)
To me, that's as if Text's style was required and expected users to do Text('...', style: DefaultTextStyle.of(context))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we use null for default then unfortunately it can have 2 meanings: either the API user wants to use the default, or we failed to get the OverlayInfo from the given BuildContext the user specified at runtime. You can probably workaround this by creating some const value to represent the default (the closest OverlayInfo), but I think it's better to keep the API simple and stupid in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't expect OverlayInfo.of to return null if there's no overlay anyway. I'd expect an exception
So this wouldn't have 2 meanings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah usually we have 2 versions for this kind of things, maybeOf and of. I'll rename Overlay.of to Overlay.maybeOf to follow the naming convention.
But I'm a bit hesitant to only expose the version that throws, especially since this is a query API. It will be a bit awkward for people to do something that could otherwise be really simple to achieve: OverlayInfo.maybeOf(outerContext) ?? Overlay.maybeOf(innerContext).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By popular demand I'll rename the current default constructor and introduce a new default constructor that doesn't take an OverlayInfo?.
8c6c383 to
801d2fc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will "mouse" region behave well for mobile phones? If not, maybe we can improve this example since flutter targets on mobiles as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an interactive example in the documentation and will be shown in a browser so I think in most cases developers will have a mouse connected while checking out the example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But yeah you're right I'll change it to a button so it's more accessible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well I am a bit confused. If assert(false), how can the lines after it be executed? (the result = false etc). Especially considering all these are wrapped in assert((){ }) such that the whole block will only exist in debug build.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll change that to throw a FlutterError then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 are these temporary code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Hixie currently we allow a node to mark itself dirty if there's no ancestors of it calling performLayout. I thought that could cause clean nodes to be re-dirtied? Changing it to setting result to false here doesn't seem to break any tests so I wonder why that was allowed? Is that for global key reparenting between LayoutBuilders?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not recall, but yeah, looking at global key reparenting and layout builders (and maybe both together) is definitely where I would start when trying to understand this. cc @goderbauer might have a better idea, he's more familiar with this code these days.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not always I think? There're cases you want to put the child on a different Overlay (that's why Overlay.of takes a bool rootOverlay = false I guess. Unfortunately it will be a bit expensive for OverlayInfo.of to get the root overlay and updates its dependents when it changes)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an interactive example in the documentation and will be shown in a browser so I think in most cases developers will have a mouse connected while checking out the example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But yeah you're right I'll change it to a button so it's more accessible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll change that to throw a FlutterError then.
|
Also on a different note, @rrousselGit @fzyzcjy how would like to receive the due credit? I can add a code comment before the |
That's fine by me :) |
Also fine for me :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moving changes to object.dart into its own PR would be good, given the sensitivity of such changes (e.g. you probably want to try to get low-level changes like this rolled into google3 before doing anything that relies on them)
|
This seems almost guaranteed to cause some unexpected interaction, but I admit I cannot think what it might be... We should have tests for at least all of the following:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given there are so few, do you think it makes sense to credit the other contributors as well? I am not sure myself because looking at the implementation, I do realize that it does differ significantly from the code we wrote.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah my apologies. Yeah I can pull the list from the bottom of this page: https://pub.dev/packages/flutter_portal. Alternatively there's an AUTHORS file, do you prefer to be added there instead (also @rrousselGit @fzyzcjy).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not really have a preference there 👍
|
Started working on adding more test, @Hixie by proxy you mean the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems like a really inefficient way of implementing:
if (_child != null) visitor(_child);
if (_overlayChild != null) visitor(_overlayChild);
Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't want to create 2 local variables just for promoting them to non-null types I guess.
I meant the (It'd be interesting to see if there was a way to implement this so that |
Something like #39104? The |
f1f0e53 to
8fccb8b
Compare
|
@LongCatIsLooong I tried checking this out to play with it but it had a lot of conflicts (and many commits so it was hard to figure out how to resolve them) -- any chance you could merge this to tip of tree? |
goderbauer
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes in the last commit LGTM
|
Thanks everyone for your input! |
|
What a long journey! Congratulations 🎉 |
|
🥳 |
Inspired by the flutter_portal package.
The flutter_portal package is created by @rrousselGit and is currently maintained by @fzyzcjy
This PR resolves many of the issues raised in #36220.
What the Render Tree Looks like:
Layout Order
_RenderDeferredLayoutBoxneeds to do layout after_RenderLayoutSurrogateProxyBox, but should also update its layout when_RenderTheatregives it a set of different constraints. Additionally,RenderObjects below a_RenderDeferredLayoutBoxshould only perform layout after the_RenderDeferredLayoutBoxis updated.So the layout order has to be
_RenderTheatre->_RenderLayoutSurrogateProxyBox->_RenderDeferredLayoutBox->overlayChild, otherwise something may have to do layout twice._RenderTheatreis the common ancestor of the 3 otherRenderObjects so it will be laid out first. It's ok to re-dirty it since it'ssizedByParent, and all that matters to the descendants is the constraints, which won't change even when re-dirtied._RenderLayoutSurrogateProxyBox->_RenderDeferredLayoutBox:_RenderDeferredLayoutBoxhas a greater depth than_RenderLayoutSurrogateProxyBoxand it can't be tree walked from_RenderTheatre._RenderLayoutSurrogateProxyBox->overlayChild: because_RenderDeferredLayoutBox->overlayChild. When_RenderDeferredLayoutBoxis not dirty,overlayChildis guaranteed to have a greater depth than_RenderLayoutSurrogateProxyBox._RenderDeferredLayoutBox->overlayChild:overlayChildcan only be tree walked from_RenderDeferredLayoutBoxand has a greater depth than_RenderDeferredLayoutBox.However since
_RenderDeferredLayoutBoxactually depends on both_RenderLayoutSurrogateProxyBoxand_RenderTheatre, it can be dirtied by_RenderTheatrewhen_RenderLayoutSurrogateProxyBoxis clean. When that happens_RenderTheatreneeds to call_RenderDeferredLayoutBox.markNeedsLayoutin aninvokeLayoutCallbackto merge the node back to the dirty list in caseoverlayChildis dirty too, sooverlayChilddoesn't perform layout before_RenderDeferredLayoutBox.Why placing the
RenderObjectunder_RenderTheatreinstead of_RenderLayoutSurrogateProxyBoxIt takes some extra lines of code to manage the render tree properly, and
_RenderLayoutSurrogateProxyBoxhas to redepth theoverlayChildbranch, but:OverlayEntryAPI would and that could make it easier for people to migrate from theOverlayEntryAPI.overlayChildneeds to repaint it doesn't have to mark_RenderLayoutSurrogateProxyBoxas needing repaint too (we havemarkParentNeedsLayoutbut notmarkParentNeedsPaint).overlayChildpaints on_RenderTheatre'sPaintContextso there's little point in marking_RenderLayoutSurrogateProxyBoxdirty. Also during compositing bits update, it could set_needsCompositingto true on some nodes that actually don't need compositing.TODOs to be addressed in future pull requests
OverlayPortalwidget reparents, the overlay child will be moved to the top of the Overlay (in terms of the z-index, for painting and hit-testing), and that's pretty strange for a declarative API.OverlayInfocurrently knows whichOverlayEntryanOverlayPortalbelongs to (for nestedOverlayPortalit shouldn't be difficult to get the closest ancestorOverlayPortaltoo), so it shouldn't be difficult to add additional information to the parent data to specify the desired z-index (in relation to the parentOverlayEntryorPortalEntry).Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.