Skip to content

Add DisplayFeatureSubScreen widget#92907

Merged
fluttergithubbot merged 12 commits intoflutter:masterfrom
andreidiaconu:foldable_support_displayfeaturesubscreen
Feb 2, 2022
Merged

Add DisplayFeatureSubScreen widget#92907
fluttergithubbot merged 12 commits intoflutter:masterfrom
andreidiaconu:foldable_support_displayfeaturesubscreen

Conversation

@andreidiaconu
Copy link
Contributor

@andreidiaconu andreidiaconu commented Nov 2, 2021

This PR adds the DisplayFeatureSubScreen widget which positions its child such that it avoids overlapping any DisplayFeature that splits the screen into sub-screens. In practical terms, it helps with displaying content on only one of the screens on dual-screen foldables.

This widget is needed for making modal routes and dialogs avoid overlapping display features.

Here is an example of the widget placing a dialog on one of the screens, instead of the middle of the screen (where the example device has a display feature):

hinge-aware-dialog-right

This is split from a larger PR for foldable support in order to make it easier to review (#77156).

Issues that will be fixed by this PR:

Depends on:

PRs that depend on this:

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

@flutter-dashboard flutter-dashboard bot added a: tests "flutter test", flutter_test, or one of our tests framework flutter/packages/flutter repository. See also f: labels. labels Nov 2, 2021
@google-cla google-cla bot added the cla: yes label Nov 2, 2021
@andreidiaconu
Copy link
Contributor Author

@goderbauer I addressed all your comments, but I think the whole widget would benefit from another look from you, since a lot of code was refactored.

@Piinks Piinks self-requested a review November 11, 2021 21:58
@Piinks Piinks added c: new feature Nothing broken; request for a new capability a: layout SystemChrome and Framework's Layout Issues labels Nov 11, 2021
@andreidiaconu
Copy link
Contributor Author

andreidiaconu commented Nov 25, 2021

@goderbauer I need help. Is there a correct way of knowing the global position on screen of a render object during its layout phase? Is there any other solution I should try / consider? Here is what I looked at and tried.

This widget needs to accomplish these points:

  1. Know its own position and size on the screen. This is needed in order to know how display features intersect with it.
  2. Layout its child when the data in point 1 changes.
  3. Not skip a frame while doing the above points.

Here are the things I tried:

  • Using findRenderObject, outside of the DisplayFeatureSubScreen build method. This can be done in showDialog() and similar methods.
    • As you also pointed out, point 2 is not satisfied. If the Overlay changes position, the DisplayFeatureSubScreen can stop working as expected.
    • Does not work in situations where the render tree is not laid out yet.
  • Using CompositedTransformTarget and CompositedTransformFollower or something similar to them. The ideas I could grasp from them is that it is possible to react to position changes, which I thought is a good start. Since DisplayFeatureSubScreen is the parent, it could theoretically be possible to do layout changes between the DisplayFeatureSubScreen paint and the child paint.
    • There is no way to do layout once the paint phase starts, and trying to change this is not reasonable (I think). Feels like fighting the framework.
    • Does not satisfy 2. The child can change position using CompositedTransformFollower but not size (relayout).
  • Looked at TextSelectionOverlay since the overlay entries created for it seem to update as text updates and changes screen position.
    • Seems to rely on addPostFrameCallback, does not satisfy point 3.
  • Instead of focusing on this widget, consider making each sub-screen have its own Overlay. This would make dialogs and other overlay entries simply use the available space.
    • Somehow this seems like a solution that would make a lot more problems than it would solve. Just wanted it in the list, to expand to other possible solutions.

My conclusion so far is that the flutter layout algorithm (Constraints go down. Sizes go up. Parent sets position), while ensuring high performance, is the reason why I can't get point 3 solved. I ended up caching the offset obtained during the paint phase. When it changes, I request a new layout in the next frame.

@Piinks
Copy link
Contributor

Piinks commented Nov 30, 2021

cc @HansMuller, I mentioned this in a meeting today

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andreidiaconu I believe your analysis in the previous comment is spot on. Thanks for writing that up so concisely; it's very helpful. This appears to be a more complex problem than initially thought. I don't have a good idea yet for how to make this better, unfortunately. I am still thinking about it.

@andreidiaconu
Copy link
Contributor Author

@andreidiaconu I believe your analysis in the previous comment is spot on. Thanks for writing that up so concisely; it's very helpful. This appears to be a more complex problem than initially thought. I don't have a good idea yet for how to make this better, unfortunately. I am still thinking about it.

I assume the CompositedTransformTarget / CompositedTransformFollower pair does work regardless of OffsetLayer usage. I don't fully undestand why they work. Is there a good resource I can study, apart from source code, so that maybe I can leverage Layers in a similar way?

Thank you for taking the time to think about this.

@goderbauer
Copy link
Member

Can we model this closer to what SafeArea does with MediaQueryData.padding? The feature implemented here is essentially a SafeArea` on steroids - but instead of just considering obstructions at screen edges the obstructions can be anywhere.

SafeArea always assumes that the padding it reads from MediaQuery is in its local coordinate system. It is the responsibility of surrounding widgets to insert a new MediaQuery with adjusted padding to ensure that SafeArea can make that assumption. To facilitate that, there's the handy convenience function MediaQuery.removePadding. SafeArea itself utilizes this function to wrap its child in a new MediaQuery, where the padding that was consumed by SafeArea is removed. This ensures that children don't re-apply padding that has already been taken care of by SafeArea.

Applied here, the new widget could always assume the the obstructions are in its local coordinates. It would then calculate EdgeInsets ("padding") for its child widget that would avoid the obstructions and put the child on one of the subscreens. It would then also wrap the child with a new MediaQuery that basically only covers the subscreen (e.g. size is adjusted, all obstructions that are outside this subscreen are removed, coordinates for remaining obstructions are adjusted, and all other properties are adjusted to pretend that the subscreen is the only screen space available). A convenience method like MediaQuery.deflate(EdgeInsets padding) that takes the previously calculated padding and adjusts the MediaQuery accordingly would be helpful for this. That method could also be used by surrounding widgets to ensure that this new widget always sees the obstructions in its local coordinate system...

WDYT?

@andreidiaconu
Copy link
Contributor Author

andreidiaconu commented Dec 7, 2021

I agree with your comparison of the two widgets and the solution does make sense. We would, however end up with two widgets that are very similar, solve the same problem and would be used together almost always. The docs for SafeArea actually say:

/// It will also indent the child by the amount necessary to avoid The Notch on
/// the iPhone X, or other similar creative physical features of the display.

I have two questions:

  1. Does it make sense to make SafeArea take into account DisplayFeatures and have an anchorPoint parameter, instead of creating this new widget?
    One consequence would be that we can't make Directionality a required ancestor, since that would mean breaking existing behaviour for SafeArea. It does not currently require such an acenstor. We could still use it if it exists, as a compromise, and fallback on Offset(0,0) if it does not.
    There might be more, unintended consequences.
  2. Does this mean that MediaQuery.removePadding and other similar methods would also need to make sure that display features make sense in the new local MediaQueryData generated? Meaning that if the padding was consumed by a SafeArea widget, then the displayFeatures array should also be shifted by left and top?

@goderbauer
Copy link
Member

  1. Integrating this into the SafeArea sounds reasonable. I don't really like the Offset(0,0) fallback as that's favoring LTR over RTL. Having this as a separate widget that can be composed with SafeAre is also reasonable.
  2. Right now I can't think of a case where that adjustment matters?

@andreidiaconu
Copy link
Contributor Author

Would making SafeArea depend on a Directionality ancestor be a better solution?
I will go through all framework usages of SafeArea to have a better picture of how to continue (option A: updating SafeArea, option B: creating this new widget) and report back. I should also be able to provide good examples for point 2 by doing so.

@andreidiaconu
Copy link
Contributor Author

@goderbauer I made DisplayFeatureSubScreen provide the child with a new MediaQueryData, similarly to SafeArea, as advised. I also had to make adjustments to the various padding types, as this widget introduces padding, same as SafeArea. The logic for making the new MediaQueryData resides in MediaQueryData, as it is very similar to removePadding. Please have another look and tell me what you think.

Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great!! Thanks for reworking it, and for your patience waiting for review. Mostly nits below.

@andreidiaconu
Copy link
Contributor Author

@Piinks Thank you for the review. If anything else seems off, please let me know.

Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thank you! Can you rebase (not merge) with master to resolve the failing customer test?

@Piinks
Copy link
Contributor

Piinks commented Jan 24, 2022

This will need a second review in order to submit, @goderbauer can you take a look?

@andreidiaconu andreidiaconu force-pushed the foldable_support_displayfeaturesubscreen branch from 829926c to 88ea530 Compare January 25, 2022 16:22
@andreidiaconu
Copy link
Contributor Author

LGTM! Thank you! Can you rebase (not merge) with master to resolve the failing customer test?

@Piinks Rebased, checks seem to pass.

@Piinks Piinks marked this pull request as ready for review January 26, 2022 21:17
@andreidiaconu
Copy link
Contributor Author

@goderbauer Made all the suggested changes and also rewrote the way I cap the anchorPoint. Please have another look. Thanks.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@fluttergithubbot fluttergithubbot merged commit 4a7e30f into flutter:master Feb 2, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 3, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 3, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 3, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 3, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 3, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Feb 4, 2022
clocksmith pushed a commit to clocksmith/flutter that referenced this pull request Mar 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: layout SystemChrome and Framework's Layout Issues a: tests "flutter test", flutter_test, or one of our tests c: new feature Nothing broken; request for a new capability framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants