Unverified Commit 7a4f5ebf authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

BottomNavigationBar: add themeable mouse cursor (#96736)

parent 6ceb418b
......@@ -14,6 +14,7 @@ import 'debug.dart';
import 'ink_well.dart';
import 'material.dart';
import 'material_localizations.dart';
import 'material_state.dart';
import 'theme.dart';
import 'tooltip.dart';
......@@ -311,9 +312,20 @@ class BottomNavigationBar extends StatefulWidget {
final bool? showSelectedLabels;
/// The cursor for a mouse pointer when it enters or is hovering over the
/// tiles.
/// items.
///
/// If this property is null, [SystemMouseCursors.click] will be used.
/// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
/// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
///
/// * [MaterialState.selected].
///
/// If null, then the value of [BottomNavigationBarThemeData.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<MouseCursor>].
final MouseCursor? mouseCursor;
/// Whether detected gestures should provide acoustic and/or haptic feedback.
......@@ -941,10 +953,17 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
);
break;
}
final MouseCursor effectiveMouseCursor = widget.mouseCursor ?? SystemMouseCursors.click;
final List<Widget> tiles = <Widget>[];
for (int i = 0; i < widget.items.length; i++) {
final Set<MaterialState> states = <MaterialState>{
if (i == widget.currentIndex) MaterialState.selected,
};
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(widget.mouseCursor, states)
?? bottomTheme.mouseCursor?.resolve(states)
?? MaterialStateMouseCursor.clickable.resolve(states);
tiles.add(_BottomNavigationTile(
_effectiveType,
widget.items[i],
......
......@@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'bottom_navigation_bar.dart';
import 'material_state.dart';
import 'theme.dart';
/// Defines default property values for descendant [BottomNavigationBar]
......@@ -45,6 +46,7 @@ class BottomNavigationBarThemeData with Diagnosticable {
this.type,
this.enableFeedback,
this.landscapeLayout,
this.mouseCursor,
});
/// The color of the [BottomNavigationBar] itself.
......@@ -124,6 +126,9 @@ class BottomNavigationBarThemeData with Diagnosticable {
/// If non-null, overrides the [BottomNavigationBar.landscapeLayout] property.
final BottomNavigationBarLandscapeLayout? landscapeLayout;
/// If specified, overrides the default value of [BottomNavigationBar.mouseCursor].
final MaterialStateProperty<MouseCursor?>? mouseCursor;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
BottomNavigationBarThemeData copyWith({
......@@ -139,7 +144,8 @@ class BottomNavigationBarThemeData with Diagnosticable {
bool? showUnselectedLabels,
BottomNavigationBarType? type,
bool? enableFeedback,
BottomNavigationBarLandscapeLayout? landscapeLayout
BottomNavigationBarLandscapeLayout? landscapeLayout,
MaterialStateProperty<MouseCursor?>? mouseCursor,
}) {
return BottomNavigationBarThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
......@@ -155,6 +161,7 @@ class BottomNavigationBarThemeData with Diagnosticable {
type: type ?? this.type,
enableFeedback: enableFeedback ?? this.enableFeedback,
landscapeLayout: landscapeLayout ?? this.landscapeLayout,
mouseCursor: mouseCursor ?? this.mouseCursor,
);
}
......@@ -179,6 +186,7 @@ class BottomNavigationBarThemeData with Diagnosticable {
type: t < 0.5 ? a?.type : b?.type,
enableFeedback: t < 0.5 ? a?.enableFeedback : b?.enableFeedback,
landscapeLayout: t < 0.5 ? a?.landscapeLayout : b?.landscapeLayout,
mouseCursor: t < 0.5 ? a?.mouseCursor : b?.mouseCursor,
);
}
......@@ -198,6 +206,7 @@ class BottomNavigationBarThemeData with Diagnosticable {
type,
enableFeedback,
landscapeLayout,
mouseCursor,
);
}
......@@ -220,7 +229,8 @@ class BottomNavigationBarThemeData with Diagnosticable {
&& other.showUnselectedLabels == showUnselectedLabels
&& other.type == type
&& other.enableFeedback == enableFeedback
&& other.landscapeLayout == landscapeLayout;
&& other.landscapeLayout == landscapeLayout
&& other.mouseCursor == mouseCursor;
}
@override
......@@ -239,6 +249,7 @@ class BottomNavigationBarThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<BottomNavigationBarType>('type', type, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('enableFeedback', enableFeedback, defaultValue: null));
properties.add(DiagnosticsProperty<BottomNavigationBarLandscapeLayout>('landscapeLayout', landscapeLayout, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<MouseCursor?>>('mouseCursor', mouseCursor, defaultValue: null));
}
}
......
......@@ -2,8 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:vector_math/vector_math_64.dart' show Vector3;
......@@ -28,6 +30,7 @@ void main() {
expect(themeData.showUnselectedLabels, null);
expect(themeData.type, null);
expect(themeData.landscapeLayout, null);
expect(themeData.mouseCursor, null);
const BottomNavigationBarTheme theme = BottomNavigationBarTheme(data: BottomNavigationBarThemeData(), child: SizedBox());
expect(theme.data.backgroundColor, null);
......@@ -42,6 +45,7 @@ void main() {
expect(theme.data.showUnselectedLabels, null);
expect(theme.data.type, null);
expect(themeData.landscapeLayout, null);
expect(themeData.mouseCursor, null);
});
testWidgets('Default BottomNavigationBarThemeData debugFillProperties', (WidgetTester tester) async {
......@@ -70,6 +74,7 @@ void main() {
showSelectedLabels: true,
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
mouseCursor: MaterialStateMouseCursor.clickable,
).debugFillProperties(builder);
final List<String> description = builder.properties
......@@ -93,6 +98,7 @@ void main() {
expect(description[8], 'showSelectedLabels: true');
expect(description[9], 'showUnselectedLabels: true');
expect(description[10], 'type: BottomNavigationBarType.fixed');
expect(description[11], 'mouseCursor: MaterialStateMouseCursor(clickable)');
});
testWidgets('BottomNavigationBar is themeable', (WidgetTester tester) async {
......@@ -108,7 +114,7 @@ void main() {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: backgroundColor,
selectedItemColor: selectedItemColor,
unselectedItemColor: unselectedItemColor,
......@@ -120,6 +126,12 @@ void main() {
type: BottomNavigationBarType.fixed,
selectedLabelStyle: selectedTextStyle,
unselectedLabelStyle: unselectedTextStyle,
mouseCursor: MaterialStateProperty.resolveWith<MouseCursor?>((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return SystemMouseCursors.grab;
}
return SystemMouseCursors.move;
}),
),
),
home: Scaffold(
......@@ -166,6 +178,24 @@ void main() {
expect(findFadeTransition, findsNothing);
expect(_material(tester).elevation, equals(elevation));
expect(_material(tester).color, equals(backgroundColor));
final Offset selectedBarItem = tester.getCenter(
find.ancestor(of: find.text('AC'),
matching: find.byType(Transform))
);
final Offset unselectedBarItem = tester.getCenter(
find.ancestor(of: find.text('Alarm'),
matching: find.byType(Transform))
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(selectedBarItem);
await tester.pumpAndSettle();
expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.grab);
await gesture.moveTo(unselectedBarItem);
await tester.pumpAndSettle();
expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.move);
});
testWidgets('BottomNavigationBar properties are taken over the theme values', (WidgetTester tester) async {
......@@ -178,6 +208,7 @@ void main() {
const TextStyle themeUnselectedTextStyle = TextStyle(fontSize: 21);
const double themeElevation = 9.0;
const BottomNavigationBarLandscapeLayout themeLandscapeLayout = BottomNavigationBarLandscapeLayout.centered;
const MaterialStateMouseCursor themeCursor = MaterialStateMouseCursor.clickable;
const Color backgroundColor = Color(0xFF000004);
const Color selectedItemColor = Color(0xFF000005);
......@@ -188,6 +219,7 @@ void main() {
const TextStyle unselectedTextStyle = TextStyle(fontSize: 26);
const double elevation = 7.0;
const BottomNavigationBarLandscapeLayout landscapeLayout = BottomNavigationBarLandscapeLayout.spread;
const MaterialStateMouseCursor cursor = MaterialStateMouseCursor.textable;
await tester.pumpWidget(
MaterialApp(
......@@ -205,6 +237,7 @@ void main() {
selectedLabelStyle: themeSelectedTextStyle,
unselectedLabelStyle: themeUnselectedTextStyle,
landscapeLayout: themeLandscapeLayout,
mouseCursor: themeCursor,
),
),
home: Scaffold(
......@@ -221,6 +254,7 @@ void main() {
selectedLabelStyle: selectedTextStyle,
unselectedLabelStyle: unselectedTextStyle,
landscapeLayout: landscapeLayout,
mouseCursor: cursor,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.ac_unit),
......@@ -263,6 +297,17 @@ void main() {
expect(findFadeTransition, findsNothing);
expect(_material(tester).elevation, equals(elevation));
expect(_material(tester).color, equals(backgroundColor));
final Offset barItem = tester.getCenter(
find.ancestor(of: find.text('AC'),
matching: find.byType(Transform))
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
addTearDown(gesture.removePointer);
await gesture.moveTo(barItem);
await tester.pumpAndSettle();
expect(RendererBinding.instance!.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
});
testWidgets('BottomNavigationBarTheme can be used to hide all labels', (WidgetTester tester) async {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment