From a6504ead31d6b7856dbc12dfd7e3f17a015dd6c3 Mon Sep 17 00:00:00 2001 From: Taha Tesser Date: Tue, 8 Feb 2022 02:35:12 +0200 Subject: [PATCH] TabBar: add themeable mouse cursor (#96737) --- .../lib/src/material/tab_bar_theme.dart | 13 +++++++++- packages/flutter/lib/src/material/tabs.dart | 25 +++++++++++++++++-- .../test/material/tab_bar_theme_test.dart | 18 +++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/packages/flutter/lib/src/material/tab_bar_theme.dart b/packages/flutter/lib/src/material/tab_bar_theme.dart index ff442fea33..183861bad9 100644 --- a/packages/flutter/lib/src/material/tab_bar_theme.dart +++ b/packages/flutter/lib/src/material/tab_bar_theme.dart @@ -37,6 +37,7 @@ class TabBarTheme with Diagnosticable { this.unselectedLabelStyle, this.overlayColor, this.splashFactory, + this.mouseCursor, }); /// Default value for [TabBar.indicator]. @@ -70,6 +71,11 @@ class TabBarTheme with Diagnosticable { /// Default value for [TabBar.splashFactory]. final InteractiveInkFeatureFactory? splashFactory; + /// {@macro flutter.material.tabs.mouseCursor} + /// + /// If specified, overrides the default value of [TabBar.mouseCursor]. + final MaterialStateProperty? mouseCursor; + /// Creates a copy of this object but with the given fields replaced with the /// new values. TabBarTheme copyWith({ @@ -82,6 +88,7 @@ class TabBarTheme with Diagnosticable { TextStyle? unselectedLabelStyle, MaterialStateProperty? overlayColor, InteractiveInkFeatureFactory? splashFactory, + MaterialStateProperty? mouseCursor, }) { return TabBarTheme( indicator: indicator ?? this.indicator, @@ -93,6 +100,7 @@ class TabBarTheme with Diagnosticable { unselectedLabelStyle: unselectedLabelStyle ?? this.unselectedLabelStyle, overlayColor: overlayColor ?? this.overlayColor, splashFactory: splashFactory ?? this.splashFactory, + mouseCursor: mouseCursor ?? this.mouseCursor, ); } @@ -120,6 +128,7 @@ class TabBarTheme with Diagnosticable { unselectedLabelStyle: TextStyle.lerp(a.unselectedLabelStyle, b.unselectedLabelStyle, t), overlayColor: _LerpColors(a.overlayColor, b.overlayColor, t), splashFactory: t < 0.5 ? a.splashFactory : b.splashFactory, + mouseCursor: t < 0.5 ? a.mouseCursor : b.mouseCursor, ); } @@ -135,6 +144,7 @@ class TabBarTheme with Diagnosticable { unselectedLabelStyle, overlayColor, splashFactory, + mouseCursor, ); } @@ -153,7 +163,8 @@ class TabBarTheme with Diagnosticable { && other.unselectedLabelColor == unselectedLabelColor && other.unselectedLabelStyle == unselectedLabelStyle && other.overlayColor == overlayColor - && other.splashFactory == splashFactory; + && other.splashFactory == splashFactory + && other.mouseCursor == mouseCursor; } } diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 1727efbc3f..3714622d22 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -803,10 +803,23 @@ class TabBar extends StatefulWidget implements PreferredSizeWidget { /// {@macro flutter.widgets.scrollable.dragStartBehavior} final DragStartBehavior dragStartBehavior; + /// {@template flutter.material.tabs.mouseCursor} /// The cursor for a mouse pointer when it enters or is hovering over the /// individual tab widgets. /// - /// If this property is null, [SystemMouseCursors.click] will be used. + /// If [mouseCursor] is a [MaterialStateProperty], + /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s: + /// + /// * [MaterialState.selected]. + /// {@endtemplate} + /// + /// If null, then the value of [TabBarTheme.mouseCursor] is used. If + /// that is also null, then [MaterialStateMouseCursor.clickable] is used. + /// + /// See also: + /// + /// * [MaterialStateMouseCursor], which can be used to create a [MouseCursor] + /// that is also a [MaterialStateProperty]. final MouseCursor? mouseCursor; /// Whether detected gestures should provide acoustic and/or haptic feedback. @@ -1222,8 +1235,16 @@ class _TabBarState extends State { // the same share of the tab bar's overall width. final int tabCount = widget.tabs.length; for (int index = 0; index < tabCount; index += 1) { + final Set states = { + if (index == _currentIndex) MaterialState.selected, + }; + + final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs(widget.mouseCursor, states) + ?? tabBarTheme.mouseCursor?.resolve(states) + ?? MaterialStateMouseCursor.clickable.resolve(states); + wrappedTabs[index] = InkWell( - mouseCursor: widget.mouseCursor ?? SystemMouseCursors.click, + mouseCursor: effectiveMouseCursor, onTap: () { _handleTap(index); }, enableFeedback: widget.enableFeedback ?? true, overlayColor: widget.overlayColor ?? tabBarTheme.overlayColor, diff --git a/packages/flutter/test/material/tab_bar_theme_test.dart b/packages/flutter/test/material/tab_bar_theme_test.dart index 33fb1ab9f3..8ce9b2745e 100644 --- a/packages/flutter/test/material/tab_bar_theme_test.dart +++ b/packages/flutter/test/material/tab_bar_theme_test.dart @@ -6,6 +6,7 @@ // machines. @Tags(['reduced-test-set']) +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -67,6 +68,7 @@ void main() { expect(const TabBarTheme().unselectedLabelStyle, null); expect(const TabBarTheme().overlayColor, null); expect(const TabBarTheme().splashFactory, null); + expect(const TabBarTheme().mouseCursor, null); }); testWidgets('Tab bar defaults - label style and selected/unselected label colors', (WidgetTester tester) async { @@ -302,6 +304,22 @@ void main() { ); }); + testWidgets('Tab bar theme overrides tab mouse cursor', (WidgetTester tester) async { + const TabBarTheme tabBarTheme = TabBarTheme(mouseCursor: MaterialStateMouseCursor.textable); + + await tester.pumpWidget(_withTheme(tabBarTheme)); + + final Offset tabBar = tester.getCenter( + find.ancestor(of: find.text('tab 1'),matching: find.byType(TabBar)), + ); + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(); + addTearDown(gesture.removePointer); + await gesture.moveTo(tabBar); + await tester.pumpAndSettle(); + expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text); + }); + testWidgets('Tab bar theme - custom tab indicator', (WidgetTester tester) async { final TabBarTheme tabBarTheme = TabBarTheme( indicator: BoxDecoration(