-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Fix SegmentedButton border doesn't respect hovered, focused, selected states
#161942
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
Fix SegmentedButton border doesn't respect hovered, focused, selected states
#161942
Conversation
SegmentedButton border doesn't respect hovered, focused stateSegmentedButton border doesn't respect hovered, focused, selected states
97133ad to
5f4a194
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.
Based on the specs, it seems if a segment is disabled, we should be able to show the disabled state on border of the specific segment (not the whole SegmentedButton). So It's not clear what expected behavior would be when a specific segment is hovered, focused and pressed. Let me double check with the material team.
|
Filed a bug and asked for clarification: b/394478041 |
bbb0745 to
4ff767b
Compare
QuncCccccc
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.
I haven't heard about the update from the material team, but I do see there's a token to indicate the outline of the whole segmented button, so I think this feature makes sense:)
Sorry for the late response. Trying my best to speed up! I'll make sure we make some progress this week!
f0ce435 to
6825b07
Compare
QuncCccccc
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.
LGTM! Thanks so much for your patience.
6825b07 to
74e10d3
Compare
|
@victorsanni Could you please approve? :) |
victorsanni
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.
LGTM
|
autosubmit label was removed for flutter/flutter/161942, because - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label. |
|
I was looking at the google testing failures, they are all image diffs. Seems like the disabled segmented button is now black instead of gray. I wasn't following the earlier conversation, but is that intended? |
|
I'll look at the internal use case to see how they use the segmented buttons. I think we handle the disabled state correctly here🤔 |
|
@QuncCccccc How's the investigation going? (from triage) |
| bool _focused = false; | ||
| bool get _selected => widget.selected.isNotEmpty; | ||
|
|
||
| Set<WidgetState> get _states => <WidgetState>{ |
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.
When we disable one of the segments by setting ButtonSegment.enabled to false, _states cannot really add WidgetState.disabled to the set, so the disabledBorder in line 628 is actually not the real disabled border and then incorrect side is applied to the disabled segment. This causes the google testing failures.
| final BorderSide effectiveSide = | ||
| resolve<BorderSide?>((ButtonStyle? style) => style?.side) ?? BorderSide.none; | ||
| final OutlinedBorder enabledBorder = effectiveBorder.copyWith(side: effectiveSide); | ||
| final OutlinedBorder disabledBorder = effectiveBorder.copyWith(side: effectiveSide); |
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.
|
Gentle ping @TahaTesser |
74e10d3 to
4653dd7
Compare
## Description This PR fixes `SegmentedButton` border side not depending on the current state. It's based on one @TahaTesser 's great PR: see #161942. 🙏 I updated some logic related to disabled border which led to Taha’s PR failing internal Google tests. I also added a test to verify the disabled border. <details><summary>Code sample for screenshots</summary> ```dart import 'package:flutter/material.dart'; void main() { runApp(const SegmentedButtonApp()); } enum Calendar { day, week, month, year } class SegmentedButtonApp extends StatefulWidget { const SegmentedButtonApp({super.key}); @OverRide State<SegmentedButtonApp> createState() => _SegmentedButtonAppState(); } class _SegmentedButtonAppState extends State<SegmentedButtonApp> { Calendar? calendarView; @OverRide Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( segmentedButtonTheme: SegmentedButtonThemeData( style: ButtonStyle( side: WidgetStateProperty.fromMap(<WidgetStatesConstraint, BorderSide?>{ WidgetState.disabled: const BorderSide(color: Colors.grey, width: 2), WidgetState.selected & WidgetState.hovered: const BorderSide( color: Colors.blue, width: 2, ), WidgetState.selected & WidgetState.focused: const BorderSide( color: Colors.pinkAccent, width: 2, ), WidgetState.hovered: const BorderSide(color: Colors.green, width: 2), WidgetState.focused: const BorderSide(color: Colors.purple, width: 2), WidgetState.any: const BorderSide(color: Colors.amber, width: 2), }), ), ), ), home: Scaffold( body: Center( child: SegmentedButton<Calendar>( segments: const <ButtonSegment<Calendar>>[ ButtonSegment<Calendar>( value: Calendar.day, label: Text('Day'), icon: Icon(Icons.calendar_view_day), enabled: false, ), ButtonSegment<Calendar>( value: Calendar.week, label: Text('Week'), icon: Icon(Icons.calendar_view_week), ), ButtonSegment<Calendar>( value: Calendar.month, label: Text('Month'), icon: Icon(Icons.calendar_view_month), ), ButtonSegment<Calendar>( value: Calendar.year, label: Text('Year'), icon: Icon(Icons.calendar_today), ), ], selected: <Calendar>{?calendarView}, emptySelectionAllowed: true, onSelectionChanged: (Set<Calendar> newSelection) { setState(() { calendarView = newSelection.isEmpty ? null : newSelection.first; }); }, ), ), floatingActionButton: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)), ), ); } } ``` </details> # Before https://github.com/user-attachments/assets/1581f431-f87a-4af3-8ef6-f1f0d170e54a # After https://github.com/user-attachments/assets/156a8a64-3d9f-4323-9a1d-60624f5ac5d4 ## Related Issue Fixes [SegmentedButton does not set its MaterialState for side ](#159884) ## Tests Adds 2 tests.
## Description This PR fixes `SegmentedButton` border side not depending on the current state. It's based on one @TahaTesser 's great PR: see flutter#161942. 🙏 I updated some logic related to disabled border which led to Taha’s PR failing internal Google tests. I also added a test to verify the disabled border. <details><summary>Code sample for screenshots</summary> ```dart import 'package:flutter/material.dart'; void main() { runApp(const SegmentedButtonApp()); } enum Calendar { day, week, month, year } class SegmentedButtonApp extends StatefulWidget { const SegmentedButtonApp({super.key}); @OverRide State<SegmentedButtonApp> createState() => _SegmentedButtonAppState(); } class _SegmentedButtonAppState extends State<SegmentedButtonApp> { Calendar? calendarView; @OverRide Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( segmentedButtonTheme: SegmentedButtonThemeData( style: ButtonStyle( side: WidgetStateProperty.fromMap(<WidgetStatesConstraint, BorderSide?>{ WidgetState.disabled: const BorderSide(color: Colors.grey, width: 2), WidgetState.selected & WidgetState.hovered: const BorderSide( color: Colors.blue, width: 2, ), WidgetState.selected & WidgetState.focused: const BorderSide( color: Colors.pinkAccent, width: 2, ), WidgetState.hovered: const BorderSide(color: Colors.green, width: 2), WidgetState.focused: const BorderSide(color: Colors.purple, width: 2), WidgetState.any: const BorderSide(color: Colors.amber, width: 2), }), ), ), ), home: Scaffold( body: Center( child: SegmentedButton<Calendar>( segments: const <ButtonSegment<Calendar>>[ ButtonSegment<Calendar>( value: Calendar.day, label: Text('Day'), icon: Icon(Icons.calendar_view_day), enabled: false, ), ButtonSegment<Calendar>( value: Calendar.week, label: Text('Week'), icon: Icon(Icons.calendar_view_week), ), ButtonSegment<Calendar>( value: Calendar.month, label: Text('Month'), icon: Icon(Icons.calendar_view_month), ), ButtonSegment<Calendar>( value: Calendar.year, label: Text('Year'), icon: Icon(Icons.calendar_today), ), ], selected: <Calendar>{?calendarView}, emptySelectionAllowed: true, onSelectionChanged: (Set<Calendar> newSelection) { setState(() { calendarView = newSelection.isEmpty ? null : newSelection.first; }); }, ), ), floatingActionButton: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)), ), ); } } ``` </details> # Before https://github.com/user-attachments/assets/1581f431-f87a-4af3-8ef6-f1f0d170e54a # After https://github.com/user-attachments/assets/156a8a64-3d9f-4323-9a1d-60624f5ac5d4 ## Related Issue Fixes [SegmentedButton does not set its MaterialState for side ](flutter#159884) ## Tests Adds 2 tests.
## Description This PR fixes `SegmentedButton` border side not depending on the current state. It's based on one @TahaTesser 's great PR: see flutter#161942. 🙏 I updated some logic related to disabled border which led to Taha’s PR failing internal Google tests. I also added a test to verify the disabled border. <details><summary>Code sample for screenshots</summary> ```dart import 'package:flutter/material.dart'; void main() { runApp(const SegmentedButtonApp()); } enum Calendar { day, week, month, year } class SegmentedButtonApp extends StatefulWidget { const SegmentedButtonApp({super.key}); @OverRide State<SegmentedButtonApp> createState() => _SegmentedButtonAppState(); } class _SegmentedButtonAppState extends State<SegmentedButtonApp> { Calendar? calendarView; @OverRide Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( segmentedButtonTheme: SegmentedButtonThemeData( style: ButtonStyle( side: WidgetStateProperty.fromMap(<WidgetStatesConstraint, BorderSide?>{ WidgetState.disabled: const BorderSide(color: Colors.grey, width: 2), WidgetState.selected & WidgetState.hovered: const BorderSide( color: Colors.blue, width: 2, ), WidgetState.selected & WidgetState.focused: const BorderSide( color: Colors.pinkAccent, width: 2, ), WidgetState.hovered: const BorderSide(color: Colors.green, width: 2), WidgetState.focused: const BorderSide(color: Colors.purple, width: 2), WidgetState.any: const BorderSide(color: Colors.amber, width: 2), }), ), ), ), home: Scaffold( body: Center( child: SegmentedButton<Calendar>( segments: const <ButtonSegment<Calendar>>[ ButtonSegment<Calendar>( value: Calendar.day, label: Text('Day'), icon: Icon(Icons.calendar_view_day), enabled: false, ), ButtonSegment<Calendar>( value: Calendar.week, label: Text('Week'), icon: Icon(Icons.calendar_view_week), ), ButtonSegment<Calendar>( value: Calendar.month, label: Text('Month'), icon: Icon(Icons.calendar_view_month), ), ButtonSegment<Calendar>( value: Calendar.year, label: Text('Year'), icon: Icon(Icons.calendar_today), ), ], selected: <Calendar>{?calendarView}, emptySelectionAllowed: true, onSelectionChanged: (Set<Calendar> newSelection) { setState(() { calendarView = newSelection.isEmpty ? null : newSelection.first; }); }, ), ), floatingActionButton: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)), ), ); } } ``` </details> # Before https://github.com/user-attachments/assets/1581f431-f87a-4af3-8ef6-f1f0d170e54a # After https://github.com/user-attachments/assets/156a8a64-3d9f-4323-9a1d-60624f5ac5d4 ## Related Issue Fixes [SegmentedButton does not set its MaterialState for side ](flutter#159884) ## Tests Adds 2 tests.
## Description This PR fixes `SegmentedButton` border side not depending on the current state. It's based on one @TahaTesser 's great PR: see flutter#161942. 🙏 I updated some logic related to disabled border which led to Taha’s PR failing internal Google tests. I also added a test to verify the disabled border. <details><summary>Code sample for screenshots</summary> ```dart import 'package:flutter/material.dart'; void main() { runApp(const SegmentedButtonApp()); } enum Calendar { day, week, month, year } class SegmentedButtonApp extends StatefulWidget { const SegmentedButtonApp({super.key}); @OverRide State<SegmentedButtonApp> createState() => _SegmentedButtonAppState(); } class _SegmentedButtonAppState extends State<SegmentedButtonApp> { Calendar? calendarView; @OverRide Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( segmentedButtonTheme: SegmentedButtonThemeData( style: ButtonStyle( side: WidgetStateProperty.fromMap(<WidgetStatesConstraint, BorderSide?>{ WidgetState.disabled: const BorderSide(color: Colors.grey, width: 2), WidgetState.selected & WidgetState.hovered: const BorderSide( color: Colors.blue, width: 2, ), WidgetState.selected & WidgetState.focused: const BorderSide( color: Colors.pinkAccent, width: 2, ), WidgetState.hovered: const BorderSide(color: Colors.green, width: 2), WidgetState.focused: const BorderSide(color: Colors.purple, width: 2), WidgetState.any: const BorderSide(color: Colors.amber, width: 2), }), ), ), ), home: Scaffold( body: Center( child: SegmentedButton<Calendar>( segments: const <ButtonSegment<Calendar>>[ ButtonSegment<Calendar>( value: Calendar.day, label: Text('Day'), icon: Icon(Icons.calendar_view_day), enabled: false, ), ButtonSegment<Calendar>( value: Calendar.week, label: Text('Week'), icon: Icon(Icons.calendar_view_week), ), ButtonSegment<Calendar>( value: Calendar.month, label: Text('Month'), icon: Icon(Icons.calendar_view_month), ), ButtonSegment<Calendar>( value: Calendar.year, label: Text('Year'), icon: Icon(Icons.calendar_today), ), ], selected: <Calendar>{?calendarView}, emptySelectionAllowed: true, onSelectionChanged: (Set<Calendar> newSelection) { setState(() { calendarView = newSelection.isEmpty ? null : newSelection.first; }); }, ), ), floatingActionButton: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)), ), ); } } ``` </details> # Before https://github.com/user-attachments/assets/1581f431-f87a-4af3-8ef6-f1f0d170e54a # After https://github.com/user-attachments/assets/156a8a64-3d9f-4323-9a1d-60624f5ac5d4 ## Related Issue Fixes [SegmentedButton does not set its MaterialState for side ](flutter#159884) ## Tests Adds 2 tests.
## Description This PR fixes `SegmentedButton` border side not depending on the current state. It's based on one @TahaTesser 's great PR: see flutter#161942. 🙏 I updated some logic related to disabled border which led to Taha’s PR failing internal Google tests. I also added a test to verify the disabled border. <details><summary>Code sample for screenshots</summary> ```dart import 'package:flutter/material.dart'; void main() { runApp(const SegmentedButtonApp()); } enum Calendar { day, week, month, year } class SegmentedButtonApp extends StatefulWidget { const SegmentedButtonApp({super.key}); @OverRide State<SegmentedButtonApp> createState() => _SegmentedButtonAppState(); } class _SegmentedButtonAppState extends State<SegmentedButtonApp> { Calendar? calendarView; @OverRide Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( segmentedButtonTheme: SegmentedButtonThemeData( style: ButtonStyle( side: WidgetStateProperty.fromMap(<WidgetStatesConstraint, BorderSide?>{ WidgetState.disabled: const BorderSide(color: Colors.grey, width: 2), WidgetState.selected & WidgetState.hovered: const BorderSide( color: Colors.blue, width: 2, ), WidgetState.selected & WidgetState.focused: const BorderSide( color: Colors.pinkAccent, width: 2, ), WidgetState.hovered: const BorderSide(color: Colors.green, width: 2), WidgetState.focused: const BorderSide(color: Colors.purple, width: 2), WidgetState.any: const BorderSide(color: Colors.amber, width: 2), }), ), ), ), home: Scaffold( body: Center( child: SegmentedButton<Calendar>( segments: const <ButtonSegment<Calendar>>[ ButtonSegment<Calendar>( value: Calendar.day, label: Text('Day'), icon: Icon(Icons.calendar_view_day), enabled: false, ), ButtonSegment<Calendar>( value: Calendar.week, label: Text('Week'), icon: Icon(Icons.calendar_view_week), ), ButtonSegment<Calendar>( value: Calendar.month, label: Text('Month'), icon: Icon(Icons.calendar_view_month), ), ButtonSegment<Calendar>( value: Calendar.year, label: Text('Year'), icon: Icon(Icons.calendar_today), ), ], selected: <Calendar>{?calendarView}, emptySelectionAllowed: true, onSelectionChanged: (Set<Calendar> newSelection) { setState(() { calendarView = newSelection.isEmpty ? null : newSelection.first; }); }, ), ), floatingActionButton: FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)), ), ); } } ``` </details> # Before https://github.com/user-attachments/assets/1581f431-f87a-4af3-8ef6-f1f0d170e54a # After https://github.com/user-attachments/assets/156a8a64-3d9f-4323-9a1d-60624f5ac5d4 ## Related Issue Fixes [SegmentedButton does not set its MaterialState for side ](flutter#159884) ## Tests Adds 2 tests.

Fixes
SegmentedButtondoes not set its MaterialState for sideCode Sample
expand to view the code sample
Before
Screen.Recording.2025-01-21.at.15.23.24.mov
After
Screen.Recording.2025-01-21.at.15.22.33.mov
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.