diff --git a/packages/devtools_app/lib/src/screens/debugger/controls.dart b/packages/devtools_app/lib/src/screens/debugger/controls.dart index 7db8774ef63..44e211c34f0 100644 --- a/packages/devtools_app/lib/src/screens/debugger/controls.dart +++ b/packages/devtools_app/lib/src/screens/debugger/controls.dart @@ -64,19 +64,37 @@ class _DebuggingControlsState extends State height: defaultButtonHeight, child: Row( children: [ - _pauseAndResumeButtons( - isPaused: serviceConnection.serviceManager.isMainIsolatePaused, - resuming: resuming, + // The debugging controls have no way to shrink further once their + // labels have already been dropped (see + // [DebuggingControls.minWidth]), so below roughly 630px the icon-only + // content still does not fit and the [Row] overflows. Making the + // controls scroll horizontally keeps every control reachable at any + // width instead of clipping them behind an overflow error. The + // libraries button stays pinned on the right, outside the scroll + // view, so it does not scroll out of reach. + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + _pauseAndResumeButtons( + isPaused: + serviceConnection.serviceManager.isMainIsolatePaused, + resuming: resuming, + ), + const SizedBox(width: denseSpacing), + _stepButtons(canStep: canStep), + const SizedBox(width: denseSpacing), + BreakOnExceptionsControl(controller: controller), + if (isVmApp) ...[ + const SizedBox(width: denseSpacing), + CodeStatisticsControls(controller: controller), + ], + ], + ), + ), ), const SizedBox(width: denseSpacing), - _stepButtons(canStep: canStep), - const SizedBox(width: denseSpacing), - BreakOnExceptionsControl(controller: controller), - if (isVmApp) ...[ - const SizedBox(width: denseSpacing), - CodeStatisticsControls(controller: controller), - ], - const Expanded(child: SizedBox(width: denseSpacing)), _librariesButton(), ], ), diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 2d797c8109e..cffd939ad67 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -44,6 +44,10 @@ TODO: Remove this section if there are not any updates. * Fix a bug in the TextMate grammar parser that could result in code after comments being classified as comments. [#9921](https://github.com/flutter/devtools/pull/9921). +* Fixed an overflow in the debugging controls when the Debugger screen is + narrow, such as when DevTools is embedded in an IDE side panel. The controls + now scroll horizontally instead of overflowing. + [#9949](https://github.com/flutter/devtools/pull/9949) ## Network profiler updates diff --git a/packages/devtools_app/test/screens/debugger/debugger_controls_overflow_test.dart b/packages/devtools_app/test/screens/debugger/debugger_controls_overflow_test.dart new file mode 100644 index 00000000000..3daafb40a6c --- /dev/null +++ b/packages/devtools_app/test/screens/debugger/debugger_controls_overflow_test.dart @@ -0,0 +1,89 @@ +// Copyright 2026 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/debugger/controls.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'; +import 'package:mockito/mockito.dart'; + +void main() { + /// Widths the debugging controls are expected to lay out at without + /// overflowing. + /// + /// The controls stop showing button labels below + /// [DebuggingControls.minWidth], but the remaining icon-only content still + /// did not fit below roughly 630px, which is a realistic width for DevTools + /// embedded in an IDE side panel. See + /// https://github.com/flutter/devtools/issues/4917. + const windowWidths = [1200.0, 800.0, 600.0, 500.0, 400.0]; + + const windowHeight = 800.0; + + final fakeServiceConnection = FakeServiceConnectionManager(); + final scriptManager = MockScriptManager(); + mockConnectedApp(fakeServiceConnection.serviceManager.connectedApp!); + setGlobal(ServiceConnectionManager, fakeServiceConnection); + setGlobal(IdeTheme, IdeTheme()); + setGlobal(ScriptManager, scriptManager); + setGlobal(NotificationService, NotificationService()); + setGlobal(BreakpointManager, BreakpointManager()); + setGlobal( + DevToolsEnvironmentParameters, + ExternalDevToolsEnvironmentParameters(), + ); + setGlobal(PreferencesController, PreferencesController()); + fakeServiceConnection.consoleService.ensureServiceInitialized(); + when( + fakeServiceConnection.errorBadgeManager.errorCountNotifier('debugger'), + ).thenReturn(ValueNotifier(0)); + final debuggerController = createMockDebuggerControllerWithDefaults(); + + Future pumpControls(WidgetTester tester) async { + await tester.pumpWidget( + wrapWithControllers( + const DebuggingControls(), + debugger: debuggerController, + ), + ); + await tester.pump(); + } + + group('DebuggingControls', () { + for (final width in windowWidths) { + testWidgetsWithWindowSize( + 'does not overflow at ${width.toInt()}px', + Size(width, windowHeight), + (WidgetTester tester) async { + await pumpControls(tester); + + expect(tester.takeException(), isNull); + }, + ); + } + + testWidgetsWithWindowSize( + 'keeps the file explorer button pinned to the right edge', + const Size(1200.0, windowHeight), + (WidgetTester tester) async { + await pumpControls(tester); + + final controlsRight = tester + .getRect(find.byType(DebuggingControls)) + .right; + final fileExplorerButtonRight = tester + .getRect( + find.widgetWithIcon(GaDevToolsButton, Icons.folder_outlined), + ) + .right; + + expect(fileExplorerButtonRight, equals(controlsRight)); + }, + ); + }); +}