Skip to content
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

Add onHover and onFocusChange callbacks for TabBar #164816

Merged
merged 8 commits into from
Mar 13, 2025

Conversation

Piinks
Copy link
Contributor

@Piinks Piinks commented Mar 7, 2025

Fixes #159444
Fixes #146089

This adds callbacks to TabBar for onHover and onFocusChange.
They pipe through to the underlying Inkwell widget that is wrapped around each Tab of the TabBar during build.

Alternatives

  • I did consider adding these callbacks to Tab instead, but felt that going through TabBar would be better. If implemented in Tab, the user would need to define callbacks for each tab. This PR makes it so there is only need for one callback, and the associated Tab index is provided. Also, since the Inkwell is applied in the TabBar, it's kludgy to have to extract that from the Tabs in TabBar later to pass on to the Inkwell. 👃
  • Digging in to the requests in the linked issues, the user wants to change various stylings in response to these events. WidgetStateProperties were considered, but there are so many potential styling properties that going this route would require greatly increasing the API surface here. Tab.child allows the user to provide whatever widget they would like to have as the content. Being able to modify Tab.child in response to these events is a better way go instead of exposing a ton of different properties.

Pre-launch Checklist

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

@Piinks Piinks requested review from QuncCccccc and chunhtai March 7, 2025 20:38
@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. labels Mar 7, 2025
@@ -844,6 +844,12 @@ class _TabBarScrollController extends ScrollController {
}
}

/// Signature for [TabBar] callbacks that report that an underlying value has
/// changed for a given [Tab] at `index`.
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe give an example for what value might be for each case. e.g. for onHover the value means hovered

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added some more docs! :)

/// changed for a given [Tab] at `index`.
///
/// Used for [TabBar.onHover] and [TabBar.onFocusChange] callbacks.
typedef TabValueChanged<T> = void Function(T value, int index);
Copy link
Contributor

Choose a reason for hiding this comment

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

Value seems a bit weird in this context. May be TabStateChanged

Copy link
Contributor

Choose a reason for hiding this comment

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

If this can only happen for one tab at a time should we change the function signature to just an index?

typedef TabChanged = ValueChanged<int?>;

This may be cleaning when, lets say changing the focus, you will only call this method once with the current affected index, instead of twice: one with true the other with false

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If this can only happen for one tab at a time should we change the function signature to just an index?

Good question! It can happen for multiple tabs at once, like in the test cases here. If focus/hover moves directly from one tab to another, they both receive callbacks for the events of entering and exiting those states.

Value seems a bit weird in this context. May be TabStateChanged

I want to stick with value since it is consistently used for focus and hover callbacks across the framework. This is really similar to the ValueChanged callback that they all use: https://api.flutter.dev/flutter/foundation/ValueChanged.html

Copy link
Contributor

Choose a reason for hiding this comment

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

they both receive callbacks for the events of entering and exiting those states.

I was suggesting the callback can just have the current hovered/focused tab as input.

I imaging the use case is to change styling of Tab of hovered/focused tab. The current callback will be called twice when the focused tab change for example. It is unclear to me at which callback they should modify their Tabs.

My suggestion is to just call the callback with the current focused tab one time since the developer mostly likely only want to know which one is currently focused

bassically instead of

int? currentFocusedIndex;

onFocused: (bool state, int index) {
  if (!state) {
     if (currentFocusedIndex == index) {
        currentFocusedIndex = null;
     }
     return;
  }
  currentFocusedIndex = index
}

we should just provide api like this

int? currentFocusedIndex;

onFocused: (int? index) {
  currentFocusedIndex = index
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The latter example does not account for focus being lost, or hover exiting. The developer wants to be able to respond to these states as well. Just passing the index does not supply the context of what the state is, and differs from current convention for these callbacks.

Copy link
Contributor

Choose a reason for hiding this comment

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

in case that no tab is hovered or focused, the callback will call with index equals to null, or -1 whichever we document on the callback.

Do we have existing convention for this kind of callback?

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 don't think it would, if no tab is hovered or focused, no callback is not called. What would trigger the callback if no event has occurred?

Copy link
Contributor

@QuncCccccc QuncCccccc left a comment

Choose a reason for hiding this comment

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

LGTM:)

@Piinks
Copy link
Contributor Author

Piinks commented Mar 11, 2025

Thanks for the review @QuncCccccc! I also have some notes from syncing with @chunhtai on this today. I need to resolve the analysis failure, and add some sample code. 👍

@github-actions github-actions bot added d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos labels Mar 12, 2025
setState(() {
focusedIndex = switch (value) {
true => index,
false => null,
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to worry about cases where false is called after true?
for example switch from focus from tab 1-> tab2
whether tabs 1 false may be called after tab 2 true

Copy link
Contributor

Choose a reason for hiding this comment

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

If we don't we should probably document this on the api

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we need to worry about cases where false is called after true?

I don't think so, I've tested it, I was curious about the same exact thing.

Copy link
Contributor

@chunhtai chunhtai Mar 12, 2025

Choose a reason for hiding this comment

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

for focus it is probably fine.

for hover I looked at the code, onExit is called before onEnter

lastAnnotations.forEach((MouseTrackerAnnotation annotation, Matrix4 transform) {
, so I think we should be fine either case.

we should still document this on the api though

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah gotcha! Added!

Copy link
Contributor

@chunhtai chunhtai left a comment

Choose a reason for hiding this comment

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

LGTM, except for documenting the behavior mentioned in #164816 (comment)

@Piinks Piinks added the autosubmit Merge PR when tree becomes green via auto submit App label Mar 12, 2025
Copy link
Contributor

auto-submit bot commented Mar 12, 2025

autosubmit label was removed for flutter/flutter/164816, because - The status or check suite Linux analyze has failed. Please fix the issues identified (or deflake) before re-applying this label.

@auto-submit auto-submit bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Mar 12, 2025
@Piinks Piinks added the autosubmit Merge PR when tree becomes green via auto submit App label Mar 13, 2025
@auto-submit auto-submit bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Mar 13, 2025
Copy link
Contributor

auto-submit bot commented Mar 13, 2025

autosubmit label was removed for flutter/flutter/164816, because - The status or check suite Linux analyze has failed. Please fix the issues identified (or deflake) before re-applying this label.

@Piinks Piinks added the autosubmit Merge PR when tree becomes green via auto submit App label Mar 13, 2025
Copy link
Contributor

auto-submit bot commented Mar 13, 2025

autosubmit label was removed for flutter/flutter/164816, because - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 14, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 14, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 14, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 15, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 15, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 16, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 17, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
3 participants