Delete legacy inspector (#9782)
diff --git a/packages/devtools_app/integration_test/test/live_connection/eval_utils.dart b/packages/devtools_app/integration_test/test/live_connection/eval_utils.dart
index 83043cd..7e554e8 100644
--- a/packages/devtools_app/integration_test/test/live_connection/eval_utils.dart
+++ b/packages/devtools_app/integration_test/test/live_connection/eval_utils.dart
@@ -73,7 +73,7 @@
Future<void> selectWidgetTreeNode(Finder finder) async {
await tapAndPump(
find.descendant(
- of: find.byKey(InspectorScreenBodyState.summaryTreeKey),
+ of: find.byKey(InspectorScreenBodyState.inspectorTreeKey),
matching: finder,
),
);
diff --git a/packages/devtools_app/lib/devtools_app.dart b/packages/devtools_app/lib/devtools_app.dart
index 5917216..58e62b5 100644
--- a/packages/devtools_app/lib/devtools_app.dart
+++ b/packages/devtools_app/lib/devtools_app.dart
@@ -24,11 +24,11 @@
export 'src/screens/deep_link_validation/deep_links_screen.dart';
export 'src/screens/dtd/dtd_tools_controller.dart';
export 'src/screens/dtd/dtd_tools_screen.dart';
-export 'src/screens/inspector/inspector_controller.dart';
-export 'src/screens/inspector/inspector_screen_body.dart';
-export 'src/screens/inspector/inspector_tree_controller.dart';
-export 'src/screens/inspector_shared/inspector_screen.dart';
-export 'src/screens/inspector_shared/inspector_screen_controller.dart';
+export 'src/screens/inspector_v2/inspector_controller.dart';
+export 'src/screens/inspector_v2/inspector_screen.dart';
+export 'src/screens/inspector_v2/inspector_screen_body.dart';
+export 'src/screens/inspector_v2/inspector_screen_controller.dart';
+export 'src/screens/inspector_v2/inspector_tree_controller.dart';
export 'src/screens/logging/log_details_controller.dart';
export 'src/screens/logging/logging_controller.dart';
export 'src/screens/logging/logging_screen.dart';
@@ -76,7 +76,7 @@
export 'src/shared/charts/treemap.dart';
export 'src/shared/console/console_service.dart';
export 'src/shared/console/eval/eval_service.dart';
-export 'src/shared/console/eval/inspector_tree.dart';
+export 'src/shared/console/eval/inspector_tree_v2.dart';
export 'src/shared/console/primitives/simple_items.dart';
export 'src/shared/console/widgets/description.dart';
export 'src/shared/diagnostics/diagnostics_node.dart';
diff --git a/packages/devtools_app/lib/src/app.dart b/packages/devtools_app/lib/src/app.dart
index 43c72cd..fdbd780 100644
--- a/packages/devtools_app/lib/src/app.dart
+++ b/packages/devtools_app/lib/src/app.dart
@@ -31,8 +31,8 @@
import 'screens/deep_link_validation/deep_links_screen.dart';
import 'screens/dtd/dtd_tools_controller.dart';
import 'screens/dtd/dtd_tools_screen.dart';
-import 'screens/inspector_shared/inspector_screen.dart';
-import 'screens/inspector_shared/inspector_screen_controller.dart';
+import 'screens/inspector_v2/inspector_screen.dart';
+import 'screens/inspector_v2/inspector_screen_controller.dart';
import 'screens/logging/logging_controller.dart';
import 'screens/logging/logging_screen.dart';
import 'screens/memory/framework/memory_controller.dart';
diff --git a/packages/devtools_app/lib/src/screens/inspector/inspector_breadcrumbs.dart b/packages/devtools_app/lib/src/screens/inspector/inspector_breadcrumbs.dart
deleted file mode 100644
index f5db041..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/inspector_breadcrumbs.dart
+++ /dev/null
@@ -1,201 +0,0 @@
-// Copyright 2021 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:flutter/material.dart';
-
-import '../../shared/console/eval/inspector_tree.dart';
-import '../../shared/primitives/diagnostics_text_styles.dart';
-import '../../shared/primitives/utils.dart';
-import '../../shared/ui/common_widgets.dart';
-
-class InspectorBreadcrumbNavigator extends StatelessWidget {
- const InspectorBreadcrumbNavigator({
- super.key,
- required this.items,
- required this.onTap,
- });
-
- /// Max number of visible breadcrumbs including root item but not 'more' item.
- /// E.g. value 5 means root and 4 breadcrumbs can be displayed, other
- /// breadcrumbs (if any) will be replaced by '...' item.
- static const _maxNumberOfBreadcrumbs = 5;
-
- final List<InspectorTreeNode> items;
- final void Function(InspectorTreeNode?) onTap;
-
- @override
- Widget build(BuildContext context) {
- if (items.isEmpty) {
- return const SizedBox();
- }
-
- final breadcrumbs = _generateBreadcrumbs(items);
- return SizedBox(
- height: Breadcrumb.height,
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 6),
- child: Row(
- children: breadcrumbs.map((item) {
- if (item.isChevron) {
- return const Icon(Icons.chevron_right, size: defaultIconSize);
- }
-
- return Flexible(
- child: _InspectorBreadcrumb(
- data: item,
- onTap: () => onTap(item.node),
- ),
- );
- }).toList(),
- ),
- ),
- );
- }
-
- List<_InspectorBreadcrumbData> _generateBreadcrumbs(
- List<InspectorTreeNode> nodes,
- ) {
- final lastNode = nodes.safeLast;
- final items = nodes.map((node) {
- return _InspectorBreadcrumbData.wrap(
- node: node,
- isSelected: node == lastNode,
- );
- }).toList();
- List<_InspectorBreadcrumbData> breadcrumbs;
- breadcrumbs = items.length > _maxNumberOfBreadcrumbs
- ? [
- items[0],
- _InspectorBreadcrumbData.more(),
- ...items.sublist(
- items.length - _maxNumberOfBreadcrumbs + 1,
- items.length,
- ),
- ]
- : items;
-
- return breadcrumbs.joinWith(_InspectorBreadcrumbData.chevron());
- }
-}
-
-class _InspectorBreadcrumb extends StatelessWidget {
- const _InspectorBreadcrumb({required this.data, required this.onTap});
-
- static const _iconScale = 0.75;
-
- final _InspectorBreadcrumbData data;
- final VoidCallback onTap;
-
- @override
- Widget build(BuildContext context) {
- final text = Text(
- data.text,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: DiagnosticsTextStyles.regular(
- Theme.of(context).colorScheme,
- ).copyWith(fontSize: 11),
- );
-
- final icon = data.icon == null
- ? null
- : Transform.scale(
- scale: _iconScale,
- child: Padding(
- padding: const EdgeInsets.only(right: iconPadding),
- child: data.icon,
- ),
- );
-
- return InkWell(
- onTap: data.isClickable ? onTap : null,
- borderRadius: defaultBorderRadius,
- child: Container(
- padding: const EdgeInsets.symmetric(
- horizontal: densePadding,
- vertical: borderPadding,
- ),
- decoration: BoxDecoration(
- borderRadius: defaultBorderRadius,
- color: data.isSelected
- ? Theme.of(context).colorScheme.selectedRowBackgroundColor
- : Colors.transparent,
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- if (icon != null) icon,
- Flexible(child: text),
- ],
- ),
- ),
- );
- }
-}
-
-class _InspectorBreadcrumbData {
- const _InspectorBreadcrumbData._({
- required this.node,
- required this.isSelected,
- required this.alternativeText,
- required this.alternativeIcon,
- });
-
- factory _InspectorBreadcrumbData.wrap({
- required InspectorTreeNode node,
- required bool isSelected,
- }) {
- return _InspectorBreadcrumbData._(
- node: node,
- isSelected: isSelected,
- alternativeText: null,
- alternativeIcon: null,
- );
- }
-
- /// Construct a special item for showing '…' symbol between other items
- factory _InspectorBreadcrumbData.more() {
- return const _InspectorBreadcrumbData._(
- node: null,
- isSelected: false,
- alternativeText: _ellipsisValue,
- alternativeIcon: null,
- );
- }
-
- factory _InspectorBreadcrumbData.chevron() {
- return const _InspectorBreadcrumbData._(
- node: null,
- isSelected: false,
- alternativeText: null,
- alternativeIcon: _breadcrumbSeparatorIcon,
- );
- }
-
- static const _ellipsisValue = '…';
- static const _breadcrumbSeparatorIcon = Icons.chevron_right;
-
- final InspectorTreeNode? node;
- final IconData? alternativeIcon;
- final String? alternativeText;
- final bool isSelected;
-
- String get text => alternativeText ?? node?.diagnostic?.description ?? '';
-
- Widget? get icon {
- if (alternativeIcon != null) {
- return const Icon(_breadcrumbSeparatorIcon, size: defaultIconSize);
- }
-
- return node?.diagnostic?.icon;
- }
-
- bool get isChevron =>
- node == null && alternativeIcon == _breadcrumbSeparatorIcon;
-
- bool get isEllipsis => node == null && alternativeText == _ellipsisValue;
-
- bool get isClickable => !isSelected && !isEllipsis;
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/inspector_controller.dart b/packages/devtools_app/lib/src/screens/inspector/inspector_controller.dart
deleted file mode 100644
index 9ddc819..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/inspector_controller.dart
+++ /dev/null
@@ -1,950 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-/// This library must not have direct dependencies on dart:html.
-///
-/// This allows tests of the complicated logic in this class to run on the VM
-/// and will help simplify porting this code to work with Hummingbird.
-///
-/// This code is directly based on
-/// src/io/flutter/view/InspectorPanel.java
-/// with some refactors to make the code more of a controller than a combination
-/// of view and controller. View specific portions of InspectorPanel.java have
-/// been moved to inspector.dart.
-library;
-
-import 'dart:async';
-
-import 'package:devtools_app_shared/utils.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:logging/logging.dart';
-import 'package:vm_service/vm_service.dart';
-
-import '../../service/service_extensions.dart' as extensions;
-import '../../shared/analytics/analytics.dart' as ga;
-import '../../shared/analytics/constants.dart' as gac;
-import '../../shared/analytics/metrics.dart';
-import '../../shared/console/eval/inspector_tree.dart';
-import '../../shared/console/primitives/simple_items.dart';
-import '../../shared/diagnostics/diagnostics_node.dart';
-import '../../shared/diagnostics/inspector_service.dart';
-import '../../shared/diagnostics/primitives/instance_ref.dart';
-import '../../shared/globals.dart';
-import '../../shared/primitives/query_parameters.dart';
-import '../../shared/primitives/utils.dart';
-import '../../shared/utils/utils.dart';
-import '../inspector_shared/inspector_screen.dart';
-import 'inspector_tree_controller.dart';
-
-final _log = Logger('inspector_controller');
-
-/// This class is based on the InspectorPanel class from the Flutter IntelliJ
-/// plugin with some refactors to make it more of a true controller than a view.
-class InspectorController extends DisposableController
- with AutoDisposeControllerMixin
- implements InspectorServiceClient {
- InspectorController({
- required this.inspectorTree,
- InspectorTreeController? detailsTree,
- required this.treeType,
- this.parent,
- this.isSummaryTree = true,
- }) : assert((detailsTree != null) == isSummaryTree) {
- unawaited(_init(detailsTree: detailsTree));
- }
-
- Future<void> _init({InspectorTreeController? detailsTree}) async {
- _refreshRateLimiter = RateLimiter(refreshFramesPerSecond, refresh);
-
- inspectorTree.config = InspectorTreeConfig(
- onNodeAdded: _onNodeAdded,
- onSelectionChange: selectionChanged,
- onExpand: _onExpand,
- onClientActiveChange: _onClientChange,
- );
- details = isSummaryTree
- ? InspectorController(
- inspectorTree: detailsTree!,
- treeType: treeType,
- parent: this,
- isSummaryTree: false,
- )
- : null;
-
- await serviceConnection.serviceManager.onServiceAvailable;
-
- if (inspectorService is InspectorService) {
- _treeGroups = InspectorObjectGroupManager(
- serviceConnection.inspectorService as InspectorService,
- 'tree',
- );
- _selectionGroups = InspectorObjectGroupManager(
- serviceConnection.inspectorService as InspectorService,
- 'selection',
- );
- }
-
- addAutoDisposeListener(
- serviceConnection.serviceManager.isolateManager.mainIsolate,
- () {
- final isolate =
- serviceConnection.serviceManager.isolateManager.mainIsolate.value;
- if (isolate != _mainIsolate) {
- onIsolateStopped();
- }
- _mainIsolate = isolate;
- },
- );
-
- // This logic only needs to be run once so run it in the outermost
- // controller.
- if (parent == null) {
- // If select mode is available, enable the on device inspector as it
- // won't interfere with users.
- addAutoDisposeListener(_supportsToggleSelectWidgetMode, () {
- if (_supportsToggleSelectWidgetMode.value) {
- safeUnawaited(
- serviceConnection.serviceManager.serviceExtensionManager
- .setServiceExtensionState(
- extensions.enableOnDeviceInspector.extension,
- enabled: true,
- value: true,
- ),
- );
- }
- });
- }
-
- addAutoDisposeListener(serviceConnection.serviceManager.connectedState, () {
- if (serviceConnection.serviceManager.connectedState.value.connected) {
- _handleConnectionStart();
- } else {
- _handleConnectionStop();
- }
- });
-
- if (serviceConnection.serviceManager.connectedAppInitialized) {
- _handleConnectionStart();
- }
-
- serviceConnection.consoleService.ensureServiceInitialized();
- }
-
- void _handleConnectionStart() {
- // Clear any existing badge/errors for older errors that were collected.
- // Do this in a post frame callback so that we are not trying to clear the
- // error notifiers for this screen while the framework is already in the
- // process of building widgets.
- // TODO(kenz): When this method is called outside createState(), this post
- // frame callback can be removed.
- WidgetsBinding.instance.addPostFrameCallback((_) {
- serviceConnection.errorBadgeManager.clearErrorCount(InspectorScreen.id);
- });
- filterErrors();
- }
-
- void _handleConnectionStop() {
- setActivate(false);
- if (isSummaryTree) {
- dispose();
- }
- }
-
- IsolateRef? _mainIsolate;
-
- ValueListenable<bool> get _supportsToggleSelectWidgetMode => serviceConnection
- .serviceManager
- .serviceExtensionManager
- .hasServiceExtension(extensions.toggleSelectWidgetMode.extension);
-
- void _onClientChange(bool added) {
- if (!added && _clientCount == 0) {
- // Don't try to remove clients if there are none
- return;
- }
-
- _clientCount += added ? 1 : -1;
- assert(_clientCount >= 0);
- if (_clientCount == 1) {
- setVisibleToUser(true);
- setActivate(true);
- } else if (_clientCount == 0) {
- setVisibleToUser(false);
- }
- }
-
- int _clientCount = 0;
-
- /// Maximum frame rate to refresh the inspector at to avoid taxing the
- /// physical device with too many requests to recompute properties and trees.
- ///
- /// A value up to around 30 frames per second could be reasonable for
- /// debugging highly interactive cases particularly when the user is on a
- /// simulator or high powered native device. The frame rate is set low
- /// for now mainly to minimize risk.
- static const refreshFramesPerSecond = 5.0;
-
- final bool isSummaryTree;
-
- /// Parent InspectorController if this is a details subtree.
- InspectorController? parent;
-
- InspectorController? details;
-
- InspectorTreeController inspectorTree;
- final FlutterTreeType treeType;
-
- late RateLimiter _refreshRateLimiter;
-
- InspectorServiceBase get inspectorService =>
- serviceConnection.inspectorService as InspectorServiceBase;
-
- /// Groups used to manage and cancel requests to load data to display directly
- /// in the tree.
- InspectorObjectGroupManager? _treeGroups;
-
- /// Groups used to manage and cancel requests to determine what the current
- /// selection is.
- ///
- /// This group needs to be kept separate from treeGroups as the selection is
- /// shared more with the details subtree.
- /// TODO(jacobr): is there a way we can unify the selection and tree groups?
- InspectorObjectGroupManager? _selectionGroups;
-
- /// Node being highlighted due to the current hover.
- InspectorTreeNode? get currentShowNode => inspectorTree.hover;
-
- set currentShowNode(InspectorTreeNode? node) => inspectorTree.hover = node;
-
- bool flutterAppFrameReady = false;
-
- bool treeLoadStarted = false;
-
- RemoteDiagnosticsNode? subtreeRoot;
-
- bool programmaticSelectionChangeInProgress = false;
-
- ValueListenable<InspectorTreeNode?> get selectedNode => _selectedNode;
- final _selectedNode = ValueNotifier<InspectorTreeNode?>(null);
-
- InspectorTreeNode? lastExpanded;
-
- bool isActive = false;
-
- final valueToInspectorTreeNode = <InspectorInstanceRef, InspectorTreeNode>{};
-
- /// When visibleToUser is false we should dispose all allocated objects and
- /// not perform any actions.
- bool visibleToUser = false;
-
- bool highlightNodesShownInBothTrees = false;
-
- bool get detailsSubtree => parent != null;
-
- RemoteDiagnosticsNode? get selectedDiagnostic =>
- selectedNode.value?.diagnostic;
-
- ValueListenable<int?> get selectedErrorIndex => _selectedErrorIndex;
- final _selectedErrorIndex = ValueNotifier<int?>(null);
-
- /// Tracks whether the first load of the inspector tree has been completed.
- ///
- /// This field is used to prevent sending multiple analytics events for
- /// inspector tree load timing.
- bool firstInspectorTreeLoadCompleted = false;
-
- FlutterTreeType getTreeType() {
- return treeType;
- }
-
- void setVisibleToUser(bool visible) {
- if (visibleToUser == visible) {
- return;
- }
- visibleToUser = visible;
-
- if (visibleToUser) {
- if (parent == null) {
- unawaited(maybeLoadUI());
- }
- } else {
- shutdownTree(false);
- }
- }
-
- bool hasDiagnosticsValue(InspectorInstanceRef ref) {
- return valueToInspectorTreeNode.containsKey(ref);
- }
-
- RemoteDiagnosticsNode? findDiagnosticsValue(InspectorInstanceRef ref) {
- return valueToInspectorTreeNode[ref]?.diagnostic;
- }
-
- void endShowNode() {
- highlightShowNode(null);
- }
-
- bool highlightShowFromNodeInstanceRef(InspectorInstanceRef ref) {
- return highlightShowNode(valueToInspectorTreeNode[ref]);
- }
-
- bool highlightShowNode(InspectorTreeNode? node) {
- if (node == null && parent != null) {
- // If nothing is highlighted, highlight the node selected in the parent
- // tree so user has context of where the node selected in the parent is
- // in the details tree.
- node = findMatchingInspectorTreeNode(parent?.selectedDiagnostic);
- }
-
- currentShowNode = node;
- return true;
- }
-
- InspectorTreeNode? findMatchingInspectorTreeNode(
- RemoteDiagnosticsNode? node,
- ) {
- final valueRef = node?.valueRef;
- if (valueRef == null) {
- return null;
- }
- return valueToInspectorTreeNode[valueRef];
- }
-
- Future<void> _waitForPendingUpdateDone() async {
- // Wait for the selection to be resolved followed by waiting for the tree to be computed.
- await _selectionGroups?.pendingUpdateDone;
- await _treeGroups?.pendingUpdateDone;
- // TODO(jacobr): are there race conditions we need to think more carefully about here?
- }
-
- Future<void> refresh() {
- if (!visibleToUser) {
- // We will refresh again once we are visible.
- // There is a risk a refresh got triggered before the view was visble.
- return Future.value();
- }
-
- // TODO(jacobr): refresh the tree as well as just the properties.
- final detailsLocal = details;
- if (detailsLocal == null) return _waitForPendingUpdateDone();
-
- return [
- _waitForPendingUpdateDone(),
- detailsLocal._waitForPendingUpdateDone(),
- ].wait;
- }
-
- // Note that this may be called after the controller is disposed. We need to handle nulls in the fields.
- void shutdownTree(bool isolateStopped) {
- // It is critical we clear all data that is kept alive by inspector object
- // references in this method as that stale data will trigger inspector
- // exceptions.
- programmaticSelectionChangeInProgress = true;
- _treeGroups?.clear(isolateStopped);
- _selectionGroups?.clear(isolateStopped);
-
- currentShowNode = null;
- _selectedNode.value = null;
- lastExpanded = null;
-
- subtreeRoot = null;
-
- inspectorTree.root = inspectorTree.createNode();
- programmaticSelectionChangeInProgress = false;
- valueToInspectorTreeNode.clear();
- }
-
- void onIsolateStopped() {
- flutterAppFrameReady = false;
- treeLoadStarted = false;
- shutdownTree(true);
- }
-
- @override
- Future<void> onForceRefresh() async {
- assert(!disposed);
- if (!visibleToUser || disposed) {
- return;
- }
- await _recomputeTreeRoot(null, null, false);
- if (disposed) {
- return;
- }
-
- filterErrors();
-
- return _waitForPendingUpdateDone();
- }
-
- void filterErrors() {
- if (isSummaryTree) {
- serviceConnection.errorBadgeManager.filterErrors(
- InspectorScreen.id,
- (id) => hasDiagnosticsValue(InspectorInstanceRef(id)),
- );
- }
- }
-
- void setActivate(bool enabled) {
- if (!enabled) {
- onIsolateStopped();
- isActive = false;
- return;
- }
- if (isActive) {
- // Already activated.
- return;
- }
-
- isActive = true;
- inspectorService.addClient(this);
- unawaited(maybeLoadUI());
- }
-
- Future<void> maybeLoadUI() async {
- if (parent != null) {
- // The parent controller will drive loading the UI.
- return;
- }
- if (!visibleToUser || !isActive) {
- return;
- }
-
- if (flutterAppFrameReady) {
- if (disposed) return;
- // We need to start by querying the inspector service to find out the
- // current state of the UI.
- final inspectorRef = DevToolsQueryParams.load().inspectorRef;
- await updateSelectionFromService(
- firstFrame: true,
- inspectorRef: inspectorRef,
- );
- } else {
- if (disposed) return;
- if (inspectorService is InspectorService) {
- final widgetTreeReady = await (inspectorService as InspectorService)
- .isWidgetTreeReady();
- flutterAppFrameReady = widgetTreeReady;
- }
- if (isActive && flutterAppFrameReady) {
- await maybeLoadUI();
- }
- }
- }
-
- Future<void> _recomputeTreeRoot(
- RemoteDiagnosticsNode? newSelection,
- RemoteDiagnosticsNode? detailsSelection,
- bool setSubtreeRoot, {
- int subtreeDepth = 2,
- }) async {
- assert(!disposed);
- final treeGroups = _treeGroups;
- if (disposed || treeGroups == null) {
- return;
- }
-
- treeGroups.cancelNext();
- try {
- final group = treeGroups.next;
- final node = await (detailsSubtree
- ? group.getDetailsSubtree(subtreeRoot, subtreeDepth: subtreeDepth)
- : group.getRoot(treeType, isSummaryTree: true));
- if (node == null || group.disposed || disposed) {
- return;
- }
- // TODO(jacobr): as a performance optimization we should check if the
- // new tree is identical to the existing tree in which case we should
- // dispose the new tree and keep the old tree.
- treeGroups.promoteNext();
- _clearValueToInspectorTreeNodeMapping();
-
- final rootNode = inspectorTree.setupInspectorTreeNode(
- inspectorTree.createNode(),
- node,
- expandChildren: true,
- expandProperties: false,
- );
- inspectorTree.root = rootNode;
-
- refreshSelection(newSelection, detailsSelection, setSubtreeRoot);
- } catch (error, st) {
- _log.shout(error, error, st);
- treeGroups.cancelNext();
- return;
- }
- }
-
- void _clearValueToInspectorTreeNodeMapping() {
- valueToInspectorTreeNode.clear();
- }
-
- /// Show the details subtree starting with node subtreeRoot highlighting
- /// node subtreeSelection.
- void _showDetailSubtrees(
- RemoteDiagnosticsNode? subtreeRoot,
- RemoteDiagnosticsNode? subtreeSelection,
- ) {
- this.subtreeRoot = subtreeRoot;
- details?.setSubtreeRoot(subtreeRoot, subtreeSelection);
- }
-
- void setSubtreeRoot(
- RemoteDiagnosticsNode? node,
- RemoteDiagnosticsNode? selection,
- ) {
- assert(detailsSubtree);
- selection ??= node;
- if (node != null && node == subtreeRoot) {
- // Select the new node in the existing subtree.
- applyNewSelection(selection, null, false);
- return;
- }
- subtreeRoot = node;
- if (node == null) {
- // Passing in a null node indicates we should clear the subtree and free any memory allocated.
- shutdownTree(false);
- return;
- }
-
- // Clear now to eliminate frame of highlighted nodes flicker.
- _clearValueToInspectorTreeNodeMapping();
- unawaited(_recomputeTreeRoot(selection, null, false));
- }
-
- InspectorTreeNode? getSubtreeRootNode() {
- if (subtreeRoot == null) {
- return null;
- }
- return valueToInspectorTreeNode[subtreeRoot!.valueRef];
- }
-
- void refreshSelection(
- RemoteDiagnosticsNode? newSelection,
- RemoteDiagnosticsNode? detailsSelection,
- bool setSubtreeRoot,
- ) {
- newSelection ??= selectedDiagnostic;
- setSelectedNode(findMatchingInspectorTreeNode(newSelection));
- syncSelectionHelper(
- maybeRerootDetailsTree: setSubtreeRoot,
- selection: newSelection,
- detailsSelection: detailsSelection,
- );
-
- final detailsLocal = details;
- if (detailsLocal != null) {
- if (subtreeRoot != null && getSubtreeRootNode() == null) {
- subtreeRoot = newSelection;
- detailsLocal.setSubtreeRoot(newSelection, detailsSelection);
- }
- }
- syncTreeSelection();
- }
-
- void syncTreeSelection() {
- programmaticSelectionChangeInProgress = true;
- inspectorTree.selection = selectedNode.value;
- inspectorTree.expandPath(selectedNode.value);
- programmaticSelectionChangeInProgress = false;
- animateTo(selectedNode.value);
- }
-
- void selectAndShowNode(RemoteDiagnosticsNode? node) {
- if (node == null) {
- return;
- }
- selectAndShowInspectorInstanceRef(node.valueRef);
- }
-
- void selectAndShowInspectorInstanceRef(InspectorInstanceRef ref) {
- final node = valueToInspectorTreeNode[ref];
- if (node == null) {
- return;
- }
- setSelectedNode(node);
- syncTreeSelection();
- }
-
- InspectorTreeNode? getTreeNode(RemoteDiagnosticsNode node) {
- return valueToInspectorTreeNode[node.valueRef];
- }
-
- void maybeUpdateValueUI(InspectorInstanceRef valueRef) {
- final node = valueToInspectorTreeNode[valueRef];
- if (node == null) {
- // The value isn't shown in the parent tree. Nothing to do.
- return;
- }
- inspectorTree.nodeChanged(node);
- }
-
- @override
- void onFlutterFrame() {
- flutterAppFrameReady = true;
- if (!visibleToUser) {
- return;
- }
-
- if (!treeLoadStarted) {
- treeLoadStarted = true;
- // This was the first frame.
- unawaited(maybeLoadUI());
- }
- _refreshRateLimiter.scheduleRequest();
- }
-
- @override
- void onInspectorSelectionChanged() {
- if (!visibleToUser) {
- // Don't do anything. We will update the view once it is visible again.
- return;
- }
- if (detailsSubtree) {
- // Wait for the master to update.
- return;
- }
- unawaited(updateSelectionFromService(firstFrame: false));
- }
-
- Future<void> updateSelectionFromService({
- required bool firstFrame,
- String? inspectorRef,
- }) async {
- if (parent != null) {
- // If we have a parent controller we should wait for the parent to update
- // our selection rather than updating it our self.
- return;
- }
- final selectionGroups = _selectionGroups;
- if (selectionGroups == null) {
- // Already disposed. Ignore this requested to update selection.
- return;
- }
- treeLoadStarted = true;
-
- selectionGroups.cancelNext();
-
- final group = selectionGroups.next;
-
- if (inspectorRef != null) {
- await group.setSelectionInspector(
- InspectorInstanceRef(inspectorRef),
- false,
- );
- if (disposed) return;
- }
- final pendingSelectionFuture = group.getSelection(
- selectedDiagnostic,
- treeType,
- restrictToLocalProject: isSummaryTree,
- );
-
- final pendingDetailsFuture = isSummaryTree
- ? group.getSelection(selectedDiagnostic, treeType)
- : null;
-
- try {
- final newSelection = await pendingSelectionFuture;
- if (disposed || group.disposed) return;
- RemoteDiagnosticsNode? detailsSelection;
-
- if (pendingDetailsFuture != null) {
- detailsSelection = await pendingDetailsFuture;
- if (disposed || group.disposed) return;
- }
-
- if (!firstFrame &&
- detailsSelection?.valueRef == details?.selectedDiagnostic?.valueRef &&
- newSelection?.valueRef == selectedDiagnostic?.valueRef) {
- // No need to change the selection as it didn't actually change.
- selectionGroups.cancelNext();
- return;
- }
- selectionGroups.promoteNext();
-
- subtreeRoot = newSelection;
-
- applyNewSelection(newSelection, detailsSelection, true);
-
- // Send an event that a widget was selected on the device.
- ga.select(
- gac.inspector,
- gac.onDeviceSelection,
- screenMetricsProvider: () => InspectorScreenMetrics.legacy(),
- );
- } catch (error, st) {
- if (selectionGroups.next == group) {
- _log.shout(error, error, st);
- selectionGroups.cancelNext();
- }
- }
- }
-
- void applyNewSelection(
- RemoteDiagnosticsNode? newSelection,
- RemoteDiagnosticsNode? detailsSelection,
- bool setSubtreeRoot,
- ) {
- final nodeInTree = findMatchingInspectorTreeNode(newSelection);
-
- if (nodeInTree == null) {
- // The tree has probably changed since we last updated. Do a full refresh
- // so that the tree includes the new node we care about.
- unawaited(
- _recomputeTreeRoot(newSelection, detailsSelection, setSubtreeRoot),
- );
- }
-
- refreshSelection(newSelection, detailsSelection, setSubtreeRoot);
- }
-
- void animateTo(InspectorTreeNode? node) {
- if (node == null) {
- return;
- }
-
- inspectorTree.animateToTargets([node]);
- }
-
- void setSelectedNode(InspectorTreeNode? newSelection) {
- if (newSelection == selectedNode.value) {
- return;
- }
-
- _selectedNode.value = newSelection;
-
- lastExpanded = null; // New selected node takes precedence.
- endShowNode();
- final detailsLocal = details;
- final parantLocal = parent;
- if (detailsLocal != null) {
- detailsLocal.endShowNode();
- } else if (parantLocal != null) {
- parantLocal.endShowNode();
- }
-
- _updateSelectedErrorFromNode(_selectedNode.value);
- animateTo(selectedNode.value);
- }
-
- /// Update the index of the selected error based on a node that has been
- /// selected in the tree.
- void _updateSelectedErrorFromNode(InspectorTreeNode? node) {
- final inspectorRef = node?.diagnostic?.valueRef.id;
-
- final errors = serviceConnection.errorBadgeManager
- .erroredItemsForPage(InspectorScreen.id)
- .value;
-
- // Check whether the node that was just selected has any errors associated
- // with it.
- var errorIndex = inspectorRef != null
- ? errors.keys.toList().indexOf(inspectorRef)
- : null;
- if (errorIndex == -1) {
- errorIndex = null;
- }
-
- _selectedErrorIndex.value = errorIndex;
-
- if (errorIndex != null) {
- // Mark the error as "seen" as this will render slightly differently
- // so the user can track which errored nodes they've viewed.
- serviceConnection.errorBadgeManager.markErrorAsRead(
- InspectorScreen.id,
- errors[inspectorRef!]!,
- );
- // Also clear the error badge since new errors may have arrived while
- // the inspector was visible (normally they're cleared when visiting
- // the screen) and visiting an errored node seems an appropriate
- // acknowledgement of the errors.
- serviceConnection.errorBadgeManager.clearErrorCount(InspectorScreen.id);
- }
- }
-
- /// Updates the index of the selected error and selects its node in the tree.
- void selectErrorByIndex(int index) {
- _selectedErrorIndex.value = index;
-
- final errors = serviceConnection.errorBadgeManager
- .erroredItemsForPage(InspectorScreen.id)
- .value;
-
- unawaited(
- updateSelectionFromService(
- firstFrame: false,
- inspectorRef: errors.keys.elementAt(index),
- ),
- );
- }
-
- void _onExpand(InspectorTreeNode node) {
- unawaited(inspectorTree.maybePopulateChildren(node));
- }
-
- Future<void> _addNodeToConsole(InspectorTreeNode node) async {
- final valueRef = node.diagnostic!.valueRef;
- final isolateRef = inspectorService.isolateRef;
- final instanceRef = await node.diagnostic!.objectGroupApi
- ?.toObservatoryInstanceRef(valueRef);
- if (disposed) return;
-
- if (instanceRef != null) {
- await serviceConnection.consoleService.appendInstanceRef(
- value: instanceRef,
- diagnostic: node.diagnostic,
- isolateRef: isolateRef,
- forceScrollIntoView: true,
- );
- }
- }
-
- void selectionChanged() {
- if (!visibleToUser) {
- return;
- }
-
- final node = inspectorTree.selection;
- if (node != null) {
- unawaited(inspectorTree.maybePopulateChildren(node));
- }
- if (programmaticSelectionChangeInProgress) {
- return;
- }
- if (node != null) {
- setSelectedNode(node);
- unawaited(_addNodeToConsole(node));
-
- // Don't reroot if the selected value is already visible in the details tree.
- final maybeReroot =
- isSummaryTree &&
- details != null &&
- selectedDiagnostic != null &&
- !details!.hasDiagnosticsValue(selectedDiagnostic!.valueRef);
- syncSelectionHelper(
- maybeRerootDetailsTree: maybeReroot,
- selection: selectedDiagnostic,
- detailsSelection: selectedDiagnostic,
- );
-
- if (!maybeReroot) {
- final parantLocal = parent;
- final detailsLocal = details;
-
- if (isSummaryTree && detailsLocal != null) {
- detailsLocal.selectAndShowNode(selectedDiagnostic);
- } else if (parantLocal != null) {
- parantLocal.selectAndShowNode(
- firstAncestorInParentTree(selectedNode.value),
- );
- }
- }
- }
- }
-
- RemoteDiagnosticsNode? firstAncestorInParentTree(InspectorTreeNode? node) {
- final parentLocal = parent;
-
- if (parentLocal == null) {
- return node?.diagnostic;
- }
- while (node != null) {
- final diagnostic = node.diagnostic;
- if (diagnostic != null &&
- parentLocal.hasDiagnosticsValue(diagnostic.valueRef)) {
- return parentLocal.findDiagnosticsValue(diagnostic.valueRef);
- }
- node = node.parent;
- }
- return null;
- }
-
- void syncSelectionHelper({
- required bool maybeRerootDetailsTree,
- required RemoteDiagnosticsNode? selection,
- required RemoteDiagnosticsNode? detailsSelection,
- }) {
- if (selection != null) {
- if (selection.isCreatedByLocalProject) {
- _navigateTo(selection);
- }
- }
- if (detailsSubtree || details == null) {
- if (selection != null) {
- var toSelect = selectedNode.value;
-
- while (toSelect != null && toSelect.diagnostic!.isProperty) {
- toSelect = toSelect.parent;
- }
-
- if (toSelect != null) {
- final diagnosticToSelect = toSelect.diagnostic!;
- unawaited(diagnosticToSelect.setSelectionInspector(true));
- }
- }
- }
-
- if (maybeRerootDetailsTree) {
- _showDetailSubtrees(selection, detailsSelection);
- } else if (selection != null) {
- // We can't rely on the details tree to update the selection on the server in this case.
- unawaited(selection.setSelectionInspector(true));
- }
- }
-
- // TODO(jacobr): implement this method and use the parameter.
- // ignore: avoid-unused-parameters
- void _navigateTo(RemoteDiagnosticsNode diagnostic) {
- // TODO(jacobr): dispatch an event over the inspectorService requesting a
- // navigate operation.
- }
-
- @override
- void dispose() {
- assert(!disposed);
- if (serviceConnection.inspectorService != null) {
- shutdownTree(false);
- }
- _treeGroups?.clear(false);
- _treeGroups = null;
- _selectionGroups?.clear(false);
- _selectionGroups = null;
- details?.dispose();
-
- _refreshRateLimiter.dispose();
- _selectedNode.dispose();
- _selectedErrorIndex.dispose();
- super.dispose();
- }
-
- void _onNodeAdded(
- InspectorTreeNode node,
- RemoteDiagnosticsNode diagnosticsNode,
- ) {
- final valueRef = diagnosticsNode.valueRef;
- // Properties do not have unique values so should not go in the valueToInspectorTreeNode map.
- if (valueRef.id != null && !diagnosticsNode.isProperty) {
- valueToInspectorTreeNode[valueRef] = node;
- }
- }
-
- Future<void> expandAllNodesInDetailsTree() async {
- final detailsLocal = details!;
- await detailsLocal._recomputeTreeRoot(
- inspectorTree.selection?.diagnostic,
- detailsLocal.inspectorTree.selection?.diagnostic ??
- detailsLocal.inspectorTree.root?.diagnostic,
- false,
- subtreeDepth: maxJsInt,
- );
- }
-
- void collapseDetailsToSelected() {
- final detailsLocal = details!;
- detailsLocal.inspectorTree.collapseToSelected();
- detailsLocal.animateTo(detailsLocal.inspectorTree.selection);
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/inspector_data_models.dart b/packages/devtools_app/lib/src/screens/inspector/inspector_data_models.dart
deleted file mode 100644
index a12ed91..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/inspector_data_models.dart
+++ /dev/null
@@ -1,887 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-/// @docImport 'layout_explorer/ui/overflow_indicator_painter.dart';
-library;
-
-import 'dart:math' as math;
-
-import 'package:flutter/rendering.dart';
-
-import '../../shared/diagnostics/diagnostics_node.dart';
-import '../../shared/primitives/math_utils.dart';
-import '../../shared/primitives/utils.dart';
-import 'layout_explorer/flex/utils.dart';
-
-const overflowEpsilon = 0.1;
-
-/// Compute real widget sizes into rendered sizes to be displayed on the details tab.
-/// The sum of the resulting render sizes may or may not be greater than the [maxSizeAvailable]
-/// In the case where it is greater, we should render it with scrolling capability.
-///
-/// Variables:
-/// - [sizes] : real size for widgets that want to be rendered / scaled
-/// - [smallestSize] : the smallest element in the array [sizes]
-/// - [largestSize] : the largest element in the array [sizes]
-/// - [smallestRenderSize] : render size for smallest element
-/// - [largestRenderSize] : render size for largest element
-/// - [maxSizeAvailable] : maximum size available for rendering the widget
-/// - [useMaxSizeAvailable] : flag for forcing the widget dimension to be at least [maxSizeAvailable]
-///
-/// if [useMaxSizeAvailable] is set to true,
-/// this method will ignore the largestRenderSize
-/// and compute its own largestRenderSize to force
-/// the sum of the render size to be equals to [maxSizeAvailable]
-///
-/// Formula for computing render size:
-/// ```
-/// renderSize[i] = (size[i] - smallestSize)
-/// * (largestRenderSize - smallestRenderSize)
-/// / (largestSize - smallestSize) + smallestRenderSize
-/// ```
-/// Explanation:
-/// - The computation formula for transforming size to renderSize is based on these two things:
-/// - smallest element will be rendered to [smallestRenderSize]
-/// - largest element will be rendered to [largestRenderSize]
-/// - any other size will be scaled accordingly
-/// - The formula above is derived from:
-/// ```
-/// (renderSize[i] - smallestRenderSize) / (largestRenderSize - smallestRenderSize)
-/// = (size[i] - smallestSize) / (size[i] - smallestSize)
-/// ```
-///
-/// Formula for computing forced [largestRenderSize]:
-/// ```
-/// largestRenderSize = (maxSizeAvailable - sizes.length * smallestRenderSize)
-/// * (largestSize - smallestSize) / sum(s[i] - ss) + smallestRenderSize
-/// ```
-/// Explanation:
-/// - This formula is derived from the equation:
-/// ```
-/// sum(renderSize) = maxSizeAvailable
-/// ```
-List<double> computeRenderSizes({
- required Iterable<double> sizes,
- required double smallestSize,
- required double largestSize,
- required double smallestRenderSize,
- required double largestRenderSize,
- required double maxSizeAvailable,
- bool useMaxSizeAvailable = true,
-}) {
- final n = sizes.length;
-
- if (smallestSize == largestSize) {
- // It means that all widget have the same size
- // and we can just divide the size evenly
- // but it should be at least as big as [smallestRenderSize]
- final renderSize = math.max(smallestRenderSize, maxSizeAvailable / n);
- return [for (final _ in sizes) renderSize];
- }
-
- List<double> transformToRenderSize(double largestRenderSize) => [
- for (final s in sizes)
- (s - smallestSize) *
- (largestRenderSize - smallestRenderSize) /
- (largestSize - smallestSize) +
- smallestRenderSize,
- ];
-
- var renderSizes = transformToRenderSize(largestRenderSize);
-
- if (useMaxSizeAvailable && sum(renderSizes) < maxSizeAvailable) {
- largestRenderSize =
- (maxSizeAvailable - n * smallestRenderSize) *
- (largestSize - smallestSize) /
- sum([for (final s in sizes) s - smallestSize]) +
- smallestRenderSize;
- renderSizes = transformToRenderSize(largestRenderSize);
- }
- return renderSizes;
-}
-
-// TODO(albertusangga): Move this to [RemoteDiagnosticsNode] once dart:html app is removed
-/// Represents parsed layout information for a specific [RemoteDiagnosticsNode].
-class LayoutProperties {
- LayoutProperties(this.node, {int copyLevel = 1})
- : description = node.description,
- size = node.size!,
- constraints = node.constraints,
- isFlex = node.isFlex,
- flexFactor = node.flexFactor,
- flexFit = node.flexFit,
- children = copyLevel == 0
- ? []
- : node.childrenNow
- .map(
- (child) => LayoutProperties(child, copyLevel: copyLevel - 1),
- )
- .toList(growable: false) {
- for (final child in children) {
- child.parent = this;
- }
- }
-
- LayoutProperties.values({
- required this.node,
- required this.children,
- required this.constraints,
- required this.description,
- required this.flexFactor,
- required this.isFlex,
- required this.size,
- required this.flexFit,
- }) {
- for (final child in children) {
- child.parent = this;
- }
- }
-
- LayoutProperties? parent;
- final RemoteDiagnosticsNode node;
- final List<LayoutProperties> children;
- final BoxConstraints? constraints;
- final String? description;
- final num? flexFactor;
- final FlexFit? flexFit;
- final bool isFlex;
- final Size size;
-
- /// Represents the order of [children] to be displayed.
- List<LayoutProperties> get displayChildren => children;
-
- bool get hasFlexFactor {
- final flexFactorLocal = flexFactor;
- if (flexFactorLocal == null) return false;
- return flexFactorLocal > 0;
- }
-
- int get totalChildren => children.length;
-
- bool get hasChildren => children.isNotEmpty;
-
- double get width => size.width;
-
- double get height => size.height;
-
- double dimension(Axis axis) => axis == Axis.horizontal ? width : height;
-
- List<double> childrenDimensions(Axis axis) {
- return displayChildren.map((child) => child.dimension(axis)).toList();
- }
-
- List<double> get childrenWidths => childrenDimensions(Axis.horizontal);
-
- List<double> get childrenHeights => childrenDimensions(Axis.vertical);
-
- String describeWidthConstraints() {
- final constraintsLocal = constraints;
- if (constraintsLocal == null) return '';
- return constraintsLocal.hasBoundedWidth
- ? describeAxis(
- constraintsLocal.minWidth,
- constraintsLocal.maxWidth,
- 'w',
- )
- : 'width is unconstrained';
- }
-
- String describeHeightConstraints() {
- final constraintsLocal = constraints;
- if (constraintsLocal == null) return '';
- return constraintsLocal.hasBoundedHeight
- ? describeAxis(
- constraintsLocal.minHeight,
- constraintsLocal.maxHeight,
- 'h',
- )
- : 'height is unconstrained';
- }
-
- String describeWidth() => 'w=${toStringAsFixed(size.width)}';
-
- String describeHeight() => 'h=${toStringAsFixed(size.height)}';
-
- bool get isOverflowWidth {
- final parentWidth = parent?.width;
- if (parentWidth == null) return false;
- final parentData = node.parentData;
- double widthUsed = width;
-
- widthUsed += parentData.offset.dx;
-
- // TODO(jacobr): certain widgets may allow overflow so this may false
- // positive a bit for cases like Stack.
- return widthUsed > parentWidth + overflowEpsilon;
- }
-
- bool get isOverflowHeight {
- final parentHeight = parent?.height;
- if (parentHeight == null) return false;
- final parentData = node.parentData;
- double heightUsed = height;
-
- heightUsed += parentData.offset.dy;
-
- return heightUsed > parentHeight + overflowEpsilon;
- }
-
- static String describeAxis(double min, double max, String axis) {
- if (min == max) return '$axis=${min.toStringAsFixed(1)}';
- return '${min.toStringAsFixed(1)}<=$axis<=${max.toStringAsFixed(1)}';
- }
-
- LayoutProperties copyWith({
- List<LayoutProperties>? children,
- BoxConstraints? constraints,
- String? description,
- int? flexFactor,
- FlexFit? flexFit,
- bool? isFlex,
- Size? size,
- }) {
- return LayoutProperties.values(
- node: node,
- children: children ?? this.children,
- constraints: constraints ?? this.constraints,
- description: description ?? this.description,
- flexFactor: flexFactor ?? this.flexFactor,
- isFlex: isFlex ?? this.isFlex,
- size: size ?? this.size,
- flexFit: flexFit ?? this.flexFit,
- );
- }
-}
-
-/// Enum object to represent which side of the widget is overflowing.
-///
-/// See also:
-/// * [OverflowIndicatorPainter]
-enum OverflowSide { right, bottom }
-
-// TODO(jacobr): is it possible to overflow on multiple sides?
-// TODO(jacobr): do we need to worry about overflowing on the left side in RTL
-// layouts? We need to audit the Flutter semantics for determining overflow to
-// make sure we are consistent.
-extension LayoutPropertiesExtension on LayoutProperties {
- OverflowSide? get overflowSide {
- if (isOverflowWidth) return OverflowSide.right;
- if (isOverflowHeight) return OverflowSide.bottom;
- return null;
- }
-}
-
-final _flexLayoutExpando = Expando<FlexLayoutProperties>();
-
-extension MainAxisAlignmentExtension on MainAxisAlignment {
- MainAxisAlignment get reversed {
- switch (this) {
- case MainAxisAlignment.start:
- return MainAxisAlignment.end;
- case MainAxisAlignment.end:
- return MainAxisAlignment.start;
- default:
- return this;
- }
- }
-}
-
-/// TODO(albertusangga): Move this to [RemoteDiagnosticsNode] once dart:html app is removed.
-class FlexLayoutProperties extends LayoutProperties {
- FlexLayoutProperties({
- required super.size,
- required super.children,
- required super.node,
- super.constraints,
- super.isFlex = false,
- super.description,
- super.flexFactor,
- super.flexFit,
- this.direction = Axis.vertical,
- this.mainAxisAlignment,
- this.crossAxisAlignment,
- this.mainAxisSize,
- required this.textDirection,
- required this.verticalDirection,
- this.textBaseline,
- }) : super.values();
-
- FlexLayoutProperties._fromNode(
- super.node, {
- this.direction = Axis.vertical,
- this.mainAxisAlignment,
- this.mainAxisSize,
- this.crossAxisAlignment,
- required this.textDirection,
- required this.verticalDirection,
- this.textBaseline,
- });
-
- factory FlexLayoutProperties.fromDiagnostics(RemoteDiagnosticsNode node) {
- // Cache the properties on an expando so that local tweaks to
- // FlexLayoutProperties persist across multiple lookups from an
- // RemoteDiagnosticsNode.
- return _flexLayoutExpando[node] ??= _buildNode(node);
- }
-
- @override
- FlexLayoutProperties copyWith({
- Size? size,
- List<LayoutProperties>? children,
- BoxConstraints? constraints,
- bool? isFlex,
- String? description,
- num? flexFactor,
- FlexFit? flexFit,
- Axis? direction,
- MainAxisAlignment? mainAxisAlignment,
- MainAxisSize? mainAxisSize,
- CrossAxisAlignment? crossAxisAlignment,
- TextDirection? textDirection,
- VerticalDirection? verticalDirection,
- TextBaseline? textBaseline,
- }) {
- return FlexLayoutProperties(
- size: size ?? this.size,
- children: children ?? this.children,
- node: node,
- constraints: constraints ?? this.constraints,
- isFlex: isFlex ?? this.isFlex,
- description: description ?? this.description,
- flexFactor: flexFactor ?? this.flexFactor,
- flexFit: flexFit ?? this.flexFit,
- direction: direction ?? this.direction,
- mainAxisAlignment: mainAxisAlignment ?? this.mainAxisAlignment,
- mainAxisSize: mainAxisSize ?? this.mainAxisSize,
- crossAxisAlignment: crossAxisAlignment ?? this.crossAxisAlignment,
- textDirection: textDirection ?? this.textDirection,
- verticalDirection: verticalDirection ?? this.verticalDirection,
- textBaseline: textBaseline ?? this.textBaseline,
- );
- }
-
- static FlexLayoutProperties _buildNode(RemoteDiagnosticsNode node) {
- final renderObjectJson = node.renderObject!.json;
- final properties = (renderObjectJson['properties'] as List<Object?>)
- .cast<Map<String, Object?>>();
-
- final data = {
- for (final property in properties)
- property['name']: property['description'] as String?,
- };
-
- return FlexLayoutProperties._fromNode(
- node,
- direction: _directionNamesToValues[data['direction']] ?? Axis.vertical,
- mainAxisAlignment:
- _mainAxisAlignmentNamesToValues[data['mainAxisAlignment']],
- mainAxisSize: _mainAxisSizeNamesToValues[data['mainAxisSize']],
- crossAxisAlignment:
- _crossAxisAlignmentNamesToValues[data['crossAxisAlignment']],
- textDirection:
- _textDirectionNamesToValues[data['textDirection']] ??
- TextDirection.ltr,
- verticalDirection:
- _verticalDirectionNamesToValues[data['verticalDirection']] ??
- VerticalDirection.down,
- textBaseline: _textBaselineNamesToValues[data['textBaseline']],
- );
- }
-
- final Axis direction;
- final MainAxisAlignment? mainAxisAlignment;
- final CrossAxisAlignment? crossAxisAlignment;
- final MainAxisSize? mainAxisSize;
- final TextDirection textDirection;
- final VerticalDirection verticalDirection;
- final TextBaseline? textBaseline;
-
- List<LayoutProperties>? _displayChildren;
-
- @override
- List<LayoutProperties> get displayChildren {
- final displayChildren = _displayChildren;
- if (displayChildren != null) return displayChildren;
- return _displayChildren = startIsTopLeft
- ? children
- : children.reversed.toList();
- }
-
- int? _totalFlex;
-
- bool get isMainAxisHorizontal => direction == Axis.horizontal;
-
- bool get isMainAxisVertical => direction == Axis.vertical;
-
- String get horizontalDirectionDescription {
- return direction == Axis.horizontal ? 'Main Axis' : 'Cross Axis';
- }
-
- String get verticalDirectionDescription {
- return direction == Axis.vertical ? 'Main Axis' : 'Cross Axis';
- }
-
- String get type => direction.flexType;
-
- num get totalFlex {
- if (children.isEmpty) return 0;
- _totalFlex ??= children
- .map((child) => child.flexFactor ?? 0)
- .reduce((value, element) => value + element)
- .toInt();
- return _totalFlex!;
- }
-
- Axis get crossAxisDirection {
- return direction == Axis.horizontal ? Axis.vertical : Axis.horizontal;
- }
-
- double get mainAxisDimension => dimension(direction);
-
- double get crossAxisDimension => dimension(crossAxisDirection);
-
- @override
- bool get isOverflowWidth {
- if (direction == Axis.horizontal) {
- return width + overflowEpsilon < sum(childrenWidths);
- }
- return width + overflowEpsilon < max(childrenWidths);
- }
-
- @override
- bool get isOverflowHeight {
- if (direction == Axis.vertical) {
- return height + overflowEpsilon < sum(childrenHeights);
- }
- return height + overflowEpsilon < max(childrenHeights);
- }
-
- bool get startIsTopLeft {
- switch (direction) {
- case Axis.horizontal:
- switch (textDirection) {
- case TextDirection.ltr:
- return true;
- case TextDirection.rtl:
- return false;
- }
- case Axis.vertical:
- switch (verticalDirection) {
- case VerticalDirection.down:
- return true;
- case VerticalDirection.up:
- return false;
- }
- }
- }
-
- /// render properties for laying out rendered Flex & Flex children widgets
- /// the computation is similar to [RenderFlex].performLayout() method
- List<RenderProperties> childrenRenderProperties({
- required double smallestRenderWidth,
- required double largestRenderWidth,
- required double smallestRenderHeight,
- required double largestRenderHeight,
- required double Function(Axis) maxSizeAvailable,
- }) {
- /// calculate the render empty spaces
- final freeSpace = dimension(direction) - sum(childrenDimensions(direction));
- final displayMainAxisAlignment = startIsTopLeft
- ? mainAxisAlignment
- : mainAxisAlignment?.reversed;
-
- double leadingSpace(double freeSpace) {
- if (children.isEmpty) return 0.0;
- switch (displayMainAxisAlignment) {
- case MainAxisAlignment.start:
- case MainAxisAlignment.end:
- return freeSpace;
- case MainAxisAlignment.center:
- return freeSpace * 0.5;
- case MainAxisAlignment.spaceBetween:
- return 0.0;
- case MainAxisAlignment.spaceAround:
- final spaceBetweenChildren = freeSpace / children.length;
- return spaceBetweenChildren * 0.5;
- case MainAxisAlignment.spaceEvenly:
- return freeSpace / (children.length + 1);
- default:
- return 0.0;
- }
- }
-
- double betweenSpace(double freeSpace) {
- if (children.isEmpty) return 0.0;
- switch (displayMainAxisAlignment) {
- case MainAxisAlignment.start:
- case MainAxisAlignment.end:
- case MainAxisAlignment.center:
- return 0.0;
- case MainAxisAlignment.spaceBetween:
- if (children.length == 1) return freeSpace;
- return freeSpace / (children.length - 1);
- case MainAxisAlignment.spaceAround:
- return freeSpace / children.length;
- case MainAxisAlignment.spaceEvenly:
- return freeSpace / (children.length + 1);
- default:
- return 0.0;
- }
- }
-
- double smallestRenderSize(Axis axis) {
- return axis == Axis.horizontal
- ? smallestRenderWidth
- : smallestRenderHeight;
- }
-
- double largestRenderSize(Axis axis) {
- final lrs = axis == Axis.horizontal
- ? largestRenderWidth
- : largestRenderHeight;
- // use all the space when visualizing cross axis
- return (axis == direction) ? lrs : maxSizeAvailable(axis);
- }
-
- List<double> renderSizes(Axis axis) {
- final sizes = childrenDimensions(axis);
- if (freeSpace > 0.0 && axis == direction) {
- /// include free space in the computation
- sizes.add(freeSpace);
- }
- final smallestSize = min(sizes);
- final largestSize = max(sizes);
- if (axis == direction ||
- (crossAxisAlignment != CrossAxisAlignment.stretch &&
- smallestSize != largestSize)) {
- return computeRenderSizes(
- sizes: sizes,
- smallestSize: smallestSize,
- largestSize: largestSize,
- smallestRenderSize: smallestRenderSize(axis),
- largestRenderSize: largestRenderSize(axis),
- maxSizeAvailable: maxSizeAvailable(axis),
- );
- } else {
- // uniform cross axis sizes.
- double size = crossAxisAlignment == CrossAxisAlignment.stretch
- ? maxSizeAvailable(axis)
- : largestSize /
- math.max(dimension(axis), 1.0) *
- maxSizeAvailable(axis);
- size = math.max(size, smallestRenderSize(axis));
- return sizes.map((_) => size).toList();
- }
- }
-
- final widths = renderSizes(Axis.horizontal);
- final heights = renderSizes(Axis.vertical);
-
- final renderFreeSpace = freeSpace > 0.0
- ? (isMainAxisHorizontal ? widths.last : heights.last)
- : 0.0;
-
- final renderLeadingSpace = leadingSpace(renderFreeSpace);
- final renderBetweenSpace = betweenSpace(renderFreeSpace);
-
- final childrenRenderProps = <RenderProperties>[];
-
- double lastMainAxisOffset() {
- if (childrenRenderProps.isEmpty) return 0.0;
- return childrenRenderProps.last.mainAxisOffset;
- }
-
- double lastMainAxisDimension() {
- if (childrenRenderProps.isEmpty) return 0.0;
- return childrenRenderProps.last.mainAxisDimension;
- }
-
- double space(int index) {
- if (index == 0) {
- if (displayMainAxisAlignment == MainAxisAlignment.start) return 0.0;
- return renderLeadingSpace;
- }
- return renderBetweenSpace;
- }
-
- double calculateMainAxisOffset(int i) {
- return lastMainAxisOffset() + lastMainAxisDimension() + space(i);
- }
-
- double calculateCrossAxisOffset(int i) {
- final maxDimension = maxSizeAvailable(crossAxisDirection);
- final usedDimension = crossAxisDirection == Axis.horizontal
- ? widths[i]
- : heights[i];
-
- if (crossAxisAlignment == CrossAxisAlignment.start ||
- crossAxisAlignment == CrossAxisAlignment.stretch ||
- maxDimension == usedDimension) {
- return 0.0;
- }
- final emptySpace = math.max(0.0, maxDimension - usedDimension);
- if (crossAxisAlignment == CrossAxisAlignment.end) return emptySpace;
- return emptySpace * 0.5;
- }
-
- for (var i = 0; i < children.length; ++i) {
- childrenRenderProps.add(
- RenderProperties(
- axis: direction,
- size: Size(widths[i], heights[i]),
- offset: Offset.zero,
- realSize: displayChildren[i].size,
- layoutProperties: displayChildren[i],
- )
- ..mainAxisOffset = calculateMainAxisOffset(i)
- ..crossAxisOffset = calculateCrossAxisOffset(i),
- );
- }
-
- final spaces = <RenderProperties>[];
- final actualLeadingSpace = leadingSpace(freeSpace);
- final actualBetweenSpace = betweenSpace(freeSpace);
- final renderPropsWithFullCrossAxisDimension =
- RenderProperties(
- axis: direction,
- isFreeSpace: true,
- layoutProperties: this,
- )
- ..crossAxisDimension = maxSizeAvailable(crossAxisDirection)
- ..crossAxisRealDimension = dimension(crossAxisDirection)
- ..crossAxisOffset = 0.0;
- if (actualLeadingSpace > 0.0 &&
- displayMainAxisAlignment != MainAxisAlignment.start) {
- spaces.add(
- renderPropsWithFullCrossAxisDimension.copyWith()
- ..mainAxisOffset = 0.0
- ..mainAxisDimension = renderLeadingSpace
- ..mainAxisRealDimension = actualLeadingSpace,
- );
- }
- if (actualBetweenSpace > 0.0) {
- for (var i = 0; i < childrenRenderProps.length - 1; ++i) {
- final child = childrenRenderProps[i];
- spaces.add(
- renderPropsWithFullCrossAxisDimension.copyWith()
- ..mainAxisDimension = renderBetweenSpace
- ..mainAxisRealDimension = actualBetweenSpace
- ..mainAxisOffset = child.mainAxisOffset + child.mainAxisDimension,
- );
- }
- }
- if (actualLeadingSpace > 0.0 &&
- displayMainAxisAlignment != MainAxisAlignment.end) {
- spaces.add(
- renderPropsWithFullCrossAxisDimension.copyWith()
- ..mainAxisOffset =
- childrenRenderProps.last.mainAxisDimension +
- childrenRenderProps.last.mainAxisOffset
- ..mainAxisDimension = renderLeadingSpace
- ..mainAxisRealDimension = actualLeadingSpace,
- );
- }
- return [...childrenRenderProps, ...spaces];
- }
-
- List<RenderProperties> crossAxisSpaces({
- required List<RenderProperties> childrenRenderProperties,
- required double Function(Axis) maxSizeAvailable,
- }) {
- if (crossAxisAlignment == CrossAxisAlignment.stretch) return [];
- final spaces = <RenderProperties>[];
- for (var i = 0; i < children.length; ++i) {
- if (dimension(crossAxisDirection) ==
- displayChildren[i].dimension(crossAxisDirection) ||
- childrenRenderProperties[i].crossAxisDimension ==
- maxSizeAvailable(crossAxisDirection)) {
- continue;
- }
-
- final renderProperties = childrenRenderProperties[i];
- final space = renderProperties.copyWith(isFreeSpace: true);
-
- space.crossAxisRealDimension =
- crossAxisDimension - space.crossAxisRealDimension;
- space.crossAxisDimension =
- maxSizeAvailable(crossAxisDirection) - space.crossAxisDimension;
- if (space.crossAxisDimension <= 0.0) continue;
- if (crossAxisAlignment == CrossAxisAlignment.center) {
- space.crossAxisDimension *= 0.5;
- final crossAxisRealDimension = space.crossAxisRealDimension;
- space.crossAxisRealDimension = crossAxisRealDimension * 0.5;
- spaces.add(space.copyWith()..crossAxisOffset = 0.0);
- spaces.add(
- space.copyWith()
- ..crossAxisOffset =
- renderProperties.crossAxisDimension +
- renderProperties.crossAxisOffset,
- );
- } else {
- space.crossAxisOffset = crossAxisAlignment == CrossAxisAlignment.end
- ? 0
- : renderProperties.crossAxisDimension;
- spaces.add(space);
- }
- }
- return spaces;
- }
-
- static final _directionNamesToValues = Axis.values.asNameMap();
- static final _mainAxisAlignmentNamesToValues = MainAxisAlignment.values
- .asNameMap();
- static final _mainAxisSizeNamesToValues = MainAxisSize.values.asNameMap();
- static final _crossAxisAlignmentNamesToValues = CrossAxisAlignment.values
- .asNameMap();
- static final _textDirectionNamesToValues = TextDirection.values.asNameMap();
- static final _verticalDirectionNamesToValues = VerticalDirection.values
- .asNameMap();
- static final _textBaselineNamesToValues = TextBaseline.values.asNameMap();
-}
-
-/// Information for rendering a [LayoutProperties] node.
-class RenderProperties {
- RenderProperties({
- required this.axis,
- required this.layoutProperties,
- this.isFreeSpace = false,
- Size? size,
- Offset? offset,
- Size? realSize,
- }) : width = size?.width ?? 0.0,
- height = size?.height ?? 0.0,
- realWidth = realSize?.width ?? 0.0,
- realHeight = realSize?.height ?? 0.0,
- dx = offset?.dx ?? 0.0,
- dy = offset?.dy ?? 0.0;
-
- final Axis axis;
-
- /// Represents which node is rendered for this object.
- final LayoutProperties layoutProperties;
-
- final bool isFreeSpace;
-
- double dx, dy;
- double width, height;
- double realWidth, realHeight;
-
- Size get size => Size(width, height);
-
- Size get realSize => Size(realWidth, realHeight);
-
- Offset get offset => Offset(dx, dy);
-
- double get mainAxisDimension => axis == Axis.horizontal ? width : height;
-
- set mainAxisDimension(double dim) {
- if (axis == Axis.horizontal) {
- width = dim;
- } else {
- height = dim;
- }
- }
-
- double get crossAxisDimension => axis == Axis.horizontal ? height : width;
-
- set crossAxisDimension(double dim) {
- if (axis == Axis.horizontal) {
- height = dim;
- } else {
- width = dim;
- }
- }
-
- double get mainAxisOffset => axis == Axis.horizontal ? dx : dy;
-
- set mainAxisOffset(double offset) {
- if (axis == Axis.horizontal) {
- dx = offset;
- } else {
- dy = offset;
- }
- }
-
- double get crossAxisOffset => axis == Axis.horizontal ? dy : dx;
-
- set crossAxisOffset(double offset) {
- if (axis == Axis.horizontal) {
- dy = offset;
- } else {
- dx = offset;
- }
- }
-
- double get mainAxisRealDimension =>
- axis == Axis.horizontal ? realWidth : realHeight;
-
- set mainAxisRealDimension(double newVal) {
- if (axis == Axis.horizontal) {
- realWidth = newVal;
- } else {
- realHeight = newVal;
- }
- }
-
- double get crossAxisRealDimension =>
- axis == Axis.horizontal ? realHeight : realWidth;
-
- set crossAxisRealDimension(double newVal) {
- if (axis == Axis.horizontal) {
- realHeight = newVal;
- } else {
- realWidth = newVal;
- }
- }
-
- RenderProperties copyWith({bool? isFreeSpace}) {
- return RenderProperties(
- axis: axis,
- size: size,
- offset: offset,
- realSize: realSize,
- layoutProperties: layoutProperties,
- isFreeSpace: isFreeSpace ?? this.isFreeSpace,
- );
- }
-
- @override
- int get hashCode =>
- axis.hashCode ^
- size.hashCode ^
- offset.hashCode ^
- realSize.hashCode ^
- isFreeSpace.hashCode;
-
- @override
- bool operator ==(Object other) {
- return other is RenderProperties &&
- axis == other.axis &&
- size.closeTo(other.size) &&
- offset.closeTo(other.offset) &&
- realSize.closeTo(other.realSize) &&
- isFreeSpace == other.isFreeSpace;
- }
-
- @override
- String toString() {
- return '{ axis: $axis, size: $size, offset: $offset, realSize: $realSize, isFreeSpace: $isFreeSpace }';
- }
-}
-
-bool _closeTo(double a, double b, {int precision = 1}) {
- return a.toStringAsPrecision(precision) == b.toStringAsPrecision(precision);
-}
-
-extension on Size {
- bool closeTo(Size other) {
- return _closeTo(width, other.width) && _closeTo(height, other.height);
- }
-}
-
-extension on Offset {
- bool closeTo(Offset other) {
- return _closeTo(dx, other.dx) && _closeTo(dy, other.dy);
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/inspector_screen_body.dart b/packages/devtools_app/lib/src/screens/inspector/inspector_screen_body.dart
deleted file mode 100644
index 5ab577d..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/inspector_screen_body.dart
+++ /dev/null
@@ -1,458 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:async';
-import 'dart:collection';
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:devtools_app_shared/utils.dart';
-import 'package:flutter/material.dart';
-
-import '../../service/service_extension_widgets.dart';
-import '../../service/service_extensions.dart' as extensions;
-import '../../shared/analytics/analytics.dart' as ga;
-import '../../shared/analytics/constants.dart' as gac;
-import '../../shared/analytics/metrics.dart';
-import '../../shared/console/eval/inspector_tree.dart';
-import '../../shared/globals.dart';
-import '../../shared/managers/banner_messages.dart';
-import '../../shared/managers/error_badge_manager.dart';
-import '../../shared/primitives/blocking_action_mixin.dart';
-import '../../shared/ui/common_widgets.dart';
-import '../../shared/ui/search.dart';
-import '../inspector_shared/inspector_controls.dart';
-import '../inspector_shared/inspector_screen.dart';
-import '../inspector_shared/inspector_settings_dialog.dart';
-import 'inspector_controller.dart';
-import 'inspector_screen_details_tab.dart';
-import 'inspector_tree_controller.dart';
-
-class InspectorScreenBody extends StatefulWidget {
- const InspectorScreenBody({super.key, required this.controller});
-
- final InspectorController controller;
-
- @override
- InspectorScreenBodyState createState() => InspectorScreenBodyState();
-}
-
-class InspectorScreenBodyState extends State<InspectorScreenBody>
- with BlockingActionMixin, AutoDisposeMixin {
- InspectorController get controller => widget.controller;
-
- InspectorTreeController get _summaryTreeController =>
- controller.inspectorTree;
-
- InspectorTreeController get _detailsTreeController =>
- controller.details!.inspectorTree;
-
- bool searchVisible = false;
-
- SearchControllerMixin get searchController => _summaryTreeController;
-
- /// Indicates whether search can be closed. The value is set to true when
- /// search target type dropdown is displayed
- /// TODO(https://github.com/flutter/devtools/issues/3489) use this variable when adding the scope dropdown
- bool searchPreventClose = false;
-
- SearchTargetType searchTarget = SearchTargetType.widget;
-
- static const summaryTreeKey = Key('Summary Tree');
- static const detailsTreeKey = Key('Details Tree');
- static const minScreenWidthForText = 900.0;
- static const serviceExtensionButtonsIncludeTextWidth = 1200.0;
-
- @override
- void initState() {
- super.initState();
- ga.screen(InspectorScreen.id);
- }
-
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
-
- if (serviceConnection.inspectorService == null) {
- // The app must not be a Flutter app.
- return;
- }
-
- cancelListeners();
- searchVisible = searchController.search.isNotEmpty;
- addAutoDisposeListener(searchController.searchFieldFocusNode, () {
- final searchFieldFocusNode = searchController.searchFieldFocusNode;
- if (searchFieldFocusNode == null) return;
- // Close the search once focus is lost and following conditions are met:
- // 1. Search string is empty.
- // 2. [searchPreventClose] == false (this is set true when searchTargetType Dropdown is opened).
- if (!searchFieldFocusNode.hasFocus &&
- searchController.search.isEmpty &&
- !searchPreventClose) {
- setState(() {
- searchVisible = false;
- });
- }
-
- // Reset [searchPreventClose] state to false after the search field gains focus.
- // Focus is returned automatically once the Dropdown menu is closed.
- if (searchFieldFocusNode.hasFocus) {
- searchPreventClose = false;
- }
- });
- addAutoDisposeListener(preferences.inspector.pubRootDirectories, () {
- if (serviceConnection.serviceManager.connectedState.value.connected &&
- controller.firstInspectorTreeLoadCompleted) {
- _refreshInspector();
- }
- });
-
- if (!controller.firstInspectorTreeLoadCompleted) {
- ga.timeStart(InspectorScreen.id, gac.pageReady);
- }
-
- _summaryTreeController.setSearchTarget(searchTarget);
-
- _showLegacyInspectorWarning(context);
- }
-
- @override
- Widget build(BuildContext context) {
- final summaryTree = _buildSummaryTreeColumn();
-
- final detailsTree = InspectorTree(
- key: detailsTreeKey,
- controller: controller,
- treeController: _detailsTreeController,
- summaryTreeController: _summaryTreeController,
- screenId: InspectorScreen.id,
- );
-
- final splitAxis = SplitPane.axisFor(context, 0.85);
- final widgetTrees = SplitPane(
- axis: splitAxis,
- initialFractions: const [0.33, 0.67],
- children: [
- summaryTree,
- InspectorDetails(detailsTree: detailsTree, controller: controller),
- ],
- );
- return Column(
- children: <Widget>[
- const InspectorControls(),
- const SizedBox(height: intermediateSpacing),
- Expanded(child: widgetTrees),
- ],
- );
- }
-
- Widget _buildSummaryTreeColumn() {
- return LayoutBuilder(
- builder: (context, constraints) {
- return RoundedOutlinedBorder(
- child: Column(
- children: [
- InspectorSummaryTreeControls(
- isSearchVisible: searchVisible,
- constraints: constraints,
- onRefreshInspectorPressed: _refreshInspector,
- onSearchVisibleToggle: _onSearchVisibleToggle,
- searchFieldBuilder: () =>
- StatelessSearchField<InspectorTreeRow>(
- controller: _summaryTreeController,
- searchFieldEnabled: true,
- shouldRequestFocus: searchVisible,
- supportsNavigation: true,
- onClose: _onSearchVisibleToggle,
- ),
- ),
- Expanded(
- child: ValueListenableBuilder(
- valueListenable: serviceConnection.errorBadgeManager
- .erroredItemsForPage(InspectorScreen.id),
- builder: (_, LinkedHashMap<String, DevToolsError> errors, _) {
- final inspectableErrors =
- errors.map(
- (key, value) => MapEntry(
- key,
- value as InspectableWidgetError,
- ),
- )
- as LinkedHashMap<String, InspectableWidgetError>;
- return Stack(
- children: [
- InspectorTree(
- key: summaryTreeKey,
- controller: controller,
- treeController: _summaryTreeController,
- isSummaryTree: true,
- widgetErrors: inspectableErrors,
- screenId: InspectorScreen.id,
- ),
- if (errors.isNotEmpty)
- ValueListenableBuilder<int?>(
- valueListenable: controller.selectedErrorIndex,
- builder: (_, selectedErrorIndex, _) => Positioned(
- top: 0,
- right: 0,
- child: ErrorNavigator(
- errors: inspectableErrors,
- errorIndex: selectedErrorIndex,
- onSelectError: controller.selectErrorByIndex,
- ),
- ),
- ),
- ],
- );
- },
- ),
- ),
- ],
- ),
- );
- },
- );
- }
-
- void _onSearchVisibleToggle() {
- setState(() {
- searchVisible = !searchVisible;
- });
- _summaryTreeController.resetSearch();
- }
-
- void _showLegacyInspectorWarning(BuildContext context) {
- if (context.mounted) {
- pushLegacyInspectorWarning(InspectorScreen.id);
- }
- }
-
- List<Widget> getServiceExtensionWidgets() {
- return [
- ServiceExtensionButtonGroup(
- minScreenWidthForText: serviceExtensionButtonsIncludeTextWidth,
- extensions: [
- extensions.slowAnimations,
- extensions.debugPaint,
- extensions.debugPaintBaselines,
- extensions.repaintRainbow,
- extensions.invertOversizedImages,
- ],
- ),
- const SizedBox(width: defaultSpacing),
- SettingsOutlinedButton(
- gaScreen: gac.inspector,
- gaSelection: gac.inspectorSettings,
- tooltip: 'Flutter Inspector Settings',
- onPressed: () {
- unawaited(
- showDialog(
- context: context,
- builder: (context) => const FlutterInspectorSettingsDialog(),
- ),
- );
- },
- ),
- // TODO(jacobr): implement TogglePlatformSelector.
- // TogglePlatformSelector().selector
- ];
- }
-
- void _refreshInspector() {
- ga.select(
- gac.inspector,
- gac.refresh,
- screenMetricsProvider: () => InspectorScreenMetrics.legacy(),
- );
- unawaited(
- blockWhileInProgress(() async {
- // If the user is force refreshing the inspector before the first load has
- // completed, this could indicate a slow load time or that the inspector
- // failed to load the tree once available.
- if (!controller.firstInspectorTreeLoadCompleted) {
- // We do not want to complete this timing operation because the force
- // refresh will skew the results.
- ga.cancelTimingOperation(InspectorScreen.id, gac.pageReady);
- ga.select(
- gac.inspector,
- gac.refreshEmptyTree,
- screenMetricsProvider: () => InspectorScreenMetrics.legacy(),
- );
- controller.firstInspectorTreeLoadCompleted = true;
- }
- await controller.onForceRefresh();
- }),
- );
- }
-}
-
-class InspectorSummaryTreeControls extends StatelessWidget {
- const InspectorSummaryTreeControls({
- super.key,
- required this.constraints,
- required this.isSearchVisible,
- required this.onRefreshInspectorPressed,
- required this.onSearchVisibleToggle,
- required this.searchFieldBuilder,
- });
-
- static const _searchBreakpoint = 375.0;
-
- final bool isSearchVisible;
- final BoxConstraints constraints;
- final VoidCallback onRefreshInspectorPressed;
- final VoidCallback onSearchVisibleToggle;
- final Widget Function() searchFieldBuilder;
-
- @override
- Widget build(BuildContext context) {
- return Column(
- children: [
- _controlsContainer(
- context,
- Row(
- children: <Widget>[
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: denseSpacing),
- child: Text(
- 'Widget Tree',
- style: Theme.of(context).textTheme.titleMedium,
- ),
- ),
- ...!isSearchVisible
- ? [
- const Spacer(),
- ToolbarAction(
- icon: Icons.search,
- onPressed: onSearchVisibleToggle,
- tooltip: 'Search Tree',
- ),
- ]
- : [
- constraints.maxWidth >= _searchBreakpoint
- ? _buildSearchControls()
- : const Spacer(),
- ],
- ToolbarAction(
- icon: Icons.refresh,
- onPressed: onRefreshInspectorPressed,
- tooltip: 'Refresh Tree',
- ),
- ],
- ),
- ),
- if (isSearchVisible && constraints.maxWidth < _searchBreakpoint)
- _controlsContainer(context, Row(children: [_buildSearchControls()])),
- ],
- );
- }
-
- Container _controlsContainer(BuildContext context, Widget child) {
- return Container(
- height: defaultHeaderHeight,
- decoration: BoxDecoration(
- border: Border(bottom: defaultBorderSide(Theme.of(context))),
- ),
- child: child,
- );
- }
-
- Widget _buildSearchControls() {
- return Expanded(
- child: SizedBox(
- height: defaultTextFieldHeight,
- child: searchFieldBuilder(),
- ),
- );
- }
-}
-
-class ErrorNavigator extends StatelessWidget {
- const ErrorNavigator({
- super.key,
- required this.errors,
- required this.errorIndex,
- required this.onSelectError,
- });
-
- final LinkedHashMap<String, InspectableWidgetError> errors;
-
- final int? errorIndex;
-
- final void Function(int) onSelectError;
-
- @override
- Widget build(BuildContext context) {
- final colorScheme = Theme.of(context).colorScheme;
- final label = errorIndex != null
- ? 'Error ${errorIndex! + 1}/${errors.length}'
- : 'Errors: ${errors.length}';
- return Container(
- color: colorScheme.errorContainer,
- child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: defaultSpacing,
- vertical: denseSpacing,
- ),
- child: Row(
- children: [
- Padding(
- padding: const EdgeInsets.only(right: denseSpacing),
- child: Text(
- label,
- style: TextStyle(color: colorScheme.onErrorContainer),
- ),
- ),
- _ErrorNavigatorButton(
- icon: Icons.keyboard_arrow_up,
- onPressed: _previousError,
- ),
- _ErrorNavigatorButton(
- icon: Icons.keyboard_arrow_down,
- onPressed: _nextError,
- ),
- ],
- ),
- ),
- );
- }
-
- void _previousError() {
- var newIndex = errorIndex == null ? errors.length - 1 : errorIndex! - 1;
- while (newIndex < 0) {
- newIndex += errors.length;
- }
-
- onSelectError(newIndex);
- }
-
- void _nextError() {
- final newIndex = errorIndex == null ? 0 : (errorIndex! + 1) % errors.length;
-
- onSelectError(newIndex);
- }
-}
-
-class _ErrorNavigatorButton extends StatelessWidget {
- const _ErrorNavigatorButton({required this.icon, required this.onPressed});
-
- final IconData icon;
- final VoidCallback onPressed;
-
- @override
- Widget build(BuildContext context) {
- return SizedBox(
- // This is required to force the button size.
- height: defaultButtonHeight,
- width: defaultButtonHeight,
- child: IconButton(
- padding: EdgeInsets.zero,
- constraints: const BoxConstraints(),
- splashRadius: defaultIconSize,
- icon: Icon(icon),
- color: Theme.of(context).colorScheme.onErrorContainer,
- onPressed: onPressed,
- ),
- );
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/inspector_screen_details_tab.dart b/packages/devtools_app/lib/src/screens/inspector/inspector_screen_details_tab.dart
deleted file mode 100644
index ae8cc53..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/inspector_screen_details_tab.dart
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:async';
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:flutter/material.dart';
-
-import '../../shared/analytics/analytics.dart' as ga;
-import '../../shared/analytics/constants.dart' as gac;
-import '../../shared/globals.dart';
-import '../../shared/preferences/preferences.dart';
-import '../../shared/primitives/blocking_action_mixin.dart';
-import '../../shared/ui/common_widgets.dart';
-import '../../shared/ui/tab.dart';
-import 'inspector_controller.dart';
-import 'inspector_screen_body.dart';
-import 'layout_explorer/layout_explorer.dart';
-
-class InspectorDetails extends StatelessWidget {
- const InspectorDetails({
- required this.detailsTree,
- required this.controller,
- super.key,
- });
-
- final Widget detailsTree;
- final InspectorController controller;
-
- @override
- Widget build(BuildContext context) {
- final tabs = [
- (
- tab: _buildTab(tabName: InspectorDetailsViewType.layoutExplorer.key),
- tabView: LayoutExplorerTab(controller: controller),
- ),
- (
- tab: _buildTab(
- tabName: InspectorDetailsViewType.widgetDetailsTree.key,
- trailing: InspectorExpandCollapseButtons(controller: controller),
- ),
- tabView: detailsTree,
- ),
- ];
- return ValueListenableBuilder(
- valueListenable: preferences.inspector.defaultDetailsView,
- builder: (BuildContext context, value, Widget? child) {
- int defaultInspectorViewIndex = 0;
-
- if (preferences.inspector.defaultDetailsView.value ==
- InspectorDetailsViewType.widgetDetailsTree) {
- defaultInspectorViewIndex = 1;
- }
-
- return AnalyticsTabbedView(
- tabs: tabs,
- gaScreen: gac.inspector,
- initialSelectedIndex: defaultInspectorViewIndex,
- );
- },
- );
- }
-
- DevToolsTab _buildTab({required String tabName, Widget? trailing}) {
- return DevToolsTab.create(
- tabName: tabName,
- gaPrefix: 'inspectorDetailsTab',
- trailing: trailing,
- );
- }
-}
-
-class InspectorExpandCollapseButtons extends StatefulWidget {
- const InspectorExpandCollapseButtons({super.key, required this.controller});
-
- final InspectorController controller;
-
- @override
- State<InspectorExpandCollapseButtons> createState() =>
- _InspectorExpandCollapseButtonsState();
-}
-
-class _InspectorExpandCollapseButtonsState
- extends State<InspectorExpandCollapseButtons>
- with BlockingActionMixin {
- bool get enableButtons => !actionInProgress;
-
- @override
- Widget build(BuildContext context) {
- return Container(
- alignment: Alignment.centerRight,
- decoration: BoxDecoration(
- border: Border(left: defaultBorderSide(Theme.of(context))),
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.end,
- mainAxisSize: MainAxisSize.min,
- children: [
- SizedBox(
- child: GaDevToolsButton(
- icon: Icons.unfold_more,
- onPressed: enableButtons ? _onExpandClick : null,
- label: 'Expand all',
- minScreenWidthForText:
- InspectorScreenBodyState.minScreenWidthForText,
- gaScreen: gac.inspector,
- gaSelection: gac.expandAll,
- outlined: false,
- ),
- ),
- const SizedBox(width: denseSpacing),
- SizedBox(
- child: GaDevToolsButton(
- icon: Icons.unfold_less,
- onPressed: enableButtons ? _onCollapseClick : null,
- label: 'Collapse to selected',
- minScreenWidthForText:
- InspectorScreenBodyState.minScreenWidthForText,
- gaScreen: gac.inspector,
- gaSelection: gac.collapseAll,
- outlined: false,
- ),
- ),
- ],
- ),
- );
- }
-
- void _onExpandClick() {
- unawaited(
- blockWhileInProgress(() async {
- ga.select(gac.inspector, gac.expandAll);
- await widget.controller.expandAllNodesInDetailsTree();
- }),
- );
- }
-
- void _onCollapseClick() {
- ga.select(gac.inspector, gac.collapseAll);
- widget.controller.collapseDetailsToSelected();
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/inspector_tree_controller.dart b/packages/devtools_app/lib/src/screens/inspector/inspector_tree_controller.dart
deleted file mode 100644
index 35b5d9b..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/inspector_tree_controller.dart
+++ /dev/null
@@ -1,1331 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:async';
-import 'dart:collection';
-import 'dart:math';
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:devtools_app_shared/utils.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:logging/logging.dart';
-
-import '../../shared/analytics/analytics.dart' as ga;
-import '../../shared/analytics/constants.dart' as gac;
-import '../../shared/analytics/metrics.dart';
-import '../../shared/console/eval/inspector_tree.dart';
-import '../../shared/console/widgets/description.dart';
-import '../../shared/diagnostics/diagnostics_node.dart';
-import '../../shared/globals.dart';
-import '../../shared/managers/error_badge_manager.dart';
-import '../../shared/primitives/collapsible_mixin.dart';
-import '../../shared/primitives/diagnostics_text_styles.dart';
-import '../../shared/primitives/utils.dart';
-import '../../shared/ui/colors.dart';
-import '../../shared/ui/common_widgets.dart';
-import '../../shared/ui/search.dart';
-import '../../shared/ui/utils.dart';
-import '../../shared/utils/utils.dart';
-import 'inspector_breadcrumbs.dart';
-import 'inspector_controller.dart';
-
-final _log = Logger('inspector_tree_controller');
-
-/// Presents a [InspectorTreeNode].
-class _InspectorTreeRowWidget extends StatefulWidget {
- /// Constructs a [_InspectorTreeRowWidget] that presents a line in the
- /// Inspector tree.
- const _InspectorTreeRowWidget({
- required super.key,
- required this.row,
- required this.inspectorTreeState,
- this.error,
- required this.scrollControllerX,
- required this.viewportWidth,
- });
-
- final _InspectorTreeState inspectorTreeState;
-
- InspectorTreeNode get node => row.node;
- final InspectorTreeRow row;
- final ScrollController scrollControllerX;
- final double viewportWidth;
-
- /// A [DevToolsError] that applies to the widget in this row.
- ///
- /// This will be null if there is no error for this row.
- final DevToolsError? error;
-
- @override
- _InspectorTreeRowState createState() => _InspectorTreeRowState();
-}
-
-class _InspectorTreeRowState extends State<_InspectorTreeRowWidget>
- with TickerProviderStateMixin, CollapsibleAnimationMixin {
- @override
- Widget build(BuildContext context) {
- return SizedBox(
- height: inspectorRowHeight,
- child: InspectorRowContent(
- row: widget.row,
- error: widget.error,
- expandArrowAnimation: expandArrowAnimation,
- controller: widget.inspectorTreeState.treeController!,
- scrollControllerX: widget.scrollControllerX,
- viewportWidth: widget.viewportWidth,
- onToggle: () {
- setExpanded(!isExpanded);
- },
- ),
- );
- }
-
- @override
- bool get isExpanded => widget.node.isExpanded;
-
- @override
- void onExpandChanged(bool expanded) {
- setState(() {
- final row = widget.row;
- if (expanded) {
- widget.inspectorTreeState.treeController!.onExpandRow(row);
- } else {
- widget.inspectorTreeState.treeController!.onCollapseRow(row);
- }
- });
- }
-
- @override
- bool shouldShow() => widget.node.shouldShow;
-}
-
-class InspectorTreeController extends DisposableController
- with SearchControllerMixin<InspectorTreeRow> {
- InspectorTreeController({this.gaId}) {
- init();
- }
-
- /// Clients the controller notifies to trigger changes to the UI.
- final _clients = <InspectorControllerClient>{};
-
- /// Identifier used when sending Google Analytics about events in this
- /// [InspectorTreeController].
- final int? gaId;
-
- InspectorTreeNode createNode() => InspectorTreeNode();
-
- SearchTargetType _searchTarget = SearchTargetType.widget;
- int _rootSetCount = 0;
-
- @override
- void init() {
- super.init();
- ga.select(
- gac.inspector,
- gac.inspectorTreeControllerInitialized,
- nonInteraction: true,
- screenMetricsProvider: () => InspectorScreenMetrics.legacy(
- inspectorTreeControllerId: gaId,
- rootSetCount: _rootSetCount,
- rowCount: _root?.subtreeSize,
- ),
- );
- }
-
- void addClient(InspectorControllerClient value) {
- final firstClient = _clients.isEmpty;
- _clients.add(value);
- if (firstClient) {
- config.onClientActiveChange?.call(true);
- }
- }
-
- void removeClient(InspectorControllerClient value) {
- _clients.remove(value);
- if (_clients.isEmpty) {
- config.onClientActiveChange?.call(false);
- }
- }
-
- // Method defined to avoid a direct Flutter dependency.
- void setState(VoidCallback fn) {
- fn();
- for (final client in _clients) {
- client.onChanged();
- }
- }
-
- void requestFocus() {
- for (final client in _clients) {
- client.requestFocus();
- }
- }
-
- InspectorTreeNode? get root => _root;
- InspectorTreeNode? _root;
-
- set root(InspectorTreeNode? node) {
- if (disposed) return;
-
- setState(() {
- _root = node;
- _populateSearchableCachedRows();
-
- ga.select(
- gac.inspector,
- gac.inspectorTreeControllerRootChange,
- nonInteraction: true,
- screenMetricsProvider: () => InspectorScreenMetrics.legacy(
- inspectorTreeControllerId: gaId,
- rootSetCount: ++_rootSetCount,
- rowCount: _root?.subtreeSize,
- ),
- );
- });
- }
-
- InspectorTreeNode? get selection => _selection;
- InspectorTreeNode? _selection;
-
- late final InspectorTreeConfig config;
-
- set selection(InspectorTreeNode? node) {
- if (node == _selection) return;
-
- setState(() {
- _selection?.selected = false;
- _selection = node;
- _selection?.selected = true;
- final configLocal = config;
- if (configLocal.onSelectionChange != null) {
- configLocal.onSelectionChange!();
- }
- });
- }
-
- InspectorTreeNode? get hover => _hover;
- InspectorTreeNode? _hover;
-
- double? lastContentWidth;
-
- final cachedRows = <InspectorTreeRow?>[];
- InspectorTreeRow? _cachedSelectedRow;
-
- /// All cached rows of the tree.
- ///
- /// Similar to [cachedRows] but:
- /// * contains every row in the tree (including collapsed rows)
- /// * items don't change when nodes are expanded or collapsed
- /// * items are populated only when root is changed
- final _searchableCachedRows = <InspectorTreeRow?>[];
-
- void setSearchTarget(SearchTargetType searchTarget) {
- _searchTarget = searchTarget;
- refreshSearchMatches();
- }
-
- // TODO: we should add a listener instead that clears the cache when the
- // root is marked as dirty.
- void _maybeClearCache() {
- final rootLocal = root;
- if (rootLocal != null && rootLocal.isDirty) {
- cachedRows.clear();
- _cachedSelectedRow = null;
- rootLocal.isDirty = false;
- lastContentWidth = null;
- }
- }
-
- void _populateSearchableCachedRows() {
- _searchableCachedRows.clear();
- for (int i = 0; i < numRows; i++) {
- _searchableCachedRows.add(getCachedRow(i));
- }
- }
-
- InspectorTreeRow? getCachedRow(int index) {
- if (index < 0) return null;
-
- _maybeClearCache();
- while (cachedRows.length <= index) {
- cachedRows.add(null);
- }
- cachedRows[index] ??= root?.getRow(index);
-
- final cachedRow = cachedRows[index];
- cachedRow?.isSearchMatch =
- _searchableCachedRows.safeGet(index)?.isSearchMatch ?? false;
-
- if (cachedRow?.isSelected == true) {
- _cachedSelectedRow = cachedRow;
- }
- return cachedRow;
- }
-
- double getRowOffset(int index) {
- return (getCachedRow(index)?.depth ?? 0) * inspectorColumnWidth;
- }
-
- List<InspectorTreeNode> getPathFromSelectedRowToRoot() {
- final selectedItem = _cachedSelectedRow?.node;
- if (selectedItem == null) return [];
-
- final pathToRoot = <InspectorTreeNode>[selectedItem];
- InspectorTreeNode? nextParentNode = selectedItem.parent;
- while (nextParentNode != null) {
- pathToRoot.add(nextParentNode);
- nextParentNode = nextParentNode.parent;
- }
- return pathToRoot.reversed.toList();
- }
-
- set hover(InspectorTreeNode? node) {
- if (node == _hover) {
- return;
- }
- setState(() {
- _hover = node;
- // TODO(jacobr): we could choose to repaint only a portion of the UI
- });
- }
-
- void navigateUp() {
- _navigateHelper(-1);
- }
-
- void navigateDown() {
- _navigateHelper(1);
- }
-
- void navigateLeft() {
- final selectionLocal = selection;
-
- // This logic is consistent with how IntelliJ handles tree navigation on
- // on left arrow key press.
- if (selectionLocal == null) {
- _navigateHelper(-1);
- return;
- }
-
- if (selectionLocal.isExpanded) {
- setState(() {
- selectionLocal.isExpanded = false;
- });
- return;
- }
- if (selectionLocal.parent != null) {
- selection = selectionLocal.parent;
- }
- }
-
- void navigateRight() {
- // This logic is consistent with how IntelliJ handles tree navigation on
- // on right arrow key press.
-
- final selectionLocal = selection;
-
- if (selectionLocal == null || selectionLocal.isExpanded) {
- _navigateHelper(1);
- return;
- }
-
- setState(() {
- selectionLocal.isExpanded = true;
- });
- }
-
- void _navigateHelper(int indexOffset) {
- if (numRows == 0) return;
-
- if (selection == null) {
- selection = root;
- return;
- }
-
- final rootLocal = root!;
-
- selection = rootLocal
- .getRow(
- (rootLocal.getRowIndex(selection!) + indexOffset).clamp(
- 0,
- numRows - 1,
- ),
- )
- ?.node;
- }
-
- static const horizontalPadding = 10.0;
-
- double getDepthIndent(int depth) {
- return (depth + 1) * inspectorColumnWidth + horizontalPadding;
- }
-
- double rowYTop(int index) {
- return inspectorRowHeight * index;
- }
-
- void nodeChanged(InspectorTreeNode node) {
- setState(() {
- node.isDirty = true;
- });
- }
-
- void removeNodeFromParent(InspectorTreeNode node) {
- setState(() {
- node.parent?.removeChild(node);
- });
- }
-
- void appendChild(InspectorTreeNode node, InspectorTreeNode child) {
- setState(() {
- node.appendChild(child);
- });
- }
-
- void expandPath(InspectorTreeNode? node) {
- setState(() {
- _expandPath(node);
- });
- }
-
- void _expandPath(InspectorTreeNode? node) {
- while (node != null) {
- if (!node.isExpanded) {
- node.isExpanded = true;
- }
- node = node.parent;
- }
- }
-
- void collapseToSelected() {
- setState(() {
- _collapseAllNodes(root!);
- if (selection == null) return;
- _expandPath(selection);
- });
- }
-
- void _collapseAllNodes(InspectorTreeNode root) {
- root.isExpanded = false;
- root.children.forEach(_collapseAllNodes);
- }
-
- int get numRows => root?.subtreeSize ?? 0;
-
- int getRowIndex(double y) => max(0, y ~/ inspectorRowHeight);
-
- InspectorTreeRow? getRowForNode(InspectorTreeNode node) {
- final rootLocal = root;
- if (rootLocal == null) return null;
- return getCachedRow(rootLocal.getRowIndex(node));
- }
-
- InspectorTreeRow? getRow(Offset offset) {
- final rootLocal = root;
- if (rootLocal == null) return null;
- final row = getRowIndex(offset.dy);
- return row < rootLocal.subtreeSize ? getCachedRow(row) : null;
- }
-
- void onExpandRow(InspectorTreeRow row) {
- setState(() {
- final onExpand = config.onExpand;
- row.node.isExpanded = true;
- if (onExpand != null) {
- onExpand(row.node);
- }
- });
- }
-
- void onCollapseRow(InspectorTreeRow row) {
- setState(() {
- row.node.isExpanded = false;
- });
- }
-
- void onSelectRow(InspectorTreeRow row) {
- onSelectNode(row.node);
- }
-
- void onSelectNode(InspectorTreeNode? node) {
- selection = node;
- ga.select(
- gac.inspector,
- gac.treeNodeSelection,
- screenMetricsProvider: () => InspectorScreenMetrics.legacy(),
- );
- expandPath(node);
- }
-
- Rect getBoundingBox(InspectorTreeRow row) {
- // For future reference: the bounding box likely needs to be in terms of
- // positions after the current animations are complete so that computations
- // to start animations to show specific widget scroll to where the target
- // nodes will be displayed rather than where they are currently displayed.
- final diagnostic = row.node.diagnostic;
- // The node width is approximated since the widgets are not available at the
- // time of calculating the bounding box.
- final approximateNodeWidth =
- DiagnosticsNodeDescription.approximateNodeWidth(diagnostic);
- return Rect.fromLTWH(
- getDepthIndent(row.depth),
- rowYTop(row.index),
- approximateNodeWidth,
- inspectorRowHeight,
- );
- }
-
- void scrollToRect(Rect targetRect) {
- for (final client in _clients) {
- client.scrollToRect(targetRect);
- }
- }
-
- /// Width each row in the tree should have ignoring its indent.
- ///
- /// Content in rows should wrap if it exceeds this width.
- final rowWidth = 1200;
-
- /// Maximum indent of the tree in pixels.
- double? _maxIndent;
-
- double get maxRowIndent {
- if (lastContentWidth == null) {
- double maxIndent = 0;
- for (int i = 0; i < numRows; i++) {
- final row = getCachedRow(i);
- if (row != null) {
- maxIndent = max(maxIndent, getDepthIndent(row.depth));
- }
- }
- lastContentWidth = maxIndent + maxIndent;
- _maxIndent = maxIndent;
- }
- return _maxIndent!;
- }
-
- void animateToTargets(List<InspectorTreeNode> targets) {
- Rect? targetRect;
-
- for (final target in targets) {
- final row = getRowForNode(target);
- if (row != null) {
- final rowRect = getBoundingBox(row);
- targetRect = targetRect == null
- ? rowRect
- : targetRect.expandToInclude(rowRect);
- }
- }
-
- if (targetRect == null || targetRect.isEmpty) return;
-
- scrollToRect(targetRect);
- }
-
- bool expandPropertiesByDefault(DiagnosticsTreeStyle style) {
- // This code matches the text style defaults for which styles are
- // by default and which aren't.
- switch (style) {
- case DiagnosticsTreeStyle.none:
- case DiagnosticsTreeStyle.singleLine:
- case DiagnosticsTreeStyle.errorProperty:
- return false;
-
- case DiagnosticsTreeStyle.sparse:
- case DiagnosticsTreeStyle.offstage:
- case DiagnosticsTreeStyle.dense:
- case DiagnosticsTreeStyle.transition:
- case DiagnosticsTreeStyle.error:
- case DiagnosticsTreeStyle.whitespace:
- case DiagnosticsTreeStyle.flat:
- case DiagnosticsTreeStyle.shallow:
- case DiagnosticsTreeStyle.truncateChildren:
- return true;
- }
- }
-
- InspectorTreeNode setupInspectorTreeNode(
- InspectorTreeNode node,
- RemoteDiagnosticsNode diagnosticsNode, {
- required bool expandChildren,
- required bool expandProperties,
- }) {
- node.diagnostic = diagnosticsNode;
- final configLocal = config;
- if (configLocal.onNodeAdded != null) {
- configLocal.onNodeAdded!(node, diagnosticsNode);
- }
-
- if (diagnosticsNode.hasChildren ||
- diagnosticsNode.inlineProperties.isNotEmpty) {
- if (diagnosticsNode.childrenReady || !diagnosticsNode.hasChildren) {
- final styleIsMultiline = expandPropertiesByDefault(
- diagnosticsNode.style,
- );
- setupChildren(
- diagnosticsNode,
- node,
- node.diagnostic!.childrenNow,
- expandChildren: expandChildren && styleIsMultiline,
- expandProperties: expandProperties && styleIsMultiline,
- );
- } else {
- node.clearChildren();
- node.appendChild(createNode());
- }
- }
- return node;
- }
-
- void setupChildren(
- RemoteDiagnosticsNode parent,
- InspectorTreeNode treeNode,
- List<RemoteDiagnosticsNode>? children, {
- required bool expandChildren,
- required bool expandProperties,
- }) {
- treeNode.isExpanded = expandChildren;
- if (treeNode.children.isNotEmpty) {
- // Only case supported is this is the loading node.
- assert(treeNode.children.length == 1);
- removeNodeFromParent(treeNode.children.first);
- }
- final inlineProperties = parent.inlineProperties;
-
- for (final property in inlineProperties) {
- appendChild(
- treeNode,
- setupInspectorTreeNode(
- createNode(),
- property,
- // We are inside a property so only expand children if
- // expandProperties is true.
- expandChildren: expandProperties,
- expandProperties: expandProperties,
- ),
- );
- }
- if (children != null) {
- for (final child in children) {
- appendChild(
- treeNode,
- setupInspectorTreeNode(
- createNode(),
- child,
- expandChildren: expandChildren,
- expandProperties: expandProperties,
- ),
- );
- }
- }
- }
-
- Future<void> maybePopulateChildren(InspectorTreeNode treeNode) async {
- final diagnostic = treeNode.diagnostic;
- if (diagnostic != null &&
- diagnostic.hasChildren &&
- (treeNode.hasPlaceholderChildren || treeNode.children.isEmpty)) {
- try {
- final children = await diagnostic.children;
- if (treeNode.hasPlaceholderChildren || treeNode.children.isEmpty) {
- setupChildren(
- diagnostic,
- treeNode,
- children,
- expandChildren: true,
- expandProperties: false,
- );
- nodeChanged(treeNode);
- if (treeNode == selection) {
- expandPath(treeNode);
- }
- }
- } catch (e, st) {
- _log.shout(e, e, st);
- }
- }
- }
-
- /* Search support */
- @override
- void onMatchChanged(int index) {
- onSelectRow(searchMatches.value[index]);
- }
-
- @override
- Duration get debounceDelay => const Duration(milliseconds: 300);
-
- @override
- List<InspectorTreeRow> matchesForSearch(
- String search, {
- bool searchPreviousMatches = false,
- }) {
- final matches = <InspectorTreeRow>[];
-
- if (searchPreviousMatches) {
- final previousMatches = searchMatches.value;
- for (final previousMatch in previousMatches) {
- if (previousMatch.node.diagnostic!.searchValue.caseInsensitiveContains(
- search,
- )) {
- matches.add(previousMatch);
- }
- }
-
- if (matches.isNotEmpty) return matches;
- }
-
- int debugStatsSearchOps = 0;
- final debugStatsWidgets = _searchableCachedRows.length;
-
- final inspectorService = serviceConnection.inspectorService;
- if (search.isEmpty ||
- inspectorService == null ||
- inspectorService.isDisposed) {
- assert(() {
- debugPrint('Search completed, no search');
- return true;
- }());
- return matches;
- }
-
- assert(() {
- debugPrint('Search started: $_searchTarget');
- return true;
- }());
-
- for (final row in _searchableCachedRows) {
- final diagnostic = row!.node.diagnostic;
- if (diagnostic == null) continue;
-
- // Widget search begin
- if (_searchTarget == SearchTargetType.widget) {
- debugStatsSearchOps++;
- if (diagnostic.searchValue.caseInsensitiveContains(search)) {
- matches.add(row);
- continue;
- }
- }
- // Widget search end
- }
-
- assert(() {
- debugPrint(
- 'Search completed with $debugStatsWidgets widgets, $debugStatsSearchOps ops',
- );
- return true;
- }());
-
- return matches;
- }
-}
-
-extension RemoteDiagnosticsNodeExtension on RemoteDiagnosticsNode {
- String get searchValue {
- final description = toStringShort();
- final textPreview = json['textPreview'];
- return textPreview is String
- ? '$description ${textPreview.replaceAll('\n', ' ')}'
- : description;
- }
-}
-
-abstract class InspectorControllerClient {
- void onChanged();
-
- void scrollToRect(Rect rect);
-
- void requestFocus();
-}
-
-class InspectorTree extends StatefulWidget {
- const InspectorTree({
- super.key,
- required this.controller,
- required this.treeController,
- this.summaryTreeController,
- this.isSummaryTree = false,
- this.widgetErrors,
- this.screenId,
- }) : assert(isSummaryTree == (summaryTreeController == null));
-
- final InspectorController controller;
-
- final InspectorTreeController? treeController;
-
- /// Stores the summary tree controller when this instance of [InspectorTree]
- /// is for the details tree (i.e. when [isSummaryTree] is false).
- ///
- /// This value should be null when this instance of [InspectorTree] is for the
- /// summary tree itself.
- final InspectorTreeController? summaryTreeController;
-
- final bool isSummaryTree;
- final LinkedHashMap<String, InspectableWidgetError>? widgetErrors;
- final String? screenId;
-
- @override
- State<InspectorTree> createState() => _InspectorTreeState();
-}
-
-// AutomaticKeepAlive is necessary so that the tree does not get recreated when we switch tabs.
-class _InspectorTreeState extends State<InspectorTree>
- with
- SingleTickerProviderStateMixin,
- AutomaticKeepAliveClientMixin<InspectorTree>,
- AutoDisposeMixin
- implements InspectorControllerClient {
- InspectorController get controller => widget.controller;
- InspectorTreeController? get treeController => widget.treeController;
-
- late ScrollController _scrollControllerY;
- late ScrollController _scrollControllerX;
- Future<void>? _currentAnimateY;
- Rect? _currentAnimateTarget;
-
- AnimationController? _constraintDisplayController;
- late FocusNode _focusNode;
-
- /// When autoscrolling, the number of rows to pad the target location with.
- static const _scrollPadCount = 3;
-
- @override
- void initState() {
- super.initState();
- _scrollControllerX = ScrollController();
- _scrollControllerY = ScrollController();
- // TODO(devoncarew): Commented out as per flutter/devtools/pull/2001.
- //_scrollControllerY.addListener(_onScrollYChange);
- if (widget.isSummaryTree) {
- _constraintDisplayController = longAnimationController(this);
- }
- _focusNode = FocusNode(debugLabel: 'inspector-tree');
- autoDisposeFocusNode(_focusNode);
- final mainIsolateState =
- serviceConnection.serviceManager.isolateManager.mainIsolateState;
- if (mainIsolateState != null) {
- callOnceWhenReady<bool>(
- trigger: mainIsolateState.isPaused,
- callback: _bindToController,
- readyWhen: (triggerValue) => !triggerValue,
- );
- }
- }
-
- @override
- void didUpdateWidget(InspectorTree oldWidget) {
- final oldTreeController = oldWidget.treeController;
- if (oldTreeController != widget.treeController) {
- oldTreeController?.removeClient(this);
-
- // TODO(elliette): Figure out if we can remove this. See explanation:
- // https://github.com/flutter/devtools/pull/1290/files#r342399899.
- cancelListeners();
-
- _bindToController();
- }
- super.didUpdateWidget(oldWidget);
- }
-
- @override
- void dispose() {
- treeController?.removeClient(this);
- _scrollControllerX.dispose();
- _scrollControllerY.dispose();
- _constraintDisplayController?.dispose();
- super.dispose();
- }
-
- @override
- void requestFocus() {
- _focusNode.requestFocus();
- }
-
- // TODO(devoncarew): Commented out as per flutter/devtools/pull/2001.
- // void _onScrollYChange() {
- // if (controller == null) return;
- //
- // // If the vertical position is already being animated we should not trigger
- // // a new animation of the horizontal position as a more direct animation of
- // // the horizontal position has already been triggered.
- // if (currentAnimateY != null) return;
- //
- // final x = _computeTargetX(_scrollControllerY.offset);
- // _scrollControllerX.animateTo(
- // x,
- // duration: defaultDuration,
- // curve: defaultCurve,
- // );
- // }
-
- @override
- Future<void> scrollToRect(Rect rect) async {
- if (rect == _currentAnimateTarget) {
- // We are in the middle of an animation to this exact rectangle.
- return;
- }
-
- final initialX = rect.left;
- final initialY = rect.top;
- final yOffsetAtViewportTop = _scrollControllerY.hasClients
- ? _scrollControllerY.offset
- : _scrollControllerY.initialScrollOffset;
- final xOffsetAtViewportLeft = _scrollControllerX.hasClients
- ? _scrollControllerX.offset
- : _scrollControllerX.initialScrollOffset;
-
- final viewPortInScrollControllerSpace = Rect.fromLTWH(
- xOffsetAtViewportLeft,
- yOffsetAtViewportTop,
- safeViewportWidth,
- safeViewportHeight,
- );
-
- final isRectInViewPort =
- viewPortInScrollControllerSpace.contains(rect.topLeft) &&
- viewPortInScrollControllerSpace.contains(rect.bottomRight);
- if (isRectInViewPort) {
- // The rect is already in view, don't scroll
- return;
- }
-
- _currentAnimateTarget = rect;
-
- final targetY = _padTargetY(initialY: initialY);
- if (_scrollControllerY.hasClients) {
- _currentAnimateY = _scrollControllerY.animateTo(
- targetY,
- duration: longDuration,
- curve: defaultCurve,
- );
- } else {
- _currentAnimateY = null;
- _scrollControllerY = ScrollController(initialScrollOffset: targetY);
- }
-
- final targetX = _padTargetX(initialX: initialX);
- if (_scrollControllerX.hasClients) {
- unawaited(
- _scrollControllerX.animateTo(
- targetX,
- duration: longDuration,
- curve: defaultCurve,
- ),
- );
- } else {
- _scrollControllerX = ScrollController(initialScrollOffset: targetX);
- }
-
- try {
- await _currentAnimateY;
- } catch (e) {
- // Doesn't matter if the animation was cancelled.
- }
- _currentAnimateY = null;
- _currentAnimateTarget = null;
- }
-
- // TODO(jacobr): resolve cases where we need to know the viewport height
- // before it is available so we don't need this approximation.
- /// Placeholder viewport height to use if we don't yet know the real
- /// viewport height.
- static const _placeholderViewportSize = Size(1000.0, 1000.0);
-
- double get safeViewportHeight {
- return _scrollControllerY.hasClients
- ? _scrollControllerY.position.viewportDimension
- : _placeholderViewportSize.height;
- }
-
- double get safeViewportWidth {
- return _scrollControllerX.hasClients
- ? _scrollControllerX.position.viewportDimension
- : _placeholderViewportSize.width;
- }
-
- /// Pad [initialX] with the horizontal indentation of [padCount] rows.
- double _padTargetX({
- required double initialX,
- int padCount = _scrollPadCount,
- }) {
- return initialX - inspectorColumnWidth * padCount;
- }
-
- /// Pad [initialY] so that a row would be placed in the vertical center of
- /// the screen.
- double _padTargetY({required double initialY}) {
- return initialY - (safeViewportHeight / 2) + inspectorRowHeight / 2;
- }
-
- /// Handle arrow keys for the InspectorTree. Ignore other key events so that
- /// other widgets have a chance to respond to them.
- KeyEventResult _handleKeyEvent(FocusNode _, KeyEvent event) {
- if (!event.isKeyDownOrRepeat) return KeyEventResult.ignored;
-
- final treeControllerLocal = treeController!;
-
- if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
- treeControllerLocal.navigateDown();
- return KeyEventResult.handled;
- } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
- treeControllerLocal.navigateUp();
- return KeyEventResult.handled;
- } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
- treeControllerLocal.navigateLeft();
- return KeyEventResult.handled;
- } else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
- treeControllerLocal.navigateRight();
- return KeyEventResult.handled;
- }
-
- return KeyEventResult.ignored;
- }
-
- void _bindToController() {
- treeController?.addClient(this);
- }
-
- @override
- void onChanged() {
- setState(() {});
- }
-
- @override
- Widget build(BuildContext context) {
- super.build(context);
- final treeControllerLocal = treeController;
- if (treeControllerLocal == null) {
- // Indicate the tree is loading.
- return const CenteredCircularProgressIndicator();
- }
- if (treeControllerLocal.numRows == 0) {
- // This works around a bug when Scrollbars are present on a short lived
- // widget.
- return const SizedBox();
- }
-
- if (!controller.firstInspectorTreeLoadCompleted && widget.isSummaryTree) {
- final screenId = widget.screenId;
- if (screenId != null) {
- ga.timeEnd(
- screenId,
- gac.pageReady,
- screenMetricsProvider: () => InspectorScreenMetrics.legacy(
- rowCount: treeControllerLocal.numRows,
- ),
- );
- unawaited(
- serviceConnection.sendDwdsEvent(
- screen: screenId,
- action: gac.pageReady,
- ),
- );
- }
- controller.firstInspectorTreeLoadCompleted = true;
- }
- return LayoutBuilder(
- builder: (context, constraints) {
- final viewportWidth = constraints.maxWidth;
- final tree = Scrollbar(
- thumbVisibility: true,
- controller: _scrollControllerX,
- child: SingleChildScrollView(
- scrollDirection: Axis.horizontal,
- controller: _scrollControllerX,
- child: ConstrainedBox(
- constraints: BoxConstraints(
- maxWidth:
- treeControllerLocal.rowWidth +
- treeControllerLocal.maxRowIndent,
- ),
- // TODO(kenz): this scrollbar needs to be sticky to the right side of
- // the visible container - right now it is lined up to the right of
- // the widest row (which is likely not visible). This may require some
- // refactoring.
- child: GestureDetector(
- onTap: _focusNode.requestFocus,
- child: Focus(
- onKeyEvent: _handleKeyEvent,
- autofocus: widget.isSummaryTree,
- focusNode: _focusNode,
- child: OffsetScrollbar(
- isAlwaysShown: true,
- axis: Axis.vertical,
- controller: _scrollControllerY,
- offsetController: _scrollControllerX,
- offsetControllerViewportDimension: viewportWidth,
- child: ListView.custom(
- itemExtent: inspectorRowHeight,
- childrenDelegate: SliverChildBuilderDelegate((
- context,
- index,
- ) {
- if (index == treeControllerLocal.numRows) {
- return const SizedBox(height: inspectorRowHeight);
- }
- final row = treeControllerLocal.getCachedRow(index)!;
- final inspectorRef = row.node.diagnostic?.valueRef.id;
- return _InspectorTreeRowWidget(
- key: PageStorageKey(row.node),
- inspectorTreeState: this,
- row: row,
- scrollControllerX: _scrollControllerX,
- viewportWidth: viewportWidth,
- error:
- widget.widgetErrors != null &&
- inspectorRef != null
- ? widget.widgetErrors![inspectorRef]
- : null,
- );
- }, childCount: treeControllerLocal.numRows + 1),
- controller: _scrollControllerY,
- ),
- ),
- ),
- ),
- ),
- ),
- );
-
- final shouldShowBreadcrumbs = !widget.isSummaryTree;
- if (shouldShowBreadcrumbs) {
- final inspectorTreeController = widget.summaryTreeController!;
-
- final parents = inspectorTreeController
- .getPathFromSelectedRowToRoot();
- return Column(
- children: [
- InspectorBreadcrumbNavigator(
- items: parents,
- onTap: (node) => inspectorTreeController.onSelectNode(node),
- ),
- Expanded(child: tree),
- ],
- );
- }
-
- return tree;
- },
- );
- }
-
- @override
- bool get wantKeepAlive => true;
-}
-
-Paint _defaultPaint(ColorScheme colorScheme) => Paint()
- ..color = colorScheme.treeGuidelineColor
- ..strokeWidth = chartLineStrokeWidth;
-
-/// Custom painter that draws lines indicating how parent and child rows are
-/// connected to each other.
-///
-/// Each rows object contains a list of ticks that indicate the x coordinates of
-/// vertical lines connecting other rows need to be drawn within the vertical
-/// area of the current row. This approach has the advantage that a row contains
-/// all information required to render all content within it but has the
-/// disadvantage that the x coordinates of each line connecting rows must be
-/// computed in advance.
-class _RowPainter extends CustomPainter {
- _RowPainter(this.row, this._controller, this.colorScheme);
-
- final InspectorTreeController _controller;
- final InspectorTreeRow row;
- final ColorScheme colorScheme;
-
- @override
- void paint(Canvas canvas, Size size) {
- double currentX = 0;
- final paint = _defaultPaint(colorScheme);
-
- final node = row.node;
- final showExpandCollapse = node.showExpandCollapse;
- for (final tick in row.ticks) {
- currentX = _controller.getDepthIndent(tick) - inspectorColumnWidth * 0.5;
- // Draw a vertical line for each tick identifying a connection between
- // an ancestor of this node and some other node in the tree.
- canvas.drawLine(
- Offset(currentX, 0.0),
- Offset(currentX, inspectorRowHeight),
- paint,
- );
- }
- // If this row is itself connected to a parent then draw the L shaped line
- // to make that connection.
- if (row.lineToParent) {
- currentX =
- _controller.getDepthIndent(row.depth - 1) -
- inspectorColumnWidth * 0.5;
- final width = showExpandCollapse
- ? inspectorColumnWidth * 0.5
- : inspectorColumnWidth;
- canvas.drawLine(
- Offset(currentX, 0.0),
- Offset(currentX, inspectorRowHeight * 0.5),
- paint,
- );
- canvas.drawLine(
- Offset(currentX, inspectorRowHeight * 0.5),
- Offset(currentX + width, inspectorRowHeight * 0.5),
- paint,
- );
- }
- }
-
- @override
- bool shouldRepaint(CustomPainter oldDelegate) {
- if (oldDelegate is _RowPainter) {
- // TODO(jacobr): check whether the row has different ticks.
- return oldDelegate.colorScheme.isLight != colorScheme.isLight;
- }
- return true;
- }
-}
-
-/// Widget defining the contents of a single row in the InspectorTree.
-///
-/// This class defines the scaffolding around the rendering of the actual
-/// content of a [RemoteDiagnosticsNode] provided by
-/// [DiagnosticsNodeDescription] to provide a tree implementation with lines
-/// drawn between parent and child nodes when nodes have multiple children.
-///
-/// Changes to how the actual content of the node within the row should
-/// be implemented by changing [DiagnosticsNodeDescription] instead.
-class InspectorRowContent extends StatelessWidget {
- const InspectorRowContent({
- super.key,
- required this.row,
- required this.controller,
- required this.onToggle,
- required this.expandArrowAnimation,
- this.error,
- required this.scrollControllerX,
- required this.viewportWidth,
- });
-
- final InspectorTreeRow row;
- final InspectorTreeController controller;
- final VoidCallback onToggle;
- final Animation<double> expandArrowAnimation;
- final ScrollController scrollControllerX;
- final double viewportWidth;
-
- /// A [DevToolsError] that applies to the widget in this row.
- ///
- /// This will be null if there is no error for this row.
- final DevToolsError? error;
-
- /// Whether this row has any error.
- bool get hasError => error != null;
-
- @override
- Widget build(BuildContext context) {
- final currentX =
- controller.getDepthIndent(row.depth) - inspectorColumnWidth;
- final theme = Theme.of(context);
- final colorScheme = theme.colorScheme;
-
- Color? backgroundColor;
- if (row.isSelected) {
- backgroundColor = hasError
- ? colorScheme.errorContainer
- : colorScheme.selectedRowBackgroundColor;
- }
-
- final node = row.node;
-
- Widget rowWidget = Padding(
- padding: EdgeInsets.only(left: currentX),
- child: ValueListenableBuilder<String>(
- valueListenable: controller.searchNotifier,
- builder: (context, searchValue, _) {
- return Opacity(
- opacity: searchValue.isEmpty || row.isSearchMatch ? 1 : 0.2,
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- node.showExpandCollapse
- ? InkWell(
- onTap: onToggle,
- child: RotationTransition(
- turns: expandArrowAnimation,
- child: const Icon(
- Icons.expand_more,
- size: defaultIconSize,
- ),
- ),
- )
- : const SizedBox(
- width: defaultSpacing,
- height: defaultSpacing,
- ),
- Expanded(
- child: Container(
- color: backgroundColor,
- child: InkWell(
- onTap: () {
- controller.onSelectRow(row);
- // TODO(gmoothart): It may be possible to capture the tap
- // and request focus directly from the InspectorTree. Then
- // we wouldn't need this.
- controller.requestFocus();
- },
- child: SizedBox(
- height: inspectorRowHeight,
- child: DiagnosticsNodeDescription(
- node.diagnostic,
- isSelected: row.isSelected,
- searchValue: searchValue,
- errorText: error?.errorMessage,
- nodeDescriptionHighlightStyle:
- searchValue.isEmpty || !row.isSearchMatch
- ? DiagnosticsTextStyles.regular(
- Theme.of(context).colorScheme,
- )
- : row.isSelected
- ? theme.searchMatchHighlightStyleFocused
- : theme.searchMatchHighlightStyle,
- ),
- ),
- ),
- ),
- ),
- ],
- ),
- );
- },
- ),
- );
-
- // Wrap with tooltip if there is an error for this node's widget.
- if (hasError) {
- rowWidget = DevToolsTooltip(
- message: error!.errorMessage,
- child: rowWidget,
- );
- }
-
- return CustomPaint(
- painter: _RowPainter(row, controller, colorScheme),
- size: Size(currentX, inspectorRowHeight),
- child: Align(
- alignment: Alignment.topLeft,
- child: AnimatedBuilder(
- animation: scrollControllerX,
- builder: (context, child) {
- final rowWidth =
- scrollControllerX.offset + viewportWidth - defaultSpacing;
- return SizedBox(
- width: max(rowWidth, currentX + 100),
- child: rowWidth > currentX ? child : const SizedBox(),
- );
- },
- child: rowWidget,
- ),
- ),
- );
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/box/box.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/box/box.dart
deleted file mode 100644
index ff57992..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/box/box.dart
+++ /dev/null
@@ -1,446 +0,0 @@
-// Copyright 2021 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:async';
-import 'dart:math' as math;
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:flutter/material.dart';
-
-import '../../../../shared/diagnostics/diagnostics_node.dart';
-import '../../../../shared/primitives/math_utils.dart';
-import '../../../../shared/primitives/utils.dart';
-import '../../inspector_data_models.dart';
-import '../ui/free_space.dart';
-import '../ui/layout_explorer_widget.dart';
-import '../ui/theme.dart';
-import '../ui/utils.dart';
-import '../ui/widget_constraints.dart';
-import '../ui/widgets_theme.dart';
-
-/// Layout visualizer for a widget with a box-layout.
-class BoxLayoutExplorerWidget extends LayoutExplorerWidget {
- const BoxLayoutExplorerWidget(super.inspectorController, {super.key});
-
- static bool shouldDisplay(RemoteDiagnosticsNode _) {
- // Pretend this layout explorer is always available. This layout explorer
- // will gracefully fall back to an error message if the required properties
- // are not needed.
- // TODO(jacobr) pass a RemoteDiagnosticsNode to this method that contains
- // the layout explorer related supplemental properties so that we can
- // accurately determine whether the widget uses box layout.
- return true;
- }
-
- @override
- State<BoxLayoutExplorerWidget> createState() =>
- BoxLayoutExplorerWidgetState();
-}
-
-class BoxLayoutExplorerWidgetState
- extends
- LayoutExplorerWidgetState<BoxLayoutExplorerWidget, LayoutProperties> {
- @override
- RemoteDiagnosticsNode? getRoot(RemoteDiagnosticsNode? node) {
- final nodeLocal = node;
- if (nodeLocal == null) return null;
- if (!shouldDisplay(nodeLocal)) return null;
- return node;
- }
-
- @override
- bool shouldDisplay(RemoteDiagnosticsNode node) {
- final selectedNodeLocal = selectedNode;
- if (selectedNodeLocal == null) return false;
- return BoxLayoutExplorerWidget.shouldDisplay(selectedNodeLocal);
- }
-
- @override
- AnimatedLayoutProperties computeAnimatedProperties(
- LayoutProperties nextProperties,
- ) {
- return AnimatedLayoutProperties(
- // If an animation is in progress, freeze it and start animating from there, else start a fresh animation from widget.properties.
- animatedProperties?.copyWith() ?? properties!,
- nextProperties,
- changeAnimation,
- );
- }
-
- @override
- LayoutProperties computeLayoutProperties(RemoteDiagnosticsNode node) =>
- LayoutProperties(node);
-
- @override
- void updateHighlighted(LayoutProperties? newProperties) {
- setState(() {
- // This implementation will need to change if we support showing more than
- // a single widget in the box visualization for the layout explorer.
- highlighted = newProperties != null && selectedNode == newProperties.node
- ? newProperties
- : null;
- });
- }
-
- @override
- Widget build(BuildContext context) {
- if (properties == null) {
- final selectedNodeLocal = selectedNode;
- return Center(
- child: Text(
- '${selectedNodeLocal?.description ?? 'Widget'} has no layout properties to display.',
- textAlign: TextAlign.center,
- overflow: TextOverflow.clip,
- ),
- );
- }
- return Container(
- margin: const EdgeInsets.all(denseSpacing),
- child: AnimatedBuilder(
- animation: changeController,
- builder: (context, _) {
- return LayoutBuilder(builder: _buildLayout);
- },
- ),
- );
- }
-
- /// Simplistic layout algorithm to roughly match minFraction restrictions for
- /// each sizes attempting to render a stylized version of the original layout.
- /// TODO(jacobr): see if we can unify with the stylized version of the overall
- /// layout used for Flex. Our constraints are quite different as we can
- /// guarantee that the entire layout fits without scrolling while in the Flex
- /// case that would be difficult.
- ///
- /// The overall layout will expand to use the full availableSize treating null
- /// values in [sizes] as an indication that the items should have zero size.
- /// On the other hand, a non-null size indicates that the minFractions
- /// constraints should be obeyed. This is needed to ensure that negative sizes
- /// are visualized reasonably.
- /// The minFractions aren't exactly obeyed but they are approximated in a way
- /// that keeps this algorithm simple and has the nice property that an initial
- /// value much smaller than the minSize results in a slightly smaller value
- /// than a value that is almost minSize.
- /// In the most extreme case an item will get not minFraction but will instead
- /// get the slightly smaller value of minFraction / (1 + minFraction)
- /// which is close enough for the simple values we need this for.
- static List<double> minFractionLayout({
- required double availableSize,
- required List<double?> sizes,
- required List<double> minFractions,
- }) {
- assert(sizes.length == minFractions.length);
- final length = sizes.length;
- double total = 1.0; // This isn't set to zero to avoid divide by zero bugs.
- final fractions = minFractions.toList();
- for (final size in sizes) {
- if (size != null) {
- total += math.max(0, size);
- }
- }
-
- double totalFraction = 0.0;
- for (int i = 0; i < length; i++) {
- final size = sizes[i];
- if (size != null) {
- fractions[i] = math.max(size / total, minFractions[i]);
- totalFraction += fractions[i];
- } else {
- fractions[i] = 0.0;
- }
- }
- if (totalFraction != 1.0) {
- for (int i = 0; i < length; i++) {
- fractions[i] = fractions[i] / totalFraction;
- }
- }
- final output = <double>[];
- for (final fraction in fractions) {
- output.add(fraction * availableSize);
- }
- return output;
- }
-
- Widget _buildChild(BuildContext context) {
- final propertiesLocal = properties!;
-
- final theme = Theme.of(context);
- final colorScheme = theme.colorScheme;
- final parentProperties =
- this.parentProperties ??
- propertiesLocal; // Fall back to this node's properties if there is no parent.
-
- final parentSize = parentProperties.size;
- final offset = propertiesLocal.node.parentData;
-
- return LayoutBuilder(
- builder: (BuildContext context, BoxConstraints constraints) {
- // Subtract out one pixel border on each side.
- final availableHeight = constraints.maxHeight - 2;
- final availableWidth = constraints.maxWidth - 2;
-
- final minFractions = [0.2, 0.5, 0.2];
- // TODO(polinach, jacobr): consider using zeros for zero values,
- // without replacing them with nulls.
- // See https://github.com/flutter/devtools/issues/3931.
- double? nullOutZero(double value) => value != 0.0 ? value : null;
- final widths = [
- nullOutZero(offset.offset.dx),
- propertiesLocal.size.width,
- nullOutZero(
- parentSize.width - (propertiesLocal.size.width + offset.offset.dx),
- ),
- ];
- final heights = [
- nullOutZero(offset.offset.dy),
- propertiesLocal.size.height,
- nullOutZero(
- parentSize.height -
- (propertiesLocal.size.height + offset.offset.dy),
- ),
- ];
- // 3 element array with [left padding, widget width, right padding].
- final displayWidths = minFractionLayout(
- availableSize: availableWidth,
- sizes: widths,
- minFractions: minFractions,
- );
- // 3 element array with [top padding, widget height, bottom padding].
- final displayHeights = minFractionLayout(
- availableSize: availableHeight,
- sizes: heights,
- minFractions: minFractions,
- );
- final widgetWidth = displayWidths[1];
- final widgetHeight = displayHeights[1];
- final safeParentSize = parentSize;
- final width0 = widths[0];
- final width2 = widths[2];
- final height0 = heights[0];
- final height2 = heights[2];
- return Container(
- width: constraints.maxWidth,
- height: constraints.maxHeight,
- decoration: BoxDecoration(
- border: Border.all(
- color: WidgetTheme.fromName(
- propertiesLocal.node.description,
- ).color,
- ),
- ),
- child: Stack(
- children: [
- LayoutExplorerBackground(colorScheme: colorScheme),
- // Left padding.
- if (width0 != null)
- PaddingVisualizerWidget(
- RenderProperties(
- axis: Axis.horizontal,
- size: Size(displayWidths[0], widgetHeight),
- offset: Offset(0, displayHeights[0]),
- realSize: Size(width0, safeParentSize.height),
- layoutProperties: propertiesLocal,
- isFreeSpace: true,
- ),
- horizontal: true,
- ),
- // Top padding.
- if (height0 != null)
- PaddingVisualizerWidget(
- RenderProperties(
- axis: Axis.horizontal,
- size: Size(widgetWidth, displayHeights[0]),
- offset: Offset(displayWidths[0], 0),
- realSize: Size(safeParentSize.width, height0),
- layoutProperties: propertiesLocal,
- isFreeSpace: true,
- ),
- horizontal: false,
- ),
- // Right padding.
- if (width2 != null)
- PaddingVisualizerWidget(
- RenderProperties(
- axis: Axis.horizontal,
- size: Size(displayWidths[2], widgetHeight),
- offset: Offset(
- displayWidths[0] + displayWidths[1],
- displayHeights[0],
- ),
- realSize: Size(width2, safeParentSize.height),
- layoutProperties: propertiesLocal,
- isFreeSpace: true,
- ),
- horizontal: true,
- ),
- // Bottom padding.
- if (height2 != null)
- PaddingVisualizerWidget(
- RenderProperties(
- axis: Axis.horizontal,
- size: Size(widgetWidth, displayHeights[2]),
- offset: Offset(
- displayWidths[0],
- displayHeights[0] + displayHeights[1],
- ),
- realSize: Size(safeParentSize.width, height2),
- layoutProperties: propertiesLocal,
- isFreeSpace: true,
- ),
- horizontal: false,
- ),
- BoxChildVisualizer(
- isSelected: true,
- state: this,
- layoutProperties: propertiesLocal,
- renderProperties: RenderProperties(
- axis: Axis.horizontal,
- size: Size(widgetWidth, widgetHeight),
- offset: Offset(displayWidths[0], displayHeights[0]),
- realSize: propertiesLocal.size,
- layoutProperties: propertiesLocal,
- ),
- ),
- ],
- ),
- );
- },
- );
- }
-
- LayoutProperties? get parentProperties {
- final parentElement = properties?.node.parentRenderElement;
- if (parentElement == null) return null;
- return computeLayoutProperties(parentElement);
- }
-
- Widget _buildLayout(BuildContext context, BoxConstraints constraints) {
- final maxHeight = constraints.maxHeight;
- final maxWidth = constraints.maxWidth;
-
- Widget widget = _buildChild(context);
- final parentProperties = this.parentProperties;
- if (parentProperties != null) {
- // Wrap with a widget visualizer for the parent if there is a valid parent.
- widget = WidgetVisualizer(
- // TODO(jacobr): this node's name can be misleading more often than
- // in the flex case the widget doesn't have its own RenderObject.
- // Consider showing the true ancestor for the summary tree that first
- // has a different render object.
- title: describeBoxName(parentProperties),
- largeTitle: true,
- layoutProperties: parentProperties,
- isSelected: false,
- child: VisualizeWidthAndHeightWithConstraints(
- properties: parentProperties,
- warnIfUnconstrained: false,
- child: Padding(
- padding: const EdgeInsets.all(denseSpacing),
- child: widget,
- ),
- ),
- );
- }
- return Container(
- constraints: BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight),
- child: widget,
- );
- }
-}
-
-String describeBoxName(LayoutProperties properties) {
- // Displaying a high quality name is more ambiguous for the Box case than the
- // Flex case because the RenderObject for each widget is often quite
- // different than the user expected as not all widgets have RenderObjects.
- // As a compromise we currently show 'WidgetName - RenderObjectName'.
- // This is clearer but risks more confusion
-
- // Widget name.
- var title = properties.node.description ?? '';
- final renderDescription = properties.node.renderObject?.description;
- // TODO(jacobr): consider de-emphasizing the render object name by putting it
- // in more transparent text or just calling the widget Parent instead of
- // surfacing a widget name.
- if (renderDescription != null) {
- // Name of the associated RenderObject if one is available.
- title += ' - $renderDescription';
- }
- return title;
-}
-
-/// Widget that represents and visualize a direct child of Flex widget.
-class BoxChildVisualizer extends StatelessWidget {
- const BoxChildVisualizer({
- super.key,
- required this.state,
- required this.layoutProperties,
- required this.renderProperties,
- required this.isSelected,
- });
-
- final BoxLayoutExplorerWidgetState state;
-
- final bool isSelected;
- final LayoutProperties layoutProperties;
- final RenderProperties renderProperties;
-
- LayoutProperties? get properties => renderProperties.layoutProperties;
-
- @override
- Widget build(BuildContext context) {
- final renderSize = renderProperties.size;
- final renderOffset = renderProperties.offset;
-
- Widget buildEntranceAnimation(BuildContext _, Widget? child) {
- final size = renderSize;
- // TODO(jacobr): does this entrance animation really add value.
- return Opacity(
- opacity: min([state.entranceCurve.value * 5, 1.0]),
- child: Padding(
- padding: EdgeInsets.symmetric(
- horizontal: math.max(0.0, (renderSize.width - size.width) / 2),
- vertical: math.max(0.0, (renderSize.height - size.height) / 2),
- ),
- child: child,
- ),
- );
- }
-
- final propertiesLocal = properties!;
-
- return Positioned(
- top: renderOffset.dy,
- left: renderOffset.dx,
- child: InkWell(
- onTap: () => unawaited(state.onTap(propertiesLocal)),
- onDoubleTap: () => state.onDoubleTap(propertiesLocal),
- child: SizedBox(
- width: safePositiveDouble(renderSize.width),
- height: safePositiveDouble(renderSize.height),
- child: AnimatedBuilder(
- animation: state.entranceController,
- builder: buildEntranceAnimation,
- child: WidgetVisualizer(
- isSelected: isSelected,
- layoutProperties: layoutProperties,
- title: describeBoxName(propertiesLocal),
- // TODO(jacobr): consider surfacing the overflow size information
- // if we determine
- // overflowSide: properties.overflowSide,
-
- // We only show one child at a time so a large title is safe.
- largeTitle: true,
- child: VisualizeWidthAndHeightWithConstraints(
- arrowHeadSize: arrowHeadSize,
- properties: propertiesLocal,
- warnIfUnconstrained: false,
- child: const SizedBox.shrink(),
- ),
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex/flex.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex/flex.dart
deleted file mode 100644
index c00d352..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex/flex.dart
+++ /dev/null
@@ -1,777 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:async';
-import 'dart:math' as math;
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:flutter/material.dart';
-
-import '../../../../shared/diagnostics/diagnostics_node.dart';
-import '../../../../shared/diagnostics/inspector_service.dart';
-import '../../../../shared/primitives/math_utils.dart';
-import '../../../../shared/ui/common_widgets.dart';
-import '../../inspector_data_models.dart';
-import '../ui/arrow.dart';
-import '../ui/free_space.dart';
-import '../ui/layout_explorer_widget.dart';
-import '../ui/theme.dart';
-import '../ui/utils.dart';
-import '../ui/widget_constraints.dart';
-import 'utils.dart';
-
-// TODO(kenz): clean up this file so that we use helper widgets instead of
-// methods that pass around build context.
-
-// TODO(kenz): densify the layout explorer visualization for flex widgets.
-
-const alignmentDropdownMaxSize = 140.0;
-
-class FlexLayoutExplorerWidget extends LayoutExplorerWidget {
- const FlexLayoutExplorerWidget(super.inspectorController, {super.key});
-
- static bool shouldDisplay(RemoteDiagnosticsNode node) {
- return (node.isFlex) || (node.parent?.isFlex ?? false);
- }
-
- @override
- State<FlexLayoutExplorerWidget> createState() =>
- FlexLayoutExplorerWidgetState();
-}
-
-class FlexLayoutExplorerWidgetState
- extends
- LayoutExplorerWidgetState<
- FlexLayoutExplorerWidget,
- FlexLayoutProperties
- > {
- final scrollController = ScrollController();
-
- Axis get direction => properties!.direction;
-
- ObjectGroup? get objectGroup =>
- properties!.node.objectGroupApi as ObjectGroup?;
-
- Color horizontalColor(ColorScheme colorScheme) =>
- properties!.isMainAxisHorizontal
- ? colorScheme.mainAxisColor
- : colorScheme.crossAxisColor;
-
- Color verticalColor(ColorScheme colorScheme) => properties!.isMainAxisVertical
- ? colorScheme.mainAxisColor
- : colorScheme.crossAxisColor;
-
- Color horizontalTextColor(ColorScheme colorScheme) =>
- properties!.isMainAxisHorizontal
- ? colorScheme.mainAxisTextColor
- : colorScheme.crossAxisTextColor;
-
- Color verticalTextColor(ColorScheme colorScheme) =>
- properties!.isMainAxisVertical
- ? colorScheme.mainAxisTextColor
- : colorScheme.crossAxisTextColor;
-
- String get flexType => properties!.type;
-
- @override
- RemoteDiagnosticsNode? getRoot(RemoteDiagnosticsNode? node) {
- if (node == null) return null;
- if (!shouldDisplay(node)) return null;
- if (node.isFlex) return node;
- return node.parent;
- }
-
- @override
- bool shouldDisplay(RemoteDiagnosticsNode node) {
- final selectedNodeLocal = selectedNode;
- if (selectedNodeLocal == null) return false;
- return FlexLayoutExplorerWidget.shouldDisplay(selectedNodeLocal);
- }
-
- @override
- AnimatedFlexLayoutProperties computeAnimatedProperties(
- FlexLayoutProperties nextProperties,
- ) {
- return AnimatedFlexLayoutProperties(
- // If an animation is in progress, freeze it and start animating from there, else start a fresh animation from widget.properties.
- animatedProperties?.copyWith() as FlexLayoutProperties? ?? properties!,
- nextProperties,
- changeAnimation,
- );
- }
-
- @override
- FlexLayoutProperties computeLayoutProperties(RemoteDiagnosticsNode node) =>
- FlexLayoutProperties.fromDiagnostics(node);
-
- @override
- void updateHighlighted(FlexLayoutProperties? newProperties) {
- setState(() {
- if (selectedNode!.isFlex) {
- highlighted = newProperties;
- } else {
- final idx =
- selectedNode?.parent?.childrenNow.indexOf(selectedNode!) ?? -1;
- if (newProperties == null) return;
- if (idx != -1) highlighted = newProperties.children[idx];
- }
- });
- }
-
- Widget _buildAxisAlignmentDropdown(Axis axis, ThemeData theme) {
- final colorScheme = theme.colorScheme;
- final color = axis == direction
- ? colorScheme.mainAxisTextColor
- : colorScheme.crossAxisTextColor;
- List<Enum> alignmentEnumEntries;
- Object? selected;
- final propertiesLocal = properties!;
- if (axis == direction) {
- alignmentEnumEntries = MainAxisAlignment.values;
- selected = propertiesLocal.mainAxisAlignment;
- } else {
- alignmentEnumEntries = CrossAxisAlignment.values.toList(growable: true);
- if (propertiesLocal.textBaseline == null) {
- // TODO(albertusangga): Look for ways to visualize baseline when it is null
- alignmentEnumEntries.remove(CrossAxisAlignment.baseline);
- }
- selected = propertiesLocal.crossAxisAlignment;
- }
- return RotatedBox(
- quarterTurns: axis == Axis.vertical ? 3 : 0,
- child: Container(
- constraints: const BoxConstraints(
- maxWidth: alignmentDropdownMaxSize,
- maxHeight: defaultButtonHeight,
- ),
- child: DropdownButton(
- value: selected,
- isDense: true,
- isExpanded: true,
- // Avoid showing an underline for the main axis and cross-axis drop downs.
- underline: const SizedBox(),
- iconEnabledColor: axis == propertiesLocal.direction
- ? colorScheme.mainAxisColor
- : colorScheme.crossAxisColor,
- selectedItemBuilder: (context) {
- return [
- for (final alignment in alignmentEnumEntries)
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Expanded(
- child: Text(
- alignment.name,
- style: theme.regularTextStyleWithColor(color),
- textAlign: TextAlign.center,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- SizedBox(
- height: actionsIconSize,
- width: actionsIconSize,
- child: Image.asset(
- (axis == direction)
- ? mainAxisAssetImageUrl(
- direction,
- alignment as MainAxisAlignment,
- )
- : crossAxisAssetImageUrl(
- direction,
- alignment as CrossAxisAlignment,
- ),
- height: axisAlignmentAssetImageHeight,
- fit: BoxFit.fitHeight,
- color: color,
- ),
- ),
- ],
- ),
- ];
- },
- items: [
- for (final alignment in alignmentEnumEntries)
- DropdownMenuItem(
- value: alignment,
- child: Container(
- padding: const EdgeInsets.symmetric(vertical: margin),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Expanded(
- child: Text(
- alignment.name,
- style: theme.regularTextStyleWithColor(color),
- textAlign: TextAlign.center,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- SizedBox(
- height: actionsIconSize,
- width: actionsIconSize,
- child: Image.asset(
- (axis == direction)
- ? mainAxisAssetImageUrl(
- direction,
- alignment as MainAxisAlignment,
- )
- : crossAxisAssetImageUrl(
- direction,
- alignment as CrossAxisAlignment,
- ),
- fit: BoxFit.fitHeight,
- color: color,
- ),
- ),
- ],
- ),
- ),
- ),
- ],
- onChanged: (Object? newSelection) async {
- // newSelection is an object instead of type here because
- // the type is dependent on the `axis` parameter
- // if the axis is the main axis the type should be [MainAxisAlignment]
- // if the axis is the cross axis the type should be [CrossAxisAlignment]
- FlexLayoutProperties changedProperties;
- changedProperties = axis == direction
- ? propertiesLocal.copyWith(
- mainAxisAlignment: newSelection as MainAxisAlignment?,
- )
- : propertiesLocal.copyWith(
- crossAxisAlignment: newSelection as CrossAxisAlignment?,
- );
- final valueRef = propertiesLocal.node.valueRef;
- markAsDirty();
- await objectGroup!.invokeSetFlexProperties(
- valueRef,
- changedProperties.mainAxisAlignment,
- changedProperties.crossAxisAlignment,
- );
- },
- ),
- ),
- );
- }
-
- @override
- Widget build(BuildContext context) {
- if (properties == null) return const SizedBox();
- return Container(
- margin: const EdgeInsets.all(margin),
- padding: const EdgeInsets.only(bottom: margin, right: margin),
- child: AnimatedBuilder(
- animation: changeController,
- builder: (context, _) {
- return LayoutBuilder(builder: _buildLayout);
- },
- ),
- );
- }
-
- Widget _buildLayout(BuildContext context, BoxConstraints constraints) {
- final theme = Theme.of(context);
- final colorScheme = theme.colorScheme;
- final maxHeight = constraints.maxHeight;
- final maxWidth = constraints.maxWidth;
- final propertiesLocal = properties!;
- final flexDescription = Align(
- alignment: Alignment.centerLeft,
- child: Container(
- margin: const EdgeInsets.only(
- top: mainAxisArrowIndicatorSize,
- left: crossAxisArrowIndicatorSize + margin,
- ),
- child: InkWell(
- onTap: () => unawaited(onTap(propertiesLocal)),
- child: WidgetVisualizer(
- title: flexType,
- layoutProperties: propertiesLocal,
- isSelected: highlighted == properties,
- overflowSide: propertiesLocal.overflowSide,
- hint: Container(
- padding: const EdgeInsets.all(4.0),
- child: Text(
- 'Total Flex Factor: ${propertiesLocal.totalFlex.toInt()}',
- style: theme.regularTextStyleWithColor(emphasizedTextColor),
- overflow: TextOverflow.ellipsis,
- ),
- ),
- child: VisualizeFlexChildren(
- state: this,
- properties: propertiesLocal,
- children: children,
- highlighted: highlighted,
- scrollController: scrollController,
- direction: direction,
- ),
- ),
- ),
- ),
- );
-
- final verticalAxisDescription = Align(
- alignment: Alignment.bottomLeft,
- child: Container(
- margin: const EdgeInsets.only(top: mainAxisArrowIndicatorSize + margin),
- width: crossAxisArrowIndicatorSize,
- child: Column(
- children: [
- Expanded(
- child: ArrowWrapper.unidirectional(
- arrowColor: verticalColor(colorScheme),
- type: ArrowType.down,
- child: Truncateable(
- truncate: maxHeight <= minHeightToAllowTruncating,
- child: RotatedBox(
- quarterTurns: 3,
- child: Text(
- propertiesLocal.verticalDirectionDescription,
- overflow: TextOverflow.ellipsis,
- textAlign: TextAlign.center,
- style: theme.regularTextStyleWithColor(
- verticalTextColor(colorScheme),
- ),
- ),
- ),
- ),
- ),
- ),
- Truncateable(
- truncate: maxHeight <= minHeightToAllowTruncating,
- child: _buildAxisAlignmentDropdown(Axis.vertical, theme),
- ),
- ],
- ),
- ),
- );
-
- final horizontalAxisDescription = Align(
- alignment: Alignment.topRight,
- child: Container(
- margin: const EdgeInsets.only(
- left: crossAxisArrowIndicatorSize + margin,
- ),
- height: mainAxisArrowIndicatorSize,
- child: Row(
- children: [
- Expanded(
- child: ArrowWrapper.unidirectional(
- arrowColor: horizontalColor(colorScheme),
- type: ArrowType.right,
- child: Truncateable(
- truncate: maxWidth <= minWidthToAllowTruncating,
- child: Text(
- propertiesLocal.horizontalDirectionDescription,
- overflow: TextOverflow.ellipsis,
- textAlign: TextAlign.center,
- style: theme.regularTextStyleWithColor(
- horizontalTextColor(colorScheme),
- ),
- ),
- ),
- ),
- ),
- Truncateable(
- truncate: maxWidth <= minWidthToAllowTruncating,
- child: _buildAxisAlignmentDropdown(Axis.horizontal, theme),
- ),
- ],
- ),
- ),
- );
-
- return Container(
- constraints: BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight),
- child: Stack(
- children: [
- flexDescription,
- verticalAxisDescription,
- horizontalAxisDescription,
- ],
- ),
- );
- }
-}
-
-class VisualizeFlexChildren extends StatefulWidget {
- const VisualizeFlexChildren({
- super.key,
- required this.state,
- required this.properties,
- required this.children,
- required this.highlighted,
- required this.scrollController,
- required this.direction,
- });
-
- final FlexLayoutProperties properties;
- final List<LayoutProperties> children;
- final LayoutProperties? highlighted;
- final ScrollController scrollController;
- final Axis direction;
- final FlexLayoutExplorerWidgetState state;
-
- @override
- State<VisualizeFlexChildren> createState() => _VisualizeFlexChildrenState();
-}
-
-class _VisualizeFlexChildrenState extends State<VisualizeFlexChildren> {
- LayoutProperties? lastHighlighted;
- static final selectedChildKey = GlobalKey(debugLabel: 'selectedChild');
-
- @override
- Widget build(BuildContext context) {
- if (lastHighlighted != widget.highlighted) {
- lastHighlighted = widget.highlighted;
- if (widget.highlighted != null) {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- final selectedRenderObject = selectedChildKey.currentContext
- ?.findRenderObject();
- if (selectedRenderObject != null &&
- widget.scrollController.hasClients) {
- unawaited(
- widget.scrollController.position.ensureVisible(
- selectedRenderObject,
- alignment: 0.5,
- duration: defaultDuration,
- ),
- );
- }
- });
- }
- }
-
- if (!widget.properties.hasChildren) {
- return const CenteredMessage(message: 'No Children');
- }
-
- final theme = Theme.of(context);
- final colorScheme = theme.colorScheme;
-
- final contents = Container(
- decoration: BoxDecoration(
- border: Border.all(color: theme.primaryColorLight),
- ),
- margin: const EdgeInsets.only(top: margin, left: margin),
- child: LayoutBuilder(
- builder: (context, constraints) {
- final maxWidth = constraints.maxWidth;
- final maxHeight = constraints.maxHeight;
-
- double maxSizeAvailable(Axis axis) {
- return axis == Axis.horizontal ? maxWidth : maxHeight;
- }
-
- final childrenAndMainAxisSpacesRenderProps = widget.properties
- .childrenRenderProperties(
- smallestRenderWidth: minRenderWidth,
- largestRenderWidth: defaultMaxRenderWidth,
- smallestRenderHeight: minRenderHeight,
- largestRenderHeight: defaultMaxRenderHeight,
- maxSizeAvailable: maxSizeAvailable,
- );
-
- final renderProperties = childrenAndMainAxisSpacesRenderProps
- .where((renderProps) => !renderProps.isFreeSpace)
- .toList();
- final mainAxisSpaces = childrenAndMainAxisSpacesRenderProps
- .where((renderProps) => renderProps.isFreeSpace)
- .toList();
- final crossAxisSpaces = widget.properties.crossAxisSpaces(
- childrenRenderProperties: renderProperties,
- maxSizeAvailable: maxSizeAvailable,
- );
-
- final childrenRenderWidgets = <Widget>[];
- Widget? selectedWidget;
- for (var i = 0; i < widget.children.length; i++) {
- final child = widget.children[i];
- final isSelected = widget.highlighted == child;
-
- final visualizer = FlexChildVisualizer(
- key: isSelected ? selectedChildKey : null,
- state: widget.state,
- layoutProperties: child,
- isSelected: isSelected,
- renderProperties: renderProperties[i],
- );
-
- if (isSelected) {
- selectedWidget = visualizer;
- } else {
- childrenRenderWidgets.add(visualizer);
- }
- }
-
- // Selected widget needs to be last to draw its border over other children
- if (selectedWidget != null) {
- childrenRenderWidgets.add(selectedWidget);
- }
-
- final freeSpacesWidgets = [
- for (final renderProperties in [
- ...mainAxisSpaces,
- ...crossAxisSpaces,
- ])
- FreeSpaceVisualizerWidget(renderProperties),
- ];
- return Scrollbar(
- thumbVisibility: true,
- controller: widget.scrollController,
- child: SingleChildScrollView(
- scrollDirection: widget.properties.direction,
- controller: widget.scrollController,
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minWidth: maxWidth,
- minHeight: maxHeight,
- maxWidth: widget.direction == Axis.horizontal
- ? sum(
- childrenAndMainAxisSpacesRenderProps.map(
- (renderSize) => renderSize.width,
- ),
- )
- : maxWidth,
- maxHeight: widget.direction == Axis.vertical
- ? sum(
- childrenAndMainAxisSpacesRenderProps.map(
- (renderSize) => renderSize.height,
- ),
- )
- : maxHeight,
- ).normalize(),
- child: Stack(
- children: [
- LayoutExplorerBackground(colorScheme: colorScheme),
- ...freeSpacesWidgets,
- ...childrenRenderWidgets,
- ],
- ),
- ),
- ),
- );
- },
- ),
- );
- return VisualizeWidthAndHeightWithConstraints(
- properties: widget.properties,
- child: contents,
- );
- }
-}
-
-/// Widget that represents and visualize a direct child of Flex widget.
-class FlexChildVisualizer extends StatelessWidget {
- const FlexChildVisualizer({
- super.key,
- required this.state,
- required this.layoutProperties,
- required this.renderProperties,
- required this.isSelected,
- });
-
- final FlexLayoutExplorerWidgetState state;
-
- final bool isSelected;
-
- final LayoutProperties layoutProperties;
-
- final RenderProperties renderProperties;
-
- // TODO(polina-c, jacob314): consider refactoring to remove `!`.
- FlexLayoutProperties get root => state.properties!;
-
- LayoutProperties get properties => renderProperties.layoutProperties;
-
- ObjectGroup? get objectGroup =>
- properties.node.objectGroupApi as ObjectGroup?;
-
- void onChangeFlexFactor(int? newFlexFactor) async {
- state.markAsDirty();
- await objectGroup!.invokeSetFlexFactor(
- properties.node.valueRef,
- newFlexFactor,
- );
- }
-
- void onChangeFlexFit(FlexFit? newFlexFit) async {
- state.markAsDirty();
- await objectGroup!.invokeSetFlexFit(properties.node.valueRef, newFlexFit!);
- }
-
- Widget _buildFlexFactorChangerDropdown(
- int maximumFlexFactor,
- ThemeData theme,
- ) {
- final propertiesLocal = properties;
-
- Widget buildMenuitemChild(int? flexFactor) {
- return Text(
- 'flex: $flexFactor',
- style: flexFactor == propertiesLocal.flexFactor
- ? theme.boldTextStyle.copyWith(color: emphasizedTextColor)
- : theme.regularTextStyleWithColor(emphasizedTextColor),
- );
- }
-
- DropdownMenuItem<int> buildMenuItem(int? flexFactor) {
- return DropdownMenuItem(
- value: flexFactor,
- child: buildMenuitemChild(flexFactor),
- );
- }
-
- return DropdownButton<int>(
- value: propertiesLocal.flexFactor?.toInt().clamp(0, maximumFlexFactor),
- onChanged: onChangeFlexFactor,
- iconEnabledColor: textColor,
- underline: buildUnderline(),
- items: <DropdownMenuItem<int>>[
- buildMenuItem(null),
- for (var i = 0; i <= maximumFlexFactor; ++i) buildMenuItem(i),
- ],
- );
- }
-
- Widget _buildFlexFitChangerDropdown(ThemeData theme) {
- Widget flexFitDescription(FlexFit flexFit) => Text(
- 'fit: ${flexFit.name}',
- style: theme.regularTextStyleWithColor(emphasizedTextColor),
- );
-
- final propertiesLocal = properties;
-
- // Disable FlexFit changer if widget is Expanded.
- if (propertiesLocal.description == 'Expanded') {
- return flexFitDescription(FlexFit.tight);
- }
-
- DropdownMenuItem<FlexFit> buildMenuItem(FlexFit flexFit) {
- return DropdownMenuItem(
- value: flexFit,
- child: flexFitDescription(flexFit),
- );
- }
-
- return DropdownButton<FlexFit>(
- value: propertiesLocal.flexFit,
- onChanged: onChangeFlexFit,
- underline: buildUnderline(),
- iconEnabledColor: emphasizedTextColor,
- items: <DropdownMenuItem<FlexFit>>[
- buildMenuItem(FlexFit.loose),
- if (propertiesLocal.description != 'Expanded')
- buildMenuItem(FlexFit.tight),
- ],
- );
- }
-
- Widget _buildContent(ThemeData theme) {
- // TODO(https://github.com/flutter/devtools/issues/4058) allow more dynamic
- // flex factor input
- final currentFlexFactor = properties.flexFactor?.toInt() ?? 0;
- final currentMaxFlexFactor = math.max(
- currentFlexFactor,
- maximumFlexFactorOptions,
- );
-
- return Container(
- margin: const EdgeInsets.only(top: margin, left: margin),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- Flexible(
- child: _buildFlexFactorChangerDropdown(currentMaxFlexFactor, theme),
- ),
- if (!properties.hasFlexFactor)
- Text(
- 'unconstrained ${root.isMainAxisHorizontal ? 'horizontal' : 'vertical'}',
- style: theme.regularTextStyle.copyWith(
- color: theme.colorScheme.unconstrainedColor,
- fontStyle: FontStyle.italic,
- ),
- maxLines: 2,
- softWrap: true,
- overflow: TextOverflow.ellipsis,
- textScaler: const TextScaler.linear(smallTextScaleFactor),
- textAlign: TextAlign.center,
- ),
- _buildFlexFitChangerDropdown(theme),
- ],
- ),
- );
- }
-
- @override
- Widget build(BuildContext context) {
- final renderSize = renderProperties.size;
- final renderOffset = renderProperties.offset;
- final propertiesLocal = properties;
- final rootLocal = root;
-
- Widget buildEntranceAnimation(BuildContext _, Widget? child) {
- final vertical = rootLocal.isMainAxisVertical;
- final horizontal = rootLocal.isMainAxisHorizontal;
-
- late Size size;
- size = propertiesLocal.hasFlexFactor
- ? SizeTween(
- begin: Size(
- horizontal ? minRenderWidth - entranceMargin : renderSize.width,
- vertical ? minRenderHeight - entranceMargin : renderSize.height,
- ),
- end: renderSize,
- ).evaluate(state.entranceCurve)!
- : renderSize;
- // Not-expanded widgets enter much faster.
- return Opacity(
- opacity: min([state.entranceCurve.value * 5, 1.0]),
- child: Padding(
- padding: EdgeInsets.symmetric(
- horizontal: math.max(0.0, (renderSize.width - size.width) / 2),
- vertical: math.max(0.0, (renderSize.height - size.height) / 2),
- ),
- child: child,
- ),
- );
- }
-
- return Positioned(
- top: renderOffset.dy,
- left: renderOffset.dx,
- child: GestureDetector(
- onTap: () => unawaited(state.onTap(propertiesLocal)),
- onDoubleTap: () => state.onDoubleTap(propertiesLocal),
- onLongPress: () => state.onDoubleTap(propertiesLocal),
- child: SizedBox(
- width: renderSize.width,
- height: renderSize.height,
- child: AnimatedBuilder(
- animation: state.entranceController,
- builder: buildEntranceAnimation,
- child: WidgetVisualizer(
- isSelected: isSelected,
- layoutProperties: layoutProperties,
- title: propertiesLocal.description ?? '',
- overflowSide: propertiesLocal.overflowSide,
- child: VisualizeWidthAndHeightWithConstraints(
- arrowHeadSize: arrowHeadSize,
- properties: propertiesLocal,
- child: Align(
- alignment: Alignment.topRight,
- child: _buildContent(Theme.of(context)),
- ),
- ),
- ),
- ),
- ),
- ),
- );
- }
-
- /// define the number of flex factor to be shown in the flex dropdown button
- /// for example if it's set to 5 the dropdown will consist of 6 items (null and 0..5)
- static const maximumFlexFactorOptions = 5;
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex/utils.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex/utils.dart
deleted file mode 100644
index cc3b201..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/flex/utils.dart
+++ /dev/null
@@ -1,198 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:ui';
-
-import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
-
-import '../../inspector_data_models.dart';
-import '../ui/utils.dart';
-
-String crossAxisAssetImageUrl(Axis direction, CrossAxisAlignment alignment) {
- return 'assets/img/layout_explorer/cross_axis_alignment/'
- '${direction.flexType.toLowerCase()}_${alignment.name}.png';
-}
-
-String mainAxisAssetImageUrl(Axis direction, MainAxisAlignment alignment) {
- return 'assets/img/layout_explorer/main_axis_alignment/'
- '${direction.flexType.toLowerCase()}_${alignment.name}.png';
-}
-
-class AnimatedFlexLayoutProperties
- extends AnimatedLayoutProperties<FlexLayoutProperties>
- implements FlexLayoutProperties {
- AnimatedFlexLayoutProperties(super.begin, super.end, super.animation);
-
- @override
- CrossAxisAlignment? get crossAxisAlignment => end.crossAxisAlignment;
-
- @override
- MainAxisAlignment? get mainAxisAlignment => end.mainAxisAlignment;
-
- @override
- List<RenderProperties> childrenRenderProperties({
- required double smallestRenderWidth,
- required double largestRenderWidth,
- required double smallestRenderHeight,
- required double largestRenderHeight,
- required double Function(Axis) maxSizeAvailable,
- }) {
- final beginRenderProperties = begin.childrenRenderProperties(
- smallestRenderHeight: smallestRenderHeight,
- smallestRenderWidth: smallestRenderWidth,
- largestRenderHeight: largestRenderHeight,
- largestRenderWidth: largestRenderWidth,
- maxSizeAvailable: maxSizeAvailable,
- );
- final endRenderProperties = end.childrenRenderProperties(
- smallestRenderHeight: smallestRenderHeight,
- smallestRenderWidth: smallestRenderWidth,
- largestRenderHeight: largestRenderHeight,
- largestRenderWidth: largestRenderWidth,
- maxSizeAvailable: maxSizeAvailable,
- );
- final result = <RenderProperties>[];
- for (var i = 0; i < children.length; i++) {
- final beginProps = beginRenderProperties[i];
- final endProps = endRenderProperties[i];
- final t = animation.value;
- result.add(
- RenderProperties(
- axis: endProps.axis,
- offset: Offset.lerp(beginProps.offset, endProps.offset, t),
- size: Size.lerp(beginProps.size, endProps.size, t),
- realSize: Size.lerp(beginProps.realSize, endProps.realSize, t),
- layoutProperties: AnimatedLayoutProperties(
- beginProps.layoutProperties,
- endProps.layoutProperties,
- animation,
- ),
- ),
- );
- }
- // Add in the free space from the end.
- // TODO(djshuckerow): We should make free space a part of
- // RenderProperties so that we can animate between those.
- result.addAll(endRenderProperties.where((prop) => prop.isFreeSpace));
- return result;
- }
-
- @override
- double get crossAxisDimension => lerpDouble(
- begin.crossAxisDimension,
- end.crossAxisDimension,
- animation.value,
- )!;
-
- @override
- Axis get crossAxisDirection => end.crossAxisDirection;
-
- @override
- List<RenderProperties> crossAxisSpaces({
- required List<RenderProperties> childrenRenderProperties,
- required double Function(Axis) maxSizeAvailable,
- }) {
- return end.crossAxisSpaces(
- childrenRenderProperties: childrenRenderProperties,
- maxSizeAvailable: maxSizeAvailable,
- );
- }
-
- @override
- Axis get direction => end.direction;
-
- @override
- String get horizontalDirectionDescription =>
- end.horizontalDirectionDescription;
-
- @override
- bool get isMainAxisHorizontal => end.isMainAxisHorizontal;
-
- @override
- bool get isMainAxisVertical => end.isMainAxisVertical;
-
- @override
- double get mainAxisDimension => lerpDouble(
- begin.mainAxisDimension,
- end.mainAxisDimension,
- animation.value,
- )!;
-
- @override
- MainAxisSize? get mainAxisSize => end.mainAxisSize;
-
- @override
- TextBaseline? get textBaseline => end.textBaseline;
-
- @override
- TextDirection get textDirection => end.textDirection;
-
- @override
- double get totalFlex =>
- lerpDouble(begin.totalFlex, end.totalFlex, animation.value)!;
-
- @override
- String get type => end.type;
-
- @override
- VerticalDirection get verticalDirection => end.verticalDirection;
-
- @override
- String get verticalDirectionDescription => end.verticalDirectionDescription;
-
- /// Returns a frozen copy of these FlexLayoutProperties that does not animate.
- ///
- /// Useful for interrupting an animation with a transition to another [FlexLayoutProperties].
- @override
- FlexLayoutProperties copyWith({
- Size? size,
- List<LayoutProperties>? children,
- BoxConstraints? constraints,
- bool? isFlex,
- String? description,
- num? flexFactor,
- FlexFit? flexFit,
- Axis? direction,
- MainAxisAlignment? mainAxisAlignment,
- MainAxisSize? mainAxisSize,
- CrossAxisAlignment? crossAxisAlignment,
- TextDirection? textDirection,
- VerticalDirection? verticalDirection,
- TextBaseline? textBaseline,
- }) {
- return FlexLayoutProperties(
- size: size ?? this.size,
- children: children ?? this.children,
- node: node,
- constraints: constraints ?? this.constraints,
- isFlex: isFlex ?? this.isFlex,
- description: description ?? this.description,
- flexFactor: flexFactor ?? this.flexFactor,
- direction: direction ?? this.direction,
- mainAxisAlignment: mainAxisAlignment ?? this.mainAxisAlignment,
- mainAxisSize: mainAxisSize ?? this.mainAxisSize,
- crossAxisAlignment: crossAxisAlignment ?? this.crossAxisAlignment,
- textDirection: textDirection ?? this.textDirection,
- verticalDirection: verticalDirection ?? this.verticalDirection,
- textBaseline: textBaseline ?? this.textBaseline,
- );
- }
-
- @override
- bool get startIsTopLeft => end.startIsTopLeft;
-}
-
-/// LayoutProperties extension to be reused on LayoutProperties and AnimatedLayoutProperties
-
-extension AxisExtension on Axis {
- String get flexType {
- switch (this) {
- case Axis.horizontal:
- return 'Row';
- case Axis.vertical:
- return 'Column';
- }
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/layout_explorer.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/layout_explorer.dart
deleted file mode 100644
index 116a3c6..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/layout_explorer.dart
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app_shared/utils.dart';
-import 'package:flutter/widgets.dart';
-
-import '../../../shared/diagnostics/diagnostics_node.dart';
-import '../inspector_controller.dart';
-import '../layout_explorer/box/box.dart';
-import '../layout_explorer/flex/flex.dart';
-
-/// Tab that acts as a proxy to decide which widget to be displayed
-class LayoutExplorerTab extends StatefulWidget {
- const LayoutExplorerTab({super.key, required this.controller});
-
- final InspectorController controller;
-
- @override
- State<LayoutExplorerTab> createState() => _LayoutExplorerTabState();
-}
-
-class _LayoutExplorerTabState extends State<LayoutExplorerTab>
- with AutomaticKeepAliveClientMixin<LayoutExplorerTab>, AutoDisposeMixin {
- InspectorController get controller => widget.controller;
-
- RemoteDiagnosticsNode? get selected =>
- controller.selectedNode.value?.diagnostic;
-
- RemoteDiagnosticsNode? previousSelection;
-
- Widget rootWidget(RemoteDiagnosticsNode? node) {
- if (node != null && FlexLayoutExplorerWidget.shouldDisplay(node)) {
- return FlexLayoutExplorerWidget(controller);
- }
- if (node != null && BoxLayoutExplorerWidget.shouldDisplay(node)) {
- return BoxLayoutExplorerWidget(controller);
- }
- return Center(
- child: Text(
- node != null
- ? 'Currently, Layout Explorer only supports Box and Flex-based widgets.'
- : 'Select a widget to view its layout.',
- textAlign: TextAlign.center,
- overflow: TextOverflow.clip,
- ),
- );
- }
-
- void onSelectionChanged() {
- if (rootWidget(previousSelection).runtimeType !=
- rootWidget(selected).runtimeType) {
- setState(() => previousSelection = selected);
- }
- }
-
- @override
- void initState() {
- super.initState();
- addAutoDisposeListener(controller.selectedNode, onSelectionChanged);
- }
-
- @override
- Widget build(BuildContext context) {
- super.build(context);
- return rootWidget(selected);
- }
-
- @override
- bool get wantKeepAlive => true;
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/arrow.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/arrow.dart
deleted file mode 100644
index 7fcf002..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/arrow.dart
+++ /dev/null
@@ -1,280 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:math';
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:flutter/material.dart';
-
-const defaultArrowColor = Colors.white;
-const defaultArrowStrokeWidth = 2.0;
-const defaultDistanceToArrow = 4.0;
-
-enum ArrowType { up, left, right, down }
-
-Axis axis(ArrowType type) => (type == ArrowType.up || type == ArrowType.down)
- ? Axis.vertical
- : Axis.horizontal;
-
-/// Widget that draws a bidirectional arrow around another widget.
-///
-/// This widget is typically used to help draw diagrams.
-@immutable
-class ArrowWrapper extends StatelessWidget {
- ArrowWrapper.unidirectional({
- super.key,
- this.child,
- required ArrowType type,
- this.arrowColor = defaultArrowColor,
- double? arrowHeadSize,
- this.arrowStrokeWidth = defaultArrowStrokeWidth,
- this.childMarginFromArrow = defaultDistanceToArrow,
- }) : assert(childMarginFromArrow > 0.0),
- direction = axis(type),
- isBidirectional = false,
- startArrowType = type,
- endArrowType = type,
- arrowHeadSize = arrowHeadSize ?? defaultIconSize;
-
- const ArrowWrapper.bidirectional({
- super.key,
- this.child,
- required this.direction,
- this.arrowColor = defaultArrowColor,
- required this.arrowHeadSize,
- this.arrowStrokeWidth = defaultArrowStrokeWidth,
- this.childMarginFromArrow = defaultDistanceToArrow,
- }) : assert(arrowHeadSize >= 0.0),
- assert(childMarginFromArrow >= 0.0),
- isBidirectional = true,
- startArrowType = direction == Axis.horizontal
- ? ArrowType.left
- : ArrowType.up,
- endArrowType = direction == Axis.horizontal
- ? ArrowType.right
- : ArrowType.down;
-
- final Color arrowColor;
- final double arrowHeadSize;
- final double arrowStrokeWidth;
- final Widget? child;
-
- final Axis direction;
- final double childMarginFromArrow;
-
- final bool isBidirectional;
- final ArrowType startArrowType;
- final ArrowType endArrowType;
-
- double get verticalMarginFromArrow {
- if (child == null || direction == Axis.horizontal) return 0.0;
- return childMarginFromArrow;
- }
-
- double get horizontalMarginFromArrow {
- if (child == null || direction == Axis.vertical) return 0.0;
- return childMarginFromArrow;
- }
-
- @override
- Widget build(BuildContext context) {
- return Flex(
- direction: direction,
- children: <Widget>[
- Expanded(
- child: Container(
- margin: EdgeInsets.only(
- bottom: verticalMarginFromArrow,
- right: horizontalMarginFromArrow,
- ),
- child: ArrowWidget(
- color: arrowColor,
- headSize: arrowHeadSize,
- strokeWidth: arrowStrokeWidth,
- type: startArrowType,
- shouldDrawHead: isBidirectional
- ? true
- : (startArrowType == ArrowType.left ||
- startArrowType == ArrowType.up),
- ),
- ),
- ),
- if (child != null) child!,
- Expanded(
- child: Container(
- margin: EdgeInsets.only(
- top: verticalMarginFromArrow,
- left: horizontalMarginFromArrow,
- ),
- child: ArrowWidget(
- color: arrowColor,
- headSize: arrowHeadSize,
- strokeWidth: arrowStrokeWidth,
- type: endArrowType,
- shouldDrawHead: isBidirectional
- ? true
- : (endArrowType == ArrowType.right ||
- endArrowType == ArrowType.down),
- ),
- ),
- ),
- ],
- );
- }
-}
-
-/// Widget that draws a fully sized, centered, unidirectional arrow according to its constraints
-@immutable
-class ArrowWidget extends StatelessWidget {
- ArrowWidget({
- this.color = defaultArrowColor,
- required this.headSize,
- super.key,
- this.shouldDrawHead = true,
- this.strokeWidth = defaultArrowStrokeWidth,
- required this.type,
- }) : assert(headSize > 0.0),
- assert(strokeWidth > 0.0),
- _painter = _ArrowPainter(
- headSize: headSize,
- color: color,
- strokeWidth: strokeWidth,
- type: type,
- shouldDrawHead: shouldDrawHead,
- );
-
- final Color color;
-
- /// The arrow head is a Equilateral triangle
- final double headSize;
-
- final double strokeWidth;
-
- final ArrowType type;
-
- final CustomPainter _painter;
-
- final bool shouldDrawHead;
-
- @override
- Widget build(BuildContext context) {
- return CustomPaint(painter: _painter, child: Container());
- }
-}
-
-class _ArrowPainter extends CustomPainter {
- _ArrowPainter({
- required this.headSize,
- this.strokeWidth = defaultArrowStrokeWidth,
- this.color = defaultArrowColor,
- this.shouldDrawHead = true,
- required this.type,
- }) : // the height of an equilateral triangle
- headHeight = 0.5 * sqrt(3) * headSize;
-
- final Color color;
- final double headSize;
- final bool shouldDrawHead;
- final double strokeWidth;
- final ArrowType type;
-
- final double headHeight;
-
- bool headIsGreaterThanConstraint(Size size) {
- if (type == ArrowType.left || type == ArrowType.right) {
- return headHeight >= (size.width);
- }
- return headHeight >= (size.height);
- }
-
- @override
- bool shouldRepaint(CustomPainter oldDelegate) =>
- !(oldDelegate is _ArrowPainter &&
- headSize == oldDelegate.headSize &&
- strokeWidth == oldDelegate.strokeWidth &&
- color == oldDelegate.color &&
- type == oldDelegate.type);
-
- @override
- void paint(Canvas canvas, Size size) {
- final paint = Paint()
- ..color = color
- ..strokeWidth = strokeWidth;
-
- final originX = size.width / 2, originY = size.height / 2;
- Offset lineStartingPoint = Offset.zero;
- Offset lineEndingPoint = Offset.zero;
-
- if (!headIsGreaterThanConstraint(size) && shouldDrawHead) {
- Offset p1, p2, p3;
- final headSizeDividedBy2 = headSize / 2;
- switch (type) {
- case ArrowType.up:
- p1 = Offset(originX, 0);
- p2 = Offset(originX - headSizeDividedBy2, headHeight);
- p3 = Offset(originX + headSizeDividedBy2, headHeight);
- break;
- case ArrowType.left:
- p1 = Offset(0, originY);
- p2 = Offset(headHeight, originY - headSizeDividedBy2);
- p3 = Offset(headHeight, originY + headSizeDividedBy2);
- break;
- case ArrowType.right:
- final startingX = size.width - headHeight;
- p1 = Offset(size.width, originY);
- p2 = Offset(startingX, originY - headSizeDividedBy2);
- p3 = Offset(startingX, originY + headSizeDividedBy2);
- break;
- case ArrowType.down:
- final startingY = size.height - headHeight;
- p1 = Offset(originX, size.height);
- p2 = Offset(originX - headSizeDividedBy2, startingY);
- p3 = Offset(originX + headSizeDividedBy2, startingY);
- break;
- }
- final path = Path()
- ..moveTo(p1.dx, p1.dy)
- ..lineTo(p2.dx, p2.dy)
- ..lineTo(p3.dx, p3.dy)
- ..close();
- canvas.drawPath(path, paint);
-
- switch (type) {
- case ArrowType.up:
- lineStartingPoint = Offset(originX, headHeight);
- lineEndingPoint = Offset(originX, size.height);
- break;
- case ArrowType.left:
- lineStartingPoint = Offset(headHeight, originY);
- lineEndingPoint = Offset(size.width, originY);
- break;
- case ArrowType.right:
- final arrowHeadStartingX = size.width - headHeight;
- lineStartingPoint = Offset(0, originY);
- lineEndingPoint = Offset(arrowHeadStartingX, originY);
- break;
- case ArrowType.down:
- final headStartingY = size.height - headHeight;
- lineStartingPoint = Offset(originX, 0);
- lineEndingPoint = Offset(originX, headStartingY);
- break;
- }
- } else {
- // draw full line
- switch (type) {
- case ArrowType.up:
- case ArrowType.down:
- lineStartingPoint = Offset(originX, 0);
- lineEndingPoint = Offset(originX, size.height);
- break;
- case ArrowType.left:
- case ArrowType.right:
- lineStartingPoint = Offset(0, originY);
- lineEndingPoint = Offset(size.width, originY);
- break;
- }
- }
- canvas.drawLine(lineStartingPoint, lineEndingPoint, paint);
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/dimension.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/dimension.dart
deleted file mode 100644
index cc5e40c..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/dimension.dart
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:flutter/material.dart';
-
-import 'theme.dart';
-
-/// Text widget for displaying width / height.
-Widget dimensionDescription(
- TextSpan description,
- bool overflow,
- ColorScheme colorScheme,
-) {
- final text = Text.rich(
- description,
- textAlign: TextAlign.center,
- style: overflow
- ? overflowingDimensionIndicatorTextStyle(colorScheme)
- : dimensionIndicatorTextStyle,
- overflow: TextOverflow.ellipsis,
- );
- if (overflow) {
- return Container(
- padding: const EdgeInsets.symmetric(
- vertical: minPadding,
- horizontal: overflowTextHorizontalPadding,
- ),
- decoration: BoxDecoration(
- color: colorScheme.overflowBackgroundColor,
- borderRadius: BorderRadius.circular(4.0),
- ),
- child: Center(child: text),
- );
- }
- return text;
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/free_space.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/free_space.dart
deleted file mode 100644
index d515ed5..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/free_space.dart
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:flutter/material.dart';
-
-import '../../../../shared/primitives/utils.dart';
-import '../../inspector_data_models.dart';
-import 'arrow.dart';
-import 'dimension.dart';
-import 'theme.dart';
-
-class FreeSpaceVisualizerWidget extends StatelessWidget {
- const FreeSpaceVisualizerWidget(this.renderProperties, {super.key});
-
- final RenderProperties renderProperties;
-
- @override
- Widget build(BuildContext context) {
- final colorScheme = Theme.of(context).colorScheme;
- final heightDescription =
- 'h=${toStringAsFixed(renderProperties.realHeight)}';
- final widthDescription = 'w=${toStringAsFixed(renderProperties.realWidth)}';
- final showWidth =
- renderProperties.realWidth != renderProperties.layoutProperties.width;
- final widthWidget = Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Flexible(
- child: dimensionDescription(
- TextSpan(text: widthDescription),
- false,
- colorScheme,
- ),
- ),
- Container(
- margin: const EdgeInsets.symmetric(vertical: arrowMargin),
- child: const ArrowWrapper.bidirectional(
- arrowColor: widthIndicatorColor,
- direction: Axis.horizontal,
- arrowHeadSize: arrowHeadSize,
- ),
- ),
- ],
- );
- final heightWidget = SizedBox(
- width: heightOnlyIndicatorSize,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Flexible(
- child: dimensionDescription(
- TextSpan(text: heightDescription),
- false,
- colorScheme,
- ),
- ),
- Container(
- margin: const EdgeInsets.symmetric(horizontal: arrowMargin),
- child: const ArrowWrapper.bidirectional(
- arrowColor: heightIndicatorColor,
- direction: Axis.vertical,
- arrowHeadSize: arrowHeadSize,
- childMarginFromArrow: 0.0,
- ),
- ),
- ],
- ),
- );
- return Positioned(
- top: renderProperties.offset.dy,
- left: renderProperties.offset.dx,
- child: SizedBox(
- width: renderProperties.width,
- height: renderProperties.height,
- child: DevToolsTooltip(
- message: '$widthDescription\n$heightDescription',
- child: showWidth ? widthWidget : heightWidget,
- ),
- ),
- );
- }
-}
-
-class PaddingVisualizerWidget extends StatelessWidget {
- const PaddingVisualizerWidget(
- this.renderProperties, {
- required this.horizontal,
- super.key,
- });
-
- final RenderProperties renderProperties;
- final bool horizontal;
-
- @override
- Widget build(BuildContext context) {
- final colorScheme = Theme.of(context).colorScheme;
- final heightDescription =
- 'h=${toStringAsFixed(renderProperties.realHeight)}';
- final widthDescription = 'w=${toStringAsFixed(renderProperties.realWidth)}';
- final widthWidget = Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Flexible(
- child: dimensionDescription(
- TextSpan(text: widthDescription),
- false,
- colorScheme,
- ),
- ),
- Container(
- margin: const EdgeInsets.symmetric(vertical: arrowMargin),
- child: const ArrowWrapper.bidirectional(
- arrowColor: widthIndicatorColor,
- direction: Axis.horizontal,
- arrowHeadSize: arrowHeadSize,
- ),
- ),
- ],
- );
- final heightWidget = SizedBox(
- width: heightOnlyIndicatorSize,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Flexible(
- child: dimensionDescription(
- TextSpan(text: heightDescription),
- false,
- colorScheme,
- ),
- ),
- Container(
- margin: const EdgeInsets.symmetric(horizontal: arrowMargin),
- child: const ArrowWrapper.bidirectional(
- arrowColor: heightIndicatorColor,
- direction: Axis.vertical,
- arrowHeadSize: arrowHeadSize,
- childMarginFromArrow: 0.0,
- ),
- ),
- ],
- ),
- );
- return Positioned(
- top: renderProperties.offset.dy,
- left: renderProperties.offset.dx,
- child: SizedBox(
- width: safePositiveDouble(renderProperties.width),
- height: safePositiveDouble(renderProperties.height),
- child: horizontal ? widthWidget : heightWidget,
- ),
- );
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/layout_explorer_widget.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/layout_explorer_widget.dart
deleted file mode 100644
index ac4229c..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/layout_explorer_widget.dart
+++ /dev/null
@@ -1,296 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:async';
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
-
-import '../../../../shared/diagnostics/diagnostics_node.dart';
-import '../../../../shared/diagnostics/inspector_service.dart';
-import '../../../../shared/globals.dart';
-import '../../../../shared/primitives/utils.dart';
-import '../../inspector_controller.dart';
-import '../../inspector_data_models.dart';
-import 'utils.dart';
-
-const maxRequestsPerSecond = 3.0;
-
-/// Base class for layout widgets for all widget types.
-abstract class LayoutExplorerWidget extends StatefulWidget {
- const LayoutExplorerWidget(this.inspectorController, {super.key});
-
- final InspectorController inspectorController;
-}
-
-/// Base class for state objects for layout widgets for all widget types.
-abstract class LayoutExplorerWidgetState<
- W extends LayoutExplorerWidget,
- L extends LayoutProperties
->
- extends State<W>
- with TickerProviderStateMixin
- implements InspectorServiceClient {
- LayoutExplorerWidgetState() {
- _onSelectionChangedCallback = onSelectionChanged;
- }
-
- late AnimationController entranceController;
- late CurvedAnimation entranceCurve;
- late AnimationController changeController;
-
- late CurvedAnimation changeAnimation;
-
- L? _previousProperties;
-
- L? _properties;
-
- InspectorObjectGroupManager? objectGroupManager;
-
- AnimatedLayoutProperties<L>? get animatedProperties => _animatedProperties;
- AnimatedLayoutProperties<L>? _animatedProperties;
-
- L? get properties =>
- _previousProperties ?? _animatedProperties as L? ?? _properties;
-
- RemoteDiagnosticsNode? get selectedNode =>
- inspectorController.selectedNode.value?.diagnostic;
-
- InspectorController get inspectorController => widget.inspectorController;
-
- InspectorService? get inspectorService =>
- serviceConnection.inspectorService as InspectorService?;
-
- late RateLimiter rateLimiter;
-
- late Future<void> Function() _onSelectionChangedCallback;
-
- Future<void> onSelectionChanged() async {
- if (!mounted) return;
- final selectedNodeLocal = selectedNode;
- if (selectedNodeLocal == null) return;
- if (!shouldDisplay(selectedNodeLocal)) return;
- final prevRootId = id(_properties?.node);
- final newRootId = id(getRoot(selectedNodeLocal));
- final shouldFetch = prevRootId != newRootId;
- if (shouldFetch) {
- _dirty = false;
- final newSelection = await fetchLayoutProperties();
- _setProperties(newSelection);
- } else {
- updateHighlighted(_properties);
- }
- }
-
- /// Whether this layout explorer can work with this kind of node.
- bool shouldDisplay(RemoteDiagnosticsNode node);
-
- List<LayoutProperties> get children => properties!.displayChildren;
-
- LayoutProperties? highlighted;
-
- /// Returns the root widget to show.
- ///
- /// For cases such as Flex widgets or in the future ListView widgets we may
- /// want to show the layout for all widgets under a root that is the parent
- /// of the current widget.
- RemoteDiagnosticsNode? getRoot(RemoteDiagnosticsNode? node);
-
- Future<L?> fetchLayoutProperties() async {
- objectGroupManager?.cancelNext();
- final manager = objectGroupManager!;
- final nextObjectGroup = manager.next;
- final node = await nextObjectGroup.getLayoutExplorerNode(
- getRoot(selectedNode),
- );
- if (node == null || node.renderObject == null) return null;
-
- if (!nextObjectGroup.disposed) {
- assert(manager.next == nextObjectGroup);
- manager.promoteNext();
- }
- return computeLayoutProperties(node);
- }
-
- L computeLayoutProperties(RemoteDiagnosticsNode node);
-
- AnimatedLayoutProperties<L> computeAnimatedProperties(L nextProperties);
-
- void updateHighlighted(L? newProperties);
-
- String? id(RemoteDiagnosticsNode? node) => node?.valueRef.id;
-
- void _registerInspectorControllerService() {
- inspectorController.selectedNode.addListener(_onSelectionChangedCallback);
- inspectorService?.addClient(this);
- }
-
- void _unregisterInspectorControllerService() {
- inspectorController.selectedNode.removeListener(
- _onSelectionChangedCallback,
- );
- inspectorService?.removeClient(this);
- }
-
- @override
- void initState() {
- super.initState();
- rateLimiter = RateLimiter(maxRequestsPerSecond, refresh);
- _registerInspectorControllerService();
- _initAnimationStates();
- _updateObjectGroupManager();
- // TODO(jacobr): put inspector controller in Controllers and
- // update on didChangeDependencies.
- _animateProperties();
- }
-
- @override
- void didUpdateWidget(W oldWidget) {
- super.didUpdateWidget(oldWidget);
- _updateObjectGroupManager();
- _animateProperties();
- if (oldWidget.inspectorController != inspectorController) {
- _unregisterInspectorControllerService();
- _registerInspectorControllerService();
- }
- }
-
- @override
- void dispose() {
- entranceController.dispose();
- changeController.dispose();
- _unregisterInspectorControllerService();
- super.dispose();
- }
-
- void _animateProperties() {
- if (_animatedProperties != null) {
- unawaited(changeController.forward());
- }
- if (_previousProperties != null) {
- unawaited(entranceController.reverse());
- } else {
- unawaited(entranceController.forward());
- }
- }
-
- // update selected widget in the device without triggering selection listener event.
- // this is required so that we don't change focus
- // when tapping on a child is also Flex-based widget.
- Future<void> setSelectionInspector(RemoteDiagnosticsNode node) async {
- final service = node.objectGroupApi;
- if (service != null && service is ObjectGroup) {
- await service.setSelectionInspector(node.valueRef, false);
- }
- }
-
- // update selected widget and trigger selection listener event to change focus.
- void refreshSelection(RemoteDiagnosticsNode node) {
- inspectorController.refreshSelection(node, node, true);
- }
-
- Future<void> onTap(LayoutProperties properties) async {
- setState(() => highlighted = properties);
- await setSelectionInspector(properties.node);
- }
-
- void onDoubleTap(LayoutProperties properties) {
- refreshSelection(properties.node);
- }
-
- Future<void> refresh() async {
- if (!_dirty) return;
- _dirty = false;
- final updatedProperties = await fetchLayoutProperties();
- if (updatedProperties != null) {
- _changeProperties(updatedProperties);
- }
- }
-
- void _changeProperties(L nextProperties) {
- if (!mounted) return;
- updateHighlighted(nextProperties);
- setState(() {
- _animatedProperties = computeAnimatedProperties(nextProperties);
- unawaited(changeController.forward(from: 0.0));
- });
- }
-
- void _setProperties(L? newProperties) {
- if (!mounted) return;
- updateHighlighted(newProperties);
- if (_properties == newProperties) {
- return;
- }
- setState(() {
- _previousProperties ??= _properties;
- _properties = newProperties;
- });
- _animateProperties();
- }
-
- void _initAnimationStates() {
- entranceController = longAnimationController(this)
- ..addStatusListener((status) {
- if (status == AnimationStatus.dismissed) {
- setState(() {
- _previousProperties = null;
- unawaited(entranceController.forward());
- });
- }
- });
- entranceCurve = defaultCurvedAnimation(entranceController);
- changeController = longAnimationController(this)
- ..addStatusListener((status) {
- if (status == AnimationStatus.completed) {
- setState(() {
- _properties = _animatedProperties!.end;
- _animatedProperties = null;
- changeController.value = 0.0;
- });
- }
- });
- changeAnimation = defaultCurvedAnimation(changeController);
- }
-
- void _updateObjectGroupManager() {
- final service = serviceConnection.inspectorService;
- if (service != objectGroupManager?.inspectorService) {
- objectGroupManager = InspectorObjectGroupManager(
- service as InspectorService,
- 'flex-layout',
- );
- }
- unawaited(onSelectionChanged());
- }
-
- bool _dirty = false;
-
- @override
- void onFlutterFrame() {
- if (!mounted) return;
- if (_dirty) {
- rateLimiter.scheduleRequest();
- }
- }
-
- // TODO(albertusangga): Investigate why onForceRefresh is not getting called.
- @override
- Future<void> onForceRefresh() async {
- final properties = await fetchLayoutProperties();
- if (properties != null) {
- _setProperties(properties);
- }
- }
-
- /// Currently this is not working so we should listen to controller selection event instead.
- @override
- Future<void> onInspectorSelectionChanged() async {}
-
- /// Register callback to be executed once Flutter frame is ready.
- void markAsDirty() {
- _dirty = true;
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/overflow_indicator_painter.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/overflow_indicator_painter.dart
deleted file mode 100644
index 298b778..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/overflow_indicator_painter.dart
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:ui' as ui;
-
-import 'package:flutter/rendering.dart';
-
-import '../../inspector_data_models.dart';
-
-/// CustomPainter for drawing [DebugOverflowIndicatorMixin]'s patterned background.
-/// Draws overflow pattern on the [OverflowSide] of the widget.
-///
-/// [DebugOverflowIndicatorMixin] can not be reused here
-/// because it is a mixin on RenderObject and requires real overflows on the widget.
-///
-/// If [side] is set to [OverflowSide.right],
-/// the pattern will occupy the whole height
-/// and the width will be the given [size].
-///
-/// If [side] is set to [OverflowSide.bottom],
-/// the pattern will occupy the whole width
-/// and the height will be the given [size].
-///
-/// See also:
-/// * [DebugOverflowIndicatorMixin]
-class OverflowIndicatorPainter extends CustomPainter {
- const OverflowIndicatorPainter(this.side, this.size);
-
- final OverflowSide side;
- final double size;
-
- /// These static variables are taken from [DebugOverflowIndicatorMixin]
- /// since all of them are private.
- static const black = Color(0xBF000000);
- static const yellow = Color(0xBFFFFF00);
- static final indicatorPaint = Paint()
- ..shader = ui.Gradient.linear(
- const Offset(0.0, 0.0),
- const Offset(10.0, 10.0),
- <Color>[black, yellow, yellow, black],
- <double>[0.25, 0.25, 0.75, 0.75],
- TileMode.repeated,
- );
-
- @override
- void paint(Canvas canvas, Size size) {
- final bottomOverflow = OverflowSide.bottom == side;
- final width = bottomOverflow ? size.width : this.size;
- final height = !bottomOverflow ? size.height : this.size;
-
- final left = bottomOverflow ? 0.0 : size.width - width;
- final top = side == OverflowSide.right ? 0.0 : size.height - height;
- final rect = Rect.fromLTWH(left, top, width, height);
- canvas.drawRect(rect, indicatorPaint);
- }
-
- @override
- bool shouldRepaint(CustomPainter oldDelegate) {
- return oldDelegate is OverflowIndicatorPainter &&
- (side != oldDelegate.side || size != oldDelegate.size);
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/theme.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/theme.dart
deleted file mode 100644
index 974a0a5..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/theme.dart
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2021 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:flutter/material.dart';
-
-const margin = 6.0;
-
-const arrowHeadSize = 8.0;
-const arrowMargin = 4.0;
-const arrowStrokeWidth = 1.5;
-
-/// Hardcoded sizes for scaling the flex children widget properly.
-const minRenderWidth = 250.0;
-const minRenderHeight = 250.0;
-
-const minPadding = 2.0;
-const overflowTextHorizontalPadding = 8.0;
-
-/// The size to shrink a widget by when animating it in.
-const entranceMargin = 50.0;
-
-const defaultMaxRenderWidth = 400.0;
-const defaultMaxRenderHeight = 400.0;
-
-const widgetTitleMaxWidthPercentage = 0.75;
-
-/// Hardcoded arrow size respective to its cross axis (because it's unconstrained).
-const heightAndConstraintIndicatorSize = 48.0;
-const widthAndConstraintIndicatorSize = 56.0;
-const mainAxisArrowIndicatorSize = 48.0;
-const crossAxisArrowIndicatorSize = 48.0;
-
-const heightOnlyIndicatorSize = 72.0;
-
-/// Minimum size to display width/height inside the arrow
-const minWidthToDisplayWidthInsideArrow = 200.0;
-const minHeightToDisplayHeightInsideArrow = 200.0;
-
-const smallTextScaleFactor = 0.8;
-
-/// Height for limiting asset image (selected one in the drop down).
-const axisAlignmentAssetImageHeight = 24.0;
-
-const minHeightToAllowTruncating = 375.0;
-const minWidthToAllowTruncating = 375.0;
-
-// Story of Layout colors
-const mainAxisLightColor = Color(0xff2c5daa);
-const mainAxisDarkColor = Color(0xff2c5daa);
-
-const textColor = Color(0xff55767f);
-const emphasizedTextColor = Color(0xff009aca);
-
-const crossAxisLightColor = Color(0xff8ac652);
-const crossAxisDarkColor = Color(0xff8ac652);
-
-const mainAxisTextColorLight = Color(0xFF1375bc);
-const mainAxisTextColorDark = Color(0xFF1375bc);
-
-const crossAxisTextColorLight = Color(0xFF66672C);
-const crossAxisTextColorsDark = Color(0xFFB3D25A);
-
-const overflowBackgroundColorDark = Color(0xFFB00020);
-const overflowBackgroundColorLight = Color(0xFFB00020);
-
-const overflowTextColorDark = Color(0xfff5846b);
-const overflowTextColorLight = Color(0xffdea089);
-
-const backgroundColorSelectedDark = Color(
- 0x4d474747,
-); // TODO(jacobr): we would like Color(0x4dedeeef) but that makes the background show through.
-const backgroundColorSelectedLight = Color(0x4dedeeef);
-
-extension LayoutExplorerColorScheme on ColorScheme {
- Color get mainAxisColor => isLight ? mainAxisLightColor : mainAxisDarkColor;
-
- Color get widgetNameColor => isLight ? Colors.white : Colors.black;
-
- Color get crossAxisColor =>
- isLight ? crossAxisLightColor : crossAxisDarkColor;
-
- Color get mainAxisTextColor =>
- isLight ? mainAxisTextColorLight : mainAxisTextColorDark;
-
- Color get crossAxisTextColor =>
- isLight ? crossAxisTextColorLight : crossAxisTextColorsDark;
-
- Color get overflowBackgroundColor =>
- isLight ? overflowBackgroundColorLight : overflowBackgroundColorDark;
-
- Color get overflowTextColor =>
- isLight ? overflowTextColorLight : overflowTextColorDark;
-
- Color get backgroundColorSelected =>
- isLight ? backgroundColorSelectedLight : backgroundColorSelectedDark;
-
- Color get unconstrainedColor =>
- isLight ? unconstrainedLightColor : unconstrainedDarkColor;
-}
-
-const backgroundColorDark = Color(0xff30302f);
-const backgroundColorLight = Color(0xffffffff);
-
-const unconstrainedDarkColor = Color(0xffdea089);
-const unconstrainedLightColor = Color(0xfff5846b);
-
-const widthIndicatorColor = textColor;
-const heightIndicatorColor = textColor;
-
-const negativeSpaceDarkAssetName =
- 'assets/img/layout_explorer/negative_space_dark.png';
-const negativeSpaceLightAssetName =
- 'assets/img/layout_explorer/negative_space_light.png';
-
-const dimensionIndicatorTextStyle = TextStyle(
- height: 1.0,
- letterSpacing: 1.1,
- color: emphasizedTextColor,
- fontSize: defaultFontSize,
-);
-
-TextStyle overflowingDimensionIndicatorTextStyle(ColorScheme colorScheme) =>
- dimensionIndicatorTextStyle.merge(
- TextStyle(
- fontWeight: FontWeight.bold,
- color: colorScheme.overflowTextColor,
- ),
- );
-
-Widget buildUnderline() {
- return Container(
- height: 1.0,
- decoration: const BoxDecoration(
- border: Border(bottom: BorderSide(color: textColor, width: 0.0)),
- ),
- );
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/utils.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/utils.dart
deleted file mode 100644
index 798ea7a..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/utils.dart
+++ /dev/null
@@ -1,439 +0,0 @@
-// Copyright 2021 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:ui';
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:flutter/material.dart';
-
-import '../../../../shared/diagnostics/diagnostics_node.dart';
-import '../../../../shared/primitives/utils.dart';
-import '../../inspector_data_models.dart';
-import 'overflow_indicator_painter.dart';
-import 'theme.dart';
-import 'widgets_theme.dart';
-
-/// A widget for positioning sized widgets that follows layout as follows:
-/// | top |
-/// left | center | right
-/// | bottom |
-@immutable
-class BorderLayout extends StatelessWidget {
- const BorderLayout({
- super.key,
- this.left,
- this.leftWidth,
- this.top,
- this.topHeight,
- this.right,
- this.rightWidth,
- this.bottom,
- this.bottomHeight,
- this.center,
- }) : assert(
- left != null ||
- top != null ||
- right != null ||
- bottom != null ||
- center != null,
- );
-
- final Widget? center;
- final Widget? top;
- final Widget? left;
- final Widget? right;
- final Widget? bottom;
-
- final double? leftWidth;
- final double? rightWidth;
- final double? topHeight;
- final double? bottomHeight;
-
- @override
- Widget build(BuildContext context) {
- return Stack(
- children: <Widget>[
- Center(
- child: Container(
- margin: EdgeInsets.only(
- left: leftWidth ?? 0,
- right: rightWidth ?? 0,
- top: topHeight ?? 0,
- bottom: bottomHeight ?? 0,
- ),
- child: center,
- ),
- ),
- if (top != null)
- Align(
- alignment: Alignment.topCenter,
- child: SizedBox(height: topHeight, child: top),
- ),
- if (left != null)
- Align(
- alignment: Alignment.centerLeft,
- child: SizedBox(width: leftWidth, child: left),
- ),
- if (right != null)
- Align(
- alignment: Alignment.centerRight,
- child: SizedBox(width: rightWidth, child: right),
- ),
- if (bottom != null)
- Align(
- alignment: Alignment.bottomCenter,
- child: SizedBox(height: bottomHeight, child: bottom),
- ),
- ],
- );
- }
-}
-
-@immutable
-class Truncateable extends StatelessWidget {
- const Truncateable({super.key, this.truncate = false, required this.child});
-
- final Widget child;
- final bool truncate;
-
- @override
- Widget build(BuildContext context) {
- return Flexible(flex: truncate ? 1 : 0, child: child);
- }
-}
-
-/// Widget that draws bounding box with the title (usually widget name) in its
-/// top left.
-///
-/// * [hint] is an optional widget to be placed in the top right of the box.
-/// * [child] is an optional widget to be placed in the center of the box.
-class WidgetVisualizer extends StatelessWidget {
- const WidgetVisualizer({
- super.key,
- required this.title,
- this.hint,
- required this.isSelected,
- required this.layoutProperties,
- required this.child,
- this.overflowSide,
- this.largeTitle = false,
- });
-
- final LayoutProperties layoutProperties;
- final String title;
- final Widget child;
- final Widget? hint;
- final bool isSelected;
- final bool largeTitle;
-
- final OverflowSide? overflowSide;
-
- static const _overflowIndicatorSize = 20.0;
- static const _borderUnselectedWidth = 1.0;
- static const _borderSelectedWidth = 3.0;
- static const _selectedPadding = 4.0;
-
- bool get drawOverflow => overflowSide != null;
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final colorScheme = theme.colorScheme;
- final properties = layoutProperties;
- final borderColor = WidgetTheme.fromName(properties.node.description).color;
- final boxAdjust = isSelected ? _selectedPadding : 0.0;
-
- return LayoutBuilder(
- builder: (context, constraints) {
- final hintLocal = hint;
- return OverflowBox(
- minWidth: constraints.minWidth + boxAdjust,
- maxWidth: constraints.maxWidth + boxAdjust,
- maxHeight: constraints.maxHeight + boxAdjust,
- minHeight: constraints.minHeight + boxAdjust,
- child: Container(
- decoration: BoxDecoration(
- border: Border.all(
- color: borderColor,
- width: isSelected
- ? _borderSelectedWidth
- : _borderUnselectedWidth,
- ),
- color: isSelected
- ? theme.canvasColor.brighten()
- : theme.canvasColor.darken(),
- boxShadow: isSelected
- ? [
- BoxShadow(
- color: Colors.black.withAlpha(255 ~/ 2),
- blurRadius: 20,
- ),
- ]
- : null,
- ),
- child: Stack(
- children: [
- if (drawOverflow)
- Positioned.fill(
- child: CustomPaint(
- painter: OverflowIndicatorPainter(
- overflowSide!,
- _overflowIndicatorSize,
- ),
- ),
- ),
- Container(
- margin: EdgeInsets.only(
- right: overflowSide == OverflowSide.right
- ? _overflowIndicatorSize
- : 0.0,
- bottom: overflowSide == OverflowSide.bottom
- ? _overflowIndicatorSize
- : 0.0,
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- mainAxisSize: MainAxisSize.min,
- children: [
- IntrinsicHeight(
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Flexible(
- child: Container(
- constraints: BoxConstraints(
- maxWidth: largeTitle
- ? defaultMaxRenderWidth
- : minRenderWidth *
- widgetTitleMaxWidthPercentage,
- ),
- decoration: BoxDecoration(color: borderColor),
- padding: const EdgeInsets.all(4.0),
- child: Center(
- child: Text(
- title,
- style: theme.regularTextStyleWithColor(
- colorScheme.widgetNameColor,
- ),
- overflow: TextOverflow.ellipsis,
- ),
- ),
- ),
- ),
- if (hintLocal != null) Flexible(child: hintLocal),
- ],
- ),
- ),
- Expanded(child: child),
- ],
- ),
- ),
- ],
- ),
- ),
- );
- },
- );
- }
-}
-
-class AnimatedLayoutProperties<T extends LayoutProperties>
- implements LayoutProperties {
- AnimatedLayoutProperties(this.begin, this.end, this.animation)
- : assert(begin.children.length == end.children.length),
- _children = [
- for (var i = 0; i < begin.children.length; i++)
- AnimatedLayoutProperties(
- begin.children[i],
- end.children[i],
- animation,
- ),
- ];
-
- final T begin;
- final T end;
- final Animation<double> animation;
- final List<LayoutProperties> _children;
-
- @override
- LayoutProperties? get parent => end.parent;
-
- @override
- set parent(LayoutProperties? parent) {
- end.parent = parent;
- }
-
- @override
- List<LayoutProperties> get children {
- return _children;
- }
-
- List<double> _lerpList(List<double> l1, List<double> l2) {
- assert(l1.length == l2.length);
- if (l1.isEmpty) return [];
- final animationLocal = animation;
- return [
- for (var i = 0; i < children.length; i++)
- lerpDouble(l1[i], l2[i], animationLocal.value)!,
- ];
- }
-
- @override
- List<double> childrenDimensions(Axis axis) {
- final beginDimensions = begin.childrenDimensions(axis);
- final endDimensions = end.childrenDimensions(axis);
- return _lerpList(beginDimensions, endDimensions);
- }
-
- @override
- List<double> get childrenHeights =>
- _lerpList(begin.childrenHeights, end.childrenHeights);
-
- @override
- List<double> get childrenWidths =>
- _lerpList(begin.childrenWidths, end.childrenWidths);
-
- @override
- BoxConstraints? get constraints {
- try {
- return BoxConstraints.lerp(
- begin.constraints,
- end.constraints,
- animation.value,
- );
- } catch (e) {
- return end.constraints;
- }
- }
-
- @override
- String describeWidthConstraints() {
- final constraintsLocal = constraints!;
- return constraintsLocal.hasBoundedWidth
- ? LayoutProperties.describeAxis(
- constraintsLocal.minWidth,
- constraintsLocal.maxWidth,
- 'w',
- )
- : 'w=unconstrained';
- }
-
- @override
- String describeHeightConstraints() {
- final constraintsLocal = constraints!;
- return constraintsLocal.hasBoundedHeight
- ? LayoutProperties.describeAxis(
- constraintsLocal.minHeight,
- constraintsLocal.maxHeight,
- 'h',
- )
- : 'h=unconstrained';
- }
-
- @override
- String describeWidth() => 'w=${toStringAsFixed(size.width)}';
-
- @override
- String describeHeight() => 'h=${toStringAsFixed(size.height)}';
-
- @override
- String? get description => end.description;
-
- @override
- double dimension(Axis axis) {
- return lerpDouble(
- begin.dimension(axis),
- end.dimension(axis),
- animation.value,
- )!;
- }
-
- @override
- num? get flexFactor =>
- lerpDouble(begin.flexFactor, end.flexFactor, animation.value);
-
- @override
- bool get hasChildren => children.isNotEmpty;
-
- @override
- double get height => size.height;
-
- @override
- bool get isFlex => begin.isFlex && end.isFlex;
-
- @override
- RemoteDiagnosticsNode get node => end.node;
-
- @override
- Size get size {
- return Size.lerp(begin.size, end.size, animation.value)!;
- }
-
- @override
- int get totalChildren => end.totalChildren;
-
- @override
- double get width => size.width;
-
- @override
- bool get hasFlexFactor => begin.hasFlexFactor && end.hasFlexFactor;
-
- @override
- LayoutProperties copyWith({
- List<LayoutProperties>? children,
- BoxConstraints? constraints,
- String? description,
- int? flexFactor,
- FlexFit? flexFit,
- bool? isFlex,
- Size? size,
- }) {
- return LayoutProperties.values(
- node: node,
- children: children ?? this.children,
- constraints: constraints ?? this.constraints,
- description: description ?? this.description,
- flexFactor: flexFactor ?? this.flexFactor,
- flexFit: flexFit ?? this.flexFit,
- isFlex: isFlex ?? this.isFlex,
- size: size ?? this.size,
- );
- }
-
- @override
- bool get isOverflowWidth => end.isOverflowWidth;
-
- @override
- bool get isOverflowHeight => end.isOverflowHeight;
-
- @override
- FlexFit? get flexFit => end.flexFit;
-
- @override
- List<LayoutProperties> get displayChildren => end.displayChildren;
-}
-
-class LayoutExplorerBackground extends StatelessWidget {
- const LayoutExplorerBackground({super.key, required this.colorScheme});
-
- final ColorScheme colorScheme;
-
- @override
- Widget build(BuildContext context) {
- return Positioned.fill(
- child: Opacity(
- opacity: colorScheme.isLight ? 0.3 : 0.2,
- child: Image.asset(
- colorScheme.isLight
- ? negativeSpaceLightAssetName
- : negativeSpaceDarkAssetName,
- fit: BoxFit.none,
- repeat: ImageRepeat.repeat,
- alignment: Alignment.topLeft,
- ),
- ),
- );
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/widget_constraints.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/widget_constraints.dart
deleted file mode 100644
index 45de404..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/widget_constraints.dart
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:flutter/material.dart';
-
-import '../../../../shared/primitives/math_utils.dart';
-import '../../../../shared/primitives/utils.dart';
-import '../../inspector_data_models.dart';
-import 'arrow.dart';
-import 'dimension.dart';
-import 'theme.dart';
-import 'utils.dart';
-
-class VisualizeWidthAndHeightWithConstraints extends StatelessWidget {
- const VisualizeWidthAndHeightWithConstraints({
- super.key,
- required this.properties,
- double? arrowHeadSize,
- required this.child,
- this.warnIfUnconstrained = true,
- }) : arrowHeadSize = arrowHeadSize ?? defaultIconSize;
-
- final Widget child;
- final LayoutProperties properties;
- final double arrowHeadSize;
- final bool warnIfUnconstrained;
-
- @override
- Widget build(BuildContext context) {
- final propertiesLocal = properties;
- final showChildrenWidthsSum =
- propertiesLocal is FlexLayoutProperties &&
- propertiesLocal.isOverflowWidth;
- const bottomHeight = widthAndConstraintIndicatorSize;
- const rightWidth = heightAndConstraintIndicatorSize;
- final colorScheme = Theme.of(context).colorScheme;
-
- final showOverflowHeight =
- properties is FlexLayoutProperties && propertiesLocal.isOverflowHeight;
- final heightDescription = RotatedBox(
- quarterTurns: 1,
- child: dimensionDescription(
- TextSpan(
- children: [
- TextSpan(text: propertiesLocal.describeHeight()),
- if (propertiesLocal.constraints != null) ...[
- if (!showOverflowHeight) const TextSpan(text: '\n'),
- TextSpan(
- text: ' (${propertiesLocal.describeHeightConstraints()})',
- style:
- propertiesLocal.constraints!.hasBoundedHeight ||
- !warnIfUnconstrained
- ? null
- : TextStyle(color: colorScheme.unconstrainedColor),
- ),
- ],
- if (showOverflowHeight)
- TextSpan(
- text:
- '\nchildren take: '
- '${toStringAsFixed(sum(propertiesLocal.childrenHeights))}',
- ),
- ],
- ),
- propertiesLocal.isOverflowHeight,
- colorScheme,
- ),
- );
- final right = Container(
- margin: const EdgeInsets.only(
- top: margin,
- left: margin,
- bottom: bottomHeight,
- right: minPadding, // custom margin for not sticking to the corner
- ),
- child: LayoutBuilder(
- builder: (context, constraints) {
- final displayHeightOutsideArrow =
- constraints.maxHeight < minHeightToDisplayHeightInsideArrow;
- return Row(
- children: [
- Truncateable(
- truncate: !displayHeightOutsideArrow,
- child: Container(
- margin: const EdgeInsets.symmetric(horizontal: arrowMargin),
- child: ArrowWrapper.bidirectional(
- arrowColor: heightIndicatorColor,
- arrowStrokeWidth: arrowStrokeWidth,
- arrowHeadSize: arrowHeadSize,
- direction: Axis.vertical,
- child: displayHeightOutsideArrow ? null : heightDescription,
- ),
- ),
- ),
- if (displayHeightOutsideArrow) Flexible(child: heightDescription),
- ],
- );
- },
- ),
- );
-
- final widthDescription = dimensionDescription(
- TextSpan(
- children: [
- TextSpan(text: '${propertiesLocal.describeWidth()}; '),
- if (propertiesLocal.constraints != null) ...[
- if (!showChildrenWidthsSum) const TextSpan(text: '\n'),
- TextSpan(
- text: '(${propertiesLocal.describeWidthConstraints()})',
- style:
- propertiesLocal.constraints!.hasBoundedWidth ||
- !warnIfUnconstrained
- ? null
- : TextStyle(color: colorScheme.unconstrainedColor),
- ),
- ],
- if (showChildrenWidthsSum)
- TextSpan(
- text:
- '\nchildren take '
- '${toStringAsFixed(sum(propertiesLocal.childrenWidths))}',
- ),
- ],
- ),
- propertiesLocal.isOverflowWidth,
- colorScheme,
- );
- final bottom = Container(
- margin: const EdgeInsets.only(
- top: margin,
- left: margin,
- right: rightWidth,
- bottom: minPadding,
- ),
- child: LayoutBuilder(
- builder: (context, constraints) {
- final maxWidth = constraints.maxWidth;
- final displayWidthOutsideArrow =
- maxWidth < minWidthToDisplayWidthInsideArrow;
- return Column(
- children: [
- Truncateable(
- truncate: !displayWidthOutsideArrow,
- child: Container(
- margin: const EdgeInsets.symmetric(vertical: arrowMargin),
- child: ArrowWrapper.bidirectional(
- arrowColor: widthIndicatorColor,
- arrowHeadSize: arrowHeadSize,
- arrowStrokeWidth: arrowStrokeWidth,
- direction: Axis.horizontal,
- child: displayWidthOutsideArrow ? null : widthDescription,
- ),
- ),
- ),
- if (displayWidthOutsideArrow)
- Flexible(
- child: Container(
- padding: const EdgeInsets.only(top: minPadding),
- child: widthDescription,
- ),
- ),
- ],
- );
- },
- ),
- );
- return BorderLayout(
- center: child,
- right: right,
- rightWidth: rightWidth,
- bottom: bottom,
- bottomHeight: bottomHeight,
- );
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/widgets_theme.dart b/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/widgets_theme.dart
deleted file mode 100644
index 4abdb55..0000000
--- a/packages/devtools_app/lib/src/screens/inspector/layout_explorer/ui/widgets_theme.dart
+++ /dev/null
@@ -1,250 +0,0 @@
-// Copyright 2021 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:flutter/material.dart';
-
-class WidgetTheme {
- const WidgetTheme({this.iconAsset, this.color = otherWidgetColor});
-
- final String? iconAsset;
- final Color color;
-
- static WidgetTheme fromName(String? widgetType) {
- if (widgetType == null) {
- return const WidgetTheme();
- }
-
- return themeMap[_stripBrackets(widgetType)] ?? const WidgetTheme();
- }
-
- /// Strips the brackets off the widget name.
- ///
- /// For example: `AnimatedBuilder<String>` -> `AnimatedBuilder`.
- static String _stripBrackets(String widgetType) {
- final bracketIndex = widgetType.indexOf('<');
- if (bracketIndex == -1) {
- return widgetType;
- }
-
- return widgetType.substring(0, bracketIndex);
- }
-
- static const contentWidgetColor = Color(0xff06AC3B);
- static const highLevelWidgetColor = Color(0xffAEAEB1);
- static const animationWidgetColor = Color(0xffE09D0E);
- static const otherWidgetColor = Color(0xff0EA7E0);
-
- static const animatedTheme = WidgetTheme(
- iconAsset: WidgetIcons.animated,
- color: animationWidgetColor,
- );
-
- static const transitionTheme = WidgetTheme(
- iconAsset: WidgetIcons.transition,
- color: animationWidgetColor,
- );
-
- static const textTheme = WidgetTheme(
- iconAsset: WidgetIcons.text,
- color: contentWidgetColor,
- );
-
- static const imageTheme = WidgetTheme(
- iconAsset: WidgetIcons.image,
- color: contentWidgetColor,
- );
-
- static const tabTheme = WidgetTheme(iconAsset: WidgetIcons.tab);
- static const scrollTheme = WidgetTheme(iconAsset: WidgetIcons.scroll);
- static const highLevelTheme = WidgetTheme(color: highLevelWidgetColor);
- static const listTheme = WidgetTheme(iconAsset: WidgetIcons.listView);
- static const expandTheme = WidgetTheme(iconAsset: WidgetIcons.expand);
- static const alignTheme = WidgetTheme(iconAsset: WidgetIcons.align);
- static const gestureTheme = WidgetTheme(iconAsset: WidgetIcons.gesture);
- static const textButtonTheme = WidgetTheme(iconAsset: WidgetIcons.textButton);
- static const toggleTheme = WidgetTheme(
- iconAsset: WidgetIcons.toggle,
- color: contentWidgetColor,
- );
-
- static const themeMap = <String, WidgetTheme>{
- // High-level
- 'RenderObjectToWidgetAdapter': WidgetTheme(
- iconAsset: WidgetIcons.root,
- color: highLevelWidgetColor,
- ),
- 'CupertinoApp': highLevelTheme,
- 'MaterialApp': WidgetTheme(iconAsset: WidgetIcons.materialApp),
- 'WidgetsApp': highLevelTheme,
-
- // Text
- 'DefaultTextStyle': textTheme,
- 'RichText': textTheme,
- 'SelectableText': textTheme,
- 'Text': textTheme,
-
- // Images
- 'Icon': imageTheme,
- 'Image': imageTheme,
- 'RawImage': imageTheme,
-
- // Animations
- 'AnimatedAlign': animatedTheme,
- 'AnimatedBuilder': animatedTheme,
- 'AnimatedContainer': animatedTheme,
- 'AnimatedCrossFade': animatedTheme,
- 'AnimatedDefaultTextStyle': animatedTheme,
- 'AnimatedListState': animatedTheme,
- 'AnimatedModalBarrier': animatedTheme,
- 'AnimatedOpacity': animatedTheme,
- 'AnimatedPhysicalModel': animatedTheme,
- 'AnimatedPositioned': animatedTheme,
- 'AnimatedSize': animatedTheme,
- 'AnimatedWidget': animatedTheme,
-
- // Transitions
- 'DecoratedBoxTransition': transitionTheme,
- 'FadeTransition': transitionTheme,
- 'PositionedTransition': transitionTheme,
- 'RotationTransition': transitionTheme,
- 'ScaleTransition': transitionTheme,
- 'SizeTransition': transitionTheme,
- 'SlideTransition': transitionTheme,
- 'Hero': WidgetTheme(
- iconAsset: WidgetIcons.hero,
- color: animationWidgetColor,
- ),
-
- // Scroll
- 'CustomScrollView': scrollTheme,
- 'DraggableScrollableSheet': scrollTheme,
- 'SingleChildScrollView': scrollTheme,
- 'Scrollable': scrollTheme,
- 'Scrollbar': scrollTheme,
- 'ScrollConfiguration': scrollTheme,
- 'GridView': WidgetTheme(iconAsset: WidgetIcons.gridView),
- 'ListView': listTheme,
- 'ReorderableListView': listTheme,
- 'NestedScrollView': listTheme,
-
- // Input
- 'Checkbox': WidgetTheme(
- iconAsset: WidgetIcons.checkbox,
- color: contentWidgetColor,
- ),
- 'Radio': WidgetTheme(
- iconAsset: WidgetIcons.radio,
- color: contentWidgetColor,
- ),
- 'Switch': toggleTheme,
- 'CupertinoSwitch': toggleTheme,
-
- // Layout
- 'Container': WidgetTheme(iconAsset: WidgetIcons.container),
- 'Center': WidgetTheme(iconAsset: WidgetIcons.center),
- 'Row': WidgetTheme(iconAsset: WidgetIcons.row),
- 'Column': WidgetTheme(iconAsset: WidgetIcons.column),
- 'Padding': WidgetTheme(iconAsset: WidgetIcons.padding),
- 'SizedBox': WidgetTheme(iconAsset: WidgetIcons.sizedBox),
- 'ConstrainedBox': WidgetTheme(iconAsset: WidgetIcons.constrainedBox),
- 'Align': alignTheme,
- 'Positioned': alignTheme,
- 'Expanded': expandTheme,
- 'Flexible': expandTheme,
- 'Stack': WidgetTheme(iconAsset: WidgetIcons.stack),
- 'Wrap': WidgetTheme(iconAsset: WidgetIcons.wrap),
-
- // Buttons
- 'FloatingActionButton': WidgetTheme(
- iconAsset: WidgetIcons.floatingActionButton,
- color: contentWidgetColor,
- ),
- 'InkWell': WidgetTheme(iconAsset: WidgetIcons.inkWell),
- 'GestureDetector': gestureTheme,
- 'RawGestureDetector': gestureTheme,
- 'TextButton': textButtonTheme,
- 'CupertinoButton': textButtonTheme,
- 'ElevatedButton': textButtonTheme,
- 'OutlinedButton': WidgetTheme(iconAsset: WidgetIcons.outlinedButton),
-
- // Tabs
- 'Tab': tabTheme,
- 'TabBar': tabTheme,
- 'TabBarView': tabTheme,
- 'BottomNavigationBar': WidgetTheme(
- iconAsset: WidgetIcons.bottomNavigationBar,
- ),
- 'CupertinoTabScaffold': tabTheme,
- 'CupertinoTabView': tabTheme,
-
- // Other
- 'Scaffold': WidgetTheme(iconAsset: WidgetIcons.scaffold),
- 'CircularProgressIndicator': WidgetTheme(
- iconAsset: WidgetIcons.circularProgress,
- ),
- 'Card': WidgetTheme(iconAsset: WidgetIcons.card),
- 'Divider': WidgetTheme(iconAsset: WidgetIcons.divider),
- 'AlertDialog': WidgetTheme(iconAsset: WidgetIcons.alertDialog),
- 'CircleAvatar': WidgetTheme(iconAsset: WidgetIcons.circleAvatar),
- 'Opacity': WidgetTheme(iconAsset: WidgetIcons.opacity),
- 'Drawer': WidgetTheme(iconAsset: WidgetIcons.drawer),
- 'PageView': WidgetTheme(iconAsset: WidgetIcons.pageView),
- 'Material': WidgetTheme(iconAsset: WidgetIcons.material),
- 'AppBar': WidgetTheme(iconAsset: WidgetIcons.appBar),
- 'HiddenGroup': WidgetTheme(iconAsset: WidgetIcons.hidden),
- };
-}
-
-class WidgetIcons {
- static const root = 'icons/inspector/widget_icons/root.png';
- static const text = 'icons/inspector/widget_icons/text.png';
- static const icon = 'icons/inspector/widget_icons/icon.png';
- static const image = 'icons/inspector/widget_icons/image.png';
- static const floatingActionButton =
- 'icons/inspector/widget_icons/floatingab.png';
- static const checkbox = 'icons/inspector/widget_icons/checkbox.png';
- static const radio = 'icons/inspector/widget_icons/radio.png';
- static const toggle = 'icons/inspector/widget_icons/toggle.png';
- static const animated = 'icons/inspector/widget_icons/animated.png';
- static const transition = 'icons/inspector/widget_icons/transition.png';
- static const hero = 'icons/inspector/widget_icons/hero.png';
- static const container = 'icons/inspector/widget_icons/container.png';
- static const center = 'icons/inspector/widget_icons/center.png';
- static const row = 'icons/inspector/widget_icons/row.png';
- static const column = 'icons/inspector/widget_icons/column.png';
- static const padding = 'icons/inspector/widget_icons/padding.png';
- static const scaffold = 'icons/inspector/widget_icons/scaffold.png';
- static const sizedBox = 'icons/inspector/widget_icons/sizedbox.png';
- static const align = 'icons/inspector/widget_icons/align.png';
- static const scroll = 'icons/inspector/widget_icons/scroll.png';
- static const stack = 'icons/inspector/widget_icons/stack.png';
- static const inkWell = 'icons/inspector/widget_icons/inkwell.png';
- static const gesture = 'icons/inspector/widget_icons/gesture.png';
- static const textButton = 'icons/inspector/widget_icons/textbutton.png';
- static const outlinedButton =
- 'icons/inspector/widget_icons/outlinedbutton.png';
- static const gridView = 'icons/inspector/widget_icons/gridview.png';
- static const listView = 'icons/inspector/widget_icons/listView.png';
-
- static const alertDialog = 'icons/inspector/widget_icons/alertdialog.png';
- static const card = 'icons/inspector/widget_icons/card.png';
- static const circleAvatar = 'icons/inspector/widget_icons/circleavatar.png';
- static const circularProgress =
- 'icons/inspector/widget_icons/circularprogress.png';
- static const constrainedBox =
- 'icons/inspector/widget_icons/constrainedbox.png';
- static const divider = 'icons/inspector/widget_icons/divider.png';
- static const drawer = 'icons/inspector/widget_icons/drawer.png';
- static const expand = 'icons/inspector/widget_icons/expand.png';
- static const material = 'icons/inspector/widget_icons/material.png';
- static const opacity = 'icons/inspector/widget_icons/opacity.png';
- static const tab = 'icons/inspector/widget_icons/tab.png';
- static const wrap = 'icons/inspector/widget_icons/wrap.png';
- static const pageView = 'icons/inspector/widget_icons/pageView.png';
- static const appBar = 'icons/inspector/widget_icons/appbar.png';
- static const materialApp = 'icons/inspector/widget_icons/materialapp.png';
- static const bottomNavigationBar =
- 'icons/inspector/widget_icons/bottomnavigationbar.png';
- static const hidden = 'icons/inspector/widget_icons/onedot.png';
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector_shared/inspector_screen.dart b/packages/devtools_app/lib/src/screens/inspector_shared/inspector_screen.dart
deleted file mode 100644
index 710743d..0000000
--- a/packages/devtools_app/lib/src/screens/inspector_shared/inspector_screen.dart
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2024 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app_shared/shared.dart';
-import 'package:devtools_app_shared/utils.dart';
-import 'package:flutter/material.dart';
-
-import '../../shared/feature_flags.dart';
-import '../../shared/framework/screen.dart';
-import '../../shared/globals.dart';
-import '../inspector/inspector_screen_body.dart' as legacy;
-import '../inspector_v2/inspector_screen_body.dart' as v2;
-import 'inspector_screen_controller.dart';
-
-class InspectorScreen extends Screen {
- InspectorScreen() : super.fromMetaData(ScreenMetaData.inspector);
-
- static const minScreenWidthForText = 900.0;
-
- static final id = ScreenMetaData.inspector.id;
-
- // There is not enough room to safely show the console in the embed view of
- // the DevTools and IDEs have their own consoles.
- @override
- bool showConsole(EmbedMode embedMode) => !embedMode.embedded;
-
- @override
- bool showAiAssistant() => true;
-
- @override
- String get docPageId => screenId;
-
- @override
- Widget buildScreenBody(BuildContext context) =>
- const InspectorScreenSwitcher();
-}
-
-class InspectorScreenSwitcher extends StatefulWidget {
- const InspectorScreenSwitcher({super.key});
-
- @override
- State<InspectorScreenSwitcher> createState() =>
- _InspectorScreenSwitcherState();
-}
-
-class _InspectorScreenSwitcherState extends State<InspectorScreenSwitcher>
- with AutoDisposeMixin {
- late InspectorScreenController controller;
-
- bool get shouldShowInspectorV2 =>
- FeatureFlags.inspectorV2.isEnabled &&
- !preferences.inspector.legacyInspectorEnabled.value;
-
- @override
- void initState() {
- super.initState();
- controller = screenControllers.lookup<InspectorScreenController>();
- addAutoDisposeListener(
- preferences.inspector.legacyInspectorEnabled,
- () async {
- controller.legacyInspectorController.setVisibleToUser(
- !shouldShowInspectorV2,
- );
- await controller.v2InspectorController.setVisibleToUser(
- shouldShowInspectorV2,
- );
- },
- );
- }
-
- @override
- Widget build(BuildContext context) {
- return ValueListenableBuilder(
- valueListenable: preferences.inspector.legacyInspectorEnabled,
- builder: (context, _, _) {
- if (shouldShowInspectorV2) {
- return v2.InspectorScreenBody(
- controller: controller.v2InspectorController,
- );
- }
-
- return legacy.InspectorScreenBody(
- controller: controller.legacyInspectorController,
- );
- },
- );
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector_shared/inspector_screen_controller.dart b/packages/devtools_app/lib/src/screens/inspector_shared/inspector_screen_controller.dart
deleted file mode 100644
index 5ca8073..0000000
--- a/packages/devtools_app/lib/src/screens/inspector_shared/inspector_screen_controller.dart
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2024 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import '../../shared/analytics/metrics.dart';
-import '../../shared/console/primitives/simple_items.dart';
-import '../../shared/framework/screen.dart';
-import '../../shared/framework/screen_controllers.dart';
-import '../inspector/inspector_controller.dart' as legacy;
-import '../inspector/inspector_tree_controller.dart' as legacy;
-import '../inspector_v2/inspector_controller.dart' as v2;
-import '../inspector_v2/inspector_tree_controller.dart' as v2;
-
-/// Screen controller for the Inspector screen.
-///
-/// This controller can be accessed from anywhere in DevTools, as long as it was
-/// first registered, by
-/// calling `screenControllers.lookup<InspectorScreenController>()`.
-///
-/// The controller lifecycle is managed by the [ScreenControllers] class. The
-/// `init` method is called lazily upon the first controller access from
-/// `screenControllers`. The `dispose` method is called by `screenControllers`
-/// when DevTools is destroying a set of DevTools screen controllers.
-class InspectorScreenController extends DevToolsScreenController {
- @override
- final screenId = ScreenMetaData.inspector.id;
-
- late v2.InspectorController v2InspectorController;
- late v2.InspectorTreeController v2InspectorTreeController;
-
- late legacy.InspectorController legacyInspectorController;
- late legacy.InspectorTreeController legacyInspectorTreeController;
- late legacy.InspectorTreeController legacyDetailsTreeController;
-
- @override
- void init() {
- super.init();
- v2InspectorTreeController = v2.InspectorTreeController(
- gaId: InspectorScreenMetrics.summaryTreeGaId,
- );
- v2InspectorController = v2.InspectorController(
- inspectorTree: v2InspectorTreeController,
- treeType: FlutterTreeType.widget,
- );
-
- // TODO(elliette): Remove legacy inspector.
- legacyInspectorTreeController = legacy.InspectorTreeController(
- gaId: InspectorScreenMetrics.summaryTreeGaId,
- );
- legacyDetailsTreeController = legacy.InspectorTreeController(
- gaId: InspectorScreenMetrics.detailsTreeGaId,
- );
- legacyInspectorController = legacy.InspectorController(
- inspectorTree: legacyInspectorTreeController,
- detailsTree: legacyDetailsTreeController,
- treeType: FlutterTreeType.widget,
- );
- }
-
- @override
- void dispose() {
- v2InspectorTreeController.dispose();
- v2InspectorController.dispose();
-
- legacyInspectorTreeController.dispose();
- legacyDetailsTreeController.dispose();
- legacyInspectorController.dispose();
- super.dispose();
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector_shared/inspector_settings_dialog.dart b/packages/devtools_app/lib/src/screens/inspector_shared/inspector_settings_dialog.dart
deleted file mode 100644
index 823a3dd..0000000
--- a/packages/devtools_app/lib/src/screens/inspector_shared/inspector_settings_dialog.dart
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright 2024 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:async';
-
-import 'package:devtools_app_shared/ui.dart';
-import 'package:devtools_app_shared/utils.dart';
-import 'package:flutter/material.dart';
-import 'package:vm_service/vm_service.dart' hide Stack;
-
-import '../../shared/analytics/analytics.dart' as ga;
-import '../../shared/analytics/constants.dart' as gac;
-import '../../shared/feature_flags.dart';
-import '../../shared/globals.dart';
-import '../../shared/managers/banner_messages.dart';
-import '../../shared/preferences/preferences.dart';
-import '../../shared/primitives/simple_items.dart';
-import '../../shared/ui/common_widgets.dart';
-import '../../shared/ui/editable_list.dart';
-import 'inspector_screen.dart';
-
-class FlutterInspectorSettingsDialog extends StatefulWidget {
- const FlutterInspectorSettingsDialog({super.key});
-
- @override
- State<FlutterInspectorSettingsDialog> createState() =>
- _FlutterInspectorSettingsDialogState();
-}
-
-class _FlutterInspectorSettingsDialogState
- extends State<FlutterInspectorSettingsDialog>
- with AutoDisposeMixin {
- @override
- void initState() {
- super.initState();
- addAutoDisposeListener(preferences.inspector.legacyInspectorEnabled, () {
- if (!preferences.inspector.legacyInspectorEnabled.value) {
- bannerMessages.removeMessageByKey(
- LegacyInspectorWarningMessage.buildKey(InspectorScreen.id),
- InspectorScreen.id,
- );
- }
- });
- }
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- const dialogHeight = 500.0;
-
- return ValueListenableBuilder(
- valueListenable: preferences.inspector.legacyInspectorEnabled,
- builder: (context, legacyInspectorEnabled, _) {
- final inspectorV2Enabled = !legacyInspectorEnabled;
- return DevToolsDialog(
- title: const DialogTitleText('Flutter Inspector Settings'),
- content: SizedBox(
- width: defaultDialogWidth,
- height: dialogHeight,
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- ...dialogSubHeader(theme, 'General'),
- CheckboxSetting(
- notifier:
- preferences.inspector.hoverEvalModeEnabled
- as ValueNotifier<bool?>,
- title: 'Enable hover inspection',
- description:
- 'Hovering over any widget displays its properties and values.',
- gaItem: gac.inspectorHoverEvalMode,
- ),
- const SizedBox(height: largeSpacing),
- if (inspectorV2Enabled) ...[
- CheckboxSetting(
- notifier:
- preferences.inspector.autoRefreshEnabled
- as ValueNotifier<bool?>,
- title: 'Enable widget tree auto-refreshing',
- description:
- 'The widget tree will automatically refresh after a hot-reload or navigation event.',
- gaItem: gac.inspectorAutoRefreshEnabled,
- ),
- ] else ...[
- const InspectorDefaultDetailsViewOption(),
- ],
- const SizedBox(height: largeSpacing),
- // TODO(https://github.com/flutter/devtools/issues/7860): Clean-up
- // after Inspector V2 has been released.
- if (FeatureFlags.inspectorV2.isEnabled)
- Flexible(
- child: CheckboxSetting(
- notifier:
- preferences.inspector.legacyInspectorEnabled
- as ValueNotifier<bool?>,
- title: 'Use legacy inspector',
- description:
- 'Disable the redesigned Flutter inspector. Please know that '
- 'the legacy inspector will be removed in a future release.',
- gaItem: gac.inspectorV2Disabled,
- ),
- ),
- const SizedBox(height: largeSpacing),
- ...dialogSubHeader(theme, 'Package Directories'),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Expanded(
- child: Text(
- 'Widgets in these directories will show up in your summary tree.',
- style: theme.subtleTextStyle,
- ),
- ),
- MoreInfoLink(
- url: DocLinks.inspectorPackageDirectories.value,
- gaScreenName: gac.inspector,
- gaSelectedItemDescription:
- gac.InspectorDocs.packageDirectoriesDocs.name,
- ),
- ],
- ),
- Text(
- '(e.g. /absolute/path/to/myPackage/)',
- style: theme.subtleTextStyle,
- ),
- const SizedBox(height: denseSpacing),
- const Expanded(child: PubRootDirectorySection()),
- ],
- ),
- ),
- actions: const [DialogCloseButton()],
- );
- },
- );
- }
-}
-
-class InspectorDefaultDetailsViewOption extends StatelessWidget {
- const InspectorDefaultDetailsViewOption({super.key});
-
- @override
- Widget build(BuildContext context) {
- return ValueListenableBuilder(
- valueListenable: preferences.inspector.defaultDetailsView,
- builder: (context, selection, _) {
- final theme = Theme.of(context);
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- 'Select the default tab for the inspector.',
- style: theme.subtleTextStyle,
- ),
- const SizedBox(height: denseSpacing),
- RadioGroup(
- groupValue: selection,
- onChanged: _onChanged,
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- const Radio<InspectorDetailsViewType>(
- value: InspectorDetailsViewType.layoutExplorer,
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- ),
- Text(InspectorDetailsViewType.layoutExplorer.key),
- const SizedBox(width: denseSpacing),
- const Radio<InspectorDetailsViewType>(
- materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
- value: InspectorDetailsViewType.widgetDetailsTree,
- ),
- Text(InspectorDetailsViewType.widgetDetailsTree.key),
- ],
- ),
- ),
- ],
- );
- },
- );
- }
-
- void _onChanged(InspectorDetailsViewType? value) {
- if (value != null) {
- preferences.inspector.setDefaultInspectorDetailsView(value);
- final item = value.name == InspectorDetailsViewType.layoutExplorer.name
- ? gac.defaultDetailsViewToLayoutExplorer
- : gac.defaultDetailsViewToWidgetDetails;
- ga.select(gac.inspector, item);
- }
- }
-}
-
-class PubRootDirectorySection extends StatelessWidget {
- const PubRootDirectorySection({super.key});
-
- @override
- Widget build(BuildContext context) {
- return ValueListenableBuilder<IsolateRef?>(
- valueListenable:
- serviceConnection.serviceManager.isolateManager.mainIsolate,
- builder: (_, _, _) {
- return SizedBox(
- height: 200.0,
- child: EditableList(
- gaScreen: gac.inspector,
- gaRefreshSelection: gac.refreshPubRoots,
- entries: preferences.inspector.pubRootDirectories,
- textFieldLabel: 'Enter a new package directory',
- isRefreshing: preferences.inspector.isRefreshingPubRootDirectories,
- onEntryAdded: (p0) => unawaited(
- preferences.inspector.addPubRootDirectories([
- p0,
- ], shouldCache: true),
- ),
- onEntryRemoved: (p0) =>
- unawaited(preferences.inspector.removePubRootDirectories([p0])),
- onRefreshTriggered: () =>
- unawaited(preferences.inspector.loadPubRootDirectories()),
- ),
- );
- },
- );
- }
-}
diff --git a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_controller.dart b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_controller.dart
index 32de022..f8694bc 100644
--- a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_controller.dart
+++ b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_controller.dart
@@ -36,8 +36,8 @@
import '../../shared/primitives/query_parameters.dart';
import '../../shared/primitives/utils.dart';
import '../../shared/utils/utils.dart';
-import '../inspector_shared/inspector_screen.dart';
import 'inspector_data_models.dart';
+import 'inspector_screen.dart';
import 'inspector_tree_controller.dart';
final _log = Logger('inspector_controller');
diff --git a/packages/devtools_app/lib/src/screens/inspector_shared/inspector_controls.dart b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_controls.dart
similarity index 95%
rename from packages/devtools_app/lib/src/screens/inspector_shared/inspector_controls.dart
rename to packages/devtools_app/lib/src/screens/inspector_v2/inspector_controls.dart
index d1f810a..2f1e53b 100644
--- a/packages/devtools_app/lib/src/screens/inspector_shared/inspector_controls.dart
+++ b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_controls.dart
@@ -13,14 +13,14 @@
import '../../shared/feature_flags.dart';
import '../../shared/globals.dart';
import '../../shared/ui/common_widgets.dart';
-import '../inspector_shared/inspector_settings_dialog.dart';
-import '../inspector_v2/inspector_controller.dart' as v2;
+import 'inspector_controller.dart';
+import 'inspector_settings_dialog.dart';
/// Control buttons for the inspector panel.
class InspectorControls extends StatelessWidget {
const InspectorControls({super.key, this.controller});
- final v2.InspectorController? controller;
+ final InspectorController? controller;
static const minScreenWidthForTextBeforeTruncating = 800.0;
static const minScreenWidthForText = 550.0;
@@ -113,7 +113,7 @@
class ShowImplementationWidgetsButton extends StatelessWidget {
const ShowImplementationWidgetsButton({super.key, required this.controller});
- final v2.InspectorController controller;
+ final InspectorController controller;
@override
Widget build(BuildContext context) {
diff --git a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_data_models.dart b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_data_models.dart
index 188dabd..1d166ce 100644
--- a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_data_models.dart
+++ b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_data_models.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-/// @docImport '../inspector/layout_explorer/ui/overflow_indicator_painter.dart';
+/// @docImport '../inspector_v2/layout_explorer/ui/overflow_indicator_painter.dart';
library;
import 'dart:math' as math;
diff --git a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen.dart b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen.dart
new file mode 100644
index 0000000..1ac60f6
--- /dev/null
+++ b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen.dart
@@ -0,0 +1,36 @@
+// Copyright 2024 The Flutter Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
+
+import 'package:devtools_app_shared/shared.dart';
+import 'package:flutter/material.dart';
+
+import '../../shared/framework/screen.dart';
+import '../../shared/globals.dart';
+import 'inspector_screen_body.dart';
+import 'inspector_screen_controller.dart';
+
+class InspectorScreen extends Screen {
+ InspectorScreen() : super.fromMetaData(ScreenMetaData.inspector);
+
+ static const minScreenWidthForText = 900.0;
+
+ static final id = ScreenMetaData.inspector.id;
+
+ // There is not enough room to safely show the console in the embed view of
+ // the DevTools and IDEs have their own consoles.
+ @override
+ bool showConsole(EmbedMode embedMode) => !embedMode.embedded;
+
+ @override
+ bool showAiAssistant() => true;
+
+ @override
+ String get docPageId => screenId;
+
+ @override
+ Widget buildScreenBody(BuildContext context) {
+ final controller = screenControllers.lookup<InspectorScreenController>();
+ return InspectorScreenBody(controller: controller.inspectorController);
+ }
+}
diff --git a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_body.dart b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_body.dart
index bcb6070..b43e9ff 100644
--- a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_body.dart
+++ b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_body.dart
@@ -19,9 +19,9 @@
import '../../shared/ui/common_widgets.dart';
import '../../shared/ui/search.dart';
import '../../shared/utils/utils.dart';
-import '../inspector_shared/inspector_controls.dart';
-import '../inspector_shared/inspector_screen.dart';
import 'inspector_controller.dart';
+import 'inspector_controls.dart';
+import 'inspector_screen.dart';
import 'inspector_tree_controller.dart';
import 'widget_details.dart';
diff --git a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_controller.dart b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_controller.dart
new file mode 100644
index 0000000..5a403db
--- /dev/null
+++ b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_screen_controller.dart
@@ -0,0 +1,47 @@
+// Copyright 2024 The Flutter Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
+
+import '../../shared/analytics/metrics.dart';
+import '../../shared/console/primitives/simple_items.dart';
+import '../../shared/framework/screen.dart';
+import '../../shared/framework/screen_controllers.dart';
+import 'inspector_controller.dart';
+import 'inspector_tree_controller.dart';
+
+/// Screen controller for the Inspector screen.
+///
+/// This controller can be accessed from anywhere in DevTools, as long as it was
+/// first registered, by
+/// calling `screenControllers.lookup<InspectorScreenController>()`.
+///
+/// The controller lifecycle is managed by the [ScreenControllers] class. The
+/// `init` method is called lazily upon the first controller access from
+/// `screenControllers`. The `dispose` method is called by `screenControllers`
+/// when DevTools is destroying a set of DevTools screen controllers.
+class InspectorScreenController extends DevToolsScreenController {
+ @override
+ final screenId = ScreenMetaData.inspector.id;
+
+ late InspectorController inspectorController;
+ late InspectorTreeController inspectorTreeController;
+
+ @override
+ void init() {
+ super.init();
+ inspectorTreeController = InspectorTreeController(
+ gaId: InspectorScreenMetrics.summaryTreeGaId,
+ );
+ inspectorController = InspectorController(
+ inspectorTree: inspectorTreeController,
+ treeType: FlutterTreeType.widget,
+ );
+ }
+
+ @override
+ void dispose() {
+ inspectorTreeController.dispose();
+ inspectorController.dispose();
+ super.dispose();
+ }
+}
diff --git a/packages/devtools_app/lib/src/screens/inspector_v2/inspector_settings_dialog.dart b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_settings_dialog.dart
new file mode 100644
index 0000000..a185822
--- /dev/null
+++ b/packages/devtools_app/lib/src/screens/inspector_v2/inspector_settings_dialog.dart
@@ -0,0 +1,127 @@
+// Copyright 2024 The Flutter Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
+
+import 'dart:async';
+
+import 'package:devtools_app_shared/ui.dart';
+import 'package:devtools_app_shared/utils.dart';
+import 'package:flutter/material.dart';
+import 'package:vm_service/vm_service.dart' hide Stack;
+
+import '../../shared/analytics/constants.dart' as gac;
+import '../../shared/globals.dart';
+import '../../shared/primitives/simple_items.dart';
+import '../../shared/ui/common_widgets.dart';
+import '../../shared/ui/editable_list.dart';
+
+class FlutterInspectorSettingsDialog extends StatefulWidget {
+ const FlutterInspectorSettingsDialog({super.key});
+
+ @override
+ State<FlutterInspectorSettingsDialog> createState() =>
+ _FlutterInspectorSettingsDialogState();
+}
+
+class _FlutterInspectorSettingsDialogState
+ extends State<FlutterInspectorSettingsDialog>
+ with AutoDisposeMixin {
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ const dialogHeight = 500.0;
+
+ return DevToolsDialog(
+ title: const DialogTitleText('Flutter Inspector Settings'),
+ content: SizedBox(
+ width: defaultDialogWidth,
+ height: dialogHeight,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ...dialogSubHeader(theme, 'General'),
+ CheckboxSetting(
+ notifier:
+ preferences.inspector.hoverEvalModeEnabled
+ as ValueNotifier<bool?>,
+ title: 'Enable hover inspection',
+ description:
+ 'Hovering over any widget displays its properties and values.',
+ gaItem: gac.inspectorHoverEvalMode,
+ ),
+ const SizedBox(height: largeSpacing),
+ CheckboxSetting(
+ notifier:
+ preferences.inspector.autoRefreshEnabled
+ as ValueNotifier<bool?>,
+ title: 'Enable widget tree auto-refreshing',
+ description:
+ 'The widget tree will automatically refresh after a hot-reload or navigation event.',
+ gaItem: gac.inspectorAutoRefreshEnabled,
+ ),
+ const SizedBox(height: largeSpacing),
+ ...dialogSubHeader(theme, 'Package Directories'),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ child: Text(
+ 'Widgets in these directories will show up in your summary tree.',
+ style: theme.subtleTextStyle,
+ ),
+ ),
+ MoreInfoLink(
+ url: DocLinks.inspectorPackageDirectories.value,
+ gaScreenName: gac.inspector,
+ gaSelectedItemDescription:
+ gac.InspectorDocs.packageDirectoriesDocs.name,
+ ),
+ ],
+ ),
+ Text(
+ '(e.g. /absolute/path/to/myPackage/)',
+ style: theme.subtleTextStyle,
+ ),
+ const SizedBox(height: denseSpacing),
+ const Expanded(child: PubRootDirectorySection()),
+ ],
+ ),
+ ),
+ actions: const [DialogCloseButton()],
+ );
+ }
+}
+
+class PubRootDirectorySection extends StatelessWidget {
+ const PubRootDirectorySection({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return ValueListenableBuilder<IsolateRef?>(
+ valueListenable:
+ serviceConnection.serviceManager.isolateManager.mainIsolate,
+ builder: (_, _, _) {
+ return SizedBox(
+ height: 200.0,
+ child: EditableList(
+ gaScreen: gac.inspector,
+ gaRefreshSelection: gac.refreshPubRoots,
+ entries: preferences.inspector.pubRootDirectories,
+ textFieldLabel: 'Enter a new package directory',
+ isRefreshing: preferences.inspector.isRefreshingPubRootDirectories,
+ onEntryAdded: (p0) => unawaited(
+ preferences.inspector.addPubRootDirectories([
+ p0,
+ ], shouldCache: true),
+ ),
+ onEntryRemoved: (p0) =>
+ unawaited(preferences.inspector.removePubRootDirectories([p0])),
+ onRefreshTriggered: () =>
+ unawaited(preferences.inspector.loadPubRootDirectories()),
+ ),
+ );
+ },
+ );
+ }
+}
diff --git a/packages/devtools_app/lib/src/screens/inspector_v2/layout_explorer/ui/utils.dart b/packages/devtools_app/lib/src/screens/inspector_v2/layout_explorer/ui/utils.dart
index 5b4b1ec..859b650 100644
--- a/packages/devtools_app/lib/src/screens/inspector_v2/layout_explorer/ui/utils.dart
+++ b/packages/devtools_app/lib/src/screens/inspector_v2/layout_explorer/ui/utils.dart
@@ -9,8 +9,8 @@
import '../../../../shared/diagnostics/diagnostics_node.dart';
import '../../../../shared/primitives/utils.dart';
-import '../../../inspector/layout_explorer/ui/dimension.dart';
import '../../inspector_data_models.dart';
+import 'dimension.dart';
import 'overflow_indicator_painter.dart';
import 'theme.dart';
import 'widgets_theme.dart';
diff --git a/packages/devtools_app/lib/src/screens/logging/logging_controller.dart b/packages/devtools_app/lib/src/screens/logging/logging_controller.dart
index da6f1ce..786ff86 100644
--- a/packages/devtools_app/lib/src/screens/logging/logging_controller.dart
+++ b/packages/devtools_app/lib/src/screens/logging/logging_controller.dart
@@ -27,7 +27,7 @@
import '../../shared/primitives/utils.dart';
import '../../shared/ui/filter.dart';
import '../../shared/ui/search.dart';
-import '../inspector/inspector_tree_controller.dart';
+import '../inspector_v2/inspector_tree_controller.dart';
import 'log_details_controller.dart';
import 'logging_screen.dart';
import 'metadata.dart';
diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart
index 45450f8..aaf721b 100644
--- a/packages/devtools_app/lib/src/shared/analytics/constants.dart
+++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart
@@ -8,7 +8,6 @@
import 'package:devtools_shared/devtools_extensions.dart';
import '../framework/screen.dart';
-import '../preferences/preferences.dart';
part 'constants/_cpu_profiler_constants.dart';
part 'constants/_debugger_constants.dart';
@@ -97,6 +96,9 @@
const inspectorSettings = 'inspectorSettings';
const loggingSettings = 'loggingSettings';
const refreshPubRoots = 'refreshPubRoots';
+
+enum InspectorDetailsViewType { layoutExplorer, widgetDetailsTree }
+
final defaultDetailsViewToLayoutExplorer =
InspectorDetailsViewType.layoutExplorer.name;
final defaultDetailsViewToWidgetDetails =
diff --git a/packages/devtools_app/lib/src/shared/analytics/metrics.dart b/packages/devtools_app/lib/src/shared/analytics/metrics.dart
index fa0195a..a1be45f 100644
--- a/packages/devtools_app/lib/src/shared/analytics/metrics.dart
+++ b/packages/devtools_app/lib/src/shared/analytics/metrics.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-/// @docImport '../../screens/inspector/inspector_tree_controller.dart';
+/// @docImport '../../screens/inspector_v2/inspector_tree_controller.dart';
/// @docImport '../../screens/performance/panes/flutter_frames/flutter_frame_model.dart';
library;
@@ -67,12 +67,6 @@
}
class InspectorScreenMetrics extends ScreenAnalyticsMetrics {
- InspectorScreenMetrics.legacy({
- this.rootSetCount,
- this.rowCount,
- this.inspectorTreeControllerId,
- }) : isV2 = false;
-
InspectorScreenMetrics.v2({
this.rootSetCount,
this.rowCount,
diff --git a/packages/devtools_app/lib/src/shared/console/eval/inspector_tree.dart b/packages/devtools_app/lib/src/shared/console/eval/inspector_tree.dart
deleted file mode 100644
index 1afbf48..0000000
--- a/packages/devtools_app/lib/src/shared/console/eval/inspector_tree.dart
+++ /dev/null
@@ -1,301 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-/// Inspector specific tree rendering support.
-///
-/// This library must not have direct dependencies on web-only libraries.
-///
-/// This allows tests of the complicated logic in this class to run on the VM.
-library;
-
-import 'package:flutter/foundation.dart';
-
-import '../../diagnostics/diagnostics_node.dart';
-import '../../ui/search.dart';
-
-/// Split text into two groups, word characters at the start of a string and all
-/// other characters.
-final treeNodePrimaryDescriptionPattern = RegExp(r'^([\w ]+)(.*)$');
-// TODO(jacobr): temporary workaround for missing structure from assertion thrown building
-// widget errors.
-final assertionThrownBuildingError = RegExp(
- r'^(The following assertion was thrown building [a-zA-Z]+)(\(.*\))(:)$',
-);
-
-typedef TreeEventCallback = void Function(InspectorTreeNode node);
-
-const iconPadding = 4.0;
-const chartLineStrokeWidth = 1.0;
-const inspectorColumnWidth = 12.0;
-const inspectorRowHeight = 16.0;
-
-/// This class could be refactored out to be a reasonable generic collapsible
-/// tree ui node class but we choose to instead make it widget inspector
-/// specific as that is the only case we care about.
-// TODO(kenz): extend TreeNode class to share tree logic.
-class InspectorTreeNode {
- InspectorTreeNode({InspectorTreeNode? parent, bool expandChildren = true})
- : _children = <InspectorTreeNode>[],
- _parent = parent,
- _isExpanded = expandChildren;
-
- bool get showLinesToChildren {
- return _children.length > 1 && !_children.last.isProperty;
- }
-
- bool get isDirty => _isDirty;
- bool _isDirty = true;
-
- set isDirty(bool dirty) {
- if (dirty) {
- _isDirty = true;
- _shouldShow = null;
- if (_childrenCount == null) {
- // Already dirty.
- return;
- }
- _childrenCount = null;
- if (parent != null) {
- parent!.isDirty = true;
- }
- } else {
- _isDirty = false;
- }
- }
-
- /// Returns whether the node is currently visible in the tree.
- void updateShouldShow(bool value) {
- if (value != _shouldShow) {
- _shouldShow = value;
- for (final child in children) {
- child.updateShouldShow(value);
- }
- }
- }
-
- bool get shouldShow {
- final parentLocal = parent;
- _shouldShow ??=
- parentLocal == null || parentLocal.isExpanded && parentLocal.shouldShow;
- return _shouldShow!;
- }
-
- bool? _shouldShow;
-
- bool selected = false;
-
- RemoteDiagnosticsNode? _diagnostic;
- final List<InspectorTreeNode> _children;
-
- Iterable<InspectorTreeNode> get children => _children;
-
- bool get isProperty {
- final diagnosticLocal = diagnostic;
- return diagnosticLocal == null || diagnosticLocal.isProperty;
- }
-
- bool get isExpanded => _isExpanded;
- bool _isExpanded;
-
- bool allowExpandCollapse = true;
-
- bool get showExpandCollapse {
- return (diagnostic?.hasChildren == true || children.isNotEmpty) &&
- allowExpandCollapse;
- }
-
- set isExpanded(bool value) {
- if (value != _isExpanded) {
- _isExpanded = value;
- isDirty = true;
- if (_shouldShow ?? false) {
- for (final child in children) {
- child.updateShouldShow(value);
- }
- }
- }
- }
-
- InspectorTreeNode? get parent => _parent;
- InspectorTreeNode? _parent;
-
- set parent(InspectorTreeNode? value) {
- _parent = value;
- _parent?.isDirty = true;
- }
-
- RemoteDiagnosticsNode? get diagnostic => _diagnostic;
-
- set diagnostic(RemoteDiagnosticsNode? v) {
- final value = v!;
- _diagnostic = value;
- _isExpanded = value.childrenReady;
- isDirty = true;
- }
-
- int get childrenCount {
- if (!isExpanded) {
- _childrenCount = 0;
- }
- final childrenCountLocal = _childrenCount;
- if (childrenCountLocal != null) {
- return childrenCountLocal;
- }
- int count = 0;
- for (final child in _children) {
- count += child.subtreeSize;
- }
- return _childrenCount = count;
- }
-
- bool get hasPlaceholderChildren {
- return children.length == 1 && children.first.diagnostic == null;
- }
-
- int? _childrenCount;
-
- int get subtreeSize => childrenCount + 1;
-
- // TODO(jacobr): move getRowIndex to the InspectorTree class.
- int getRowIndex(InspectorTreeNode node) {
- int index = 0;
- while (true) {
- final parent = node.parent;
- if (parent == null) {
- break;
- }
- for (final sibling in parent._children) {
- if (sibling == node) {
- break;
- }
- index += sibling.subtreeSize;
- }
- index += 1; // For parent itself.
- node = parent;
- }
- return index;
- }
-
- // TODO(jacobr): move this method to the InspectorTree class.
- // TODO: optimize this method.
- InspectorTreeRow? getRow(int index) {
- if (subtreeSize <= index) {
- return null;
- }
-
- final ticks = <int>[];
- InspectorTreeNode node = this;
- int current = 0;
- int depth = 0;
-
- // Iterate till getting the result to return.
- while (true) {
- final style = node.diagnostic?.style;
- final indented =
- style != DiagnosticsTreeStyle.flat &&
- style != DiagnosticsTreeStyle.error;
- if (current == index) {
- return InspectorTreeRow(
- node: node,
- index: index,
- ticks: ticks,
- depth: depth,
- lineToParent:
- !node.isProperty &&
- index != 0 &&
- node.parent!.showLinesToChildren,
- );
- }
- assert(index > current);
- current++;
- final children = node._children;
- int i;
- for (i = 0; i < children.length; ++i) {
- final child = children[i];
- final subtreeSize = child.subtreeSize;
- if (current + subtreeSize > index) {
- node = child;
- if (children.length > 1 &&
- i + 1 != children.length &&
- !children.last.isProperty) {
- if (indented) {
- ticks.add(depth);
- }
- }
- break;
- }
- current += subtreeSize;
- }
- assert(i < children.length);
- if (indented) {
- depth++;
- }
- }
- }
-
- void removeChild(InspectorTreeNode child) {
- child.parent = null;
- final removed = _children.remove(child);
- assert(removed);
- isDirty = true;
- }
-
- void appendChild(InspectorTreeNode child) {
- _children.add(child);
- child.parent = this;
- isDirty = true;
- }
-
- void clearChildren() {
- _children.clear();
- isDirty = true;
- }
-}
-
-/// A row in the tree with all information required to render it.
-class InspectorTreeRow with SearchableDataMixin {
- InspectorTreeRow({
- required this.node,
- required this.index,
- required this.ticks,
- required this.depth,
- required this.lineToParent,
- });
-
- final InspectorTreeNode node;
-
- /// Column indexes of ticks to draw lines from parents to children.
- final List<int> ticks;
- final int depth;
- final int index;
- final bool lineToParent;
-
- bool get isSelected => node.selected;
-}
-
-/// Callback issued every time a node is added to the tree.
-typedef NodeAddedCallback =
- void Function(
- InspectorTreeNode node,
- RemoteDiagnosticsNode diagnosticsNode,
- );
-
-class InspectorTreeConfig {
- InspectorTreeConfig({
- this.onNodeAdded,
- this.onClientActiveChange,
- this.onSelectionChange,
- this.onExpand,
- });
-
- final NodeAddedCallback? onNodeAdded;
- final VoidCallback? onSelectionChange;
- final void Function(bool added)? onClientActiveChange;
- final TreeEventCallback? onExpand;
-}
-
-enum SearchTargetType {
- widget,
- // TODO(https://github.com/flutter/devtools/issues/3489) implement other search scopes: details, all etc
-}
diff --git a/packages/devtools_app/lib/src/shared/console/widgets/description.dart b/packages/devtools_app/lib/src/shared/console/widgets/description.dart
index 7ea8389..f7006f9 100644
--- a/packages/devtools_app/lib/src/shared/console/widgets/description.dart
+++ b/packages/devtools_app/lib/src/shared/console/widgets/description.dart
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-/// @docImport '../../../screens/inspector/inspector_tree_controller.dart';
+/// @docImport '../../../screens/inspector_v2/inspector_tree_controller.dart';
library;
import 'package:devtools_app_shared/ui.dart';
@@ -17,7 +17,7 @@
import '../../ui/hover.dart';
import '../../ui/icons.dart';
import '../../ui/utils.dart';
-import '../eval/inspector_tree.dart';
+import '../eval/inspector_tree_v2.dart';
import 'expandable_variable.dart';
final _colorIconMaker = ColorIconMaker();
diff --git a/packages/devtools_app/lib/src/shared/managers/error_badge_manager.dart b/packages/devtools_app/lib/src/shared/managers/error_badge_manager.dart
index d3d3f7d..aadfe08 100644
--- a/packages/devtools_app/lib/src/shared/managers/error_badge_manager.dart
+++ b/packages/devtools_app/lib/src/shared/managers/error_badge_manager.dart
@@ -11,7 +11,7 @@
import 'package:flutter/foundation.dart';
import 'package:vm_service/vm_service.dart';
-import '../../screens/inspector_shared/inspector_screen.dart';
+import '../../screens/inspector_v2/inspector_screen.dart';
import '../../screens/logging/logging_screen.dart';
import '../../screens/network/network_screen.dart';
import '../../screens/performance/performance_screen.dart';
diff --git a/packages/devtools_app/lib/src/shared/preferences/_inspector_preferences.dart b/packages/devtools_app/lib/src/shared/preferences/_inspector_preferences.dart
index 440c2cc..7efdbcb 100644
--- a/packages/devtools_app/lib/src/shared/preferences/_inspector_preferences.dart
+++ b/packages/devtools_app/lib/src/shared/preferences/_inspector_preferences.dart
@@ -4,25 +4,10 @@
part of 'preferences.dart';
-enum InspectorDetailsViewType {
- layoutExplorer(nameOverride: 'Layout Explorer'),
- widgetDetailsTree(nameOverride: 'Widget Details Tree');
-
- const InspectorDetailsViewType({String? nameOverride})
- : _nameOverride = nameOverride;
-
- final String? _nameOverride;
-
- String get key => _nameOverride ?? name;
-}
-
class InspectorPreferencesController extends DisposableController
with AutoDisposeControllerMixin {
ValueListenable<bool> get hoverEvalModeEnabled => _hoverEvalMode;
- ValueListenable<bool> get legacyInspectorEnabled => _legacyInspectorEnabled;
ValueListenable<bool> get autoRefreshEnabled => _autoRefreshEnabled;
- ValueListenable<InspectorDetailsViewType> get defaultDetailsView =>
- _defaultDetailsView;
ListValueNotifier<String> get pubRootDirectories => _pubRootDirectories;
ValueListenable<bool> get isRefreshingPubRootDirectories =>
_pubRootDirectoriesAreBusy;
@@ -30,21 +15,13 @@
serviceConnection.inspectorService;
final _hoverEvalMode = ValueNotifier<bool>(false);
- final _legacyInspectorEnabled = ValueNotifier<bool>(false);
final _autoRefreshEnabled = ValueNotifier<bool>(true);
final _pubRootDirectories = ListValueNotifier<String>([]);
final _pubRootDirectoriesAreBusy = ValueNotifier<bool>(false);
final _busyCounter = ValueNotifier<int>(0);
- final _defaultDetailsView = ValueNotifier<InspectorDetailsViewType>(
- InspectorDetailsViewType.layoutExplorer,
- );
static const _hoverEvalModeStorageId = 'inspector.hoverEvalMode';
- static const _legacyInspectorEnabledStorageId =
- 'inspector.legacyInspectorEnabled';
static const _autoRefreshEnabledStorageId = 'inspector.autoRefreshEnabled';
- static const _defaultDetailsViewStorageId =
- 'inspector.defaultDetailsViewType';
static const _customPubRootDirectoriesStoragePrefix =
'inspector.customPubRootDirectories';
@@ -83,11 +60,8 @@
@override
Future<void> init() async {
await _initHoverEvalMode();
- await _initLegacyInspectorEnabled();
await _initAutoRefreshEnabled();
- // TODO(jacobr): consider initializing this first as it is not blocking.
_initPubRootDirectories();
- await _initDefaultInspectorDetailsView();
}
Future<void> _initHoverEvalMode() async {
@@ -98,16 +72,6 @@
);
}
- Future<void> _initLegacyInspectorEnabled() async {
- // TODO(https://github.com/flutter/devtools/issues/8667): Consider removing
- // the old 'inspector.inspectorV2Enabled' key if it is set.
- await _updateLegacyInspectorEnabled();
- _saveBooleanPreferenceChanges(
- preferenceStorageId: _legacyInspectorEnabledStorageId,
- preferenceNotifier: _legacyInspectorEnabled,
- );
- }
-
Future<void> _initAutoRefreshEnabled() async {
await _updateAutoRefreshEnabled();
_saveBooleanPreferenceChanges(
@@ -124,14 +88,6 @@
);
}
- Future<void> _updateLegacyInspectorEnabled() async {
- await _updateBooleanPreference(
- preferenceStorageId: _legacyInspectorEnabledStorageId,
- preferenceNotifier: _legacyInspectorEnabled,
- defaultValue: false,
- );
- }
-
Future<void> _updateAutoRefreshEnabled() async {
await _updateBooleanPreference(
preferenceStorageId: _autoRefreshEnabledStorageId,
@@ -164,31 +120,6 @@
preferenceNotifier.value = preferenceValue == 'true';
}
- Future<void> _initDefaultInspectorDetailsView() async {
- await _updateInspectorDetailsViewSelection();
-
- addAutoDisposeListener(_defaultDetailsView, () {
- safeUnawaited(
- storage.setValue(
- _defaultDetailsViewStorageId,
- _defaultDetailsView.value.name.toString(),
- ),
- );
- });
- }
-
- Future<void> _updateInspectorDetailsViewSelection() async {
- final inspectorDetailsView = await storage.getValue(
- _defaultDetailsViewStorageId,
- );
-
- if (inspectorDetailsView != null) {
- _defaultDetailsView.value = InspectorDetailsViewType.values.firstWhere(
- (e) => e.name.toString() == inspectorDetailsView,
- );
- }
- }
-
void _initPubRootDirectories() {
addAutoDisposeListener(
serviceConnection.serviceManager.connectedState,
@@ -245,9 +176,7 @@
_checkedFlutterPubRoot = false;
await _updateMainScriptRef();
await _updateHoverEvalMode();
- await _updateLegacyInspectorEnabled();
await loadPubRootDirectories();
- await _updateInspectorDetailsViewSelection();
}
Future<void> loadPubRootDirectories() async {
@@ -474,16 +403,7 @@
}
@visibleForTesting
- void setLegacyInspectorEnabled(bool legacyInspectorEnabled) {
- _legacyInspectorEnabled.value = legacyInspectorEnabled;
- }
-
- @visibleForTesting
void setAutoRefreshEnabled(bool autoRefreshEnabled) {
_autoRefreshEnabled.value = autoRefreshEnabled;
}
-
- void setDefaultInspectorDetailsView(InspectorDetailsViewType value) {
- _defaultDetailsView.value = value;
- }
}
diff --git a/packages/devtools_app/lib/src/shared/ui/icons.dart b/packages/devtools_app/lib/src/shared/ui/icons.dart
index 9feaa28..979cf15 100644
--- a/packages/devtools_app/lib/src/shared/ui/icons.dart
+++ b/packages/devtools_app/lib/src/shared/ui/icons.dart
@@ -13,7 +13,7 @@
import 'package:devtools_app_shared/ui.dart';
import 'package:flutter/material.dart';
-import '../../screens/inspector/layout_explorer/ui/widgets_theme.dart';
+import '../../screens/inspector_v2/layout_explorer/ui/widgets_theme.dart';
import 'colors.dart';
class CustomIcon extends StatelessWidget {
diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
index d4e8482..3a13606 100644
--- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
+++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
@@ -19,7 +19,8 @@
## Inspector updates
-TODO: Remove this section if there are not any updates.
+- Deleted the option to use the legacy inspector.
+ [#9782](https://github.com/flutter/devtools/pull/9782)
## Performance updates
diff --git a/packages/devtools_app/test/screens/inspector/diagnostics_test.dart b/packages/devtools_app/test/screens/inspector/diagnostics_test.dart
deleted file mode 100644
index 1e1bb3e..0000000
--- a/packages/devtools_app/test/screens/inspector/diagnostics_test.dart
+++ /dev/null
@@ -1,301 +0,0 @@
-// Copyright 2022 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:convert';
-
-import 'package:devtools_app/devtools_app.dart';
-import 'package:devtools_app/src/shared/ui/utils.dart';
-import 'package:devtools_app_shared/ui.dart';
-import 'package:devtools_app_shared/utils.dart';
-import 'package:devtools_test/devtools_test.dart';
-import 'package:devtools_test/helpers.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-void main() {
- group('DiagnosticsNodeDescription', () {
- final renderObjectJson = jsonDecode('''
- {
- "properties": [
- {
- "description": "horizontal",
- "name": "direction"
- },
- {
- "description": "start",
- "name": "mainAxisAlignment"
- },
- {
- "description": "max",
- "name": "mainAxisSize"
- },
- {
- "description": "center",
- "name": "crossAxisAlignment"
- },
- {
- "description": "ltr",
- "name": "textDirection"
- },
- {
- "description": "down",
- "name": "verticalDirection"
- }
- ]
- }
- ''');
- setUp(() {
- setGlobal(
- DevToolsEnvironmentParameters,
- ExternalDevToolsEnvironmentParameters(),
- );
- setGlobal(PreferencesController, PreferencesController());
- setGlobal(IdeTheme, IdeTheme());
- setGlobal(ServiceConnectionManager, FakeServiceConnectionManager());
- });
-
- group('hover eval', () {
- final nodeJson = <String, Object?>{
- 'widgetRuntimeType': 'Row',
- 'renderObject': renderObjectJson,
- 'hasChildren': false,
- 'children': [],
- };
- final inspectorService = MockInspectorObjectGroupBase();
- final diagnostic = RemoteDiagnosticsNode(
- nodeJson,
- inspectorService,
- false,
- null,
- );
- late DiagnosticsNodeDescription diagnosticsNodeDescription;
-
- setUp(() {
- preferences.inspector.setHoverEvalMode(true);
- diagnosticsNodeDescription = DiagnosticsNodeDescription(diagnostic);
- });
-
- testWidgets('can be enabled from preferences', (
- WidgetTester tester,
- ) async {
- await tester.pumpWidget(wrap(diagnosticsNodeDescription));
-
- final hoverCardTooltip =
- tester.widget(find.byType(HoverCardTooltip)) as HoverCardTooltip;
- expect(hoverCardTooltip.enabled(), true);
- });
-
- testWidgets('can be disabled from preferences', (
- WidgetTester tester,
- ) async {
- preferences.inspector.setHoverEvalMode(false);
-
- await tester.pumpWidget(wrap(diagnosticsNodeDescription));
-
- final hoverCardTooltip =
- tester.widget(find.byType(HoverCardTooltip)) as HoverCardTooltip;
- expect(hoverCardTooltip.enabled(), false);
- });
-
- testWidgets('disabled when inspector service not set', (
- WidgetTester tester,
- ) async {
- final diagnosticWithoutService = RemoteDiagnosticsNode(
- nodeJson,
- null,
- false,
- null,
- );
- diagnosticsNodeDescription = DiagnosticsNodeDescription(
- diagnosticWithoutService,
- );
-
- await tester.pumpWidget(wrap(diagnosticsNodeDescription));
-
- final hoverCardTooltip =
- tester.widget(find.byType(HoverCardTooltip)) as HoverCardTooltip;
- expect(hoverCardTooltip.enabled(), false);
- });
- });
-
- group('approximateNodeWidth', () {
- const epsilon = 7.0;
- testWidgets('property diagnostics node with name and description', (
- WidgetTester tester,
- ) async {
- final nodeJson = <String, Object?>{
- 'widgetRuntimeType': 'Row',
- 'renderObject': renderObjectJson,
- 'hasChildren': false,
- 'children': [],
- 'description':
- 'this is a showname description, which will show up after the name',
- 'showName': true,
- 'name': 'THE NAME to be shown',
- };
- final diagnosticWithoutService = RemoteDiagnosticsNode(
- nodeJson,
- null,
- true,
- null,
- );
- final diagnosticsNodeDescription = DiagnosticsNodeDescription(
- diagnosticWithoutService,
- );
-
- await tester.pumpWidget(wrap(diagnosticsNodeDescription));
-
- final approximatedWidth =
- DiagnosticsNodeDescription.approximateNodeWidth(
- diagnosticWithoutService,
- );
-
- final diagnosticsNodeFind = find.byType(DiagnosticsNodeDescription);
- // There are many rich texts, containg the name, and description.
- final allRichTexts = find
- .descendant(
- of: diagnosticsNodeFind,
- matching: find.byType(RichText),
- )
- .evaluate()
- .map((e) => e.widget as RichText);
- final measuredWidthOfAllRichTexts = allRichTexts.fold<double>(
- 0,
- (previousValue, richText) =>
- previousValue + calculateTextSpanWidth(richText.text as TextSpan),
- );
- expect(
- approximatedWidth,
- moreOrLessEquals(measuredWidthOfAllRichTexts, epsilon: epsilon),
- );
- });
-
- testWidgets('diagnostics node with icon and description', (
- WidgetTester tester,
- ) async {
- final nodeJson = <String, Object?>{
- 'widgetRuntimeType': 'Row',
- 'renderObject': renderObjectJson,
- 'hasChildren': false,
- 'description': 'This is the description',
- 'children': [],
- 'showName': false,
- };
- final diagnosticWithoutService = RemoteDiagnosticsNode(
- nodeJson,
- null,
- false,
- null,
- );
- final diagnosticsNodeDescription = DiagnosticsNodeDescription(
- diagnosticWithoutService,
- );
-
- await tester.pumpWidget(wrap(diagnosticsNodeDescription));
-
- final approximatedTextWidth =
- DiagnosticsNodeDescription.approximateNodeWidth(
- diagnosticWithoutService,
- );
-
- final diagnosticsNodeFind = find.byType(DiagnosticsNodeDescription);
- // The icon is part of the clickable width, so we include it.
- final measuredIconWidth = tester
- .getSize(
- find.descendant(
- of: diagnosticsNodeFind,
- matching: find.byType(AssetImageIcon),
- ),
- )
- .width;
-
- // There is only one rich text widget, containing the description.
- final richTextWidget =
- find
- .descendant(
- of: diagnosticsNodeFind,
- matching: find.byType(RichText),
- )
- .first
- .evaluate()
- .first
- .widget
- as RichText;
- final measuredTextWidth = calculateTextSpanWidth(
- richTextWidget.text as TextSpan,
- );
-
- expect(
- approximatedTextWidth,
- moreOrLessEquals(
- measuredTextWidth + measuredIconWidth,
- epsilon: epsilon,
- ),
- );
- });
-
- testWidgets('error node with different fontSize', (
- WidgetTester tester,
- ) async {
- // Nodes with normal levels default to using the default fontSize, so
- // using an error level node allows us to test different font sizes.
- final nodeJson = <String, Object?>{
- 'widgetRuntimeType': 'Row',
- 'renderObject': renderObjectJson,
- 'hasChildren': false,
- 'children': [],
- 'description':
- 'this is a showname description, which will show up after the name',
- 'showName': true,
- 'name': 'THE NAME to be shown',
- 'level': 'error',
- };
- final diagnosticWithoutService = RemoteDiagnosticsNode(
- nodeJson,
- null,
- false,
- null,
- );
-
- //Use a textStyle that is much larger than the normal style
- const textStyle = TextStyle(fontSize: 24.0, fontFamily: 'Roboto');
- final diagnosticsNodeDescription = DiagnosticsNodeDescription(
- diagnosticWithoutService,
- style: textStyle,
- );
-
- await tester.pumpWidget(wrap(diagnosticsNodeDescription));
-
- final approximatedWidth =
- DiagnosticsNodeDescription.approximateNodeWidth(
- diagnosticWithoutService,
- );
-
- final diagnosticsNodeFind = find.byType(DiagnosticsNodeDescription);
- // There are many rich texts, containg the name, and description.
- final allRichTexts = find
- .descendant(
- of: diagnosticsNodeFind,
- matching: find.byType(RichText),
- )
- .evaluate()
- .map((e) => e.widget as RichText);
-
- final measuredWidthOfAllRichTexts = allRichTexts.fold<double>(0, (
- previousValue,
- richText,
- ) {
- final originalTextSpan = richText.text as TextSpan;
-
- return previousValue + calculateTextSpanWidth(originalTextSpan);
- });
-
- expect(
- approximatedWidth,
- moreOrLessEquals(measuredWidthOfAllRichTexts, epsilon: epsilon),
- );
- });
- });
- });
-}
diff --git a/packages/devtools_app/test/screens/inspector/inspector_error_navigator_test.dart b/packages/devtools_app/test/screens/inspector/inspector_error_navigator_test.dart
deleted file mode 100644
index b546faa..0000000
--- a/packages/devtools_app/test/screens/inspector/inspector_error_navigator_test.dart
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2021 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:collection';
-
-import 'package:devtools_app/devtools_app.dart';
-import 'package:devtools_app_shared/ui.dart';
-import 'package:devtools_app_shared/utils.dart';
-import 'package:devtools_test/devtools_test.dart';
-import 'package:devtools_test/helpers.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-void main() {
- setUp(() {
- setGlobal(ServiceConnectionManager, FakeServiceConnectionManager());
- setGlobal(IdeTheme, IdeTheme());
- });
-
- group('Inspector Error Navigator', () {
- Future<void> testNavigate(
- WidgetTester tester, {
- required IconData tapIcon,
- required int errorCount,
- int? startIndex,
- int? expectedIndex,
- }) async {
- var index = startIndex;
- final navigator = ErrorNavigator(
- errorIndex: index,
- errors: _generateErrors(errorCount),
- onSelectError: (newIndex) => index = newIndex,
- );
-
- await tester.pumpWidget(wrap(navigator));
- await tester.tap(find.byIcon(tapIcon));
-
- expect(index, equals(expectedIndex));
- }
-
- testWidgets('shows count when no selection', (WidgetTester tester) async {
- await tester.pumpWidget(
- wrap(
- ErrorNavigator(
- errorIndex: null,
- errors: _generateErrors(10),
- onSelectError: (_) {},
- ),
- ),
- );
- expect(find.text('Errors: 10'), findsOneWidget);
- });
-
- testWidgets('shows x/y when selected error', (WidgetTester tester) async {
- await tester.pumpWidget(
- wrap(
- ErrorNavigator(
- errorIndex: 0,
- errors: _generateErrors(10),
- onSelectError: (_) {},
- ),
- ),
- );
- expect(find.text('Error 1/10'), findsOneWidget);
- });
-
- testWidgets(
- 'can navigate forwards',
- // Intentionally unawaited.
- // ignore: discarded_futures
- (WidgetTester tester) => testNavigate(
- tester,
- tapIcon: Icons.keyboard_arrow_down,
- errorCount: 10,
- startIndex: 5,
- expectedIndex: 6,
- ),
- );
-
- testWidgets(
- 'can navigate backwards',
- // Intentionally unawaited.
- // ignore: discarded_futures
- (WidgetTester tester) => testNavigate(
- tester,
- tapIcon: Icons.keyboard_arrow_up,
- errorCount: 10,
- startIndex: 5,
- expectedIndex: 4,
- ),
- );
-
- testWidgets(
- 'wraps forwards',
- // Intentionally unawaited.
- // ignore: discarded_futures
- (WidgetTester tester) => testNavigate(
- tester,
- tapIcon: Icons.keyboard_arrow_down,
- errorCount: 10,
- startIndex: 9,
- expectedIndex: 0,
- ),
- );
-
- testWidgets(
- 'wraps backwards',
- // Intentionally unawaited.
- // ignore: discarded_futures
- (WidgetTester tester) => testNavigate(
- tester,
- tapIcon: Icons.keyboard_arrow_up,
- errorCount: 10,
- startIndex: 0,
- expectedIndex: 9,
- ),
- );
- });
-}
-
-LinkedHashMap<String, InspectableWidgetError> _generateErrors(int count) =>
- LinkedHashMap<String, InspectableWidgetError>.fromEntries(
- List.generate(
- count,
- (index) => MapEntry(
- 'error-$index',
- InspectableWidgetError('Error $index', 'error-$index'),
- ),
- ),
- );
diff --git a/packages/devtools_app/test/screens/inspector/inspector_integration_test.dart b/packages/devtools_app/test/screens/inspector/inspector_integration_test.dart
deleted file mode 100644
index 659959a..0000000
--- a/packages/devtools_app/test/screens/inspector/inspector_integration_test.dart
+++ /dev/null
@@ -1,483 +0,0 @@
-// Copyright 2020 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app/devtools_app.dart';
-import 'package:devtools_app_shared/utils.dart';
-import 'package:devtools_test/helpers.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import '../../test_infra/flutter_test_driver.dart' show FlutterRunConfiguration;
-import '../../test_infra/flutter_test_environment.dart';
-import '../../test_infra/matchers/matchers.dart';
-
-// This is a bit conservative to ensure we do not get flakes due to
-// slow interactions with the VM Service. This delay could likely be
-// reduced to under 1 second without introducing flakes.
-const inspectorChangeSettleTime = Duration(seconds: 2);
-
-void main() {
- const windowSize = Size(2600.0, 1200.0);
- // We need to use real async in this test so we need to use this binding.
- initializeLiveTestWidgetsFlutterBindingWithAssets();
-
- late FlutterTestEnvironment env;
-
- Future<void> resetInspectorSelection() async {
- final service = serviceConnection.inspectorService;
- if (env.reuseTestEnvironment) {
- // Ensure the previous test did not set the selection on the device.
- // TODO(jacobr): add a proper method to WidgetInspectorService that does
- // this. setSelection currently ignores null selection requests which is
- // a misfeature.
- await service!.inspectorLibrary.eval(
- 'WidgetInspectorService.instance.selection.clear()',
- isAlive: null,
- );
- }
- }
-
- setUp(() async {
- await env.setupEnvironment();
- setGlobal(BannerMessagesController, BannerMessagesController());
- // Ensure the legacy inspector is enabled:
- preferences.inspector.setLegacyInspectorEnabled(true);
- });
-
- group('screenshot tests', () {
- setUpAll(() {
- env = FlutterTestEnvironment(
- const FlutterRunConfiguration(withDebugger: true),
- );
- env.afterEverySetup = resetInspectorSelection;
- });
-
- tearDownAll(() async {
- await env.tearDownEnvironment(force: true);
- });
-
- testWidgetsWithWindowSize('navigation', windowSize, (
- WidgetTester tester,
- ) async {
- await env.setupEnvironment();
- expect(serviceConnection.serviceManager.service, equals(env.service));
- expect(serviceConnection.serviceManager.isolateManager, isNotNull);
-
- final screen = InspectorScreen();
- await tester.pumpWidget(
- wrapWithInspectorControllers(Builder(builder: screen.build)),
- );
- await tester.pump(const Duration(seconds: 1));
- final InspectorScreenBodyState state = tester.state(
- find.byType(InspectorScreenBody),
- );
- final controller = state.controller;
- while (!controller.flutterAppFrameReady) {
- await controller.maybeLoadUI();
- await tester.pumpAndSettle();
- }
- // Give time for the initial animation to complete.
- await tester.pumpAndSettle(inspectorChangeSettleTime);
- await expectLater(
- find.byType(InspectorScreenBody),
- matchesDevToolsGolden(
- '../../test_infra/goldens/integration_inspector_initial_load.png',
- ),
- );
-
- // Click on the Center widget (row index #5)
- await tester.tap(find.richText('Center'));
- await tester.pumpAndSettle(inspectorChangeSettleTime);
- await expectLater(
- find.byType(InspectorScreenBody),
- matchesDevToolsGolden(
- '../../test_infra/goldens/integration_inspector_select_center.png',
- ),
- );
-
- // Select the details tree.
- await tester.tap(
- find.text(InspectorDetailsViewType.widgetDetailsTree.key),
- );
- await tester.pumpAndSettle(inspectorChangeSettleTime);
- await expectLater(
- find.byType(InspectorScreenBody),
- matchesDevToolsGolden(
- '../../test_infra/goldens/integration_inspector_select_center_details_tree.png',
- ),
- // Implementation widgets from Flutter framework are not guaranteed to
- // be stable.
- skip: 'https://github.com/flutter/flutter/issues/172037',
- );
-
- // Select the RichText row.
- await tester.tap(find.richText('RichText'));
- await tester.pumpAndSettle(inspectorChangeSettleTime);
- await expectLater(
- find.byType(InspectorScreenBody),
- matchesDevToolsGolden(
- '../../test_infra/goldens/integration_inspector_richtext_selected.png',
- ),
- // Implementation widgets from Flutter framework are not guaranteed to
- // be stable.
- skip: 'https://github.com/flutter/flutter/issues/172037',
- );
-
- // Test hovering over the icon shown when a property has its default
- // value.
- // TODO(jacobr): support tooltips in the Flutter version of the inspector.
- // https://github.com/flutter/devtools/issues/2570.
- // For example, verify that the tooltip hovering over the default value
- // icons is "Default value".
- // Test selecting a widget.
-
- // Two 'Scaffold's: a breadcrumb and an actual tree item
- expect(find.richText('Scaffold'), findsNWidgets(2));
- // select Scaffold widget in summary tree.
- await tester.tap(find.richText('Scaffold').last);
- await tester.pumpAndSettle(inspectorChangeSettleTime);
- // This tree is huge. If there is a change to package:flutter it may
- // change. If this happens don't panic and rebaseline the golden.
- await expectLater(
- find.byType(InspectorScreenBody),
- matchesDevToolsGolden(
- '../../test_infra/goldens/integration_inspector_scaffold_selected.png',
- ),
- // Implementation widgets from Flutter framework are not guaranteed to
- // be stable.
- skip: 'https://github.com/flutter/flutter/issues/172037',
- );
-
- // The important thing about this is that the details tree should scroll
- // instead of re-rooting as the selected row is already visible in the
- // details tree.
- await tester.tap(find.richText('AnimatedPhysicalModel'));
- await tester.pumpAndSettle(inspectorChangeSettleTime);
- await expectLater(
- find.byType(InspectorScreenBody),
- matchesDevToolsGolden(
- '../../test_infra/goldens/integration_animated_physical_model_selected.png',
- ),
- // Implementation widgets from Flutter framework are not guaranteed to
- // be stable.
- skip: 'https://github.com/flutter/flutter/issues/172037',
- );
-
- await env.tearDownEnvironment();
- });
-
- // TODO(jacobr): convert these tests to screenshot tests like the initial
- // state test.
- /*
-
-
- // Intentionally trigger multiple quick navigate action to ensure that
- // multiple quick navigation commands in a row do not trigger race
- // conditions getting out of order updates from the server.
- tree.navigateDown();
- tree.navigateDown();
- tree.navigateDown();
- await detailsTree.nextUiFrame;
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R][root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¼[S]Scaffold\n'
- ' ├───▼[C]Center\n'
- ' │ [/icons/inspector/textArea.png]Text\n'
- ' └─▼[A]AppBar <-- selected\n'
- ' [/icons/inspector/textArea.png]Text\n',
- ),
- );
- // Make sure we don't go off the bottom of the tree.
- tree.navigateDown();
- tree.navigateDown();
- tree.navigateDown();
- tree.navigateDown();
- tree.navigateDown();
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R][root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¼[S]Scaffold\n'
- ' ├───▼[C]Center\n'
- ' │ [/icons/inspector/textArea.png]Text\n'
- ' └─▼[A]AppBar\n'
- ' [/icons/inspector/textArea.png]Text <-- selected\n',
- ),
- );
- tree.navigateUp();
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R][root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¼[S]Scaffold\n'
- ' ├───▼[C]Center\n'
- ' │ [/icons/inspector/textArea.png]Text\n'
- ' └─▼[A]AppBar <-- selected\n'
- ' [/icons/inspector/textArea.png]Text\n',
- ),
- );
- tree.navigateLeft();
- await detailsTree.nextUiFrame;
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R][root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¼[S]Scaffold\n'
- ' ├───▼[C]Center\n'
- ' │ [/icons/inspector/textArea.png]Text\n'
- ' └─▶[A]AppBar <-- selected\n',
- ),
- );
- tree.navigateLeft();
- // First navigate left goes to the parent.
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R][root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¼[S]Scaffold <-- selected\n'
- ' ├───▼[C]Center\n'
- ' │ [/icons/inspector/textArea.png]Text\n'
- ' └─▶[A]AppBar\n',
- ),
- );
- tree.navigateLeft();
- // Next navigate left closes the parent.
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R][root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¶[S]Scaffold <-- selected\n',
- ),
- );
-
- tree.navigateRight();
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R][root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¼[S]Scaffold <-- selected\n'
- ' ├───▼[C]Center\n'
- ' │ [/icons/inspector/textArea.png]Text\n'
- ' └─▶[A]AppBar\n',
- ),
- );
-
- // Node is already expanded so this is equivalent to navigate down.
- tree.navigateRight();
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R][root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¼[S]Scaffold\n'
- ' ├───▼[C]Center <-- selected\n'
- ' │ [/icons/inspector/textArea.png]Text\n'
- ' └─▶[A]AppBar\n',
- ),
- );
-
- await detailsTree.nextUiFrame;
-
- // Make sure the details and main trees have not gotten out of sync.
- expect(
- detailsTree.toStringDeep(hidePropertyLines: true),
- equalsIgnoringHashCodes('â–¼[C]Center <-- selected\n'
- '└─▼[/icons/inspector/textArea.png]Text\n'
- ' └─▼[/icons/inspector/textArea.png]RichText\n'),
- );
-
- await env.tearDownEnvironment();
- });
- */
-
- // TODO(jacobr): uncomment hotReload test once the hot reload test is not
- // flaky. https://github.com/flutter/devtools/issues/642
- /*
- test('hotReload', () async {
- if (flutterVersion == '1.2.1') {
- // This test can be flaky in Flutter 1.2.1 because of
- // https://github.com/dart-lang/sdk/issues/33838
- // so we just skip it. This block of code can be removed after the next
- // stable flutter release.
- // TODO(dantup): Remove this.
- return;
- }
- await env.setupEnvironment();
-
- await serviceManager.performHotReload();
- // Ensure the inspector does not fall over and die after a hot reload.
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R][root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¼[S]Scaffold\n'
- ' ├───▼[C]Center\n'
- ' │ [/icons/inspector/textArea.png]Text <-- selected\n'
- ' └─▼[A]AppBar\n'
- ' [/icons/inspector/textArea.png]Text\n',
- ),
- );
-
- // TODO(jacobr): would be nice to have some tests that trigger a hot
- // reload that actually changes app state in a meaningful way.
-
- await env.tearDownEnvironment();
- });
- */
- // TODO(jacobr): uncomment out the hotRestart tests once
- // https://github.com/flutter/devtools/issues/337 is fixed.
- /*
- test('hotRestart', () async {
- await env.setupEnvironment();
-
- // The important thing about this is that the details tree should scroll
- // instead of re-rooting as the selected row is already visible in the
- // details tree.
- simulateRowClick(tree, rowIndex: 4);
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R]root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¼[S]Scaffold\n'
- ' ├───▼[C]Center <-- selected\n'
- ' │ ▼[/icons/inspector/textArea.png]Text\n'
- ' └─▼[A]AppBar\n'
- ' â–¼[/icons/inspector/textArea.png]Text\n',
- ),
- );
-
- /// After the hot restart some existing calls to the vm service may
- /// timeout and that is ok.
- serviceManager.manager.service.doNotWaitForPendingFuturesBeforeExit();
-
- await serviceManager.performHotRestart();
- // The isolate starts out paused on a hot restart so we have to resume
- // it manually to make the test pass.
-
- await serviceManager.manager.service
- .resume(serviceManager.isolateManager.selectedIsolate.id);
-
- // First UI transition is to an empty tree.
- await detailsTree.nextUiFrame;
- expect(tree.toStringDeep(), equalsIgnoringHashCodes('<empty>\n'));
-
- // Notice that the selection has been lost due to the hot restart.
- await detailsTree.nextUiFrame;
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R][root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¼[S]Scaffold\n'
- ' ├───▼[C]Center\n'
- ' │ ▼[/icons/inspector/textArea.png]Text\n'
- ' └─▼[A]AppBar\n'
- ' â–¼[/icons/inspector/textArea.png]Text\n',
- ),
- );
-
- // Verify that the selection can actually be changed after a restart.
- simulateRowClick(tree, rowIndex: 4);
- expect(
- tree.toStringDeep(),
- equalsIgnoringHashCodes(
- 'â–¼[R][root]\n'
- ' â–¼[M]MyApp\n'
- ' â–¼[M]MaterialApp\n'
- ' â–¼[S]Scaffold\n'
- ' ├───▼[C]Center <-- selected\n'
- ' │ ▼[/icons/inspector/textArea.png]Text\n'
- ' └─▼[A]AppBar\n'
- ' â–¼[/icons/inspector/textArea.png]Text\n',
- ),
- );
- await env.tearDownEnvironment();
- });
-*/
- });
-
- group('widget errors', () {
- setUpAll(() async {
- env = FlutterTestEnvironment(
- testAppDirectory: 'test/test_infra/fixtures/inspector_app',
- const FlutterRunConfiguration(withDebugger: true),
- );
- await env.setupEnvironment(
- config: const FlutterRunConfiguration(
- withDebugger: true,
- entryScript: 'lib/overflow_errors.dart',
- ),
- );
- env.afterEverySetup = resetInspectorSelection;
- // Enable the legacy inspector.
- preferences.inspector.setLegacyInspectorEnabled(true);
- });
-
- testWidgetsWithWindowSize('show navigator and error labels', windowSize, (
- WidgetTester tester,
- ) async {
- expect(serviceConnection.serviceManager.service, equals(env.service));
- expect(serviceConnection.serviceManager.isolateManager, isNotNull);
-
- final screen = InspectorScreen();
- await tester.pumpWidget(
- wrapWithInspectorControllers(Builder(builder: screen.build)),
- );
- await tester.pumpAndSettle(const Duration(seconds: 1));
- final InspectorScreenBodyState state = tester.state(
- find.byType(InspectorScreenBody),
- );
- final controller = state.controller;
- while (!controller.flutterAppFrameReady) {
- await controller.maybeLoadUI();
- await tester.pumpAndSettle();
- }
- await env.flutter!.hotReload();
- // Give time for the initial animation to complete.
- await tester.pumpAndSettle(inspectorChangeSettleTime);
- await expectLater(
- find.byType(InspectorScreenBody),
- matchesDevToolsGolden(
- '../../test_infra/goldens/integration_inspector_errors_1_initial_load.png',
- ),
- );
-
- // Navigate so one of the errors is selected.
- for (var i = 0; i < 2; i++) {
- await tester.tap(find.byIcon(Icons.keyboard_arrow_down));
- await tester.pumpAndSettle(inspectorChangeSettleTime);
- }
- await expectLater(
- find.byType(InspectorScreenBody),
- matchesDevToolsGolden(
- '../../test_infra/goldens/integration_inspector_errors_2_error_selected.png',
- ),
- );
-
- await env.tearDownEnvironment();
- });
- });
-}
diff --git a/packages/devtools_app/test/screens/inspector/inspector_screen_test.dart b/packages/devtools_app/test/screens/inspector/inspector_screen_test.dart
deleted file mode 100644
index 0a09554..0000000
--- a/packages/devtools_app/test/screens/inspector/inspector_screen_test.dart
+++ /dev/null
@@ -1,374 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-// Fake construction requires number of unawaited calls.
-// ignore_for_file: discarded_futures
-
-import 'dart:convert';
-
-import 'package:devtools_app/devtools_app.dart';
-import 'package:devtools_app/src/screens/inspector/layout_explorer/flex/flex.dart';
-import 'package:devtools_app/src/screens/inspector/layout_explorer/layout_explorer.dart';
-import 'package:devtools_app/src/screens/inspector_shared/inspector_settings_dialog.dart';
-import 'package:devtools_app/src/service/service_extensions.dart' as extensions;
-import 'package:devtools_app_shared/ui.dart';
-import 'package:devtools_app_shared/utils.dart';
-import 'package:devtools_test/devtools_test.dart';
-import 'package:devtools_test/helpers.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart' hide Fake;
-import 'package:mockito/mockito.dart';
-
-import '../../test_infra/flutter_test_storage.dart';
-
-void main() {
- final screen = InspectorScreen();
-
- late FakeServiceConnectionManager fakeServiceConnection;
- late FakeServiceExtensionManager fakeExtensionManager;
- const windowSize = Size(2600.0, 1200.0);
-
- final debuggerController = createMockDebuggerControllerWithDefaults();
-
- Widget buildInspectorScreen() {
- return wrapWithControllers(
- Builder(builder: screen.build),
- debugger: debuggerController,
- inspector: InspectorScreenController(),
- );
- }
-
- setUp(() {
- fakeServiceConnection = FakeServiceConnectionManager();
- fakeExtensionManager =
- fakeServiceConnection.serviceManager.serviceExtensionManager;
- mockConnectedApp(fakeServiceConnection.serviceManager.connectedApp!);
- when(
- fakeServiceConnection.errorBadgeManager.errorCountNotifier('inspector'),
- ).thenReturn(ValueNotifier<int>(0));
-
- setGlobal(
- DevToolsEnvironmentParameters,
- ExternalDevToolsEnvironmentParameters(),
- );
- setGlobal(ServiceConnectionManager, fakeServiceConnection);
- setGlobal(IdeTheme, IdeTheme());
- setGlobal(PreferencesController, PreferencesController());
- setGlobal(Storage, FlutterTestStorage());
- setGlobal(NotificationService, NotificationService());
- setGlobal(BannerMessagesController, BannerMessagesController());
- fakeServiceConnection.consoleService.ensureServiceInitialized();
- // Enable the legacy inspector:
- preferences.inspector.setLegacyInspectorEnabled(true);
- });
-
- Future<void> mockExtensions() async {
- fakeExtensionManager.extensionValueOnDevice = {
- extensions.toggleSelectWidgetMode.extension: true,
- extensions.enableOnDeviceInspector.extension: true,
- extensions.toggleOnDeviceWidgetInspector.extension: true,
- extensions.debugPaint.extension: false,
- };
- await fakeExtensionManager.fakeAddServiceExtension(
- extensions.toggleOnDeviceWidgetInspector.extension,
- );
- await fakeExtensionManager.fakeAddServiceExtension(
- extensions.toggleSelectWidgetMode.extension,
- );
- await fakeExtensionManager.fakeAddServiceExtension(
- extensions.enableOnDeviceInspector.extension,
- );
- await fakeExtensionManager.fakeAddServiceExtension(
- extensions.debugPaint.extension,
- );
- await fakeExtensionManager.fakeFrame();
- }
-
- void mockNoExtensionsAvailable() {
- fakeExtensionManager.extensionValueOnDevice = {
- extensions.toggleOnDeviceWidgetInspector.extension: true,
- extensions.toggleSelectWidgetMode.extension: false,
- extensions.debugPaint.extension: false,
- };
- // Don't actually send any events to the client indicating that service
- // extensions are avaiable.
- fakeExtensionManager.fakeFrame();
- }
-
- testWidgetsWithWindowSize('builds its tab', windowSize, (
- WidgetTester tester,
- ) async {
- await tester.pumpWidget(buildInspectorScreen());
- await tester.pumpAndSettle();
- expect(find.byType(InspectorScreenBody), findsOneWidget);
- });
-
- group('Widget Errors', () {
- // Display of error navigator/indicators is tested by a golden in
- // inspector_integration_test.dart
-
- testWidgetsWithWindowSize(
- 'does not render error navigator if no errors',
- windowSize,
- (WidgetTester tester) async {
- await tester.pumpWidget(buildInspectorScreen());
- expect(find.byType(ErrorNavigator), findsNothing);
- },
- );
- });
-
- testWidgetsWithWindowSize('builds with no data', windowSize, (
- WidgetTester tester,
- ) async {
- // Make sure the window is wide enough to display description text.
-
- await tester.pumpWidget(buildInspectorScreen());
- expect(find.byType(InspectorScreenBody), findsOneWidget);
- expect(find.byTooltip('Refresh Tree'), findsOneWidget);
- expect(find.text(extensions.debugPaint.title), findsOneWidget);
- // Make sure there is not an overflow if the window is narrow.
- // TODO(jacobr): determine why there are overflows in the test environment
- // but not on the actual device for this cae.
- // await setWindowSize(const Size(1000.0, 1200.0));
- // Verify that description text is no-longer shown.
- // expect(find.text(extensions.debugPaint.description), findsOneWidget);
- });
-
- testWidgetsWithWindowSize(
- 'Test toggling service extension buttons',
- windowSize,
- (WidgetTester tester) async {
- await mockExtensions();
- expect(
- fakeExtensionManager.extensionValueOnDevice[extensions
- .debugPaint
- .extension],
- isFalse,
- );
- expect(
- fakeExtensionManager.extensionValueOnDevice[extensions
- .toggleOnDeviceWidgetInspector
- .extension],
- isTrue,
- );
-
- await tester.pumpWidget(buildInspectorScreen());
-
- expect(
- fakeExtensionManager.extensionValueOnDevice[extensions
- .toggleSelectWidgetMode
- .extension],
- isTrue,
- );
-
- // We need a frame to find out that the service extension state has changed.
- expect(find.byType(InspectorScreenBody), findsOneWidget);
- expect(
- find.text(extensions.toggleSelectWidgetMode.title),
- findsOneWidget,
- );
- expect(find.text(extensions.debugPaint.title), findsOneWidget);
- await tester.pump();
- await tester.tap(find.text(extensions.toggleSelectWidgetMode.title));
- expect(
- fakeExtensionManager.extensionValueOnDevice[extensions
- .toggleSelectWidgetMode
- .extension],
- isFalse,
- );
- // Verify the other service extension's state hasn't changed.
- expect(
- fakeExtensionManager.extensionValueOnDevice[extensions
- .debugPaint
- .extension],
- isFalse,
- );
-
- await tester.tap(find.text(extensions.toggleSelectWidgetMode.title));
- expect(
- fakeExtensionManager.extensionValueOnDevice[extensions
- .toggleSelectWidgetMode
- .extension],
- isTrue,
- );
-
- await tester.tap(find.text(extensions.debugPaint.title));
- expect(
- fakeExtensionManager.extensionValueOnDevice[extensions
- .debugPaint
- .extension],
- isTrue,
- );
- },
- );
-
- testWidgetsWithWindowSize(
- 'Test toggling service extension buttons with no extensions available',
- windowSize,
- (WidgetTester tester) async {
- mockNoExtensionsAvailable();
- expect(
- fakeExtensionManager.extensionValueOnDevice[extensions
- .debugPaint
- .extension],
- isFalse,
- );
- expect(
- fakeExtensionManager.extensionValueOnDevice[extensions
- .toggleOnDeviceWidgetInspector
- .extension],
- isTrue,
- );
-
- await tester.pumpWidget(buildInspectorScreen());
- await tester.pump();
- expect(find.byType(InspectorScreenBody), findsOneWidget);
- expect(
- find.text(extensions.toggleOnDeviceWidgetInspector.title),
- findsOneWidget,
- );
- expect(find.text(extensions.debugPaint.title), findsOneWidget);
- await tester.pump();
-
- await tester.tap(
- find.text(extensions.toggleOnDeviceWidgetInspector.title),
- );
- // Verify the service extension state has not changed.
- expect(
- fakeExtensionManager.extensionValueOnDevice[extensions
- .toggleOnDeviceWidgetInspector
- .extension],
- isTrue,
- );
- await tester.tap(
- find.text(extensions.toggleOnDeviceWidgetInspector.title),
- );
- // Verify the service extension state has not changed.
- expect(
- fakeExtensionManager.extensionValueOnDevice[extensions
- .toggleOnDeviceWidgetInspector
- .extension],
- isTrue,
- );
-
- // TODO(jacobr): also verify that the service extension buttons look
- // visually disabled.
- },
- );
-
- group('LayoutDetailsTab', () {
- final renderObjectJson = jsonDecode('''
- {
- "properties": [
- {
- "description": "horizontal",
- "name": "direction"
- },
- {
- "description": "start",
- "name": "mainAxisAlignment"
- },
- {
- "description": "max",
- "name": "mainAxisSize"
- },
- {
- "description": "center",
- "name": "crossAxisAlignment"
- },
- {
- "description": "ltr",
- "name": "textDirection"
- },
- {
- "description": "down",
- "name": "verticalDirection"
- }
- ]
- }
- ''');
- final diagnostic = RemoteDiagnosticsNode(
- <String, Object?>{
- 'widgetRuntimeType': 'Row',
- 'renderObject': renderObjectJson,
- 'hasChildren': false,
- 'children': [],
- },
- null,
- false,
- null,
- );
- final treeNode = InspectorTreeNode()..diagnostic = diagnostic;
- testWidgetsWithWindowSize(
- 'should render StoryOfYourFlexWidget',
- windowSize,
- (WidgetTester tester) async {
- final controller = TestInspectorController()..setSelectedNode(treeNode);
- await tester.pumpWidget(
- MaterialApp(
- home: Scaffold(body: LayoutExplorerTab(controller: controller)),
- ),
- );
- expect(find.byType(FlexLayoutExplorerWidget), findsOneWidget);
- },
- );
-
- testWidgetsWithWindowSize(
- 'should listen to controller selection event',
- windowSize,
- (WidgetTester tester) async {
- final controller = TestInspectorController();
- await tester.pumpWidget(
- MaterialApp(
- home: Scaffold(body: LayoutExplorerTab(controller: controller)),
- ),
- );
- expect(find.byType(FlexLayoutExplorerWidget), findsNothing);
- controller.setSelectedNode(treeNode);
- await tester.pumpAndSettle();
- expect(find.byType(FlexLayoutExplorerWidget), findsOneWidget);
- },
- );
- });
-
- group('FlutterInspectorSettingsDialog', () {
- const startingHoverEvalModeValue = false;
-
- setUp(() {
- preferences.inspector.setHoverEvalMode(startingHoverEvalModeValue);
- });
-
- testWidgetsWithWindowSize(
- 'can update hover inspection setting',
- windowSize,
- (WidgetTester tester) async {
- await tester.pumpWidget(buildInspectorScreen());
-
- await tester.tap(find.byType(SettingsOutlinedButton));
- await tester.pumpAndSettle();
- expect(find.byType(FlutterInspectorSettingsDialog), findsOneWidget);
-
- final hoverCheckBoxSetting = find.ancestor(
- of: find.richTextContaining('Enable hover inspection'),
- matching: find.byType(CheckboxSetting),
- );
- final hoverModeCheckBox = find.descendant(
- of: hoverCheckBoxSetting,
- matching: find.byType(NotifierCheckbox),
- );
- await tester.tap(hoverModeCheckBox);
- await tester.pumpAndSettle();
- expect(
- preferences.inspector.hoverEvalModeEnabled.value,
- !startingHoverEvalModeValue,
- );
- },
- );
- });
-
- // TODO(jacobr): add screenshot tests that connect to a test application
- // in the same way the inspector_controller test does today and take golden
- // images. Alternately: support an offline inspector mode and add tests of
- // that mode which would enable faster tests that run as unittests.
-}
diff --git a/packages/devtools_app/test/screens/inspector/inspector_tree_test.dart b/packages/devtools_app/test/screens/inspector/inspector_tree_test.dart
deleted file mode 100644
index 17d1b12..0000000
--- a/packages/devtools_app/test/screens/inspector/inspector_tree_test.dart
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright 2021 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app/devtools_app.dart';
-import 'package:devtools_app/src/screens/inspector/inspector_breadcrumbs.dart';
-import 'package:devtools_app_shared/ui.dart';
-import 'package:devtools_app_shared/utils.dart';
-import 'package:devtools_test/devtools_test.dart';
-import 'package:devtools_test/helpers.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart' hide Fake;
-import 'package:mockito/mockito.dart';
-
-import 'utils/inspector_tree.dart';
-
-void main() {
- late FakeServiceConnectionManager fakeServiceConnection;
- late InspectorController inspectorController;
-
- setUp(() {
- fakeServiceConnection = FakeServiceConnectionManager();
- final app = fakeServiceConnection.serviceManager.connectedApp!;
- when(app.isFlutterAppNow).thenReturn(true);
- when(app.isProfileBuildNow).thenReturn(false);
-
- setGlobal(
- DevToolsEnvironmentParameters,
- ExternalDevToolsEnvironmentParameters(),
- );
- setGlobal(ServiceConnectionManager, fakeServiceConnection);
- setGlobal(IdeTheme, IdeTheme());
- setGlobal(PreferencesController, PreferencesController());
- setGlobal(NotificationService, NotificationService());
- setGlobal(BreakpointManager, BreakpointManager());
- mockConnectedApp(fakeServiceConnection.serviceManager.connectedApp!);
-
- inspectorController = InspectorController(
- inspectorTree: InspectorTreeController(),
- detailsTree: InspectorTreeController(),
- treeType: FlutterTreeType.widget,
- )..firstInspectorTreeLoadCompleted = true;
- });
-
- Future<void> pumpInspectorTree(
- WidgetTester tester, {
- required InspectorTreeController treeController,
- bool isSummaryTree = false,
- }) async {
- final debuggerController = DebuggerController();
- final summaryTreeController = isSummaryTree
- ? null
- : InspectorTreeController();
- await tester.pumpWidget(
- wrapWithControllers(
- debugger: debuggerController,
- InspectorTree(
- controller: inspectorController,
- treeController: treeController,
- summaryTreeController: summaryTreeController,
- isSummaryTree: isSummaryTree,
- ),
- ),
- );
- await tester.pumpAndSettle();
- }
-
- group('InspectorTreeController', () {
- testWidgets('Row with negative index regression test', (
- WidgetTester tester,
- ) async {
- final treeController = InspectorTreeController()
- ..config = InspectorTreeConfig(
- onNodeAdded: (_, _) {},
- onClientActiveChange: (_) {},
- );
- await pumpInspectorTree(tester, treeController: treeController);
-
- expect(treeController.getRow(const Offset(0, -100.0)), isNull);
- expect(treeController.getRowOffset(-1), equals(0));
-
- expect(treeController.getRow(const Offset(0, 0.0)), isNull);
- expect(treeController.getRowOffset(0), equals(0));
-
- treeController.root = InspectorTreeNode()
- ..appendChild(InspectorTreeNode());
-
- await pumpInspectorTree(tester, treeController: treeController);
-
- expect(treeController.getRow(const Offset(0, -20))!.index, 0);
- expect(treeController.getRowOffset(-1), equals(0));
- expect(treeController.getRow(const Offset(0, 0.0)), isNotNull);
- expect(treeController.getRowOffset(0), equals(0));
-
- // This operation would previously throw an exception in debug builds
- // and infinite loop in release builds.
- treeController.scrollToRect(const Rect.fromLTWH(0, -20, 100, 100));
- });
- });
-
- group('Inspector tree content preview', () {
- testWidgets('Shows simple text preview', (WidgetTester tester) async {
- final diagnosticNode = await widgetToInspectorTreeDiagnosticsNode(
- widget: const Text('Content'),
- tester: tester,
- );
-
- final treeController = inspectorTreeControllerFromNode(diagnosticNode);
- await pumpInspectorTree(tester, treeController: treeController);
-
- expect(find.richText('Text: "Content"'), findsOneWidget);
- });
-
- testWidgets('Shows preview from Text.rich', (WidgetTester tester) async {
- final diagnosticNode = await widgetToInspectorTreeDiagnosticsNode(
- widget: const Text.rich(
- TextSpan(
- children: [
- TextSpan(text: 'Rich '),
- TextSpan(text: 'text'),
- ],
- ),
- ),
- tester: tester,
- );
-
- final treeController = inspectorTreeControllerFromNode(diagnosticNode);
- await pumpInspectorTree(tester, treeController: treeController);
-
- expect(find.richText('Text: "Rich text"'), findsOneWidget);
- });
-
- testWidgets('Strips new lines from text preview', (
- WidgetTester tester,
- ) async {
- final diagnosticNode = await widgetToInspectorTreeDiagnosticsNode(
- widget: const Text('Multiline\ntext\n\ncontent'),
- tester: tester,
- );
-
- final treeController = inspectorTreeControllerFromNode(diagnosticNode);
- await pumpInspectorTree(tester, treeController: treeController);
-
- expect(find.richText('Text: "Multiline text content"'), findsOneWidget);
- });
-
- testWidgets('Shows breadcrumbs in Widget detail tree', (tester) async {
- final diagnosticNode = await widgetToInspectorTreeDiagnosticsNode(
- widget: const Text('Hello'),
- tester: tester,
- );
-
- final treeController = inspectorTreeControllerFromNode(diagnosticNode);
- await pumpInspectorTree(tester, treeController: treeController);
-
- expect(find.byType(InspectorBreadcrumbNavigator), findsOneWidget);
- });
-
- testWidgets('Shows no breadcrumbs widget in summary tree', (tester) async {
- final diagnosticNode = await widgetToInspectorTreeDiagnosticsNode(
- widget: const Text('Hello'),
- tester: tester,
- );
-
- final treeController = inspectorTreeControllerFromNode(diagnosticNode);
- await pumpInspectorTree(
- tester,
- treeController: treeController,
- isSummaryTree: true,
- );
-
- expect(find.byType(InspectorBreadcrumbNavigator), findsNothing);
- });
- });
-}
diff --git a/packages/devtools_app/test/screens/inspector/layout_explorer/flex/arrow_test.dart b/packages/devtools_app/test/screens/inspector/layout_explorer/flex/arrow_test.dart
deleted file mode 100644
index 2b5bf8c..0000000
--- a/packages/devtools_app/test/screens/inspector/layout_explorer/flex/arrow_test.dart
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app/src/screens/inspector/layout_explorer/ui/arrow.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import '../../../../test_infra/matchers/matchers.dart';
-
-void main() {
- const relativeGoldenPath =
- '../../../../test_infra/goldens/inspector/layout_explorer/flex';
-
- group('Arrow Golden Tests', () {
- group('Unidirectional', () {
- Widget buildUnidirectionalArrowWrapper(ArrowType type) => Directionality(
- textDirection: TextDirection.ltr,
- child: SizedBox(
- width: 100,
- height: 100,
- child: ArrowWrapper.unidirectional(
- type: type,
- arrowColor: Colors.black,
- arrowHeadSize: 8.0,
- child: Container(width: 10, height: 10, color: Colors.red),
- ),
- ),
- );
- testWidgets('left', (WidgetTester tester) async {
- final widget = buildUnidirectionalArrowWrapper(ArrowType.left);
- await tester.pumpWidget(widget);
- await expectLater(
- find.byWidget(widget),
- matchesDevToolsGolden(
- '$relativeGoldenPath/arrow_unidirectional_left.png',
- ),
- );
- }, skip: kIsWeb);
- testWidgets('up', (WidgetTester tester) async {
- final widget = buildUnidirectionalArrowWrapper(ArrowType.up);
- await tester.pumpWidget(widget);
- await expectLater(
- find.byWidget(widget),
- matchesDevToolsGolden(
- '$relativeGoldenPath/arrow_unidirectional_up.png',
- ),
- );
- }, skip: kIsWeb);
- testWidgets('right', (WidgetTester tester) async {
- final widget = buildUnidirectionalArrowWrapper(ArrowType.right);
- await tester.pumpWidget(widget);
- await expectLater(
- find.byWidget(widget),
- matchesDevToolsGolden(
- '$relativeGoldenPath/arrow_unidirectional_right.png',
- ),
- );
- }, skip: kIsWeb);
- testWidgets('down', (WidgetTester tester) async {
- final widget = buildUnidirectionalArrowWrapper(ArrowType.down);
- await tester.pumpWidget(widget);
- await expectLater(
- find.byWidget(widget),
- matchesDevToolsGolden(
- '$relativeGoldenPath/arrow_unidirectional_down.png',
- ),
- );
- }, skip: kIsWeb);
- });
-
- group('Bidirectional', () {
- Widget buildBidirectionalArrowWrapper(Axis direction) => Directionality(
- textDirection: TextDirection.ltr,
- child: SizedBox(
- width: 100,
- height: 100,
- child: ArrowWrapper.bidirectional(
- direction: direction,
- arrowColor: Colors.black,
- arrowHeadSize: 8.0,
- child: Container(width: 10, height: 10, color: Colors.red),
- ),
- ),
- );
- testWidgets('horizontal', (WidgetTester tester) async {
- final widget = buildBidirectionalArrowWrapper(Axis.horizontal);
- await tester.pumpWidget(widget);
- await expectLater(
- find.byWidget(widget),
- matchesDevToolsGolden(
- '$relativeGoldenPath/arrow_bidirectional_horizontal.png',
- ),
- );
- }, skip: kIsWeb);
- testWidgets('vertical', (WidgetTester tester) async {
- final widget = buildBidirectionalArrowWrapper(Axis.vertical);
- await tester.pumpWidget(widget);
- await expectLater(
- find.byWidget(widget),
- matchesDevToolsGolden(
- '$relativeGoldenPath/arrow_bidirectional_vertical.png',
- ),
- );
- }, skip: kIsWeb);
- });
- });
-}
diff --git a/packages/devtools_app/test/screens/inspector/layout_explorer/flex/flex_test.dart b/packages/devtools_app/test/screens/inspector/layout_explorer/flex/flex_test.dart
deleted file mode 100644
index a8b5264..0000000
--- a/packages/devtools_app/test/screens/inspector/layout_explorer/flex/flex_test.dart
+++ /dev/null
@@ -1,292 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'dart:convert';
-
-import 'package:devtools_app/src/screens/inspector/layout_explorer/flex/flex.dart';
-import 'package:devtools_app/src/shared/console/eval/inspector_tree.dart';
-import 'package:devtools_app/src/shared/diagnostics/diagnostics_node.dart';
-import 'package:devtools_test/devtools_test.dart';
-import 'package:devtools_test/helpers.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import '../../../../test_infra/matchers/matchers.dart';
-
-// TODO(albertusangga): Re-enable tests in this files
-// https://github.com/flutter/devtools/issues/1403
-void main() {
- const windowSize = Size(1750, 1750);
- const relativeGoldenPath =
- '../../../../test_infra/goldens/inspector/layout_explorer/flex';
-
- Map<String, Object> buildDiagnosticsNodeJson(Axis axis) => jsonDecode('''
- {
- "description": "${axis == Axis.horizontal ? 'Row' : 'Column'}",
- "type": "_ElementDiagnosticableTreeNode",
- "style": "dense",
- "hasChildren": true,
- "allowWrap": false,
- "objectId": "inspector-267513",
- "valueId": "inspector-251",
- "summaryTree": true,
- "constraints": {
- "type": "BoxConstraints",
- "description": "BoxConstraints(w=300.0, h=60.0)",
- "minWidth": "300.0",
- "minHeight": "60.0",
- "maxHeight": "60.0",
- "maxWidth": "300.0"
- },
- "size": {
- "width": "300.0",
- "height": "60.0"
- },
- "isFlex": true,
- "children": [
- {
- "description": "Container",
- "type": "_ElementDiagnosticableTreeNode",
- "style": "dense",
- "hasChildren": true,
- "allowWrap": false,
- "objectId": "inspector-267524",
- "valueId": "inspector-269",
- "summaryTree": true,
- "constraints": {
- "type": "BoxConstraints",
- "description": "BoxConstraints(0.0<=w<=Infinity, 0.0<=h<=56.0)",
- "minWidth": "0.0",
- "minHeight": "0.0",
- "maxHeight": "56.0",
- "maxWidth": "Infinity"
- },
- "size": {
- "width": "56.0",
- "height": "25.0"
- },
- "flexFactor": null,
- "createdByLocalProject": true,
- "children": [],
- "widgetRuntimeType": "Container",
- "stateful": false
- },
- {
- "description": "Expanded",
- "type": "_ElementDiagnosticableTreeNode",
- "style": "dense",
- "hasChildren": true,
- "allowWrap": false,
- "objectId": "inspector-267563",
- "valueId": "inspector-332",
- "summaryTree": true,
- "constraints": {
- "type": "BoxConstraints",
- "description": "BoxConstraints(w=40.0, 0.0<=h<=56.0)",
- "minWidth": "40.0",
- "minHeight": "0.0",
- "maxHeight": "56.0",
- "maxWidth": "40.0"
- },
- "size": {
- "width": "40.0",
- "height": "31.0"
- },
- "flexFactor": 1,
- "createdByLocalProject": true,
- "children": [],
- "widgetRuntimeType": "Expanded"
- }
- ],
- "widgetRuntimeType": "${axis == Axis.horizontal ? 'Row' : 'Column'}",
- "renderObject": {
- "description": "RenderFlex#6cfb1 relayoutBoundary=up5",
- "type": "DiagnosticableTreeNode",
- "hasChildren": true,
- "allowWrap": false,
- "objectId": "inspector-3758",
- "valueId": "inspector-118",
- "summaryTree": true,
- "properties": [
- {
- "description": "<none> (can use size)",
- "type": "DiagnosticsProperty<ParentData>",
- "name": "parentData",
- "style": "singleLine",
- "allowNameWrap": true,
- "objectId": "inspector-3759",
- "valueId": "inspector-120",
- "summaryTree": true,
- "properties": [],
- "ifNull": "MISSING",
- "tooltip": "can use size",
- "missingIfNull": true,
- "propertyType": "ParentData",
- "defaultLevel": "info"
- },
- {
- "description": "${axis.name}",
- "type": "EnumProperty<Axis>",
- "name": "direction",
- "style": "singleLine",
- "allowNameWrap": true,
- "objectId": "inspector-3762",
- "valueId": "inspector-126",
- "summaryTree": true,
- "properties": [],
- "missingIfNull": false,
- "propertyType": "Axis",
- "defaultLevel": "info"
- },
- {
- "description": "start",
- "type": "EnumProperty<MainAxisAlignment>",
- "name": "mainAxisAlignment",
- "style": "singleLine",
- "allowNameWrap": true,
- "objectId": "inspector-3763",
- "valueId": "inspector-128",
- "summaryTree": true,
- "properties": [],
- "missingIfNull": false,
- "propertyType": "MainAxisAlignment",
- "defaultLevel": "info"
- },
- {
- "description": "max",
- "type": "EnumProperty<MainAxisSize>",
- "name": "mainAxisSize",
- "style": "singleLine",
- "allowNameWrap": true,
- "objectId": "inspector-3764",
- "valueId": "inspector-130",
- "summaryTree": true,
- "properties": [],
- "missingIfNull": false,
- "propertyType": "MainAxisSize",
- "defaultLevel": "info"
- },
- {
- "description": "center",
- "type": "EnumProperty<CrossAxisAlignment>",
- "name": "crossAxisAlignment",
- "style": "singleLine",
- "allowNameWrap": true,
- "objectId": "inspector-3765",
- "valueId": "inspector-132",
- "summaryTree": true,
- "properties": [],
- "missingIfNull": false,
- "propertyType": "CrossAxisAlignment",
- "defaultLevel": "info"
- },
- {
- "description": "ltr",
- "type": "EnumProperty<TextDirection>",
- "name": "textDirection",
- "style": "singleLine",
- "allowNameWrap": true,
- "objectId": "inspector-3766",
- "valueId": "inspector-83",
- "summaryTree": true,
- "properties": [],
- "defaultValue": "null",
- "missingIfNull": false,
- "propertyType": "TextDirection",
- "defaultLevel": "info"
- },
- {
- "description": "down",
- "type": "EnumProperty<VerticalDirection>",
- "name": "verticalDirection",
- "style": "singleLine",
- "allowNameWrap": true,
- "objectId": "inspector-3767",
- "valueId": "inspector-135",
- "summaryTree": true,
- "properties": [],
- "defaultValue": "null",
- "missingIfNull": false,
- "propertyType": "VerticalDirection",
- "defaultLevel": "info"
- },
- {
- "description": "alphabetic",
- "type": "EnumProperty<TextBaseline>",
- "name": "textBaseline",
- "style": "singleLine",
- "allowNameWrap": true,
- "objectId": "inspector-3767",
- "valueId": "inspector-135",
- "summaryTree": true,
- "properties": [],
- "defaultValue": "null",
- "missingIfNull": false,
- "propertyType": "TextBaseline",
- "defaultLevel": "info"
- }
- ]
- }
- }
- ''');
-
- Widget wrap(Widget widget) {
- return MaterialApp(home: Scaffold(body: widget));
- }
-
- /// current workaround for flaky image asset testing.
- /// https://github.com/flutter/flutter/issues/38997
- Future<void> pump(WidgetTester tester, Widget w) async {
- await tester.runAsync(() async {
- await tester.pumpWidget(w);
- for (final element in find.byType(Image).evaluate()) {
- final widget = element.widget as Image;
- final image = widget.image;
- await precacheImage(image, element);
- await tester.pumpAndSettle();
- }
- });
- }
-
- testWidgetsWithWindowSize('Row golden test', windowSize, (
- WidgetTester tester,
- ) async {
- final rowWidgetJsonNode = buildDiagnosticsNodeJson(Axis.horizontal);
- final diagnostic = RemoteDiagnosticsNode(
- rowWidgetJsonNode,
- null,
- false,
- null,
- );
- final treeNode = InspectorTreeNode()..diagnostic = diagnostic;
- final controller = TestInspectorController()..setSelectedNode(treeNode);
- final widget = wrap(FlexLayoutExplorerWidget(controller));
- await pump(tester, widget);
- await tester.pumpAndSettle();
- await expectLater(
- find.byWidget(widget),
- matchesDevToolsGolden('$relativeGoldenPath/story_of_row_layout.png'),
- );
- }, skip: true);
-
- testWidgetsWithWindowSize('Column golden test', windowSize, (
- WidgetTester tester,
- ) async {
- final columnWidgetJsonNode = buildDiagnosticsNodeJson(Axis.vertical);
- final diagnostic = RemoteDiagnosticsNode(
- columnWidgetJsonNode,
- null,
- false,
- null,
- );
- final treeNode = InspectorTreeNode()..diagnostic = diagnostic;
- final controller = TestInspectorController()..setSelectedNode(treeNode);
- final widget = wrap(FlexLayoutExplorerWidget(controller));
- await pump(tester, widget);
- await expectLater(
- find.byWidget(widget),
- matchesDevToolsGolden('$relativeGoldenPath/story_of_column_layout.png'),
- );
- }, skip: true);
-}
diff --git a/packages/devtools_app/test/screens/inspector/layout_explorer/inspector_data_models_test.dart b/packages/devtools_app/test/screens/inspector/layout_explorer/inspector_data_models_test.dart
deleted file mode 100644
index a8e0cd0..0000000
--- a/packages/devtools_app/test/screens/inspector/layout_explorer/inspector_data_models_test.dart
+++ /dev/null
@@ -1,442 +0,0 @@
-// Copyright 2019 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app/src/screens/inspector/inspector_data_models.dart';
-import 'package:devtools_app/src/screens/inspector/layout_explorer/ui/theme.dart';
-import 'package:devtools_app/src/shared/primitives/math_utils.dart';
-import 'package:devtools_app_shared/ui.dart';
-import 'package:devtools_app_shared/utils.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import 'layout_explorer_test_utils.dart';
-
-void main() {
- setGlobal(IdeTheme, IdeTheme());
-
- group('FlexLayoutProperties tests', () {
- Future<FlexLayoutProperties> toFlexLayoutProperties(
- Flex flex, {
- required WidgetTester tester,
- int subtreeDepth = 2,
- double? width,
- double? height,
- }) async {
- final wrappedWidget = SizedBox(width: width, height: height, child: flex);
- final rootNodeDiagnostics =
- await widgetToLayoutExplorerRemoteDiagnosticsNode(
- widget: wrappedWidget,
- tester: tester,
- subtreeDepth: subtreeDepth,
- );
- final flexDiagnostics = rootNodeDiagnostics.childrenNow.first;
- return FlexLayoutProperties.fromDiagnostics(flexDiagnostics);
- }
-
- testWidgets(
- 'FlexLayoutProperties.fromJson creates correct value from enum',
- (tester) async {
- const widget = Row(
- textDirection: TextDirection.ltr,
- children: [SizedBox()],
- );
- final flexProperties = await toFlexLayoutProperties(
- widget,
- tester: tester,
- );
- expect(flexProperties.direction, Axis.horizontal);
- expect(flexProperties.mainAxisAlignment, MainAxisAlignment.start);
- expect(flexProperties.mainAxisSize, MainAxisSize.max);
- expect(flexProperties.crossAxisAlignment, CrossAxisAlignment.center);
- expect(flexProperties.textDirection, TextDirection.ltr);
- expect(flexProperties.verticalDirection, VerticalDirection.down);
- expect(flexProperties.textBaseline, null);
- },
- );
-
- testWidgets('startIsTopLeft should return false', (tester) async {
- const columnWidget = Column(
- verticalDirection: VerticalDirection.up,
- children: [SizedBox()],
- );
- final columnProperties = await toFlexLayoutProperties(
- columnWidget,
- tester: tester,
- );
- expect(columnProperties.startIsTopLeft, false);
-
- const rowWidget = Row(
- textDirection: TextDirection.rtl,
- children: [SizedBox()],
- );
- final rowProperties = await toFlexLayoutProperties(
- rowWidget,
- tester: tester,
- );
- expect(rowProperties.startIsTopLeft, false);
- });
-
- testWidgets(
- 'displayChildren is the same as children when start is top left',
- (tester) async {
- final widget = Column(children: [const SizedBox(), Container()]);
- final properties = await toFlexLayoutProperties(widget, tester: tester);
- expect(properties.startIsTopLeft, true);
- expect(properties.displayChildren[0].description, 'SizedBox');
- expect(properties.displayChildren[1].description, 'Container');
- },
- );
-
- testWidgets(
- 'displayChildren is a reversed children when start is not top left',
- (tester) async {
- final widget = Column(
- verticalDirection: VerticalDirection.up,
- children: [const SizedBox(), Container()],
- );
- final properties = await toFlexLayoutProperties(widget, tester: tester);
- expect(properties.startIsTopLeft, false);
- expect(properties.displayChildren[0].description, 'Container');
- expect(properties.displayChildren[1].description, 'SizedBox');
- },
- );
-
- group('childrenRenderProperties tests', () {
- const maxMainAxisDimension = 500.0;
-
- double maxSizeAvailable(Axis _) => maxMainAxisDimension;
-
- List<RenderProperties> childrenRenderProperties(
- FlexLayoutProperties properties,
- ) => properties.childrenRenderProperties(
- smallestRenderWidth: minRenderWidth,
- largestRenderWidth: defaultMaxRenderWidth,
- smallestRenderHeight: minRenderHeight,
- largestRenderHeight: defaultMaxRenderHeight,
- maxSizeAvailable: maxSizeAvailable,
- );
-
- final childrenWidgets = <Widget>[
- const SizedBox(width: 50.0),
- const SizedBox(width: 75.0, height: 25.0),
- ];
-
- testWidgets(
- 'returns correct RenderProperties with main axis not flipped when start is top left',
- (tester) async {
- final widget = Row(children: childrenWidgets);
- final properties = await toFlexLayoutProperties(
- widget,
- width: maxMainAxisDimension,
- tester: tester,
- subtreeDepth: 3,
- );
- final renderProps = properties.childrenRenderProperties(
- smallestRenderWidth: minRenderWidth,
- largestRenderWidth: defaultMaxRenderWidth,
- smallestRenderHeight: minRenderHeight,
- largestRenderHeight: defaultMaxRenderHeight,
- maxSizeAvailable: maxSizeAvailable,
- );
- expect(renderProps.length, 3);
- expect(renderProps, [
- RenderProperties(
- axis: Axis.horizontal,
- size: const Size(250, 250),
- realSize: const Size(50.0, 0.0),
- offset: const Offset(0.0, 125.0),
- layoutProperties: properties,
- ),
- RenderProperties(
- axis: Axis.horizontal,
- size: const Size(261.5, 500),
- realSize: const Size(75.0, 25.0),
- offset: const Offset(250.0, 0.0),
- layoutProperties: properties,
- ),
- RenderProperties(
- axis: Axis.horizontal,
- size: const Size(400, 500),
- realSize: const Size(375.0, 25.0),
- offset: const Offset(511.5, 0.0),
- isFreeSpace: true,
- layoutProperties: properties,
- ),
- ]);
- },
- );
-
- testWidgets(
- 'returns correct RenderProperties with main axis flipped when start is not top left',
- (tester) async {
- final widget = Row(
- textDirection: TextDirection.rtl,
- children: childrenWidgets,
- );
- final properties = await toFlexLayoutProperties(
- widget,
- tester: tester,
- width: maxMainAxisDimension,
- subtreeDepth: 3,
- );
- final renderProps = properties.childrenRenderProperties(
- smallestRenderWidth: minRenderWidth,
- largestRenderWidth: defaultMaxRenderWidth,
- smallestRenderHeight: minRenderHeight,
- largestRenderHeight: defaultMaxRenderHeight,
- maxSizeAvailable: maxSizeAvailable,
- );
- expect(renderProps.length, 3);
- expect(renderProps, [
- RenderProperties(
- axis: Axis.horizontal,
- size: const Size(261.5, 500.0),
- realSize: const Size(75.0, 25.0),
- offset: const Offset(400.0, 0.0),
- layoutProperties: properties,
- ),
- RenderProperties(
- axis: Axis.horizontal,
- size: const Size(250.0, 250.0),
- realSize: const Size(50.0, 0.0),
- offset: const Offset(661.5, 125.0),
- layoutProperties: properties,
- ),
- RenderProperties(
- axis: Axis.horizontal,
- size: const Size(400, 500),
- realSize: const Size(375.0, 25.0),
- offset: const Offset(0.0, 0.0),
- isFreeSpace: true,
- layoutProperties: properties,
- ),
- ]);
- },
- );
-
- testWidgets(
- 'when the start is not top left, render properties should be equals to its mirrored version',
- (tester) async {
- Row buildWidget({
- required bool flipMainAxis,
- required MainAxisAlignment mainAxisAlignment,
- }) => Row(
- textDirection: flipMainAxis ? TextDirection.rtl : TextDirection.ltr,
- mainAxisAlignment: flipMainAxis
- ? mainAxisAlignment.reversed
- : mainAxisAlignment,
- children: flipMainAxis
- ? childrenWidgets.reversed.toList()
- : childrenWidgets,
- );
- for (final mainAxisAlignment in MainAxisAlignment.values) {
- final originalWidgetRenderProperties = childrenRenderProperties(
- await toFlexLayoutProperties(
- buildWidget(
- flipMainAxis: false,
- mainAxisAlignment: mainAxisAlignment,
- ),
- tester: tester,
- ),
- );
- final mirroredWidgetRenderProperties = childrenRenderProperties(
- await toFlexLayoutProperties(
- buildWidget(
- flipMainAxis: true,
- mainAxisAlignment: mainAxisAlignment,
- ),
- tester: tester,
- ),
- );
- expect(
- originalWidgetRenderProperties,
- mirroredWidgetRenderProperties,
- );
- }
- },
- );
- });
- });
-
- group('LayoutProperties tests', () {
- testWidgets('deserializes RemoteDiagnosticsNode correctly', (tester) async {
- const constraints = BoxConstraints(
- minWidth: 432.0,
- maxWidth: 432.0,
- minHeight: 56.0,
- maxHeight: 56.0,
- );
- const size = Size(432.0, 56.0);
- final widget = Container(
- width: size.width,
- height: size.height,
- constraints: constraints,
- child: const Row(children: [SizedBox()]),
- );
- final diagnosticsNode = await widgetToLayoutExplorerRemoteDiagnosticsNode(
- widget: widget,
- tester: tester,
- subtreeDepth: 2,
- );
- final rowDiagnosticsNode = diagnosticsNode.childrenNow.first;
- final layoutProperties = LayoutProperties(rowDiagnosticsNode);
-
- expect(layoutProperties.size, size);
- expect(layoutProperties.constraints, constraints);
- expect(layoutProperties.totalChildren, 1);
- });
-
- group('describeWidthConstraints and describeHeightConstraints', () {
- testWidgets('single value', (tester) async {
- const width = 25.0;
- const height = 56.0;
- const constraints = BoxConstraints.tightFor(
- width: width,
- height: height,
- );
- final widget = ConstrainedBox(
- constraints: constraints,
- child: const SizedBox(),
- );
- final constrainedBoxDiagnosticsNode =
- await widgetToLayoutExplorerRemoteDiagnosticsNode(
- widget: widget,
- tester: tester,
- );
- final sizedBoxDiagnosticsNode =
- constrainedBoxDiagnosticsNode.childrenNow.first;
- final layoutProperties = LayoutProperties(sizedBoxDiagnosticsNode);
- expect(layoutProperties.describeHeightConstraints(), 'h=$height');
- expect(layoutProperties.describeWidthConstraints(), 'w=$width');
- });
-
- testWidgets('range value', (tester) async {
- const minWidth = 25.0, maxWidth = 50.0;
- const minHeight = 75.0, maxHeight = 100.0;
- const constraints = BoxConstraints(
- minWidth: minWidth,
- maxWidth: maxWidth,
- minHeight: minHeight,
- maxHeight: maxHeight,
- );
- final widget = ConstrainedBox(
- constraints: constraints,
- child: const SizedBox(),
- );
- final constrainedBoxDiagnosticsNode =
- await widgetToLayoutExplorerRemoteDiagnosticsNode(
- widget: widget,
- tester: tester,
- );
- final sizedBoxDiagnosticsNode =
- constrainedBoxDiagnosticsNode.childrenNow.first;
- final layoutProperties = LayoutProperties(sizedBoxDiagnosticsNode);
- expect(
- layoutProperties.describeHeightConstraints(),
- '$minHeight<=h<=$maxHeight',
- );
- expect(
- layoutProperties.describeWidthConstraints(),
- '$minWidth<=w<=$maxWidth',
- );
- });
-
- testWidgets('unconstrained width', (tester) async {
- final widget = Row(children: [Container()]);
- final rowDiagnosticsNode =
- await widgetToLayoutExplorerRemoteDiagnosticsNode(
- widget: widget,
- tester: tester,
- );
- final containerDiagnosticsNode = rowDiagnosticsNode.childrenNow.first;
- final layoutProperties = LayoutProperties(containerDiagnosticsNode);
- expect(
- layoutProperties.describeWidthConstraints(),
- 'width is unconstrained',
- );
- });
-
- testWidgets('unconstrained height', (tester) async {
- final widget = Column(children: [Container()]);
- final columnDiagnosticsNode =
- await widgetToLayoutExplorerRemoteDiagnosticsNode(
- widget: widget,
- tester: tester,
- );
- final containerDiagnosticsNode =
- columnDiagnosticsNode.childrenNow.first;
- final layoutProperties = LayoutProperties(containerDiagnosticsNode);
- expect(
- layoutProperties.describeHeightConstraints(),
- 'height is unconstrained',
- );
- });
- });
-
- testWidgets('describeWidth and describeHeight', (tester) async {
- const width = 432.5, height = 56.0;
- final widget = SizedBox(width: width, height: height, child: Container());
- final sizedBoxNode = await widgetToLayoutExplorerRemoteDiagnosticsNode(
- widget: widget,
- tester: tester,
- );
- final containerNode = sizedBoxNode.childrenNow.first;
- final layoutProperties = LayoutProperties(containerNode);
- expect(layoutProperties.describeHeight(), 'h=$height');
- expect(layoutProperties.describeWidth(), 'w=$width');
- });
- });
-
- group('computeRenderSizes', () {
- test(
- 'scale sizes so the largestSize maps to largestRenderSize with forceToOccupyMaxSize=false',
- () {
- final renderSizes = computeRenderSizes(
- sizes: [100.0, 200.0, 300.0],
- smallestSize: 100.0,
- largestSize: 300.0,
- smallestRenderSize: 200.0,
- largestRenderSize: 600.0,
- maxSizeAvailable: 2000,
- useMaxSizeAvailable: false,
- );
- expect(renderSizes, [200.0, 400.0, 600.0]);
- expect(sum(renderSizes), lessThan(2000));
- },
- );
-
- test(
- 'scale sizes so the items fit maxSizeAvailable with forceToOccupyMaxSize=true',
- () {
- final renderSizes = computeRenderSizes(
- sizes: [100.0, 200.0, 300.0],
- smallestSize: 100.0,
- largestSize: 300.0,
- smallestRenderSize: 200.0,
- largestRenderSize: 600.0,
- maxSizeAvailable: 2000,
- );
- expect(renderSizes, [200.0, 666.6666666666667, 1133.3333333333335]);
- expect(sum(renderSizes) - 2000.0, lessThan(0.01));
- },
- );
-
- test(
- 'scale sizes when the items exceeds maxSizeAvailable with forceToOccupyMaxSize=true should not change any behavior',
- () {
- final renderSizes = computeRenderSizes(
- sizes: [100.0, 200.0, 300.0],
- smallestSize: 100.0,
- largestSize: 300.0,
- smallestRenderSize: 300.0,
- largestRenderSize: 900.0,
- maxSizeAvailable: 250.0,
- );
- expect(renderSizes, [300.0, 600.0, 900.0]);
- expect(sum(renderSizes), greaterThan(250.0));
- },
- );
- });
-}
diff --git a/packages/devtools_app/test/screens/inspector/layout_explorer/layout_explorer_serialization_delegate.dart b/packages/devtools_app/test/screens/inspector/layout_explorer/layout_explorer_serialization_delegate.dart
deleted file mode 100644
index 3f1b609..0000000
--- a/packages/devtools_app/test/screens/inspector/layout_explorer/layout_explorer_serialization_delegate.dart
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2020 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:flutter/rendering.dart';
-import 'package:flutter/widgets.dart';
-
-/// This class is merely a duplicate of the InspectorSerializationDelegate inside the `assets/scripts/inspector_polyfill_script.dart`.
-/// This delegate is used for testing Widget Diagnostics Node so that we don't have to create manual JSON each time we want to create new test cases.
-/// TODO(adalberht): Ask Jacob on what's the better solution on code reuse between the Layout Explorer polyfillscripts and the code inside test package?
-class LayoutExplorerSerializationDelegate
- extends InspectorSerializationDelegate {
- LayoutExplorerSerializationDelegate({
- String super.groupName = '',
- super.subtreeDepth,
- required super.service,
- }) : super(
- summaryTree: true,
- addAdditionalPropertiesCallback: (node, delegate) {
- final additionalJson = <String, Object>{};
- final value = node.value;
- if (value is Element) {
- final renderObject = value.renderObject!;
- additionalJson['renderObject'] = renderObject
- .toDiagnosticsNode()
- .toJsonMap(
- delegate.copyWith(subtreeDepth: 0, includeProperties: true),
- );
- // Required for test.
- // ignore: invalid_use_of_protected_member
- final constraints = renderObject.constraints;
-
- final constraintsProperty = <String, Object>{
- 'type': constraints.runtimeType.toString(),
- 'description': constraints.toString(),
- };
- if (constraints is BoxConstraints) {
- constraintsProperty.addAll(<String, Object>{
- 'minWidth': constraints.minWidth.toString(),
- 'minHeight': constraints.minHeight.toString(),
- 'maxWidth': constraints.maxWidth.toString(),
- 'maxHeight': constraints.maxHeight.toString(),
- });
- }
- additionalJson['constraints'] = constraintsProperty;
-
- if (renderObject is RenderBox) {
- additionalJson['size'] = <String, Object>{
- 'width': renderObject.size.width.toString(),
- 'height': renderObject.size.height.toString(),
- };
-
- final parentData = renderObject.parentData;
- if (parentData is FlexParentData) {
- additionalJson['flexFactor'] = parentData.flex ?? 0;
- additionalJson['flexFit'] =
- (parentData.fit ?? FlexFit.tight).name;
- }
- }
- }
- return additionalJson;
- },
- );
-}
diff --git a/packages/devtools_app/test/screens/inspector/layout_explorer/layout_explorer_test_utils.dart b/packages/devtools_app/test/screens/inspector/layout_explorer/layout_explorer_test_utils.dart
deleted file mode 100644
index c2b1f44..0000000
--- a/packages/devtools_app/test/screens/inspector/layout_explorer/layout_explorer_test_utils.dart
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2020 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app/src/shared/diagnostics/diagnostics_node.dart';
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-import 'layout_explorer_serialization_delegate.dart';
-
-Future<RemoteDiagnosticsNode> widgetToLayoutExplorerRemoteDiagnosticsNode({
- required Widget widget,
- required WidgetTester tester,
- int subtreeDepth = 1,
-}) async {
- await tester.pumpWidget(MaterialApp(home: Scaffold(body: widget)));
- final element = find.byWidget(widget).evaluate().first;
- final nodeJson = element
- .toDiagnosticsNode(style: DiagnosticsTreeStyle.dense)
- .toJsonMap(
- LayoutExplorerSerializationDelegate(
- subtreeDepth: subtreeDepth,
- service: WidgetInspectorService.instance,
- ),
- );
- return RemoteDiagnosticsNode(nodeJson, null, false, null);
-}
diff --git a/packages/devtools_app/test/screens/inspector/layout_explorer/widget_theme_test.dart b/packages/devtools_app/test/screens/inspector/layout_explorer/widget_theme_test.dart
deleted file mode 100644
index 95beb1f..0000000
--- a/packages/devtools_app/test/screens/inspector/layout_explorer/widget_theme_test.dart
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2021 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app/src/screens/inspector/layout_explorer/ui/widgets_theme.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-void main() {
- group('Test WidgetTheme', () {
- test('Correct asset from widget with a type', () {
- const widgetName = 'AnimatedBuilder<String>';
- final theme = WidgetTheme.fromName(widgetName);
- expect(theme.iconAsset, 'icons/inspector/widget_icons/animated.png');
- });
-
- test('Has default theme for custom widget', () {
- const widgetName = 'CustomWidget';
- final theme = WidgetTheme.fromName(widgetName);
- expect(theme.color, WidgetTheme.otherWidgetColor);
- });
- });
-}
diff --git a/packages/devtools_app/test/screens/inspector/utils/inspector_tree.dart b/packages/devtools_app/test/screens/inspector/utils/inspector_tree.dart
deleted file mode 100644
index 8896361..0000000
--- a/packages/devtools_app/test/screens/inspector/utils/inspector_tree.dart
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2021 The Flutter Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-
-import 'package:devtools_app/devtools_app.dart';
-import 'package:devtools_test/helpers.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/rendering.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-/// Create an `InspectorTreeControllerFlutter` from a single `RemoteDiagnosticsNode`
-InspectorTreeController inspectorTreeControllerFromNode(
- RemoteDiagnosticsNode node,
-) {
- final controller = InspectorTreeController()
- ..config = InspectorTreeConfig(
- onNodeAdded: (_, _) {},
- onClientActiveChange: (_) {},
- );
-
- controller.root = InspectorTreeNode()
- ..appendChild(InspectorTreeNode()..diagnostic = node);
-
- return controller;
-}
-
-/// Replicates the functionality of `getRootWidgetSummaryTreeWithPreviews` from
-/// inspector_polyfill_script.dart
-Future<RemoteDiagnosticsNode> widgetToInspectorTreeDiagnosticsNode({
- required Widget widget,
- required WidgetTester tester,
-}) async {
- await tester.pumpWidget(wrap(widget));
- final element = find.byWidget(widget).evaluate().first;
- final nodeJson = element
- .toDiagnosticsNode(style: DiagnosticsTreeStyle.dense)
- .toJsonMap(
- InspectorSerializationDelegate(
- service: WidgetInspectorService.instance,
- subtreeDepth: 1000000,
- summaryTree: true,
- addAdditionalPropertiesCallback: (node, delegate) {
- final additionalJson = <String, Object>{};
-
- final value = node.value;
- if (value is Element) {
- final renderObject = value.renderObject;
- if (renderObject is RenderParagraph) {
- additionalJson['textPreview'] = renderObject.text.toPlainText();
- }
- }
-
- return additionalJson;
- },
- ),
- );
-
- return RemoteDiagnosticsNode(nodeJson, null, false, null);
-}
diff --git a/packages/devtools_app/test/screens/inspector_v2/inspector_error_navigator_test.dart b/packages/devtools_app/test/screens/inspector_v2/inspector_error_navigator_test.dart
index 6db56eb..0d339af 100644
--- a/packages/devtools_app/test/screens/inspector_v2/inspector_error_navigator_test.dart
+++ b/packages/devtools_app/test/screens/inspector_v2/inspector_error_navigator_test.dart
@@ -5,6 +5,7 @@
import 'dart:collection';
import 'package:devtools_app/devtools_app.dart';
+
import 'package:devtools_app/src/shared/feature_flags.dart';
import 'package:devtools_app_shared/ui.dart';
import 'package:devtools_app_shared/utils.dart';
diff --git a/packages/devtools_app/test/screens/inspector_v2/inspector_integration_test.dart b/packages/devtools_app/test/screens/inspector_v2/inspector_integration_test.dart
index c9f902f..2e5a7fa 100644
--- a/packages/devtools_app/test/screens/inspector_v2/inspector_integration_test.dart
+++ b/packages/devtools_app/test/screens/inspector_v2/inspector_integration_test.dart
@@ -8,13 +8,7 @@
import 'dart:io';
import 'package:collection/collection.dart';
-import 'package:devtools_app/devtools_app.dart'
- hide InspectorScreenBodyState, InspectorScreenBody, InspectorRowContent;
-import 'package:devtools_app/src/screens/inspector/inspector_screen_body.dart'
- as legacy;
-import 'package:devtools_app/src/screens/inspector_shared/inspector_controls.dart';
-import 'package:devtools_app/src/screens/inspector_v2/inspector_screen_body.dart';
-import 'package:devtools_app/src/screens/inspector_v2/inspector_tree_controller.dart';
+import 'package:devtools_app/devtools_app.dart';
import 'package:devtools_app/src/screens/inspector_v2/layout_explorer/ui/utils.dart';
import 'package:devtools_app/src/screens/inspector_v2/widget_properties/properties_view.dart';
import 'package:devtools_app_shared/ui.dart';
@@ -63,15 +57,11 @@
setUp(() async {
await env.setupEnvironment();
- // Enable the V2 inspector:
- preferences.inspector.setLegacyInspectorEnabled(false);
setGlobal(BannerMessagesController, BannerMessagesController());
});
tearDown(() async {
await env.tearDownEnvironment(force: true);
- // Re-set changes to preferences:
- preferences.inspector.setLegacyInspectorEnabled(true);
});
tearDownAll(() {
@@ -369,65 +359,6 @@
);
});
- testWidgetsWithWindowSize('can revert to legacy inspector', windowSize, (
- WidgetTester tester,
- ) async {
- await _loadInspectorUI(tester);
-
- // Select the CustomCenter widget (row index #4)
- await tester.tap(find.richText('CustomCenter'));
- await tester.pumpAndSettle(inspectorChangeSettleTime);
-
- // Disable Inspector V2:
- await toggleLegacyInspector(tester);
- await tester.pumpAndSettle(inspectorChangeSettleTime);
-
- // Verify the legacy inspector is visible:
- await expectLater(
- find.byType(legacy.InspectorScreenBody),
- matchesDevToolsGolden(
- '../../test_infra/goldens/integration_inspector_v2_revert_to_legacy.png',
- ),
- );
- });
-
- // Test to verify https://github.com/flutter/devtools/issues/8487 is fixed.
- testWidgetsWithWindowSize(
- 'revert to legacy inspector, hot-restart, and back to new inspector',
- windowSize,
- (WidgetTester tester) async {
- await _loadInspectorUI(tester);
-
- // Disable Inspector V2.
- await toggleLegacyInspector(tester);
- await tester.pumpAndSettle(inspectorChangeSettleTime);
-
- // Verify the legacy inspector is visible.
- expect(find.richTextContaining('Widget Details Tree'), findsOneWidget);
-
- // Trigger a hot restart.
- await env.flutter!.hotRestart();
- await tester.pumpAndSettle(inspectorChangeSettleTime);
-
- // Enable Inspector V2.
- await toggleLegacyInspector(tester);
- await tester.pumpAndSettle(inspectorChangeSettleTime);
-
- // Verify the legacy inspector is not visible.
- expect(find.richTextContaining('Widget Details Tree'), findsNothing);
-
- // Wait for the widget tree to load.
- final centerWidgetFinder = find.richText('Center');
- final centerWidgetFinderWithRetries = await retryUntilFound(
- centerWidgetFinder,
- tester: tester,
- retries: 10,
- );
- expect(centerWidgetFinderWithRetries, findsOneWidget);
- },
- skip: true, // https://github.com/flutter/devtools/issues/8490
- );
-
testWidgetsWithWindowSize(
'tree nodes contain only essential information',
windowSize,
@@ -688,33 +619,6 @@
return expandCollapseButtonFinder;
}
-Future<void> toggleLegacyInspector(WidgetTester tester) async {
- // Open settings dialog.
- final inspectorSettingsDialogButton = find.descendant(
- of: find.byType(InspectorServiceExtensionButtonGroup),
- matching: find.byType(SettingsOutlinedButton),
- );
- await tester.tap(inspectorSettingsDialogButton);
- await tester.pumpAndSettle(inspectorChangeSettleTime);
-
- // Toggle the "legacy Inspector" checkbox.
- final settingsRow = find.ancestor(
- of: find.richTextContaining('Use legacy inspector'),
- matching: find.byType(Row),
- );
- final inspectorCheckbox = find.descendant(
- of: settingsRow,
- matching: find.byType(NotifierCheckbox),
- );
- await tester.tap(inspectorCheckbox);
- await tester.pumpAndSettle(inspectorChangeSettleTime);
-
- // Close the settings dialog.
- final closeButton = find.byType(DialogCloseButton);
- await tester.tap(closeButton);
- await tester.pumpAndSettle(inspectorChangeSettleTime);
-}
-
void verifyPropertyIsVisible({
required String name,
required String value,
diff --git a/packages/devtools_app/test/screens/inspector_v2/inspector_screen_test.dart b/packages/devtools_app/test/screens/inspector_v2/inspector_screen_test.dart
index 813cc1e..2886318 100644
--- a/packages/devtools_app/test/screens/inspector_v2/inspector_screen_test.dart
+++ b/packages/devtools_app/test/screens/inspector_v2/inspector_screen_test.dart
@@ -7,19 +7,11 @@
import 'dart:convert';
-import 'package:devtools_app/devtools_app.dart'
- hide
- InspectorController,
- InspectorTreeController,
- InspectorScreenBody,
- ErrorNavigator,
- InspectorTreeNode;
-import 'package:devtools_app/src/screens/inspector_shared/inspector_settings_dialog.dart';
-import 'package:devtools_app/src/screens/inspector_v2/inspector_screen_body.dart';
+import 'package:devtools_app/devtools_app.dart';
+import 'package:devtools_app/src/screens/inspector_v2/inspector_settings_dialog.dart';
import 'package:devtools_app/src/screens/inspector_v2/layout_explorer/flex/flex.dart';
import 'package:devtools_app/src/screens/inspector_v2/widget_details.dart';
import 'package:devtools_app/src/service/service_extensions.dart' as extensions;
-import 'package:devtools_app/src/shared/console/eval/inspector_tree_v2.dart';
import 'package:devtools_app/src/shared/feature_flags.dart';
import 'package:devtools_app/src/shared/ui/tab.dart';
import 'package:devtools_app_shared/ui.dart';
@@ -311,7 +303,7 @@
'should render StoryOfYourFlexWidget',
windowSize,
(WidgetTester tester) async {
- final controller = TestInspectorV2Controller()
+ final controller = TestInspectorController()
..setSelectedNode(treeNode)
..setSelectedDiagnostic(diagnostic);
await tester.pumpWidget(
@@ -332,7 +324,7 @@
'should listen to controller selection event',
windowSize,
(WidgetTester tester) async {
- final controller = TestInspectorV2Controller();
+ final controller = TestInspectorController();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(body: WidgetDetails(controller: controller)),
diff --git a/packages/devtools_app/test/screens/inspector_v2/inspector_tree_test.dart b/packages/devtools_app/test/screens/inspector_v2/inspector_tree_test.dart
index 5a7c896..feda195 100644
--- a/packages/devtools_app/test/screens/inspector_v2/inspector_tree_test.dart
+++ b/packages/devtools_app/test/screens/inspector_v2/inspector_tree_test.dart
@@ -2,16 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-import 'package:devtools_app/devtools_app.dart'
- hide
- InspectorController,
- InspectorTreeController,
- InspectorTree,
- InspectorTreeConfig,
- InspectorTreeNode;
-import 'package:devtools_app/src/screens/inspector_v2/inspector_controller.dart';
-import 'package:devtools_app/src/screens/inspector_v2/inspector_tree_controller.dart';
-import 'package:devtools_app/src/shared/console/eval/inspector_tree_v2.dart';
+import 'package:devtools_app/devtools_app.dart';
+
import 'package:devtools_app_shared/ui.dart';
import 'package:devtools_app_shared/utils.dart';
import 'package:devtools_test/devtools_test.dart';
diff --git a/packages/devtools_app/test/screens/inspector_v2/layout_explorer/flex/flex_test.dart b/packages/devtools_app/test/screens/inspector_v2/layout_explorer/flex/flex_test.dart
index 5f4ea4a..9c64239 100644
--- a/packages/devtools_app/test/screens/inspector_v2/layout_explorer/flex/flex_test.dart
+++ b/packages/devtools_app/test/screens/inspector_v2/layout_explorer/flex/flex_test.dart
@@ -260,7 +260,7 @@
null,
);
final treeNode = InspectorTreeNode()..diagnostic = diagnostic;
- final controller = TestInspectorV2Controller()..setSelectedNode(treeNode);
+ final controller = TestInspectorController()..setSelectedNode(treeNode);
final widget = wrap(FlexLayoutExplorerWidget(controller));
await pump(tester, widget);
await tester.pumpAndSettle();
@@ -281,7 +281,7 @@
null,
);
final treeNode = InspectorTreeNode()..diagnostic = diagnostic;
- final controller = TestInspectorV2Controller()..setSelectedNode(treeNode);
+ final controller = TestInspectorController()..setSelectedNode(treeNode);
final widget = wrap(FlexLayoutExplorerWidget(controller));
await pump(tester, widget);
await expectLater(
diff --git a/packages/devtools_app/test/screens/inspector_v2/utils/inspector_tree.dart b/packages/devtools_app/test/screens/inspector_v2/utils/inspector_tree.dart
index d2aac27..8896361 100644
--- a/packages/devtools_app/test/screens/inspector_v2/utils/inspector_tree.dart
+++ b/packages/devtools_app/test/screens/inspector_v2/utils/inspector_tree.dart
@@ -2,10 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
-import 'package:devtools_app/devtools_app.dart'
- hide InspectorTreeController, InspectorTreeConfig, InspectorTreeNode;
-import 'package:devtools_app/src/screens/inspector_v2/inspector_tree_controller.dart';
-import 'package:devtools_app/src/shared/console/eval/inspector_tree_v2.dart';
+import 'package:devtools_app/devtools_app.dart';
import 'package:devtools_test/helpers.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
diff --git a/packages/devtools_app/test/shared/managers/error_badge_manager_test.dart b/packages/devtools_app/test/shared/managers/error_badge_manager_test.dart
index 69b74f5..13ea519 100644
--- a/packages/devtools_app/test/shared/managers/error_badge_manager_test.dart
+++ b/packages/devtools_app/test/shared/managers/error_badge_manager_test.dart
@@ -4,7 +4,7 @@
import 'package:devtools_app/src/screens/app_size/app_size_screen.dart';
import 'package:devtools_app/src/screens/debugger/debugger_screen.dart';
-import 'package:devtools_app/src/screens/inspector_shared/inspector_screen.dart';
+import 'package:devtools_app/src/screens/inspector_v2/inspector_screen.dart';
import 'package:devtools_app/src/screens/logging/logging_screen.dart';
import 'package:devtools_app/src/screens/memory/framework/memory_screen.dart';
import 'package:devtools_app/src/screens/network/network_screen.dart';
diff --git a/packages/devtools_app/test/test_infra/goldens/integration_inspector_v2_implementation_widgets_collapsed.png b/packages/devtools_app/test/test_infra/goldens/integration_inspector_v2_implementation_widgets_collapsed.png
index e148d46..d719b72 100644
--- a/packages/devtools_app/test/test_infra/goldens/integration_inspector_v2_implementation_widgets_collapsed.png
+++ b/packages/devtools_app/test/test_infra/goldens/integration_inspector_v2_implementation_widgets_collapsed.png
Binary files differ
diff --git a/packages/devtools_app/test/test_infra/goldens/memory/load_offline_data_profile_tab.png b/packages/devtools_app/test/test_infra/goldens/memory/load_offline_data_profile_tab.png
index 3faf47d..0693188 100644
--- a/packages/devtools_app/test/test_infra/goldens/memory/load_offline_data_profile_tab.png
+++ b/packages/devtools_app/test/test_infra/goldens/memory/load_offline_data_profile_tab.png
Binary files differ
diff --git a/packages/devtools_test/lib/src/mocks/mocks.dart b/packages/devtools_test/lib/src/mocks/mocks.dart
index fee470e..d4fd888 100644
--- a/packages/devtools_test/lib/src/mocks/mocks.dart
+++ b/packages/devtools_test/lib/src/mocks/mocks.dart
@@ -7,12 +7,6 @@
import 'dart:io';
import 'package:devtools_app/devtools_app.dart';
-// ignore: implementation_imports, required to separate V2 inspector imports.
-import 'package:devtools_app/src/screens/inspector_v2/inspector_controller.dart'
- as inspector_v2;
-// ignore: implementation_imports, required to separate V2 inspector imports.
-import 'package:devtools_app/src/shared/console/eval/inspector_tree_v2.dart'
- as inspector_v2;
import 'package:devtools_app_shared/service.dart';
import 'package:devtools_shared/devtools_shared.dart';
import 'package:flutter/foundation.dart';
@@ -78,38 +72,19 @@
final _selectedNode = ValueNotifier<InspectorTreeNode?>(null);
@override
- void setSelectedNode(InspectorTreeNode? newSelection) {
- _selectedNode.value = newSelection;
- }
-
- @override
- InspectorService get inspectorService => service;
-}
-
-class TestInspectorV2Controller extends Fake
- implements inspector_v2.InspectorController {
- InspectorService service = FakeInspectorService();
-
- @override
- ValueListenable<inspector_v2.InspectorTreeNode?> get selectedNode =>
- _selectedNode;
- final _selectedNode = ValueNotifier<inspector_v2.InspectorTreeNode?>(null);
-
- @override
RemoteDiagnosticsNode? get selectedDiagnostic => _selectedDiagnostic;
RemoteDiagnosticsNode? _selectedDiagnostic;
@override
- ValueListenable<inspector_v2.WidgetTreeNodeProperties>
- get selectedNodeProperties =>
- ValueNotifier<inspector_v2.WidgetTreeNodeProperties>((
+ ValueListenable<WidgetTreeNodeProperties> get selectedNodeProperties =>
+ ValueNotifier<WidgetTreeNodeProperties>((
widgetProperties: [],
renderProperties: [],
layoutProperties: null,
));
@override
- void setSelectedNode(inspector_v2.InspectorTreeNode? newSelection) {
+ void setSelectedNode(InspectorTreeNode? newSelection) {
_selectedNode.value = newSelection;
}