Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
502edd48
Unverified
Commit
502edd48
authored
Oct 19, 2021
by
Markus Aksli
Committed by
GitHub
Oct 19, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Reland "Add TooltipVisibility widget" (#92090)
parent
8474f41e
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
325 additions
and
18 deletions
+325
-18
material.dart
packages/flutter/lib/material.dart
+1
-0
tooltip.dart
packages/flutter/lib/src/material/tooltip.dart
+34
-18
tooltip_theme.dart
packages/flutter/lib/src/material/tooltip_theme.dart
+4
-0
tooltip_visibility.dart
packages/flutter/lib/src/material/tooltip_visibility.dart
+63
-0
tooltip_visibility_test.dart
packages/flutter/test/material/tooltip_visibility_test.dart
+223
-0
No files found.
packages/flutter/lib/material.dart
View file @
502edd48
...
...
@@ -158,6 +158,7 @@ export 'src/material/toggle_buttons_theme.dart';
export
'src/material/toggleable.dart'
;
export
'src/material/tooltip.dart'
;
export
'src/material/tooltip_theme.dart'
;
export
'src/material/tooltip_visibility.dart'
;
export
'src/material/typography.dart'
;
export
'src/material/user_accounts_drawer_header.dart'
;
export
'widgets.dart'
;
packages/flutter/lib/src/material/tooltip.dart
View file @
502edd48
...
...
@@ -14,6 +14,7 @@ import 'colors.dart';
import
'feedback.dart'
;
import
'theme.dart'
;
import
'tooltip_theme.dart'
;
import
'tooltip_visibility.dart'
;
/// A material design tooltip.
///
...
...
@@ -69,6 +70,7 @@ import 'tooltip_theme.dart';
///
/// * <https://material.io/design/components/tooltips.html>
/// * [TooltipTheme] or [ThemeData.tooltipTheme]
/// * [TooltipVisibility]
class
Tooltip
extends
StatefulWidget
{
/// Creates a tooltip.
///
...
...
@@ -327,6 +329,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
late
bool
enableFeedback
;
late
bool
_isConcealed
;
late
bool
_forceRemoval
;
late
bool
_visible
;
/// The plain text message for this tooltip.
///
...
...
@@ -352,6 +355,12 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
GestureBinding
.
instance
!.
pointerRouter
.
addGlobalRoute
(
_handlePointerEvent
);
}
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
_visible
=
TooltipVisibility
.
of
(
context
);
}
// https://material.io/components/tooltips#specs
double
_getDefaultTooltipHeight
()
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
...
...
@@ -483,8 +492,11 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
/// Shows the tooltip if it is not already visible.
///
/// Returns `false` when the tooltip was already visible.
/// Returns `false` when the tooltip shouldn't be shown or when the tooltip
/// was already visible.
bool
ensureTooltipVisible
()
{
if
(!
_visible
)
return
false
;
_showTimer
?.
cancel
();
_showTimer
=
null
;
_forceRemoval
=
false
;
...
...
@@ -671,27 +683,31 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
triggerMode
=
widget
.
triggerMode
??
tooltipTheme
.
triggerMode
??
_defaultTriggerMode
;
enableFeedback
=
widget
.
enableFeedback
??
tooltipTheme
.
enableFeedback
??
_defaultEnableFeedback
;
Widget
result
=
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onLongPress:
(
triggerMode
==
TooltipTriggerMode
.
longPress
)
?
_handlePress
:
null
,
onTap:
(
triggerMode
==
TooltipTriggerMode
.
tap
)
?
_handlePress
:
null
,
excludeFromSemantics:
true
,
child:
Semantics
(
label:
excludeFromSemantics
?
null
:
_tooltipMessage
,
child:
widget
.
child
,
),
Widget
result
=
Semantics
(
label:
excludeFromSemantics
?
null
:
_tooltipMessage
,
child:
widget
.
child
,
);
// Only check for hovering if there is a mouse connected.
if
(
_mouseIsConnected
)
{
result
=
MouseRegion
(
onEnter:
(
_
)
=>
_handleMouseEnter
(),
onExit:
(
_
)
=>
_handleMouseExit
(),
// Only check for gestures if tooltip should be visible.
if
(
_visible
)
{
result
=
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onLongPress:
(
triggerMode
==
TooltipTriggerMode
.
longPress
)
?
_handlePress
:
null
,
onTap:
(
triggerMode
==
TooltipTriggerMode
.
tap
)
?
_handlePress
:
null
,
excludeFromSemantics:
true
,
child:
result
,
);
// Only check for hovering if there is a mouse connected.
if
(
_mouseIsConnected
)
{
result
=
MouseRegion
(
onEnter:
(
_
)
=>
_handleMouseEnter
(),
onExit:
(
_
)
=>
_handleMouseExit
(),
child:
result
,
);
}
}
return
result
;
...
...
packages/flutter/lib/src/material/tooltip_theme.dart
View file @
502edd48
...
...
@@ -242,6 +242,10 @@ class TooltipThemeData with Diagnosticable {
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [TooltipVisibility], which can be used to visually disable descendant [Tooltip]s.
class
TooltipTheme
extends
InheritedTheme
{
/// Creates a tooltip theme that controls the configurations for
/// [Tooltip].
...
...
packages/flutter/lib/src/material/tooltip_visibility.dart
0 → 100644
View file @
502edd48
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/widgets.dart'
;
class
_TooltipVisibilityScope
extends
InheritedWidget
{
const
_TooltipVisibilityScope
({
Key
?
key
,
required
Widget
child
,
required
this
.
visible
,
})
:
super
(
key:
key
,
child:
child
);
final
bool
visible
;
@override
bool
updateShouldNotify
(
_TooltipVisibilityScope
old
)
{
return
old
.
visible
!=
visible
;
}
}
/// Overrides the visibility of descendant [Tooltip] widgets.
///
/// If disabled, the descendant [Tooltip] widgets will not display a tooltip
/// when tapped, long-pressed, hovered by the mouse, or when
/// `ensureTooltipVisible` is called. This only visually disables tooltips but
/// continues to provide any semantic information that is provided.
class
TooltipVisibility
extends
StatelessWidget
{
/// Creates a widget that configures the visibility of [Tooltip].
///
/// Both arguments must not be null.
const
TooltipVisibility
({
Key
?
key
,
required
this
.
visible
,
required
this
.
child
,
})
:
super
(
key:
key
);
/// The widget below this widget in the tree.
///
/// The entire app can be wrapped in this widget to globally control [Tooltip]
/// visibility.
///
/// {@macro flutter.widgets.ProxyWidget.child}
final
Widget
child
;
/// Determines the visibility of [Tooltip] widgets that inherit from this widget.
final
bool
visible
;
/// The [visible] of the closest instance of this class that encloses the
/// given context. Defaults to `true` if none are found.
static
bool
of
(
BuildContext
context
)
{
final
_TooltipVisibilityScope
?
visibility
=
context
.
dependOnInheritedWidgetOfExactType
<
_TooltipVisibilityScope
>();
return
visibility
?.
visible
??
true
;
}
@override
Widget
build
(
BuildContext
context
)
{
return
_TooltipVisibilityScope
(
visible:
visible
,
child:
child
,
);
}
}
packages/flutter/test/material/tooltip_visibility_test.dart
0 → 100644
View file @
502edd48
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:ui'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
_ensureTooltipVisible
(
GlobalKey
key
)
{
// This function uses "as dynamic" to defeat the static analysis. In general
// you want to avoid using this style in your code, as it will cause the
// analyzer to be unable to help you catch errors.
//
// In this case, we do it because we are trying to call internal methods of
// the tooltip code in order to test it. Normally, the state of a tooltip is a
// private class, but by using a GlobalKey we can get a handle to that object
// and by using "as dynamic" we can bypass the analyzer's type checks and call
// methods that we aren't supposed to be able to know about.
//
// It's ok to do this in tests, but you really don't want to do it in
// production code.
// ignore: avoid_dynamic_calls
(
key
.
currentState
as
dynamic
).
ensureTooltipVisible
();
}
const
String
tooltipText
=
'TIP'
;
void
main
(
)
{
testWidgets
(
'Tooltip does not build MouseRegion when mouse is detected and in TooltipVisibility with visibility = false'
,
(
WidgetTester
tester
)
async
{
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
addTearDown
(()
async
{
if
(
gesture
!=
null
)
return
gesture
.
removePointer
();
});
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
const
Offset
(
1.0
,
1.0
));
await
tester
.
pump
();
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
TooltipVisibility
(
visible:
false
,
child:
Tooltip
(
message:
tooltipText
,
child:
SizedBox
(
width:
100.0
,
height:
100.0
,
),
),
),
),
);
expect
(
find
.
descendant
(
of:
find
.
byType
(
Tooltip
),
matching:
find
.
byType
(
MouseRegion
)),
findsNothing
);
});
testWidgets
(
'Tooltip does not show when hovered when in TooltipVisibility with visible = false'
,
(
WidgetTester
tester
)
async
{
const
Duration
waitDuration
=
Duration
.
zero
;
final
TestGesture
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
addTearDown
(()
async
{
if
(
gesture
!=
null
)
return
gesture
.
removePointer
();
});
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
const
Offset
(
1.0
,
1.0
));
await
tester
.
pump
();
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
Center
(
child:
TooltipVisibility
(
visible:
false
,
child:
Tooltip
(
message:
tooltipText
,
waitDuration:
waitDuration
,
child:
SizedBox
(
width:
100.0
,
height:
100.0
,
),
),
),
),
),
);
final
Finder
tooltip
=
find
.
byType
(
Tooltip
);
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pump
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
tooltip
));
await
tester
.
pump
();
// Wait for it to appear.
await
tester
.
pump
(
waitDuration
);
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
});
testWidgets
(
'Tooltip shows when hovered when in TooltipVisibility with visible = true'
,
(
WidgetTester
tester
)
async
{
const
Duration
waitDuration
=
Duration
.
zero
;
TestGesture
?
gesture
=
await
tester
.
createGesture
(
kind:
PointerDeviceKind
.
mouse
);
addTearDown
(()
async
{
if
(
gesture
!=
null
)
return
gesture
.
removePointer
();
});
await
gesture
.
addPointer
();
await
gesture
.
moveTo
(
const
Offset
(
1.0
,
1.0
));
await
tester
.
pump
();
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
Center
(
child:
TooltipVisibility
(
visible:
true
,
child:
Tooltip
(
message:
tooltipText
,
waitDuration:
waitDuration
,
child:
SizedBox
(
width:
100.0
,
height:
100.0
,
),
),
),
),
),
);
final
Finder
tooltip
=
find
.
byType
(
Tooltip
);
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pump
();
await
gesture
.
moveTo
(
tester
.
getCenter
(
tooltip
));
await
tester
.
pump
();
// Wait for it to appear.
await
tester
.
pump
(
waitDuration
);
expect
(
find
.
text
(
tooltipText
),
findsOneWidget
);
// Wait for it to disappear.
await
gesture
.
moveTo
(
Offset
.
zero
);
await
tester
.
pumpAndSettle
();
await
gesture
.
removePointer
();
gesture
=
null
;
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
});
testWidgets
(
'Tooltip does not build GestureDetector when in TooltipVisibility with visibility = false'
,
(
WidgetTester
tester
)
async
{
await
setWidgetForTooltipMode
(
tester
,
TooltipTriggerMode
.
tap
,
false
);
expect
(
find
.
byType
(
GestureDetector
),
findsNothing
);
});
testWidgets
(
'Tooltip triggers on tap when trigger mode is tap and in TooltipVisibility with visible = true'
,
(
WidgetTester
tester
)
async
{
await
setWidgetForTooltipMode
(
tester
,
TooltipTriggerMode
.
tap
,
true
);
final
Finder
tooltip
=
find
.
byType
(
Tooltip
);
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
await
testGestureTap
(
tester
,
tooltip
);
expect
(
find
.
text
(
tooltipText
),
findsOneWidget
);
});
testWidgets
(
'Tooltip does not trigger manually when in TooltipVisibility with visible = false'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
TooltipVisibility
(
visible:
false
,
child:
Tooltip
(
key:
key
,
message:
tooltipText
,
child:
const
SizedBox
(
width:
100.0
,
height:
100.0
),
),
),
),
);
_ensureTooltipVisible
(
key
);
await
tester
.
pump
();
expect
(
find
.
text
(
tooltipText
),
findsNothing
);
});
testWidgets
(
'Tooltip triggers manually when in TooltipVisibility with visible = true'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
TooltipVisibility
(
visible:
true
,
child:
Tooltip
(
key:
key
,
message:
tooltipText
,
child:
const
SizedBox
(
width:
100.0
,
height:
100.0
),
),
),
),
);
_ensureTooltipVisible
(
key
);
await
tester
.
pump
();
expect
(
find
.
text
(
tooltipText
),
findsOneWidget
);
});
}
Future
<
void
>
setWidgetForTooltipMode
(
WidgetTester
tester
,
TooltipTriggerMode
triggerMode
,
bool
visibility
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
home:
TooltipVisibility
(
visible:
visibility
,
child:
Tooltip
(
message:
tooltipText
,
triggerMode:
triggerMode
,
child:
const
SizedBox
(
width:
100.0
,
height:
100.0
),
),
),
),
);
}
Future
<
void
>
testGestureTap
(
WidgetTester
tester
,
Finder
tooltip
)
async
{
await
tester
.
tap
(
tooltip
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
10
));
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment