Combine shallow and retained size columns into one to save horizontal space. (#5299)

diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/diff_pane_controller.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/diff_pane_controller.dart
index 71cc19c..1593b2e 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/diff_pane_controller.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/controller/diff_pane_controller.dart
@@ -19,6 +19,7 @@
 import '../../../shared/heap/heap.dart';
 import '../../../shared/heap/model.dart';
 import '../../../shared/primitives/memory_utils.dart';
+import '../../../shared/primitives/simple_elements.dart';
 import 'heap_diff.dart';
 import 'item_controller.dart';
 import 'simple_controllers.dart';
@@ -35,6 +36,8 @@
 
   final retainingPathController = RetainingPathController();
 
+  final sizeTypeToShowForDiff = ValueNotifier<SizeType>(SizeType.retained);
+
   final core = CoreData();
   late final derived = DerivedData(core);
 
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/class_details/paths.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/class_details/paths.dart
index 06c7fc7..76401ca 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/class_details/paths.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/class_details/paths.dart
@@ -54,7 +54,7 @@
   _ShallowSizeColumn(bool isDiff)
       : super(
           isDiff ? 'Shallow\nSize Delta' : 'Shallow\nDart Size',
-          titleTooltip: shallowSizeColumnTooltip,
+          titleTooltip: SizeType.shallow.description,
           fixedWidthPx: scaleByFontFactor(85.0),
           alignment: ColumnAlignment.right,
         );
@@ -77,7 +77,7 @@
   _RetainedSizeColumn(bool isDiff)
       : super(
           isDiff ? 'Retained\nSize Delta' : 'Retained\nDart Size',
-          titleTooltip: retainedSizeColumnTooltip,
+          titleTooltip: SizeType.retained.description,
           fixedWidthPx: scaleByFontFactor(85.0),
           alignment: ColumnAlignment.right,
         );
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart
index b0a1fdb..8d308f7 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_diff.dart
@@ -6,6 +6,7 @@
 
 import '../../../../../shared/analytics/analytics.dart' as ga;
 import '../../../../../shared/analytics/constants.dart' as gac;
+import '../../../../../shared/common_widgets.dart';
 import '../../../../../shared/feature_flags.dart';
 import '../../../../../shared/globals.dart';
 import '../../../../../shared/memory/adapted_heap_data.dart';
@@ -27,11 +28,6 @@
   persisted,
 }
 
-enum _SizeType {
-  shallow,
-  retained,
-}
-
 class _ClassNameColumn extends ColumnData<DiffClassStats>
     implements
         ColumnRenderer<DiffClassStats>,
@@ -185,7 +181,7 @@
         );
 
   final _DataPart dataPart;
-  final _SizeType sizeType;
+  final SizeType sizeType;
 
   static String columnTitle(_DataPart dataPart) {
     switch (dataPart) {
@@ -203,7 +199,7 @@
   @override
   int getValue(DiffClassStats classStats) {
     switch (sizeType) {
-      case _SizeType.shallow:
+      case SizeType.shallow:
         switch (dataPart) {
           case _DataPart.created:
             return classStats.total.created.shallowSize;
@@ -214,7 +210,7 @@
           case _DataPart.persisted:
             return classStats.total.persisted.shallowSize;
         }
-      case _SizeType.retained:
+      case SizeType.retained:
         switch (dataPart) {
           case _DataPart.created:
             return classStats.total.created.retainedSize;
@@ -243,19 +239,21 @@
 
 class _ClassesTableDiffColumns {
   _ClassesTableDiffColumns(
-    this.classFilterButton, {
+    this.classFilterButton,
+    this.sizeTypeToShow, {
     required this.before,
     required this.after,
   });
 
   final Widget classFilterButton;
 
-  final retainedSizeDeltaColumn =
-      _SizeColumn(_DataPart.delta, _SizeType.retained);
+  late final sizeDeltaColumn = _SizeColumn(_DataPart.delta, sizeTypeToShow);
 
   final AdaptedHeapData before;
   final AdaptedHeapData after;
 
+  final SizeType sizeTypeToShow;
+
   late final List<ColumnData<DiffClassStats>> columnList =
       <ColumnData<DiffClassStats>>[
     _ClassNameColumn(classFilterButton),
@@ -263,17 +261,51 @@
     _InstanceColumn(_DataPart.deleted, before),
     _InstanceColumn(_DataPart.delta, null),
     _InstanceColumn(_DataPart.persisted, after),
-    _SizeColumn(_DataPart.created, _SizeType.shallow),
-    _SizeColumn(_DataPart.deleted, _SizeType.shallow),
-    _SizeColumn(_DataPart.delta, _SizeType.shallow),
-    _SizeColumn(_DataPart.persisted, _SizeType.shallow),
-    _SizeColumn(_DataPart.created, _SizeType.retained),
-    _SizeColumn(_DataPart.deleted, _SizeType.retained),
-    retainedSizeDeltaColumn,
-    _SizeColumn(_DataPart.persisted, _SizeType.retained),
+    _SizeColumn(_DataPart.created, sizeTypeToShow),
+    _SizeColumn(_DataPart.deleted, sizeTypeToShow),
+    sizeDeltaColumn,
+    _SizeColumn(_DataPart.persisted, sizeTypeToShow),
   ];
 }
 
+class _SizeTitle extends StatelessWidget {
+  const _SizeTitle({required this.sizeTypeToShowForDiff});
+  final ValueNotifier<SizeType> sizeTypeToShowForDiff;
+
+  @override
+  Widget build(BuildContext context) {
+    final sizeType = sizeTypeToShowForDiff.value;
+
+    return maybeWrapWithTooltip(
+      child: Padding(
+        padding: const EdgeInsets.all(densePadding),
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.center,
+          children: [
+            RoundedDropDownButton<SizeType>(
+              isDense: true,
+              value: sizeType,
+              onChanged: (SizeType? value) =>
+                  sizeTypeToShowForDiff.value = value!,
+              items: SizeType.values
+                  .map(
+                    (sizeType) => DropdownMenuItem<SizeType>(
+                      value: sizeType,
+                      child: Text(sizeType.displayName),
+                    ),
+                  )
+                  .toList(),
+            ),
+            const SizedBox(width: denseSpacing),
+            const Text('Size'),
+          ],
+        ),
+      ),
+      tooltip: '${sizeType.displayName} size:\n${sizeType.description}',
+    );
+  }
+}
+
 class ClassesTableDiff extends StatelessWidget {
   const ClassesTableDiff({
     Key? key,
@@ -282,60 +314,65 @@
     required this.classFilterButton,
     required this.before,
     required this.after,
+    required this.sizeTypeToShowForDiff,
   }) : super(key: key);
 
   final List<DiffClassStats> classes;
   final ValueNotifier<DiffClassStats?> selection;
   final AdaptedHeapData before;
   final AdaptedHeapData after;
+  final ValueNotifier<SizeType> sizeTypeToShowForDiff;
 
-  static final _columnGroups = [
-    ColumnGroup.fromText(
-      title: '',
-      range: const Range(0, 1),
-    ),
-    ColumnGroup.fromText(
-      title: 'Instances',
-      range: const Range(1, 5),
-      tooltip: nonGcableInstancesColumnTooltip,
-    ),
-    ColumnGroup.fromText(
-      title: 'Shallow Dart Size',
-      range: const Range(5, 9),
-      tooltip: shallowSizeColumnTooltip,
-    ),
-    ColumnGroup.fromText(
-      title: 'Retained Dart Size',
-      range: const Range(9, 13),
-      tooltip: retainedSizeColumnTooltip,
-    ),
-  ];
+  List<ColumnGroup> _columnGroups(SizeType sizeType, BuildContext context) {
+    return [
+      ColumnGroup.fromText(
+        title: '',
+        range: const Range(0, 1),
+      ),
+      ColumnGroup.fromText(
+        title: 'Instances',
+        range: const Range(1, 5),
+        tooltip: nonGcableInstancesColumnTooltip,
+      ),
+      ColumnGroup(
+        title: _SizeTitle(sizeTypeToShowForDiff: sizeTypeToShowForDiff),
+        range: const Range(5, 9),
+      ),
+    ];
+  }
 
   final Widget classFilterButton;
 
   @override
   Widget build(BuildContext context) {
-    // We want to preserve the sorting and sort directions for ClassesTableDiff
-    // no matter what the data passed to it is.
-    const dataKey = 'ClassesTableDiff';
-    final columns = _ClassesTableDiffColumns(
-      classFilterButton,
-      before: before,
-      after: after,
-    );
-    return FlatTable<DiffClassStats>(
-      columns: columns.columnList,
-      columnGroups: _columnGroups,
-      data: classes,
-      dataKey: dataKey,
-      keyFactory: (e) => Key(e.heapClass.fullName),
-      selectionNotifier: selection,
-      onItemSelected: (_) => ga.select(
-        gac.memory,
-        gac.MemoryEvent.diffClassDiffSelect,
-      ),
-      defaultSortColumn: columns.retainedSizeDeltaColumn,
-      defaultSortDirection: SortDirection.descending,
+    return ValueListenableBuilder<SizeType>(
+      valueListenable: sizeTypeToShowForDiff,
+      builder: (context, sizeType, _) {
+        // We want to preserve the sorting and sort directions for ClassesTableDiff
+        // no matter what the data passed to it is.
+        const dataKey = 'ClassesTableDiff';
+        final columns = _ClassesTableDiffColumns(
+          classFilterButton,
+          sizeType,
+          before: before,
+          after: after,
+        );
+
+        return FlatTable<DiffClassStats>(
+          columns: columns.columnList,
+          columnGroups: _columnGroups(sizeType, context),
+          data: classes,
+          dataKey: dataKey,
+          keyFactory: (e) => Key(e.heapClass.fullName),
+          selectionNotifier: selection,
+          onItemSelected: (_) => ga.select(
+            gac.memory,
+            gac.MemoryEvent.diffClassDiffSelect,
+          ),
+          defaultSortColumn: columns.sizeDeltaColumn,
+          defaultSortDirection: SortDirection.descending,
+        );
+      },
     );
   }
 }
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_single.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_single.dart
index c4c03dc..36ee4e5 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_single.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/classes_table_single.dart
@@ -118,7 +118,7 @@
   _ShallowSizeColumn()
       : super(
           'Shallow\nDart Size',
-          titleTooltip: shallowSizeColumnTooltip,
+          titleTooltip: SizeType.shallow.description,
           fixedWidthPx: scaleByFontFactor(85.0),
           alignment: ColumnAlignment.right,
         );
@@ -141,7 +141,7 @@
   _RetainedSizeColumn(this.totalSize)
       : super(
           'Retained Dart Size',
-          titleTooltip: retainedSizeColumnTooltip,
+          titleTooltip: SizeType.retained.description,
           fixedWidthPx: scaleByFontFactor(140.0),
           alignment: ColumnAlignment.right,
         );
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/snapshot_view.dart b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/snapshot_view.dart
index fa4fd95..3d2ce08 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/snapshot_view.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/snapshot_view.dart
@@ -73,6 +73,7 @@
             classFilterButton: classFilterButton,
             before: diffHeapClasses.before,
             after: diffHeapClasses.after,
+            sizeTypeToShowForDiff: controller.sizeTypeToShowForDiff,
           );
         } else {
           throw StateError('singleClasses or diffClasses should not be null.');
diff --git a/packages/devtools_app/lib/src/screens/memory/panes/profile/profile_view.dart b/packages/devtools_app/lib/src/screens/memory/panes/profile/profile_view.dart
index 481cc45..027973f 100644
--- a/packages/devtools_app/lib/src/screens/memory/panes/profile/profile_view.dart
+++ b/packages/devtools_app/lib/src/screens/memory/panes/profile/profile_view.dart
@@ -139,7 +139,7 @@
   _FieldDartHeapSizeColumn({required super.heap})
       : super._(
           title: 'Dart Heap',
-          titleTooltip: shallowSizeColumnTooltip,
+          titleTooltip: SizeType.shallow.description,
         );
 
   @override
diff --git a/packages/devtools_app/lib/src/screens/memory/shared/primitives/simple_elements.dart b/packages/devtools_app/lib/src/screens/memory/shared/primitives/simple_elements.dart
index 51e1344..8f74649 100644
--- a/packages/devtools_app/lib/src/screens/memory/shared/primitives/simple_elements.dart
+++ b/packages/devtools_app/lib/src/screens/memory/shared/primitives/simple_elements.dart
@@ -2,17 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-const String shallowSizeColumnTooltip =
-    'The total shallow size of all of the instances.\n'
-    'The shallow size of an object is the size of the object\n'
-    'plus the references it holds to other Dart objects\n'
-    "in its fields (this doesn't include the size of\n"
-    'the fields - just the size of the references).';
-
-const String retainedSizeColumnTooltip =
-    'Total shallow Dart size of objects plus shallow Dart size of objects they retain,\n'
-    'taking into account only the shortest retaining path for the referenced objects.';
-
 const String nonGcableInstancesColumnTooltip =
     'Number of instances of the class,\n'
     'that are reachable, i.e. have a retaining path from the root\n'
@@ -37,3 +26,26 @@
   final String hash;
   String get value => '$url#$hash';
 }
+
+enum SizeType {
+  shallow(
+    displayName: 'Shallow',
+    description: 'The total shallow size of all of the instances.\n'
+        'The shallow size of an object is the size of the object\n'
+        'plus the references it holds to other Dart objects\n'
+        "in its fields (this doesn't include the size of\n"
+        'the fields - just the size of the references).',
+  ),
+  retained(
+    displayName: 'Retained',
+    description:
+        'Total shallow Dart size of objects plus shallow Dart size of objects they retain,\n'
+        'taking into account only the shortest retaining path for the referenced objects.',
+  ),
+  ;
+
+  const SizeType({required this.displayName, required this.description});
+
+  final String displayName;
+  final String description;
+}
diff --git a/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_custom_diff.png b/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_custom_diff.png
index 97ee8af..91870c7 100644
--- a/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_custom_diff.png
+++ b/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_custom_diff.png
Binary files differ
diff --git a/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_except_diff.png b/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_except_diff.png
index 7959f26..928e0c0 100644
--- a/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_except_diff.png
+++ b/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_except_diff.png
Binary files differ
diff --git a/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_only_diff.png b/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_only_diff.png
index 97ee8af..91870c7 100644
--- a/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_only_diff.png
+++ b/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_only_diff.png
Binary files differ
diff --git a/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_scene_diff.png b/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_scene_diff.png
index 3f8dcb0..f7e660a 100644
--- a/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_scene_diff.png
+++ b/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_scene_diff.png
Binary files differ
diff --git a/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_showAll_diff.png b/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_showAll_diff.png
index 7959f26..928e0c0 100644
--- a/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_showAll_diff.png
+++ b/packages/devtools_app/test/test_infra/goldens/memory_diff_snapshot_showAll_diff.png
Binary files differ