[beta] CP request for https://github.com/flutter/flutter/pull/167677 (#168386)
### CP request for https://github.com/flutter/flutter/pull/167677 into flutter-3.32-candidate.0 **Impacted Users:** Some subset of widget inspector users with a specific configuration of `package:go_router`, see upvotes and comments on https://github.com/flutter/flutter/issues/166118. **Impact Description:** Depending on how users have configured their routes using `package:go_router`, enabling / disabling the widget inspector can be destructive to their app's routing state, preventing them from inspecting widgets on secondary screens of their app. **Workaround:** No workaround available. **Risk:** Low **Test Coverage:** Yes, tests were added and this has been manually tested as well **Validation Steps:** - Run a Flutter app - Open the DevTools Inspector for the running app - Toggle "Select widget mode" - An additional button has been added to the on-device inspector that allows developers to both interact with their app (e.g. navigate to a new page) and select widgets while in Widget Selection mode. See gif below. 
This commit is contained in:
parent
87cd28951e
commit
8545480266
@ -15,6 +15,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
|
|
||||||
import 'button.dart';
|
import 'button.dart';
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
|
import 'constants.dart';
|
||||||
import 'icons.dart';
|
import 'icons.dart';
|
||||||
import 'interface_level.dart';
|
import 'interface_level.dart';
|
||||||
import 'localizations.dart';
|
import 'localizations.dart';
|
||||||
@ -534,46 +535,44 @@ class _CupertinoAppState extends State<CupertinoApp> {
|
|||||||
Widget _exitWidgetSelectionButtonBuilder(
|
Widget _exitWidgetSelectionButtonBuilder(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
required GlobalKey key,
|
required GlobalKey key,
|
||||||
}) {
|
}) {
|
||||||
return CupertinoButton(
|
return _CupertinoInspectorButton.filled(
|
||||||
key: key,
|
|
||||||
color: _widgetSelectionButtonsBackgroundColor(context),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
child: Icon(
|
semanticLabel: semanticLabel,
|
||||||
CupertinoIcons.xmark,
|
icon: CupertinoIcons.xmark,
|
||||||
size: 28.0,
|
buttonKey: key,
|
||||||
color: _widgetSelectionButtonsForegroundColor(context),
|
|
||||||
semanticLabel: 'Exit Select Widget mode.',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _moveExitWidgetSelectionButtonBuilder(
|
Widget _moveExitWidgetSelectionButtonBuilder(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
bool isLeftAligned = true,
|
bool isLeftAligned = true,
|
||||||
}) {
|
}) {
|
||||||
return CupertinoButton(
|
return _CupertinoInspectorButton.iconOnly(
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
padding: EdgeInsets.zero,
|
semanticLabel: semanticLabel,
|
||||||
child: Icon(
|
icon: isLeftAligned ? CupertinoIcons.arrow_right : CupertinoIcons.arrow_left,
|
||||||
isLeftAligned ? CupertinoIcons.arrow_right : CupertinoIcons.arrow_left,
|
|
||||||
size: 32.0,
|
|
||||||
color: _widgetSelectionButtonsBackgroundColor(context),
|
|
||||||
semanticLabel:
|
|
||||||
'Move "Exit Select Widget mode" button to the ${isLeftAligned ? 'right' : 'left'}.',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _widgetSelectionButtonsForegroundColor(BuildContext context) {
|
Widget _tapBehaviorButtonBuilder(
|
||||||
return CupertinoTheme.of(context).primaryContrastingColor;
|
BuildContext context, {
|
||||||
}
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
Color _widgetSelectionButtonsBackgroundColor(BuildContext context) {
|
required bool selectionOnTapEnabled,
|
||||||
return CupertinoTheme.of(context).primaryColor;
|
}) {
|
||||||
|
return _CupertinoInspectorButton.toggle(
|
||||||
|
onPressed: onPressed,
|
||||||
|
semanticLabel: semanticLabel,
|
||||||
|
// This icon is also used for the Material-styled button and for DevTools.
|
||||||
|
// It should be updated in all 3 places if changed.
|
||||||
|
icon: CupertinoIcons.cursor_rays,
|
||||||
|
toggledOn: selectionOnTapEnabled,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
WidgetsApp _buildWidgetApp(BuildContext context) {
|
WidgetsApp _buildWidgetApp(BuildContext context) {
|
||||||
@ -607,6 +606,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
|
|||||||
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
||||||
exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder,
|
exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder,
|
||||||
moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder,
|
moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder,
|
||||||
|
tapBehaviorButtonBuilder: _tapBehaviorButtonBuilder,
|
||||||
shortcuts: widget.shortcuts,
|
shortcuts: widget.shortcuts,
|
||||||
actions: widget.actions,
|
actions: widget.actions,
|
||||||
restorationScopeId: widget.restorationScopeId,
|
restorationScopeId: widget.restorationScopeId,
|
||||||
@ -642,6 +642,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
|
|||||||
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
||||||
exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder,
|
exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder,
|
||||||
moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder,
|
moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder,
|
||||||
|
tapBehaviorButtonBuilder: _tapBehaviorButtonBuilder,
|
||||||
shortcuts: widget.shortcuts,
|
shortcuts: widget.shortcuts,
|
||||||
actions: widget.actions,
|
actions: widget.actions,
|
||||||
restorationScopeId: widget.restorationScopeId,
|
restorationScopeId: widget.restorationScopeId,
|
||||||
@ -680,3 +681,83 @@ class _CupertinoAppState extends State<CupertinoApp> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CupertinoInspectorButton extends InspectorButton {
|
||||||
|
const _CupertinoInspectorButton.filled({
|
||||||
|
required super.onPressed,
|
||||||
|
required super.semanticLabel,
|
||||||
|
required super.icon,
|
||||||
|
super.buttonKey,
|
||||||
|
}) : super.filled();
|
||||||
|
|
||||||
|
const _CupertinoInspectorButton.toggle({
|
||||||
|
required super.onPressed,
|
||||||
|
required super.semanticLabel,
|
||||||
|
required super.icon,
|
||||||
|
super.toggledOn,
|
||||||
|
}) : super.toggle();
|
||||||
|
|
||||||
|
const _CupertinoInspectorButton.iconOnly({
|
||||||
|
required super.onPressed,
|
||||||
|
required super.semanticLabel,
|
||||||
|
required super.icon,
|
||||||
|
}) : super.iconOnly();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final Icon buttonIcon = Icon(
|
||||||
|
icon,
|
||||||
|
semanticLabel: semanticLabel,
|
||||||
|
size: iconSizeForVariant,
|
||||||
|
color: foregroundColor(context),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
key: buttonKey,
|
||||||
|
padding: const EdgeInsets.all(
|
||||||
|
(kMinInteractiveDimensionCupertino - InspectorButton.buttonSize) / 2,
|
||||||
|
),
|
||||||
|
child:
|
||||||
|
variant == InspectorButtonVariant.toggle && !toggledOn!
|
||||||
|
? CupertinoButton.tinted(
|
||||||
|
minSize: InspectorButton.buttonSize,
|
||||||
|
onPressed: onPressed,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: buttonIcon,
|
||||||
|
)
|
||||||
|
: CupertinoButton(
|
||||||
|
minSize: InspectorButton.buttonSize,
|
||||||
|
onPressed: onPressed,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
color: backgroundColor(context),
|
||||||
|
child: buttonIcon,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color foregroundColor(BuildContext context) {
|
||||||
|
final Color primaryColor = CupertinoTheme.of(context).primaryColor;
|
||||||
|
final Color secondaryColor = CupertinoTheme.of(context).primaryContrastingColor;
|
||||||
|
switch (variant) {
|
||||||
|
case InspectorButtonVariant.filled:
|
||||||
|
return secondaryColor;
|
||||||
|
case InspectorButtonVariant.iconOnly:
|
||||||
|
return primaryColor;
|
||||||
|
case InspectorButtonVariant.toggle:
|
||||||
|
return !toggledOn! ? primaryColor : secondaryColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color backgroundColor(BuildContext context) {
|
||||||
|
final Color primaryColor = CupertinoTheme.of(context).primaryColor;
|
||||||
|
switch (variant) {
|
||||||
|
case InspectorButtonVariant.filled:
|
||||||
|
case InspectorButtonVariant.toggle:
|
||||||
|
return primaryColor;
|
||||||
|
case InspectorButtonVariant.iconOnly:
|
||||||
|
return const Color(0x00000000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,8 +20,8 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'arc.dart';
|
import 'arc.dart';
|
||||||
|
import 'button_style.dart';
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
import 'floating_action_button.dart';
|
|
||||||
import 'icon_button.dart';
|
import 'icon_button.dart';
|
||||||
import 'icons.dart';
|
import 'icons.dart';
|
||||||
import 'material_localizations.dart';
|
import 'material_localizations.dart';
|
||||||
@ -29,6 +29,7 @@ import 'page.dart';
|
|||||||
import 'scaffold.dart' show ScaffoldMessenger, ScaffoldMessengerState;
|
import 'scaffold.dart' show ScaffoldMessenger, ScaffoldMessengerState;
|
||||||
import 'scrollbar.dart';
|
import 'scrollbar.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
|
import 'theme_data.dart';
|
||||||
import 'tooltip.dart';
|
import 'tooltip.dart';
|
||||||
|
|
||||||
// Examples can assume:
|
// Examples can assume:
|
||||||
@ -903,9 +904,6 @@ class MaterialScrollBehavior extends ScrollBehavior {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MaterialAppState extends State<MaterialApp> {
|
class _MaterialAppState extends State<MaterialApp> {
|
||||||
static const double _moveExitWidgetSelectionIconSize = 32;
|
|
||||||
static const double _moveExitWidgetSelectionTargetSize = 40;
|
|
||||||
|
|
||||||
late HeroController _heroController;
|
late HeroController _heroController;
|
||||||
|
|
||||||
bool get _usesRouter => widget.routerDelegate != null || widget.routerConfig != null;
|
bool get _usesRouter => widget.routerDelegate != null || widget.routerConfig != null;
|
||||||
@ -938,52 +936,47 @@ class _MaterialAppState extends State<MaterialApp> {
|
|||||||
Widget _exitWidgetSelectionButtonBuilder(
|
Widget _exitWidgetSelectionButtonBuilder(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
required GlobalKey key,
|
required GlobalKey key,
|
||||||
}) {
|
}) {
|
||||||
return FloatingActionButton(
|
return _MaterialInspectorButton.filled(
|
||||||
key: key,
|
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
mini: true,
|
semanticLabel: semanticLabel,
|
||||||
backgroundColor: _widgetSelectionButtonsBackgroundColor(context),
|
icon: Icons.close,
|
||||||
foregroundColor: _widgetSelectionButtonsForegroundColor(context),
|
isDarkTheme: _isDarkTheme(context),
|
||||||
child: const Icon(Icons.close, semanticLabel: 'Exit Select Widget mode.'),
|
buttonKey: key,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _moveExitWidgetSelectionButtonBuilder(
|
Widget _moveExitWidgetSelectionButtonBuilder(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
bool isLeftAligned = true,
|
bool isLeftAligned = true,
|
||||||
}) {
|
}) {
|
||||||
return IconButton(
|
return _MaterialInspectorButton.iconOnly(
|
||||||
color: _widgetSelectionButtonsBackgroundColor(context),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
iconSize: _moveExitWidgetSelectionIconSize,
|
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
constraints: const BoxConstraints(
|
semanticLabel: semanticLabel,
|
||||||
minWidth: _moveExitWidgetSelectionTargetSize,
|
icon: isLeftAligned ? Icons.arrow_right : Icons.arrow_left,
|
||||||
minHeight: _moveExitWidgetSelectionTargetSize,
|
isDarkTheme: _isDarkTheme(context),
|
||||||
),
|
|
||||||
icon: Icon(
|
|
||||||
isLeftAligned ? Icons.arrow_right : Icons.arrow_left,
|
|
||||||
semanticLabel:
|
|
||||||
'Move "Exit Select Widget mode" button to the ${isLeftAligned ? 'right' : 'left'}.',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _widgetSelectionButtonsForegroundColor(BuildContext context) {
|
Widget _tapBehaviorButtonBuilder(
|
||||||
final ThemeData theme = Theme.of(context);
|
BuildContext context, {
|
||||||
return _isDarkTheme(context)
|
required VoidCallback onPressed,
|
||||||
? theme.colorScheme.onPrimaryContainer
|
required String semanticLabel,
|
||||||
: theme.colorScheme.primaryContainer;
|
required bool selectionOnTapEnabled,
|
||||||
}
|
}) {
|
||||||
|
return _MaterialInspectorButton.toggle(
|
||||||
Color _widgetSelectionButtonsBackgroundColor(BuildContext context) {
|
onPressed: onPressed,
|
||||||
final ThemeData theme = Theme.of(context);
|
semanticLabel: semanticLabel,
|
||||||
return _isDarkTheme(context)
|
// This icon is also used for the Cupertino-styled button and for DevTools.
|
||||||
? theme.colorScheme.primaryContainer
|
// It should be updated in all 3 places if changed.
|
||||||
: theme.colorScheme.onPrimaryContainer;
|
icon: CupertinoIcons.cursor_rays,
|
||||||
|
isDarkTheme: _isDarkTheme(context),
|
||||||
|
toggledOn: selectionOnTapEnabled,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isDarkTheme(BuildContext context) {
|
bool _isDarkTheme(BuildContext context) {
|
||||||
@ -1100,6 +1093,7 @@ class _MaterialAppState extends State<MaterialApp> {
|
|||||||
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
||||||
exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder,
|
exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder,
|
||||||
moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder,
|
moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder,
|
||||||
|
tapBehaviorButtonBuilder: _tapBehaviorButtonBuilder,
|
||||||
shortcuts: widget.shortcuts,
|
shortcuts: widget.shortcuts,
|
||||||
actions: widget.actions,
|
actions: widget.actions,
|
||||||
restorationScopeId: widget.restorationScopeId,
|
restorationScopeId: widget.restorationScopeId,
|
||||||
@ -1135,6 +1129,7 @@ class _MaterialAppState extends State<MaterialApp> {
|
|||||||
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
|
||||||
exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder,
|
exitWidgetSelectionButtonBuilder: _exitWidgetSelectionButtonBuilder,
|
||||||
moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder,
|
moveExitWidgetSelectionButtonBuilder: _moveExitWidgetSelectionButtonBuilder,
|
||||||
|
tapBehaviorButtonBuilder: _tapBehaviorButtonBuilder,
|
||||||
shortcuts: widget.shortcuts,
|
shortcuts: widget.shortcuts,
|
||||||
actions: widget.actions,
|
actions: widget.actions,
|
||||||
restorationScopeId: widget.restorationScopeId,
|
restorationScopeId: widget.restorationScopeId,
|
||||||
@ -1173,3 +1168,101 @@ class _MaterialAppState extends State<MaterialApp> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _MaterialInspectorButton extends InspectorButton {
|
||||||
|
const _MaterialInspectorButton.filled({
|
||||||
|
required super.onPressed,
|
||||||
|
required super.semanticLabel,
|
||||||
|
required super.icon,
|
||||||
|
required this.isDarkTheme,
|
||||||
|
super.buttonKey,
|
||||||
|
}) : super.filled();
|
||||||
|
|
||||||
|
const _MaterialInspectorButton.toggle({
|
||||||
|
required super.onPressed,
|
||||||
|
required super.semanticLabel,
|
||||||
|
required super.icon,
|
||||||
|
required this.isDarkTheme,
|
||||||
|
super.toggledOn,
|
||||||
|
}) : super.toggle();
|
||||||
|
|
||||||
|
const _MaterialInspectorButton.iconOnly({
|
||||||
|
required super.onPressed,
|
||||||
|
required super.semanticLabel,
|
||||||
|
required super.icon,
|
||||||
|
required this.isDarkTheme,
|
||||||
|
}) : super.iconOnly();
|
||||||
|
|
||||||
|
final bool isDarkTheme;
|
||||||
|
|
||||||
|
static const EdgeInsets _buttonPadding = EdgeInsets.zero;
|
||||||
|
static const BoxConstraints _buttonConstraints = BoxConstraints.tightFor(
|
||||||
|
width: InspectorButton.buttonSize,
|
||||||
|
height: InspectorButton.buttonSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
key: buttonKey,
|
||||||
|
onPressed: onPressed,
|
||||||
|
iconSize: iconSizeForVariant,
|
||||||
|
padding: _buttonPadding,
|
||||||
|
constraints: _buttonConstraints,
|
||||||
|
style: _selectionButtonsIconStyle(context),
|
||||||
|
icon: Icon(icon, semanticLabel: semanticLabel),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ButtonStyle _selectionButtonsIconStyle(BuildContext context) {
|
||||||
|
final Color foreground = foregroundColor(context);
|
||||||
|
final Color background = backgroundColor(context);
|
||||||
|
|
||||||
|
return IconButton.styleFrom(
|
||||||
|
foregroundColor: foreground,
|
||||||
|
backgroundColor: background,
|
||||||
|
side:
|
||||||
|
variant == InspectorButtonVariant.toggle && !toggledOn!
|
||||||
|
? BorderSide(color: foreground)
|
||||||
|
: null,
|
||||||
|
tapTargetSize: MaterialTapTargetSize.padded,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color foregroundColor(BuildContext context) {
|
||||||
|
final Color primaryColor = _primaryColor(context);
|
||||||
|
final Color secondaryColor = _secondaryColor(context);
|
||||||
|
switch (variant) {
|
||||||
|
case InspectorButtonVariant.filled:
|
||||||
|
return primaryColor;
|
||||||
|
case InspectorButtonVariant.iconOnly:
|
||||||
|
return secondaryColor;
|
||||||
|
case InspectorButtonVariant.toggle:
|
||||||
|
return !toggledOn! ? secondaryColor : primaryColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color backgroundColor(BuildContext context) {
|
||||||
|
final Color secondaryColor = _secondaryColor(context);
|
||||||
|
switch (variant) {
|
||||||
|
case InspectorButtonVariant.filled:
|
||||||
|
return secondaryColor;
|
||||||
|
case InspectorButtonVariant.iconOnly:
|
||||||
|
return Colors.transparent;
|
||||||
|
case InspectorButtonVariant.toggle:
|
||||||
|
return !toggledOn! ? Colors.transparent : secondaryColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _primaryColor(BuildContext context) {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
return isDarkTheme ? theme.colorScheme.onPrimaryContainer : theme.colorScheme.primaryContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _secondaryColor(BuildContext context) {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
return isDarkTheme ? theme.colorScheme.primaryContainer : theme.colorScheme.onPrimaryContainer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -355,6 +355,7 @@ class WidgetsApp extends StatefulWidget {
|
|||||||
this.debugShowCheckedModeBanner = true,
|
this.debugShowCheckedModeBanner = true,
|
||||||
this.exitWidgetSelectionButtonBuilder,
|
this.exitWidgetSelectionButtonBuilder,
|
||||||
this.moveExitWidgetSelectionButtonBuilder,
|
this.moveExitWidgetSelectionButtonBuilder,
|
||||||
|
this.tapBehaviorButtonBuilder,
|
||||||
this.shortcuts,
|
this.shortcuts,
|
||||||
this.actions,
|
this.actions,
|
||||||
this.restorationScopeId,
|
this.restorationScopeId,
|
||||||
@ -446,6 +447,7 @@ class WidgetsApp extends StatefulWidget {
|
|||||||
this.debugShowCheckedModeBanner = true,
|
this.debugShowCheckedModeBanner = true,
|
||||||
this.exitWidgetSelectionButtonBuilder,
|
this.exitWidgetSelectionButtonBuilder,
|
||||||
this.moveExitWidgetSelectionButtonBuilder,
|
this.moveExitWidgetSelectionButtonBuilder,
|
||||||
|
this.tapBehaviorButtonBuilder,
|
||||||
this.shortcuts,
|
this.shortcuts,
|
||||||
this.actions,
|
this.actions,
|
||||||
this.restorationScopeId,
|
this.restorationScopeId,
|
||||||
@ -1041,19 +1043,27 @@ class WidgetsApp extends StatefulWidget {
|
|||||||
|
|
||||||
/// Builds the widget the [WidgetInspector] uses to exit selection mode.
|
/// Builds the widget the [WidgetInspector] uses to exit selection mode.
|
||||||
///
|
///
|
||||||
/// This lets [MaterialApp] to use a Material Design button to exit the
|
/// This lets [MaterialApp] and [CupertinoApp] use an appropriately styled
|
||||||
/// inspector select mode without requiring [WidgetInspector] to depend on the
|
/// button for their design systems without requiring [WidgetInspector] to
|
||||||
/// Material package.
|
/// depend on the Material or Cupertino packages.
|
||||||
final ExitWidgetSelectionButtonBuilder? exitWidgetSelectionButtonBuilder;
|
final ExitWidgetSelectionButtonBuilder? exitWidgetSelectionButtonBuilder;
|
||||||
|
|
||||||
/// Builds the widget the [WidgetInspector] uses to move the exit selection
|
/// Builds the widget the [WidgetInspector] uses to move the exit selection
|
||||||
/// mode button.
|
/// mode button.
|
||||||
///
|
///
|
||||||
/// This lets [MaterialApp] to use a Material Design button to change the
|
/// This lets [MaterialApp] and [CupertinoApp] use an appropriately styled
|
||||||
/// alignment without requiring [WidgetInspector] to depend on the Material
|
/// button for their design systems without requiring [WidgetInspector] to
|
||||||
/// package.
|
/// depend on the Material or Cupertino packages.
|
||||||
final MoveExitWidgetSelectionButtonBuilder? moveExitWidgetSelectionButtonBuilder;
|
final MoveExitWidgetSelectionButtonBuilder? moveExitWidgetSelectionButtonBuilder;
|
||||||
|
|
||||||
|
/// Builds the widget the [WidgetInspector] uses to change the default
|
||||||
|
/// behavior when tapping on widgets in the app.
|
||||||
|
///
|
||||||
|
/// This lets [MaterialApp] and [CupertinoApp] use an appropriately styled
|
||||||
|
/// button for their design systems without requiring [WidgetInspector] to
|
||||||
|
/// depend on the Material or Cupertino packages.
|
||||||
|
final TapBehaviorButtonBuilder? tapBehaviorButtonBuilder;
|
||||||
|
|
||||||
/// {@template flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
|
/// {@template flutter.widgets.widgetsApp.debugShowCheckedModeBanner}
|
||||||
/// Turns on a little "DEBUG" banner in debug mode to indicate
|
/// Turns on a little "DEBUG" banner in debug mode to indicate
|
||||||
/// that the app is in debug mode. This is on by default (in
|
/// that the app is in debug mode. This is on by default (in
|
||||||
@ -1824,6 +1834,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
|||||||
return WidgetInspector(
|
return WidgetInspector(
|
||||||
exitWidgetSelectionButtonBuilder: widget.exitWidgetSelectionButtonBuilder,
|
exitWidgetSelectionButtonBuilder: widget.exitWidgetSelectionButtonBuilder,
|
||||||
moveExitWidgetSelectionButtonBuilder: widget.moveExitWidgetSelectionButtonBuilder,
|
moveExitWidgetSelectionButtonBuilder: widget.moveExitWidgetSelectionButtonBuilder,
|
||||||
|
tapBehaviorButtonBuilder: widget.tapBehaviorButtonBuilder,
|
||||||
child: child!,
|
child: child!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -492,6 +492,15 @@ mixin WidgetsBinding
|
|||||||
_debugShowWidgetInspectorOverrideNotifierObject ??= ValueNotifier<bool>(false);
|
_debugShowWidgetInspectorOverrideNotifierObject ??= ValueNotifier<bool>(false);
|
||||||
ValueNotifier<bool>? _debugShowWidgetInspectorOverrideNotifierObject;
|
ValueNotifier<bool>? _debugShowWidgetInspectorOverrideNotifierObject;
|
||||||
|
|
||||||
|
/// The notifier for whether or not taps on the device are treated as widget
|
||||||
|
/// selections when the widget inspector is enabled.
|
||||||
|
///
|
||||||
|
/// - If true, taps in the app are intercepted by the widget inspector.
|
||||||
|
/// - If false, taps in the app are not intercepted by the widget inspector.
|
||||||
|
ValueNotifier<bool> get debugWidgetInspectorSelectionOnTapEnabled =>
|
||||||
|
_debugWidgetInspectorSelectionOnTapEnabledNotifierObject ??= ValueNotifier<bool>(true);
|
||||||
|
ValueNotifier<bool>? _debugWidgetInspectorSelectionOnTapEnabledNotifierObject;
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
@override
|
@override
|
||||||
void resetInternalState() {
|
void resetInternalState() {
|
||||||
@ -499,6 +508,8 @@ mixin WidgetsBinding
|
|||||||
super.resetInternalState();
|
super.resetInternalState();
|
||||||
_debugShowWidgetInspectorOverrideNotifierObject?.dispose();
|
_debugShowWidgetInspectorOverrideNotifierObject?.dispose();
|
||||||
_debugShowWidgetInspectorOverrideNotifierObject = null;
|
_debugShowWidgetInspectorOverrideNotifierObject = null;
|
||||||
|
_debugWidgetInspectorSelectionOnTapEnabledNotifierObject?.dispose();
|
||||||
|
_debugWidgetInspectorSelectionOnTapEnabledNotifierObject = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _debugAddStackFilters() {
|
void _debugAddStackFilters() {
|
||||||
|
@ -34,6 +34,7 @@ import 'binding.dart';
|
|||||||
import 'debug.dart';
|
import 'debug.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
import 'gesture_detector.dart';
|
import 'gesture_detector.dart';
|
||||||
|
import 'icon_data.dart';
|
||||||
import 'service_extensions.dart';
|
import 'service_extensions.dart';
|
||||||
import 'view.dart';
|
import 'view.dart';
|
||||||
|
|
||||||
@ -43,13 +44,29 @@ typedef ExitWidgetSelectionButtonBuilder =
|
|||||||
Widget Function(
|
Widget Function(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
required GlobalKey key,
|
required GlobalKey key,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Signature for the builder callback used by
|
/// Signature for the builder callback used by
|
||||||
/// [WidgetInspector.moveExitWidgetSelectionButtonBuilder].
|
/// [WidgetInspector.moveExitWidgetSelectionButtonBuilder].
|
||||||
typedef MoveExitWidgetSelectionButtonBuilder =
|
typedef MoveExitWidgetSelectionButtonBuilder =
|
||||||
Widget Function(BuildContext context, {required VoidCallback onPressed, bool isLeftAligned});
|
Widget Function(
|
||||||
|
BuildContext context, {
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
|
bool isLeftAligned,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Signature for the builder callback used by
|
||||||
|
/// [WidgetInspector.tapBehaviorButtonBuilder].
|
||||||
|
typedef TapBehaviorButtonBuilder =
|
||||||
|
Widget Function(
|
||||||
|
BuildContext context, {
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
|
required bool selectionOnTapEnabled,
|
||||||
|
});
|
||||||
|
|
||||||
/// Signature for a method that registers the service extension `callback` with
|
/// Signature for a method that registers the service extension `callback` with
|
||||||
/// the given `name`.
|
/// the given `name`.
|
||||||
@ -2768,6 +2785,7 @@ class WidgetInspector extends StatefulWidget {
|
|||||||
const WidgetInspector({
|
const WidgetInspector({
|
||||||
super.key,
|
super.key,
|
||||||
required this.child,
|
required this.child,
|
||||||
|
required this.tapBehaviorButtonBuilder,
|
||||||
required this.exitWidgetSelectionButtonBuilder,
|
required this.exitWidgetSelectionButtonBuilder,
|
||||||
required this.moveExitWidgetSelectionButtonBuilder,
|
required this.moveExitWidgetSelectionButtonBuilder,
|
||||||
});
|
});
|
||||||
@ -2790,6 +2808,15 @@ class WidgetInspector extends StatefulWidget {
|
|||||||
/// The button UI should respond to the `leftAligned` argument.
|
/// The button UI should respond to the `leftAligned` argument.
|
||||||
final MoveExitWidgetSelectionButtonBuilder? moveExitWidgetSelectionButtonBuilder;
|
final MoveExitWidgetSelectionButtonBuilder? moveExitWidgetSelectionButtonBuilder;
|
||||||
|
|
||||||
|
/// A builder that is called to create the button that changes the default tap
|
||||||
|
/// behavior when Select Widget mode is enabled.
|
||||||
|
///
|
||||||
|
/// The `onPressed` callback passed as an argument to the builder should be
|
||||||
|
/// hooked up to the returned widget.
|
||||||
|
///
|
||||||
|
/// The button UI should respond to the `selectionOnTapEnabled` argument.
|
||||||
|
final TapBehaviorButtonBuilder? tapBehaviorButtonBuilder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<WidgetInspector> createState() => _WidgetInspectorState();
|
State<WidgetInspector> createState() => _WidgetInspectorState();
|
||||||
}
|
}
|
||||||
@ -2809,6 +2836,11 @@ class _WidgetInspectorState extends State<WidgetInspector> with WidgetsBindingOb
|
|||||||
/// as selecting the edge of the bounding box.
|
/// as selecting the edge of the bounding box.
|
||||||
static const double _edgeHitMargin = 2.0;
|
static const double _edgeHitMargin = 2.0;
|
||||||
|
|
||||||
|
ValueNotifier<bool> get _selectionOnTapEnabled =>
|
||||||
|
WidgetsBinding.instance.debugWidgetInspectorSelectionOnTapEnabled;
|
||||||
|
|
||||||
|
bool get _isSelectModeWithSelectionOnTapEnabled => isSelectMode && _selectionOnTapEnabled.value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -2817,6 +2849,7 @@ class _WidgetInspectorState extends State<WidgetInspector> with WidgetsBindingOb
|
|||||||
WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier.addListener(
|
WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier.addListener(
|
||||||
_selectionInformationChanged,
|
_selectionInformationChanged,
|
||||||
);
|
);
|
||||||
|
_selectionOnTapEnabled.addListener(_selectionInformationChanged);
|
||||||
selection = WidgetInspectorService.instance.selection;
|
selection = WidgetInspectorService.instance.selection;
|
||||||
isSelectMode = WidgetsBinding.instance.debugShowWidgetInspectorOverride;
|
isSelectMode = WidgetsBinding.instance.debugShowWidgetInspectorOverride;
|
||||||
}
|
}
|
||||||
@ -2827,6 +2860,7 @@ class _WidgetInspectorState extends State<WidgetInspector> with WidgetsBindingOb
|
|||||||
WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier.removeListener(
|
WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier.removeListener(
|
||||||
_selectionInformationChanged,
|
_selectionInformationChanged,
|
||||||
);
|
);
|
||||||
|
_selectionOnTapEnabled.removeListener(_selectionInformationChanged);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2911,7 +2945,7 @@ class _WidgetInspectorState extends State<WidgetInspector> with WidgetsBindingOb
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _inspectAt(Offset position) {
|
void _inspectAt(Offset position) {
|
||||||
if (!isSelectMode) {
|
if (!_isSelectModeWithSelectionOnTapEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2952,7 +2986,7 @@ class _WidgetInspectorState extends State<WidgetInspector> with WidgetsBindingOb
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleTap() {
|
void _handleTap() {
|
||||||
if (!isSelectMode) {
|
if (!_isSelectModeWithSelectionOnTapEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_lastPointerLocation != null) {
|
if (_lastPointerLocation != null) {
|
||||||
@ -2975,11 +3009,16 @@ class _WidgetInspectorState extends State<WidgetInspector> with WidgetsBindingOb
|
|||||||
onPanUpdate: _handlePanUpdate,
|
onPanUpdate: _handlePanUpdate,
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
excludeFromSemantics: true,
|
excludeFromSemantics: true,
|
||||||
child: IgnorePointer(ignoring: isSelectMode, key: _ignorePointerKey, child: widget.child),
|
child: IgnorePointer(
|
||||||
|
ignoring: _isSelectModeWithSelectionOnTapEnabled,
|
||||||
|
key: _ignorePointerKey,
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
_InspectorOverlay(selection: selection),
|
_InspectorOverlay(selection: selection),
|
||||||
if (isSelectMode && widget.exitWidgetSelectionButtonBuilder != null)
|
if (isSelectMode && widget.exitWidgetSelectionButtonBuilder != null)
|
||||||
_ExitWidgetSelectionButtonGroup(
|
_WidgetInspectorButtonGroup(
|
||||||
|
tapBehaviorButtonBuilder: widget.tapBehaviorButtonBuilder,
|
||||||
exitWidgetSelectionButtonBuilder: widget.exitWidgetSelectionButtonBuilder!,
|
exitWidgetSelectionButtonBuilder: widget.exitWidgetSelectionButtonBuilder!,
|
||||||
moveExitWidgetSelectionButtonBuilder: widget.moveExitWidgetSelectionButtonBuilder,
|
moveExitWidgetSelectionButtonBuilder: widget.moveExitWidgetSelectionButtonBuilder,
|
||||||
),
|
),
|
||||||
@ -2988,6 +3027,123 @@ class _WidgetInspectorState extends State<WidgetInspector> with WidgetsBindingOb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines the visual and behavioral variants for an [InspectorButton].
|
||||||
|
enum InspectorButtonVariant {
|
||||||
|
/// A standard button with a filled background and foreground icon.
|
||||||
|
filled,
|
||||||
|
|
||||||
|
/// A button that can be toggled on or off, visually representing its state.
|
||||||
|
///
|
||||||
|
/// The [InspectorButton.toggledOn] property determines its current state.
|
||||||
|
toggle,
|
||||||
|
|
||||||
|
/// A button that displays only an icon, typically with a transparent background.
|
||||||
|
iconOnly,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An abstract base class for creating Material or Cupertino-styled inspector
|
||||||
|
/// buttons.
|
||||||
|
///
|
||||||
|
/// Subclasses are responsible for implementing the design-specific rendering
|
||||||
|
/// logic in the [build] method and providing design-specific colors via
|
||||||
|
/// [foregroundColor] and [backgroundColor].
|
||||||
|
abstract class InspectorButton extends StatelessWidget {
|
||||||
|
/// Creates an inspector button.
|
||||||
|
///
|
||||||
|
/// This is the base constructor used by named constructors.
|
||||||
|
const InspectorButton({
|
||||||
|
super.key,
|
||||||
|
required this.onPressed,
|
||||||
|
required this.semanticLabel,
|
||||||
|
required this.icon,
|
||||||
|
this.buttonKey,
|
||||||
|
required this.variant,
|
||||||
|
this.toggledOn,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Creates an inspector button with the [InspectorButtonVariant.filled] style.
|
||||||
|
///
|
||||||
|
/// This button typically has a solid background color and a contrasting icon.
|
||||||
|
const InspectorButton.filled({
|
||||||
|
super.key,
|
||||||
|
required this.onPressed,
|
||||||
|
required this.semanticLabel,
|
||||||
|
required this.icon,
|
||||||
|
this.buttonKey,
|
||||||
|
}) : variant = InspectorButtonVariant.filled,
|
||||||
|
toggledOn = null;
|
||||||
|
|
||||||
|
/// Creates an inspector button with the [InspectorButtonVariant.toggle] style.
|
||||||
|
///
|
||||||
|
/// This button can be in an "on" or "off" state, visually indicated.
|
||||||
|
/// The [toggledOn] parameter defaults to `true`.
|
||||||
|
const InspectorButton.toggle({
|
||||||
|
super.key,
|
||||||
|
required this.onPressed,
|
||||||
|
required this.semanticLabel,
|
||||||
|
required this.icon,
|
||||||
|
bool this.toggledOn = true,
|
||||||
|
}) : buttonKey = null,
|
||||||
|
variant = InspectorButtonVariant.toggle;
|
||||||
|
|
||||||
|
/// Creates an inspector button with the [InspectorButtonVariant.iconOnly] style.
|
||||||
|
///
|
||||||
|
/// This button typically displays only an icon with a transparent background.
|
||||||
|
const InspectorButton.iconOnly({
|
||||||
|
super.key,
|
||||||
|
required this.onPressed,
|
||||||
|
required this.semanticLabel,
|
||||||
|
required this.icon,
|
||||||
|
}) : buttonKey = null,
|
||||||
|
variant = InspectorButtonVariant.iconOnly,
|
||||||
|
toggledOn = null;
|
||||||
|
|
||||||
|
/// The callback that is called when the button is tapped.
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
|
/// The semantic label for the button, used for accessibility.
|
||||||
|
final String semanticLabel;
|
||||||
|
|
||||||
|
/// The icon to display within the button.
|
||||||
|
final IconData icon;
|
||||||
|
|
||||||
|
/// An optional key to identify the button widget.
|
||||||
|
final GlobalKey? buttonKey;
|
||||||
|
|
||||||
|
/// The visual and behavioral variant of the button.
|
||||||
|
///
|
||||||
|
/// See [InspectorButtonVariant] for available styles.
|
||||||
|
final InspectorButtonVariant variant;
|
||||||
|
|
||||||
|
/// For [InspectorButtonVariant.toggle] buttons, this determines if the button
|
||||||
|
/// is currently in the "on" (true) or "off" (false) state.
|
||||||
|
final bool? toggledOn;
|
||||||
|
|
||||||
|
/// The standard height and width for the button.
|
||||||
|
static const double buttonSize = 32.0;
|
||||||
|
|
||||||
|
/// The standard size for the icon when it's not the only element (e.g., in filled or toggle buttons).
|
||||||
|
///
|
||||||
|
/// For [InspectorButtonVariant.iconOnly], the icon typically takes up the full [buttonSize].
|
||||||
|
static const double buttonIconSize = 18.0;
|
||||||
|
|
||||||
|
/// Gets the appropriate icon size based on the button's [variant].
|
||||||
|
///
|
||||||
|
/// Returns [buttonSize] if the variant is [InspectorButtonVariant.iconOnly],
|
||||||
|
/// otherwise returns [buttonIconSize].
|
||||||
|
double get iconSizeForVariant =>
|
||||||
|
variant == InspectorButtonVariant.iconOnly ? buttonSize : buttonIconSize;
|
||||||
|
|
||||||
|
/// Provides the appropriate foreground color for the button's icon.
|
||||||
|
Color foregroundColor(BuildContext context);
|
||||||
|
|
||||||
|
/// Provides the appropriate background color for the button.
|
||||||
|
Color backgroundColor(BuildContext context);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context);
|
||||||
|
}
|
||||||
|
|
||||||
/// Mutable selection state of the inspector.
|
/// Mutable selection state of the inspector.
|
||||||
class InspectorSelection with ChangeNotifier {
|
class InspectorSelection with ChangeNotifier {
|
||||||
/// Creates an instance of [InspectorSelection].
|
/// Creates an instance of [InspectorSelection].
|
||||||
@ -3455,22 +3611,24 @@ const double _kOffScreenMargin = 1.0;
|
|||||||
|
|
||||||
const TextStyle _messageStyle = TextStyle(color: Color(0xFFFFFFFF), fontSize: 10.0, height: 1.2);
|
const TextStyle _messageStyle = TextStyle(color: Color(0xFFFFFFFF), fontSize: 10.0, height: 1.2);
|
||||||
|
|
||||||
class _ExitWidgetSelectionButtonGroup extends StatefulWidget {
|
class _WidgetInspectorButtonGroup extends StatefulWidget {
|
||||||
const _ExitWidgetSelectionButtonGroup({
|
const _WidgetInspectorButtonGroup({
|
||||||
required this.exitWidgetSelectionButtonBuilder,
|
required this.exitWidgetSelectionButtonBuilder,
|
||||||
required this.moveExitWidgetSelectionButtonBuilder,
|
required this.moveExitWidgetSelectionButtonBuilder,
|
||||||
|
required this.tapBehaviorButtonBuilder,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ExitWidgetSelectionButtonBuilder exitWidgetSelectionButtonBuilder;
|
final ExitWidgetSelectionButtonBuilder exitWidgetSelectionButtonBuilder;
|
||||||
final MoveExitWidgetSelectionButtonBuilder? moveExitWidgetSelectionButtonBuilder;
|
final MoveExitWidgetSelectionButtonBuilder? moveExitWidgetSelectionButtonBuilder;
|
||||||
|
final TapBehaviorButtonBuilder? tapBehaviorButtonBuilder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_ExitWidgetSelectionButtonGroup> createState() => _ExitWidgetSelectionButtonGroupState();
|
State<_WidgetInspectorButtonGroup> createState() => _WidgetInspectorButtonGroupState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ExitWidgetSelectionButtonGroupState extends State<_ExitWidgetSelectionButtonGroup> {
|
class _WidgetInspectorButtonGroupState extends State<_WidgetInspectorButtonGroup> {
|
||||||
static const double _kExitWidgetSelectionButtonPadding = 4.0;
|
|
||||||
static const double _kExitWidgetSelectionButtonMargin = 10.0;
|
static const double _kExitWidgetSelectionButtonMargin = 10.0;
|
||||||
|
static const bool _defaultSelectionOnTapEnabled = true;
|
||||||
|
|
||||||
final GlobalKey _exitWidgetSelectionButtonKey = GlobalKey(
|
final GlobalKey _exitWidgetSelectionButtonKey = GlobalKey(
|
||||||
debugLabel: 'Exit Widget Selection button',
|
debugLabel: 'Exit Widget Selection button',
|
||||||
@ -3480,31 +3638,78 @@ class _ExitWidgetSelectionButtonGroupState extends State<_ExitWidgetSelectionBut
|
|||||||
|
|
||||||
bool _leftAligned = true;
|
bool _leftAligned = true;
|
||||||
|
|
||||||
|
ValueNotifier<bool> get _selectionOnTapEnabled =>
|
||||||
|
WidgetsBinding.instance.debugWidgetInspectorSelectionOnTapEnabled;
|
||||||
|
|
||||||
|
Widget? get _moveExitWidgetSelectionButton {
|
||||||
|
final MoveExitWidgetSelectionButtonBuilder? buttonBuilder =
|
||||||
|
widget.moveExitWidgetSelectionButtonBuilder;
|
||||||
|
if (buttonBuilder == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String buttonLabel = 'Move to the ${_leftAligned ? 'right' : 'left'}';
|
||||||
|
return _WidgetInspectorButton(
|
||||||
|
button: buttonBuilder(
|
||||||
|
context,
|
||||||
|
onPressed: () {
|
||||||
|
_changeButtonGroupAlignment();
|
||||||
|
_onTooltipHidden();
|
||||||
|
},
|
||||||
|
semanticLabel: buttonLabel,
|
||||||
|
isLeftAligned: _leftAligned,
|
||||||
|
),
|
||||||
|
onTooltipVisible: () {
|
||||||
|
_changeTooltipMessage(buttonLabel);
|
||||||
|
},
|
||||||
|
onTooltipHidden: _onTooltipHidden,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget get _exitWidgetSelectionButton {
|
||||||
|
const String buttonLabel = 'Exit Select Widget mode';
|
||||||
|
return _WidgetInspectorButton(
|
||||||
|
button: widget.exitWidgetSelectionButtonBuilder(
|
||||||
|
context,
|
||||||
|
onPressed: _exitWidgetSelectionMode,
|
||||||
|
semanticLabel: buttonLabel,
|
||||||
|
key: _exitWidgetSelectionButtonKey,
|
||||||
|
),
|
||||||
|
onTooltipVisible: () {
|
||||||
|
_changeTooltipMessage(buttonLabel);
|
||||||
|
},
|
||||||
|
onTooltipHidden: _onTooltipHidden,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget? get _tapBehaviorButton {
|
||||||
|
final TapBehaviorButtonBuilder? buttonBuilder = widget.tapBehaviorButtonBuilder;
|
||||||
|
if (buttonBuilder == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _WidgetInspectorButton(
|
||||||
|
button: buttonBuilder(
|
||||||
|
context,
|
||||||
|
onPressed: _changeSelectionOnTapMode,
|
||||||
|
semanticLabel: 'Change widget selection mode for taps',
|
||||||
|
selectionOnTapEnabled: _selectionOnTapEnabled.value,
|
||||||
|
),
|
||||||
|
onTooltipVisible: _changeSelectionOnTapTooltip,
|
||||||
|
onTooltipHidden: _onTooltipHidden,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _tooltipVisible => _tooltipMessage != null;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final Widget? moveExitWidgetSelectionButton =
|
final Widget selectionModeButtons = Column(
|
||||||
widget.moveExitWidgetSelectionButtonBuilder != null
|
children: <Widget>[
|
||||||
? Padding(
|
if (_tapBehaviorButton != null) _tapBehaviorButton!,
|
||||||
padding: EdgeInsets.only(
|
_exitWidgetSelectionButton,
|
||||||
left: _leftAligned ? _kExitWidgetSelectionButtonPadding : 0.0,
|
],
|
||||||
right: _leftAligned ? 0.0 : _kExitWidgetSelectionButtonPadding,
|
);
|
||||||
),
|
|
||||||
child: _TooltipGestureDetector(
|
|
||||||
button: widget.moveExitWidgetSelectionButtonBuilder!(
|
|
||||||
context,
|
|
||||||
onPressed: () {
|
|
||||||
_changeButtonGroupAlignment();
|
|
||||||
_onTooltipHidden();
|
|
||||||
},
|
|
||||||
isLeftAligned: _leftAligned,
|
|
||||||
),
|
|
||||||
onTooltipVisible: () {
|
|
||||||
_changeTooltipMessage('Move to the ${_leftAligned ? 'right' : 'left'}');
|
|
||||||
},
|
|
||||||
onTooltipHidden: _onTooltipHidden,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
final Widget buttonGroup = Stack(
|
final Widget buttonGroup = Stack(
|
||||||
alignment: AlignmentDirectional.topCenter,
|
alignment: AlignmentDirectional.topCenter,
|
||||||
@ -3517,22 +3722,12 @@ class _ExitWidgetSelectionButtonGroupState extends State<_ExitWidgetSelectionBut
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (!_leftAligned && moveExitWidgetSelectionButton != null)
|
if (_leftAligned) selectionModeButtons,
|
||||||
moveExitWidgetSelectionButton,
|
if (_moveExitWidgetSelectionButton != null) _moveExitWidgetSelectionButton!,
|
||||||
_TooltipGestureDetector(
|
if (!_leftAligned) selectionModeButtons,
|
||||||
button: widget.exitWidgetSelectionButtonBuilder(
|
|
||||||
context,
|
|
||||||
onPressed: _exitWidgetSelectionMode,
|
|
||||||
key: _exitWidgetSelectionButtonKey,
|
|
||||||
),
|
|
||||||
onTooltipVisible: () {
|
|
||||||
_changeTooltipMessage('Exit Select Widget mode');
|
|
||||||
},
|
|
||||||
onTooltipHidden: _onTooltipHidden,
|
|
||||||
),
|
|
||||||
if (_leftAligned && moveExitWidgetSelectionButton != null)
|
|
||||||
moveExitWidgetSelectionButton,
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -3548,6 +3743,25 @@ class _ExitWidgetSelectionButtonGroupState extends State<_ExitWidgetSelectionBut
|
|||||||
|
|
||||||
void _exitWidgetSelectionMode() {
|
void _exitWidgetSelectionMode() {
|
||||||
WidgetInspectorService.instance._changeWidgetSelectionMode(false);
|
WidgetInspectorService.instance._changeWidgetSelectionMode(false);
|
||||||
|
// Reset to default selection on tap behavior on exit.
|
||||||
|
_changeSelectionOnTapMode(selectionOnTapEnabled: _defaultSelectionOnTapEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _changeSelectionOnTapMode({bool? selectionOnTapEnabled}) {
|
||||||
|
final bool newValue = selectionOnTapEnabled ?? !_selectionOnTapEnabled.value;
|
||||||
|
_selectionOnTapEnabled.value = newValue;
|
||||||
|
WidgetInspectorService.instance.selection.clear();
|
||||||
|
if (_tooltipVisible) {
|
||||||
|
_changeSelectionOnTapTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _changeSelectionOnTapTooltip() {
|
||||||
|
_changeTooltipMessage(
|
||||||
|
_selectionOnTapEnabled.value
|
||||||
|
? 'Disable widget selection for taps'
|
||||||
|
: 'Enable widget selection for taps',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _changeButtonGroupAlignment() {
|
void _changeButtonGroupAlignment() {
|
||||||
@ -3571,8 +3785,8 @@ class _ExitWidgetSelectionButtonGroupState extends State<_ExitWidgetSelectionBut
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TooltipGestureDetector extends StatefulWidget {
|
class _WidgetInspectorButton extends StatefulWidget {
|
||||||
const _TooltipGestureDetector({
|
const _WidgetInspectorButton({
|
||||||
required this.button,
|
required this.button,
|
||||||
required this.onTooltipVisible,
|
required this.onTooltipVisible,
|
||||||
required this.onTooltipHidden,
|
required this.onTooltipHidden,
|
||||||
@ -3586,10 +3800,10 @@ class _TooltipGestureDetector extends StatefulWidget {
|
|||||||
static const Duration _tooltipDelayDuration = Duration(milliseconds: 100);
|
static const Duration _tooltipDelayDuration = Duration(milliseconds: 100);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_TooltipGestureDetector> createState() => _TooltipGestureDetectorState();
|
State<_WidgetInspectorButton> createState() => _WidgetInspectorButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TooltipGestureDetectorState extends State<_TooltipGestureDetector> {
|
class _WidgetInspectorButtonState extends State<_WidgetInspectorButton> {
|
||||||
Timer? _tooltipVisibleTimer;
|
Timer? _tooltipVisibleTimer;
|
||||||
Timer? _tooltipHiddenTimer;
|
Timer? _tooltipHiddenTimer;
|
||||||
|
|
||||||
@ -3609,18 +3823,18 @@ class _TooltipGestureDetectorState extends State<_TooltipGestureDetector> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
_tooltipVisibleAfter(_TooltipGestureDetector._tooltipDelayDuration);
|
_tooltipVisibleAfter(_WidgetInspectorButton._tooltipDelayDuration);
|
||||||
_tooltipHiddenAfter(
|
_tooltipHiddenAfter(
|
||||||
_TooltipGestureDetector._tooltipShownOnLongPressDuration +
|
_WidgetInspectorButton._tooltipShownOnLongPressDuration +
|
||||||
_TooltipGestureDetector._tooltipDelayDuration,
|
_WidgetInspectorButton._tooltipDelayDuration,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
onEnter: (_) {
|
onEnter: (_) {
|
||||||
_tooltipVisibleAfter(_TooltipGestureDetector._tooltipDelayDuration);
|
_tooltipVisibleAfter(_WidgetInspectorButton._tooltipDelayDuration);
|
||||||
},
|
},
|
||||||
onExit: (_) {
|
onExit: (_) {
|
||||||
_tooltipHiddenAfter(_TooltipGestureDetector._tooltipDelayDuration);
|
_tooltipHiddenAfter(_WidgetInspectorButton._tooltipDelayDuration);
|
||||||
},
|
},
|
||||||
child: widget.button,
|
child: widget.button,
|
||||||
),
|
),
|
||||||
|
@ -343,6 +343,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
child: WidgetInspector(
|
child: WidgetInspector(
|
||||||
exitWidgetSelectionButtonBuilder: null,
|
exitWidgetSelectionButtonBuilder: null,
|
||||||
moveExitWidgetSelectionButtonBuilder: null,
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text('a', textDirection: TextDirection.ltr),
|
Text('a', textDirection: TextDirection.ltr),
|
||||||
@ -370,6 +371,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
Widget exitWidgetSelectionButtonBuilder(
|
Widget exitWidgetSelectionButtonBuilder(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
required GlobalKey key,
|
required GlobalKey key,
|
||||||
}) {
|
}) {
|
||||||
exitWidgetSelectionButtonKey = key;
|
exitWidgetSelectionButtonKey = key;
|
||||||
@ -422,6 +424,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
key: inspectorKey,
|
key: inspectorKey,
|
||||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||||
moveExitWidgetSelectionButtonBuilder: null,
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
child: Material(
|
child: Material(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -515,6 +518,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
child: WidgetInspector(
|
child: WidgetInspector(
|
||||||
exitWidgetSelectionButtonBuilder: null,
|
exitWidgetSelectionButtonBuilder: null,
|
||||||
moveExitWidgetSelectionButtonBuilder: null,
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
child: Transform(
|
child: Transform(
|
||||||
transform: Matrix4.identity()..scale(0.0),
|
transform: Matrix4.identity()..scale(0.0),
|
||||||
child: const Stack(
|
child: const Stack(
|
||||||
@ -545,6 +549,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
Widget exitWidgetSelectionButtonBuilder(
|
Widget exitWidgetSelectionButtonBuilder(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
required GlobalKey key,
|
required GlobalKey key,
|
||||||
}) {
|
}) {
|
||||||
exitWidgetSelectionButtonKey = key;
|
exitWidgetSelectionButtonKey = key;
|
||||||
@ -558,6 +563,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
key: inspectorKey,
|
key: inspectorKey,
|
||||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||||
moveExitWidgetSelectionButtonBuilder: null,
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
dragStartBehavior: DragStartBehavior.down,
|
dragStartBehavior: DragStartBehavior.down,
|
||||||
children: <Widget>[Container(key: childKey, height: 5000.0)],
|
children: <Widget>[Container(key: childKey, height: 5000.0)],
|
||||||
@ -621,6 +627,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
child: WidgetInspector(
|
child: WidgetInspector(
|
||||||
exitWidgetSelectionButtonBuilder: null,
|
exitWidgetSelectionButtonBuilder: null,
|
||||||
moveExitWidgetSelectionButtonBuilder: null,
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
expect(didLongPress, isFalse);
|
expect(didLongPress, isFalse);
|
||||||
@ -688,6 +695,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
key: inspectorKey,
|
key: inspectorKey,
|
||||||
exitWidgetSelectionButtonBuilder: null,
|
exitWidgetSelectionButtonBuilder: null,
|
||||||
moveExitWidgetSelectionButtonBuilder: null,
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
child: Overlay(
|
child: Overlay(
|
||||||
initialEntries: <OverlayEntry>[
|
initialEntries: <OverlayEntry>[
|
||||||
entry1 = OverlayEntry(
|
entry1 = OverlayEntry(
|
||||||
@ -747,6 +755,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
child: WidgetInspector(
|
child: WidgetInspector(
|
||||||
exitWidgetSelectionButtonBuilder: null,
|
exitWidgetSelectionButtonBuilder: null,
|
||||||
moveExitWidgetSelectionButtonBuilder: null,
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
child: ColoredBox(
|
child: ColoredBox(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: Center(
|
child: Center(
|
||||||
@ -791,7 +800,12 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
final GlobalKey child2Key = GlobalKey();
|
final GlobalKey child2Key = GlobalKey();
|
||||||
|
|
||||||
ExitWidgetSelectionButtonBuilder exitWidgetSelectionButtonBuilder(Key key) {
|
ExitWidgetSelectionButtonBuilder exitWidgetSelectionButtonBuilder(Key key) {
|
||||||
return (BuildContext context, {required VoidCallback onPressed, required GlobalKey key}) {
|
return (
|
||||||
|
BuildContext context, {
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
|
required GlobalKey key,
|
||||||
|
}) {
|
||||||
return Material(child: ElevatedButton(onPressed: onPressed, key: key, child: null));
|
return Material(child: ElevatedButton(onPressed: onPressed, key: key, child: null));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -813,6 +827,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
selectButton1Key,
|
selectButton1Key,
|
||||||
),
|
),
|
||||||
moveExitWidgetSelectionButtonBuilder: null,
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
child: Container(key: child1Key, child: const Text('Child 1')),
|
child: Container(key: child1Key, child: const Text('Child 1')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -823,6 +838,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
selectButton2Key,
|
selectButton2Key,
|
||||||
),
|
),
|
||||||
moveExitWidgetSelectionButtonBuilder: null,
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
child: Container(key: child2Key, child: const Text('Child 2')),
|
child: Container(key: child2Key, child: const Text('Child 2')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -858,6 +874,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
Widget exitWidgetSelectionButtonBuilder(
|
Widget exitWidgetSelectionButtonBuilder(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
required GlobalKey key,
|
required GlobalKey key,
|
||||||
}) {
|
}) {
|
||||||
return Material(child: ElevatedButton(onPressed: onPressed, key: key, child: null));
|
return Material(child: ElevatedButton(onPressed: onPressed, key: key, child: null));
|
||||||
@ -869,6 +886,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
child: WidgetInspector(
|
child: WidgetInspector(
|
||||||
key: inspectorKey,
|
key: inspectorKey,
|
||||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
moveExitWidgetSelectionButtonBuilder: null,
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
child: const Text('Child 1'),
|
child: const Text('Child 1'),
|
||||||
),
|
),
|
||||||
@ -918,6 +936,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
Widget exitWidgetSelectionButtonBuilder(
|
Widget exitWidgetSelectionButtonBuilder(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
required GlobalKey key,
|
required GlobalKey key,
|
||||||
}) {
|
}) {
|
||||||
return Material(
|
return Material(
|
||||||
@ -932,6 +951,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
Widget moveWidgetSelectionButtonBuilder(
|
Widget moveWidgetSelectionButtonBuilder(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required VoidCallback onPressed,
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
bool isLeftAligned = true,
|
bool isLeftAligned = true,
|
||||||
}) {
|
}) {
|
||||||
return Material(
|
return Material(
|
||||||
@ -953,6 +973,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
key: inspectorKey,
|
key: inspectorKey,
|
||||||
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||||
moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder,
|
moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
child: const Text('APP'),
|
child: const Text('APP'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -990,6 +1011,118 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
|
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'WidgetInspector Tap behavior button',
|
||||||
|
(WidgetTester tester) async {
|
||||||
|
Widget exitWidgetSelectionButtonBuilder(
|
||||||
|
BuildContext context, {
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
|
required GlobalKey key,
|
||||||
|
}) {
|
||||||
|
return Material(child: ElevatedButton(onPressed: onPressed, key: key, child: null));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget tapBehaviorButtonBuilder(
|
||||||
|
BuildContext context, {
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
required String semanticLabel,
|
||||||
|
required bool selectionOnTapEnabled,
|
||||||
|
}) {
|
||||||
|
return Material(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: onPressed,
|
||||||
|
child: Text(selectionOnTapEnabled ? 'SELECTION ON TAP' : 'APP INTERACTION ON TAP'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Finder buttonFinder(String buttonText) {
|
||||||
|
return find.ancestor(of: find.text(buttonText), matching: find.byType(ElevatedButton));
|
||||||
|
}
|
||||||
|
|
||||||
|
int navigateEventsCount() =>
|
||||||
|
service.dispatchedEvents('navigate', stream: 'ToolEvent').length;
|
||||||
|
|
||||||
|
// Enable widget selection mode.
|
||||||
|
WidgetInspectorService.instance.isSelectMode = true;
|
||||||
|
|
||||||
|
// Pump the test widget.
|
||||||
|
final GlobalKey inspectorKey = GlobalKey();
|
||||||
|
setupDefaultPubRootDirectory(service);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: WidgetInspector(
|
||||||
|
key: inspectorKey,
|
||||||
|
exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder,
|
||||||
|
tapBehaviorButtonBuilder: tapBehaviorButtonBuilder,
|
||||||
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
|
child: const Row(children: <Widget>[Text('Child 1'), Text('Child 2')]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify there are no navigate events yet.
|
||||||
|
expect(navigateEventsCount(), equals(0));
|
||||||
|
|
||||||
|
// Tap on the first child widget.
|
||||||
|
final Finder child1 = find.text('Child 1');
|
||||||
|
await tester.tap(child1, warnIfMissed: false);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Verify the selection matches the first child widget.
|
||||||
|
final Element child1Element = child1.evaluate().first;
|
||||||
|
expect(service.selection.current, equals(child1Element.renderObject));
|
||||||
|
|
||||||
|
// Verify that a navigate event was sent.
|
||||||
|
expect(navigateEventsCount(), equals(1));
|
||||||
|
|
||||||
|
// Tap on the SELECTION ON TAP button.
|
||||||
|
final Finder tapBehaviorButtonBefore = buttonFinder('SELECTION ON TAP');
|
||||||
|
expect(tapBehaviorButtonBefore, findsOneWidget);
|
||||||
|
await tester.tap(tapBehaviorButtonBefore);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Verify the tap behavior button's UI has been updated.
|
||||||
|
expect(tapBehaviorButtonBefore, findsNothing);
|
||||||
|
final Finder tapBehaviorButtonAfter = buttonFinder('APP INTERACTION ON TAP');
|
||||||
|
expect(tapBehaviorButtonAfter, findsOneWidget);
|
||||||
|
|
||||||
|
// Tap on the second child widget.
|
||||||
|
final Finder child2 = find.text('Child 2');
|
||||||
|
await tester.tap(child2, warnIfMissed: false);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Verify there is no selection.
|
||||||
|
expect(service.selection.current, isNull);
|
||||||
|
|
||||||
|
// Verify no navigate events were sent.
|
||||||
|
expect(navigateEventsCount(), equals(1));
|
||||||
|
|
||||||
|
// Tap on the SELECTION ON TAP button again.
|
||||||
|
await tester.tap(tapBehaviorButtonAfter);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Verify the tap behavior button's UI has been reset.
|
||||||
|
expect(tapBehaviorButtonAfter, findsNothing);
|
||||||
|
expect(tapBehaviorButtonBefore, findsOneWidget);
|
||||||
|
|
||||||
|
// Tap on the second child widget again.
|
||||||
|
await tester.tap(child2, warnIfMissed: false);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Verify the selection now matches the second child widget.
|
||||||
|
final Element child2Element = child2.evaluate().first;
|
||||||
|
expect(service.selection.current, equals(child2Element.renderObject));
|
||||||
|
|
||||||
|
// Verify another navigate event was sent.
|
||||||
|
expect(navigateEventsCount(), equals(2));
|
||||||
|
},
|
||||||
|
// [intended] Test requires --track-widget-creation flag.
|
||||||
|
skip: !WidgetInspectorService.instance.isWidgetCreationTracked(),
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('test transformDebugCreator will re-order if after stack trace', (
|
testWidgets('test transformDebugCreator will re-order if after stack trace', (
|
||||||
WidgetTester tester,
|
WidgetTester tester,
|
||||||
) async {
|
) async {
|
||||||
@ -3867,6 +4000,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService {
|
|||||||
child: WidgetInspector(
|
child: WidgetInspector(
|
||||||
exitWidgetSelectionButtonBuilder: null,
|
exitWidgetSelectionButtonBuilder: null,
|
||||||
moveExitWidgetSelectionButtonBuilder: null,
|
moveExitWidgetSelectionButtonBuilder: null,
|
||||||
|
tapBehaviorButtonBuilder: null,
|
||||||
child: _applyConstructor(_TrivialWidget.new),
|
child: _applyConstructor(_TrivialWidget.new),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user