Skip to content

Conversation

@Piinks
Copy link
Contributor

@Piinks Piinks commented Jun 13, 2023

Fixes #126299

This adds support for the static viewport method ensureVisible to work in the 2D viewport. This means default focus traversal will work as well out of the box! :)

Screen.Recording.2023-06-13.at.2.49.46.PM.mov

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.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@Piinks Piinks added c: new feature Nothing broken; request for a new capability framework flutter/packages/flutter repository. See also f: labels. f: scrolling Viewports, list views, slivers, etc. labels Jun 13, 2023
@Piinks Piinks requested a review from goderbauer June 13, 2023 18:01
@Piinks
Copy link
Contributor Author

Piinks commented Jun 13, 2023

Ok, this is ready for review. I know there is a breakage in flutter/cocoon from the change in function signature. I am looking to see what options there are to land this with that.

@Piinks
Copy link
Contributor Author

Piinks commented Jun 13, 2023

Looking to resolve the customer failure in flutter/cocoon#2838

auto-submit bot pushed a commit to flutter/cocoon that referenced this pull request Jun 13, 2023
This removes the _FakeViewport from the build dashboard implementation. It was added in #741, and we think it's sole purpose was the preserve the 1D expectations of widgets in the framework like scrollbars and overscroll indicators. Since adding support for 2D in the framework, the framework has been updated so that the fake viewport should no longer be necessary.

All of the tests pass, so I think this is safe to remove now that the framework is 2D compatible.

This came up as a test failure in flutter/flutter#128812, where the function signature of getOffsetToReveal is being changed.
@Piinks
Copy link
Contributor Author

Piinks commented Jun 13, 2023

Customer patch landed, updating customer tests in flutter/tests#271

@Piinks Piinks force-pushed the ensureVisible2D branch from 33c4de8 to 2b5a2b2 Compare June 14, 2023 02:34
Copy link
Member

Choose a reason for hiding this comment

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

So, if I need to know where to scroll my 2d scrollable to to make something visible, I have to call this twice, once for each axis?

Copy link
Member

Choose a reason for hiding this comment

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

What offset will I get if I ask a 2D scroller without providing an axis?

Copy link
Member

Choose a reason for hiding this comment

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

So, if I need to know where to scroll my 2d scrollable to to make something visible, I have to call this twice, once for each axis?

Wouldn't this make RevealedOffset.rect for both individually somewhat meaningless since, when the offsets in both axises are applied (which I think is the goal - would you ever just want to only reveal in once axis?), the target will be located at neither rects? (I forget what exactly we use those rects for, I think it is relevant for a11y scrolling, see next comment?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What offset will I get if I ask a 2D scroller without providing an axis?

It will assert.

Since ensureVisible must go through the scroll position, which calls getOffsetToReveal, returning two offsets is irrelevant to only one of the two scroll positions.

Copy link
Member

Choose a reason for hiding this comment

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

nit: this and line 458 will find the same renderobject, maybe only find it once and store it in a variable since it cannot change between search operations?

Copy link
Member

Choose a reason for hiding this comment

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

The comment on line 906 said that pivot is the last RenderBox before the ViewPort. Doesn't that mean that its parent is this Viewport? In 1D scrollables there are slivers between the Viewport and the last RenderBox, what do we have in the 2d case there where we don't have slivers?

What am I missing about how this code works?

Copy link
Member

Choose a reason for hiding this comment

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

Now that I am re-reading the comment on lone 905/906: In a 2d viewport, what is the difference between child and pivot ? Wouldn't they always point to the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the case of the code in the PR description, after 7 loops the child is the repaint boundary and pivot is the actual 200x200 constrained box it wraps. The target the method is called with is the focus node of the actual checkbox within, so we bubble up from there to the top level child.

Copy link
Member

Choose a reason for hiding this comment

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

What does the render tree look like for that scenario?

Copy link
Member

Choose a reason for hiding this comment

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

I am wondering how this change plays into implicit accessibility scrolling. This method is basically the workhorse behind implicit a11y scrolling powering RenderViewportBase.showInViewport, which is called from RenderObject.showOnScreen.

(Implicit a11y scrolling is if you move the a11y focus beyond the edge of a scrollable and it then automatically reveals the item that has gotten a11y focus.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added the equivalent implementations of showInViewport and showOnScreen, I need to add more tests, but wanted to check first to see what you thought of the approach. I am carrying through the concept of providing which axis of the two. We did a similar change to Scrollable.of, and I think it makes sense to stay consistent here.

Copy link
Member

Choose a reason for hiding this comment

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

After reading through this PR, I wonder if instead of adding the axis param (and then somewhat awkwardly asserting whether you passed in the right one (for 1D scrollers) or any axis at all (for 2D scrollers)), we could "just" extend the return type RevealedOffset to include a second offset. The existing RevealedOffset.offset would be the offset for the mainAxis (= only axis for 1D scrollers and whatever was declared as main for 2D scrollers) and the newly added notMainAxisOffset property (name TBD) would be either 0 for 1D scrollers or the offset required to reveal the thing in the other axis for 2D scrollers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It would still need to be called twice since is is called for the individual scroll position of each axis. If it returns two offsets, the vertical scroll position cannot do anything about the horizontal offset that would be returned if we returned both.

@goderbauer
Copy link
Member

This means default focus traversal will work as well out of the box! :)

I forget, do these use the same APIs as implicit a11y scrolling? Or did we wire that up differently?

@Piinks
Copy link
Contributor Author

Piinks commented Jun 15, 2023

I am going to mark this as a draft while I make some changes. I thought I would add showInViewport and showOnScreen to the 2DViewport as separate changes, but I think they are more tightly coupled than I originally thought and should probably be included here.

@Piinks Piinks marked this pull request as draft June 15, 2023 15:38
@Piinks Piinks force-pushed the ensureVisible2D branch from 1db6757 to 6344f71 Compare July 13, 2023 19:38
@Piinks Piinks changed the title Support ensureVisible for 2D viewport Support ensureVisible/showOnScreen/showInViewport for 2D viewport Jul 13, 2023
@Piinks Piinks requested a review from goderbauer July 13, 2023 20:19
@Piinks Piinks marked this pull request as ready for review July 13, 2023 20:19
Comment on lines +2059 to +2063
throw FlutterError(
'The _performEnsureVisible method was called for the vertical scrollable '
'of a TwoDimensionalScrollable. This should not happen as the horizontal '
'scrollable handles both axes.'
);
Copy link
Member

Choose a reason for hiding this comment

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

Should this be in an assert? Or is it important that we can throw this error in release mode as well?

///
/// * [RenderObject.showOnScreen], overridden by
/// [RenderTwoDimensionalViewport] to delegate to this method.
static Rect? showInViewportForAxisDirection({
Copy link
Member

Choose a reason for hiding this comment

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

This likely relates somehow to RenderViewportBase.showInViewport. Should the methods link to each other explaining the difference?

Also, what's the behavior if you call RenderViewportBase.showInViewport for a 2D scroller? AFICT, there's nothing stopping you from doing that?

}

offset.moveTo(targetOffset.offset, duration: duration, curve: curve);
return targetOffset.rect;
Copy link
Member

Choose a reason for hiding this comment

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

Looks like the implementation is exactly the same as RenderViewportBase.showInViewport - except that we pass axisDirection in and to getOffsetToReveal. Can we either just add that functionality to RenderViewportBase.showInViewport or find other ways to share this not exactly trivial logic? The other method also has a more extensive implementation comment that would help in understanding what is happening here.

viewport: this,
offset: horizontalOffset,
axisDirection: horizontalAxisDirection,
rect: newRect,
Copy link
Member

Choose a reason for hiding this comment

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

My brain broke thinking about how newRect is used here. Technically, newRect here isn't the area of descendant that is to be made visible because newRect has already been adjusted for the vertical scroll above. I think, for some cases it still ends up working, because we know (or assume) that the first call to showInViewportForAxisDirection only adjusts one dimension of the rect and this call only cares about the other un-adjusted dimension. However, if we have any arbitrary transforms between descendant and this we'd run into trouble. One case I can see here is that the transform from line 1133 is essentially applied twice to the rect (once for the showInViewportForAxisDirection in line 1030 and then again for the call in 1042).

Copy link
Member

Choose a reason for hiding this comment

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

What does the render tree look like for that scenario?

@Piinks
Copy link
Contributor Author

Piinks commented Aug 2, 2023

Note to self when I get back to this starting net week: https://github.com/flutter/flutter/pull/131641/files#r1280947388

@Piinks Piinks mentioned this pull request Aug 2, 2023
8 tasks
@Piinks
Copy link
Contributor Author

Piinks commented Aug 15, 2023

Closing this for now to take out of the review queue, I still need to rework a few things.

@Piinks Piinks closed this Aug 15, 2023
@Piinks Piinks deleted the ensureVisible2D branch March 14, 2025 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c: new feature Nothing broken; request for a new capability f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2D Follow up: ensureVisible

2 participants