Unverified Commit 212e3f4e authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

More mouse tweaks (#82293)

parent 99d49edc
...@@ -399,7 +399,7 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> { ...@@ -399,7 +399,7 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> {
void handleHover(PointerHoverEvent event) { void handleHover(PointerHoverEvent event) {
super.handleHover(event); super.handleHover(event);
// Check if the position of the pointer falls over the painted scrollbar // Check if the position of the pointer falls over the painted scrollbar
if (isPointerOverScrollbar(event.position, event.kind)) { if (isPointerOverScrollbar(event.position, event.kind, forHover: true)) {
// Pointer is hovering over the scrollbar // Pointer is hovering over the scrollbar
setState(() { _hoverIsActive = true; }); setState(() { _hoverIsActive = true; });
_hoverAnimationController.forward(); _hoverAnimationController.forward();
......
...@@ -481,22 +481,35 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { ...@@ -481,22 +481,35 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
/// Same as hitTest, but includes some padding when the [PointerEvent] is /// Same as hitTest, but includes some padding when the [PointerEvent] is
/// caused by [PointerDeviceKind.touch] to make sure that the region /// caused by [PointerDeviceKind.touch] to make sure that the region
/// isn't too small to be interacted with by the user. /// isn't too small to be interacted with by the user.
bool hitTestInteractive(Offset position, PointerDeviceKind kind) { ///
/// The hit test area for hovering with [PointerDeviceKind.mouse] over the
/// scrollbar also uses this extra padding. This is to make it easier to
/// interact with the scrollbar by presenting it to the mouse for interaction
/// based on proximity. When `forHover` is true, the larger hit test area will
/// be used.
bool hitTestInteractive(Offset position, PointerDeviceKind kind, { bool forHover = false }) {
if (_thumbRect == null) { if (_thumbRect == null) {
// We have never painted the scrollbar, so we do not know where it will be.
return false; return false;
} }
// The scrollbar is not able to be hit when transparent.
final Rect interactiveRect = _trackRect ?? _thumbRect!;
final Rect paddedRect = interactiveRect.expandToInclude(
Rect.fromCircle(center: _thumbRect!.center, radius: _kMinInteractiveSize / 2),
);
// The scrollbar is not able to be hit when transparent - except when
// hovering with a mouse. This should bring the scrollbar into view so the
// mouse can interact with it.
if (fadeoutOpacityAnimation.value == 0.0) { if (fadeoutOpacityAnimation.value == 0.0) {
if (forHover && kind == PointerDeviceKind.mouse)
return paddedRect.contains(position);
return false; return false;
} }
final Rect interactiveRect = _trackRect ?? _thumbRect!;
switch (kind) { switch (kind) {
case PointerDeviceKind.touch: case PointerDeviceKind.touch:
final Rect touchScrollbarRect = interactiveRect.expandToInclude( return paddedRect.contains(position);
Rect.fromCircle(center: _thumbRect!.center, radius: _kMinInteractiveSize / 2),
);
return touchScrollbarRect.contains(position);
case PointerDeviceKind.mouse: case PointerDeviceKind.mouse:
case PointerDeviceKind.stylus: case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus: case PointerDeviceKind.invertedStylus:
...@@ -1280,13 +1293,18 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1280,13 +1293,18 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
} }
/// Returns true if the provided [Offset] is located over the track or thumb /// Returns true if the provided [Offset] is located over the track or thumb
/// of the [RawScrollbar]. /// of the [RawScrollbar].
///
/// The hit test area for mouse hovering over the scrollbar is larger than
/// regular hit testing. This is to make it easier to interact with the
/// scrollbar and present it to the mouse for interaction based on proximity.
/// When `forHover` is true, the larger hit test area will be used.
@protected @protected
bool isPointerOverScrollbar(Offset position, PointerDeviceKind kind) { bool isPointerOverScrollbar(Offset position, PointerDeviceKind kind, { bool forHover = false }) {
if (_scrollbarPainterKey.currentContext == null) { if (_scrollbarPainterKey.currentContext == null) {
return false; return false;
} }
final Offset localOffset = _getLocalOffset(_scrollbarPainterKey, position); final Offset localOffset = _getLocalOffset(_scrollbarPainterKey, position);
return scrollbarPainter.hitTestInteractive(localOffset, kind); return scrollbarPainter.hitTestInteractive(localOffset, kind, forHover: true);
} }
/// Cancels the fade out animation so the scrollbar will remain visible for /// Cancels the fade out animation so the scrollbar will remain visible for
...@@ -1301,8 +1319,11 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1301,8 +1319,11 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
@mustCallSuper @mustCallSuper
void handleHover(PointerHoverEvent event) { void handleHover(PointerHoverEvent event) {
// Check if the position of the pointer falls over the painted scrollbar // Check if the position of the pointer falls over the painted scrollbar
if (isPointerOverScrollbar(event.position, event.kind)) { if (isPointerOverScrollbar(event.position, event.kind, forHover: true)) {
_hoverIsActive = true; _hoverIsActive = true;
// Bring the scrollbar back into view if it has faded or started to fade
// away.
_fadeoutAnimationController.forward();
_fadeoutTimer?.cancel(); _fadeoutTimer?.cancel();
} else if (_hoverIsActive) { } else if (_hoverIsActive) {
// Pointer is not over painted scrollbar. // Pointer is not over painted scrollbar.
......
...@@ -697,6 +697,67 @@ void main() { ...@@ -697,6 +697,67 @@ void main() {
); );
}); });
testWidgets('Scrollbar will fade back in when hovering over known track area', (WidgetTester tester) async {
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData(),
child: RawScrollbar(
child: SingleChildScrollView(
child: SizedBox(width: 4000.0, height: 4000.0),
),
),
),
),
);
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(SingleChildScrollView)));
await gesture.moveBy(const Offset(0.0, -20.0));
await tester.pump();
// Scrollbar fully showing
await tester.pump(const Duration(milliseconds: 500));
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0),
color: const Color(0x66BCBCBC),
),
);
await gesture.up();
await tester.pump(_kScrollbarTimeToFade);
await tester.pump(_kScrollbarFadeDuration * 0.5);
// Scrollbar is fading out
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0),
color: const Color(0x4fbcbcbc),
),
);
// Hover over scrollbar with mouse to bring opacity back up
final TestGesture mouseGesture = await tester.createGesture(kind: ui.PointerDeviceKind.mouse);
await mouseGesture.addPointer();
addTearDown(mouseGesture.removePointer);
await mouseGesture.moveTo(const Offset(794.0, 5.0));
await tester.pumpAndSettle();
// Scrollbar should be visible
expect(
find.byType(RawScrollbar),
paints
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
..rect(
rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0),
color: const Color(0x66BCBCBC),
),
);
});
testWidgets('Scrollbar thumb can be dragged', (WidgetTester tester) async { testWidgets('Scrollbar thumb can be dragged', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
await tester.pumpWidget( await tester.pumpWidget(
......
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