diff --git a/packages/devtools_app/lib/src/screens/debugger/program_explorer.dart b/packages/devtools_app/lib/src/screens/debugger/program_explorer.dart index 41f5ffb43e3..6863296b0c9 100644 --- a/packages/devtools_app/lib/src/screens/debugger/program_explorer.dart +++ b/packages/devtools_app/lib/src/screens/debugger/program_explorer.dart @@ -15,15 +15,13 @@ import '../../shared/primitives/auto_dispose.dart'; import '../../shared/primitives/utils.dart'; import '../../shared/theme.dart'; import '../../shared/tree.dart'; -import '../../shared/utils.dart'; import 'program_explorer_controller.dart'; import 'program_explorer_model.dart'; const containerIcon = Icons.folder; const libraryIcon = Icons.insert_drive_file; -double get _programExplorerRowHeight => scaleByFontFactor(22.0); -double get _selectedNodeTopSpacing => _programExplorerRowHeight * 3; +double get _selectedNodeTopSpacing => defaultTreeViewRowHeight * 3; class _ProgramExplorerRow extends StatelessWidget { const _ProgramExplorerRow({ @@ -313,7 +311,7 @@ class _FileExplorerState extends State<_FileExplorer> with AutoDisposeMixin { double get selectedNodeOffset => widget.controller.selectedNodeIndex.value == -1 ? -1 - : widget.controller.selectedNodeIndex.value * _programExplorerRowHeight; + : widget.controller.selectedNodeIndex.value * defaultTreeViewRowHeight; @override void initState() { @@ -334,7 +332,6 @@ class _FileExplorerState extends State<_FileExplorer> with AutoDisposeMixin { @override Widget build(BuildContext context) { return TreeView( - itemExtent: _programExplorerRowHeight, dataRootsListenable: widget.controller.rootObjectNodes, onItemSelected: widget.onItemSelected, onItemExpanded: widget.onItemExpanded, @@ -392,7 +389,6 @@ class _ProgramOutlineView extends StatelessWidget { return const CenteredCircularProgressIndicator(); } return TreeView( - itemExtent: _programExplorerRowHeight, dataRootsListenable: controller.outlineNodes, onItemSelected: onItemSelected, onItemExpanded: onItemExpanded, diff --git a/packages/devtools_app/lib/src/screens/memory/panes/chart/chart_pane.dart b/packages/devtools_app/lib/src/screens/memory/panes/chart/chart_pane.dart index 2c6e9b12f7f..8fb756ca688 100644 --- a/packages/devtools_app/lib/src/screens/memory/panes/chart/chart_pane.dart +++ b/packages/devtools_app/lib/src/screens/memory/panes/chart/chart_pane.dart @@ -327,8 +327,7 @@ class _MemoryChartPaneState extends State borderRadius: BorderRadius.circular(defaultBorderRadius), ), width: _hoverWidth, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: ListView( children: [ Container( width: _hoverWidth, @@ -476,37 +475,12 @@ class _MemoryChartPaneState extends State } List _displayExtensionEventsInHover(ChartsValues chartsValues) { - final widgets = []; - - final eventsDisplayed = chartsValues.extensionEventsToDisplay; - - for (var entry in eventsDisplayed.entries) { - if (entry.key.endsWith(eventsDisplayName)) { - widgets.add( - SizedBox( - height: _hoverEventsHeight, - child: ListView( - shrinkWrap: true, - primary: false, - children: [ - _listItem( - allEvents: chartsValues.extensionEvents, - title: entry.key, - ), - ], - ), - ), - ); - } else { - widgets.add(_hoverRow(name: entry.key, image: entry.value)); - - /// Pull out the event name, and custom values. - final output = - _displayEvent(null, chartsValues.extensionEvents.first).trim(); - widgets.add(_hoverRow(name: output)); - } - } - return widgets; + return [ + if (chartsValues.hasExtensionEvents) + ..._extensionEvents( + allEvents: chartsValues.extensionEvents, + ), + ]; } List _displayEventsInHover(ChartsValues chartsValues) { @@ -523,87 +497,70 @@ class _MemoryChartPaneState extends State return results; } - Widget _listItem({ + List _extensionEvents({ required List> allEvents, - required String title, }) { + final theme = Theme.of(context); + final widgets = []; var index = 1; for (var event in allEvents) { - final output = _displayEvent(index, event); - widgets.add(_cardWidget(output)); - index++; - } + late String? name; + if (event[eventName] == devToolsEvent && event.containsKey(customEvent)) { + final custom = event[customEvent] as Map; + name = custom[customEventName]; + } else { + name = event[eventName] as String?; + } - final theme = Theme.of(context); - final colorScheme = theme.colorScheme; - final collapsedColor = colorScheme.defaultBackgroundColor; - - return Material( - color: Colors.transparent, - child: Theme( - // TODO(kenz): why are we using a Material and a Theme widget here? - data: ThemeData(), - child: ExpansionTile( - tilePadding: EdgeInsets.zero, - childrenPadding: EdgeInsets.zero, - leading: Container( - padding: const EdgeInsets.only(left: 5, top: 4), - child: Image( - image: allEvents.length > 1 - ? const AssetImage(eventsLegend) - : const AssetImage(eventLegend), - ), + final output = _decodeEventValues(event); + widgets.add( + Padding( + padding: const EdgeInsets.only(left: intermediateSpacing), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '$index. $name', + overflow: TextOverflow.ellipsis, + style: theme.legendTextStyle, + ), + Padding( + padding: const EdgeInsets.only(left: densePadding), + child: Text( + output, + overflow: TextOverflow.ellipsis, + style: theme.legendTextStyle, + ), + ), + ], ), - backgroundColor: collapsedColor, - collapsedBackgroundColor: collapsedColor, - title: Text(title, style: theme.legendTextStyle), - children: widgets, ), - ), - ); - } - - Widget _cardWidget(String value) { - final theme = Theme.of(context); - final colorScheme = theme.colorScheme; - final expandedGradient = colorScheme.verticalGradient; + ); + index++; + } - return Container( - margin: const EdgeInsets.only(bottom: 8), - width: _hoverWidth, - decoration: BoxDecoration( - gradient: expandedGradient, - ), - child: Row( - children: [ - const SizedBox(width: 10), - Text( - value, - overflow: TextOverflow.ellipsis, - style: theme.legendTextStyle, + final eventsLength = allEvents.length; + final title = Row( + children: [ + Container( + padding: const EdgeInsets.only(left: 6.0), + child: Image( + image: AssetImage(eventLegendAsset(eventsLength)), ), - ], - ), + ), + const SizedBox(width: denseSpacing), + Text( + '$eventsLength ${pluralize('Event', eventsLength)}', + style: theme.legendTextStyle, + ), + ], ); - } - - String _displayEvent(int? index, Map event) { - final output = StringBuffer(); - - String? name; - - if (event[eventName] == devToolsEvent && event.containsKey(customEvent)) { - final custom = event[customEvent] as Map; - name = custom[customEventName]; - } else { - name = event[eventName] as String?; - } - - output.writeln(index == null ? name : '$index. $name'); - output.write(_decodeEventValues(event)); - - return output.toString(); + return [ + title, + ...widgets, + ]; } String _decodeEventValues(Map event) { diff --git a/packages/devtools_app/lib/src/screens/memory/panes/chart/legend.dart b/packages/devtools_app/lib/src/screens/memory/panes/chart/legend.dart index c85c474b7f9..fe5329b6f32 100644 --- a/packages/devtools_app/lib/src/screens/memory/panes/chart/legend.dart +++ b/packages/devtools_app/lib/src/screens/memory/panes/chart/legend.dart @@ -217,11 +217,12 @@ Map> eventLegendContent(bool isLight) => { manualGCLegendName: traceRender( image: gcManualLegend, ), + // TODO: why do we need both a singular and plural legend entry for event? eventLegendName: traceRender( - image: eventLegend, + image: eventLegendAsset(1), ), eventsLegendName: traceRender( - image: eventsLegend, + image: eventLegendAsset(2), ), }; diff --git a/packages/devtools_app/lib/src/screens/memory/panes/chart/memory_charts.dart b/packages/devtools_app/lib/src/screens/memory/panes/chart/memory_charts.dart index e8638767053..f542508dec5 100644 --- a/packages/devtools_app/lib/src/screens/memory/panes/chart/memory_charts.dart +++ b/packages/devtools_app/lib/src/screens/memory/panes/chart/memory_charts.dart @@ -270,22 +270,6 @@ class ChartsValues { return eventsDisplayed; } - Map get extensionEventsToDisplay { - final eventsDisplayed = {}; - - if (hasExtensionEvents) { - final eventLength = extensionEventsLength; - if (eventLength > 0) { - final displayKey = '$eventLength' - '${eventLength == 1 ? eventDisplayName : eventsDisplayName}'; - eventsDisplayed[displayKey] = - eventLength == 1 ? eventLegend : eventsLegend; - } - } - - return eventsDisplayed; - } - Map> displayVmDataToDisplay(List traces) { final vmDataDisplayed = >{}; diff --git a/packages/devtools_app/lib/src/screens/memory/panes/chart/memory_events_pane.dart b/packages/devtools_app/lib/src/screens/memory/panes/chart/memory_events_pane.dart index 29d9e7c4568..a1ea122449b 100644 --- a/packages/devtools_app/lib/src/screens/memory/panes/chart/memory_events_pane.dart +++ b/packages/devtools_app/lib/src/screens/memory/panes/chart/memory_events_pane.dart @@ -10,6 +10,7 @@ import '../../../../shared/charts/chart_controller.dart'; import '../../../../shared/charts/chart_trace.dart' as trace; import '../../../../shared/charts/chart_trace.dart' show ChartType; import '../../../../shared/primitives/auto_dispose.dart'; +import '../../../../shared/primitives/utils.dart'; import '../../../../shared/theme.dart'; import '../../../../shared/utils.dart'; import '../../framework/connected/memory_controller.dart'; @@ -24,8 +25,8 @@ const resetDarkLegend = '${_base}reset_glyph_dark.png'; const resetLightLegend = '${_base}reset_glyph_light.png'; const gcManualLegend = '${_base}gc_manual_glyph.png'; const gcVMLegend = '${_base}gc_vm_glyph.png'; -const eventLegend = '${_base}event_glyph.png'; -const eventsLegend = '${_base}events_glyph.png'; +String eventLegendAsset(int eventCount) => + '$_base${pluralize('event', eventCount)}_glyph.png'; /// Events trace name displayed const manualSnapshotLegendName = 'Snapshot'; diff --git a/packages/devtools_app/lib/src/shared/console/widgets/expandable_variable.dart b/packages/devtools_app/lib/src/shared/console/widgets/expandable_variable.dart index 4bffde63483..12348396817 100644 --- a/packages/devtools_app/lib/src/shared/console/widgets/expandable_variable.dart +++ b/packages/devtools_app/lib/src/shared/console/widgets/expandable_variable.dart @@ -36,7 +36,6 @@ class ExpandableVariable extends StatelessWidget { return TreeView( dataRootsListenable: FixedValueListenable>([variable]), - shrinkWrap: true, dataDisplayProvider: dataDisplayProvider ?? (variable, onPressed) => DisplayProvider( variable: variable, diff --git a/packages/devtools_app/lib/src/shared/tree.dart b/packages/devtools_app/lib/src/shared/tree.dart index 7f9d0500e9b..8bafdb648ee 100644 --- a/packages/devtools_app/lib/src/shared/tree.dart +++ b/packages/devtools_app/lib/src/shared/tree.dart @@ -11,6 +11,9 @@ import 'collapsible_mixin.dart'; import 'primitives/auto_dispose.dart'; import 'primitives/trees.dart'; import 'theme.dart'; +import 'utils.dart'; + +double get defaultTreeViewRowHeight => scaleByFontFactor(20.0); class TreeView> extends StatefulWidget { const TreeView({ @@ -19,8 +22,6 @@ class TreeView> extends StatefulWidget { required this.dataDisplayProvider, required this.onItemSelected, this.onItemExpanded, - this.shrinkWrap = false, - this.itemExtent, this.onTraverse, this.emptyTreeViewBuilder, this.scrollController, @@ -29,14 +30,6 @@ class TreeView> extends StatefulWidget { final ValueListenable> dataRootsListenable; - /// Use [shrinkWrap] iff you need to place a TreeView inside a ListView or - /// other container with unconstrained height. - /// - /// Enabling shrinkWrap impacts performance. - /// - /// Defaults to false. - final bool shrinkWrap; - final Widget Function(T, VoidCallback) dataDisplayProvider; /// Invoked when a tree node is selected. If [onItemExpanded] is not @@ -48,8 +41,6 @@ class TreeView> extends StatefulWidget { /// Otherwise, [onItemSelected] will be invoked, if provided. final FutureOr Function(T)? onItemExpanded; - final double? itemExtent; - /// Called on traversal of child node during [buildFlatList]. final void Function(T)? onTraverse; @@ -82,23 +73,25 @@ class _TreeViewState> extends State> @override Widget build(BuildContext context) { if (dataFlatList.isEmpty) return _emptyTreeViewBuilder(); - final content = SelectionArea( - child: ListView.builder( - itemCount: dataFlatList.length, - itemExtent: widget.itemExtent, - shrinkWrap: widget.shrinkWrap, - physics: widget.shrinkWrap ? const ClampingScrollPhysics() : null, - controller: widget.scrollController, - itemBuilder: (context, index) { - final T item = dataFlatList[index]; - return _TreeViewItem( - item, - buildDisplay: (onPressed) => - widget.dataDisplayProvider(item, onPressed), - onItemSelected: _onItemSelected, - onItemExpanded: _onItemExpanded, - ); - }, + final content = SizedBox( + height: dataFlatList.length * defaultTreeViewRowHeight, + child: SelectionArea( + child: ListView.builder( + itemCount: dataFlatList.length, + itemExtent: defaultTreeViewRowHeight, + physics: const ClampingScrollPhysics(), + controller: widget.scrollController, + itemBuilder: (context, index) { + final T item = dataFlatList[index]; + return _TreeViewItem( + item, + buildDisplay: (onPressed) => + widget.dataDisplayProvider(item, onPressed), + onItemSelected: _onItemSelected, + onItemExpanded: _onItemExpanded, + ); + }, + ), ), ); if (widget.includeScrollbar) { diff --git a/packages/devtools_app/lib/src/shared/ui/search.dart b/packages/devtools_app/lib/src/shared/ui/search.dart index 47e89be0cde..10423857a08 100644 --- a/packages/devtools_app/lib/src/shared/ui/search.dart +++ b/packages/devtools_app/lib/src/shared/ui/search.dart @@ -406,7 +406,7 @@ class AutoCompleteState extends State with AutoDisposeMixin { width: isMaxWidth ? box.size.width : AutoCompleteSearchControllerMixin.minPopupWidth, - height: bottom ? null : count * tileEntryHeight, + height: count * tileEntryHeight, child: CompositedTransformFollower( link: controller.autoCompleteLayerLink, showWhenUnlinked: false, @@ -417,7 +417,6 @@ class AutoCompleteState extends State with AutoDisposeMixin { child: TextFieldTapRegion( child: ListView( padding: EdgeInsets.zero, - shrinkWrap: true, itemExtent: tileEntryHeight, children: autoCompleteTiles, ),