Commit b8fb46e4 authored by Adam Barth's avatar Adam Barth

Improve tooltip behavior (#4284)

As requested by the material design team.

Fixes #4182
parent 787fb3be
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
...@@ -12,7 +13,7 @@ import 'typography.dart'; ...@@ -12,7 +13,7 @@ import 'typography.dart';
const double _kScreenEdgeMargin = 10.0; const double _kScreenEdgeMargin = 10.0;
const Duration _kFadeDuration = const Duration(milliseconds: 200); const Duration _kFadeDuration = const Duration(milliseconds: 200);
const Duration _kShowDuration = const Duration(seconds: 2); const Duration _kShowDuration = const Duration(milliseconds: 1500);
/// A material design tooltip. /// A material design tooltip.
/// ///
...@@ -105,21 +106,8 @@ class _TooltipState extends State<Tooltip> { ...@@ -105,21 +106,8 @@ class _TooltipState extends State<Tooltip> {
} }
void _handleStatusChanged(AnimationStatus status) { void _handleStatusChanged(AnimationStatus status) {
switch (status) { if (status == AnimationStatus.dismissed)
case AnimationStatus.completed: _removeEntry();
assert(_entry != null);
assert(_timer == null);
resetShowTimer();
break;
case AnimationStatus.dismissed:
assert(_entry != null);
assert(_timer == null);
_entry.remove();
_entry = null;
break;
default:
break;
}
} }
@override @override
...@@ -134,14 +122,9 @@ class _TooltipState extends State<Tooltip> { ...@@ -134,14 +122,9 @@ class _TooltipState extends State<Tooltip> {
_entry.markNeedsBuild(); _entry.markNeedsBuild();
} }
void resetShowTimer() { void ensureTooltipVisible() {
assert(_controller.status == AnimationStatus.completed); if (_entry != null)
assert(_entry != null); return; // Already visible.
_timer = new Timer(_kShowDuration, hideTooltip);
}
void showTooltip() {
if (_entry == null) {
RenderBox box = context.findRenderObject(); RenderBox box = context.findRenderObject();
Point target = box.localToGlobal(box.size.center(Point.origin)); Point target = box.localToGlobal(box.size.center(Point.origin));
_entry = new OverlayEntry(builder: (BuildContext context) { _entry = new OverlayEntry(builder: (BuildContext context) {
...@@ -159,36 +142,38 @@ class _TooltipState extends State<Tooltip> { ...@@ -159,36 +142,38 @@ class _TooltipState extends State<Tooltip> {
); );
}); });
Overlay.of(context, debugRequiredFor: config).insert(_entry); Overlay.of(context, debugRequiredFor: config).insert(_entry);
} GestureBinding.instance.pointerRouter.addGlobalRoute(_handlePointerEvent);
_timer?.cancel();
if (_controller.status != AnimationStatus.completed) {
_timer = null;
_controller.forward(); _controller.forward();
} else {
resetShowTimer();
}
} }
void hideTooltip() { void _removeEntry() {
assert(_entry != null); assert(_entry != null);
_timer?.cancel(); _timer?.cancel();
_timer = null; _timer = null;
_entry.remove();
_entry = null;
GestureBinding.instance.pointerRouter.removeGlobalRoute(_handlePointerEvent);
}
void _handlePointerEvent(PointerEvent event) {
assert(_entry != null);
if (event is PointerUpEvent || event is PointerCancelEvent)
_timer = new Timer(_kShowDuration, _controller.reverse);
else if (event is PointerDownEvent)
_controller.reverse(); _controller.reverse();
} }
@override @override
void deactivate() { void deactivate() {
if (_entry != null) if (_entry != null)
hideTooltip(); _controller.reverse();
super.deactivate(); super.deactivate();
} }
@override @override
void dispose() { void dispose() {
_controller.stop(); if (_entry != null)
_entry?.remove(); _removeEntry();
_entry = null;
assert(_timer == null);
super.dispose(); super.dispose();
} }
...@@ -197,7 +182,7 @@ class _TooltipState extends State<Tooltip> { ...@@ -197,7 +182,7 @@ class _TooltipState extends State<Tooltip> {
assert(Overlay.of(context, debugRequiredFor: config) != null); assert(Overlay.of(context, debugRequiredFor: config) != null);
return new GestureDetector( return new GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onLongPress: showTooltip, onLongPress: ensureTooltipVisible,
excludeFromSemantics: true, excludeFromSemantics: true,
child: new Semantics( child: new Semantics(
label: config.message, label: config.message,
......
...@@ -57,7 +57,7 @@ void main() { ...@@ -57,7 +57,7 @@ void main() {
] ]
) )
); );
(key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen /********************* 800x600 screen
...@@ -105,7 +105,7 @@ void main() { ...@@ -105,7 +105,7 @@ void main() {
] ]
) )
); );
(key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen /********************* 800x600 screen
...@@ -154,7 +154,7 @@ void main() { ...@@ -154,7 +154,7 @@ void main() {
] ]
) )
); );
(key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen /********************* 800x600 screen
...@@ -205,7 +205,7 @@ void main() { ...@@ -205,7 +205,7 @@ void main() {
] ]
) )
); );
(key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
// we try to put it here but it doesn't fit: // we try to put it here but it doesn't fit:
...@@ -267,7 +267,7 @@ void main() { ...@@ -267,7 +267,7 @@ void main() {
] ]
) )
); );
(key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen /********************* 800x600 screen
...@@ -317,7 +317,7 @@ void main() { ...@@ -317,7 +317,7 @@ void main() {
] ]
) )
); );
(key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen /********************* 800x600 screen
...@@ -369,7 +369,7 @@ void main() { ...@@ -369,7 +369,7 @@ void main() {
] ]
) )
); );
(key.currentState as dynamic).showTooltip(); // before using "as dynamic" in your code, see note top of file (key.currentState as dynamic).ensureTooltipVisible(); // before using "as dynamic" in your code, see note top of file
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
/********************* 800x600 screen /********************* 800x600 screen
...@@ -434,7 +434,7 @@ void main() { ...@@ -434,7 +434,7 @@ void main() {
client.updates.clear(); client.updates.clear();
// before using "as dynamic" in your code, see note top of file // before using "as dynamic" in your code, see note top of file
(key.currentState as dynamic).showTooltip(); // this triggers a rebuild of the semantics because the tree changes (key.currentState as dynamic).ensureTooltipVisible(); // this triggers a rebuild of the semantics because the tree changes
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
expect(client.updates.length, equals(2)); expect(client.updates.length, equals(2));
......
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