Skip to content

Commit 34ddb37

Browse files
Fix RenderFlex overflow in debugging controls at narrow widths (#9949)
* Fix RenderFlex overflow in debugging controls at narrow widths The debugging controls drop their button labels below DebuggingControls.minWidth, but the remaining icon-only content still does not fit below roughly 630px, so the controls Row overflowed at widths that are realistic for DevTools embedded in an IDE side panel. Make the controls scroll horizontally so every control stays reachable instead of being clipped behind an overflow error. The file explorer button stays pinned outside the scroll view so it does not scroll out of reach, which keeps the wide layout visually unchanged. Fixes #4917 * Add release note for the debugging controls overflow fix * Point the release note at the actual PR number * Move test setup into a setUp block
1 parent 168bb17 commit 34ddb37

3 files changed

Lines changed: 128 additions & 11 deletions

File tree

packages/devtools_app/lib/src/screens/debugger/controls.dart

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,37 @@ class _DebuggingControlsState extends State<DebuggingControls>
6464
height: defaultButtonHeight,
6565
child: Row(
6666
children: [
67-
_pauseAndResumeButtons(
68-
isPaused: serviceConnection.serviceManager.isMainIsolatePaused,
69-
resuming: resuming,
67+
// The debugging controls have no way to shrink further once their
68+
// labels have already been dropped (see
69+
// [DebuggingControls.minWidth]), so below roughly 630px the icon-only
70+
// content still does not fit and the [Row] overflows. Making the
71+
// controls scroll horizontally keeps every control reachable at any
72+
// width instead of clipping them behind an overflow error. The
73+
// libraries button stays pinned on the right, outside the scroll
74+
// view, so it does not scroll out of reach.
75+
Expanded(
76+
child: SingleChildScrollView(
77+
scrollDirection: Axis.horizontal,
78+
child: Row(
79+
children: [
80+
_pauseAndResumeButtons(
81+
isPaused:
82+
serviceConnection.serviceManager.isMainIsolatePaused,
83+
resuming: resuming,
84+
),
85+
const SizedBox(width: denseSpacing),
86+
_stepButtons(canStep: canStep),
87+
const SizedBox(width: denseSpacing),
88+
BreakOnExceptionsControl(controller: controller),
89+
if (isVmApp) ...[
90+
const SizedBox(width: denseSpacing),
91+
CodeStatisticsControls(controller: controller),
92+
],
93+
],
94+
),
95+
),
7096
),
7197
const SizedBox(width: denseSpacing),
72-
_stepButtons(canStep: canStep),
73-
const SizedBox(width: denseSpacing),
74-
BreakOnExceptionsControl(controller: controller),
75-
if (isVmApp) ...[
76-
const SizedBox(width: denseSpacing),
77-
CodeStatisticsControls(controller: controller),
78-
],
79-
const Expanded(child: SizedBox(width: denseSpacing)),
8098
_librariesButton(),
8199
],
82100
),

packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ TODO: Remove this section if there are not any updates.
4444
* Fix a bug in the TextMate grammar parser that could result in code after
4545
comments being classified as comments.
4646
[#9921](https://github.com/flutter/devtools/pull/9921).
47+
* Fixed an overflow in the debugging controls when the Debugger screen is
48+
narrow, such as when DevTools is embedded in an IDE side panel. The controls
49+
now scroll horizontally instead of overflowing.
50+
[#9949](https://github.com/flutter/devtools/pull/9949)
4751

4852
## Network profiler updates
4953

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright 2026 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
4+
5+
import 'package:devtools_app/devtools_app.dart';
6+
import 'package:devtools_app/src/screens/debugger/controls.dart';
7+
import 'package:devtools_app_shared/ui.dart';
8+
import 'package:devtools_app_shared/utils.dart';
9+
import 'package:devtools_test/devtools_test.dart';
10+
import 'package:devtools_test/helpers.dart';
11+
import 'package:flutter/material.dart';
12+
import 'package:flutter_test/flutter_test.dart';
13+
import 'package:mockito/mockito.dart';
14+
15+
void main() {
16+
/// Widths the debugging controls are expected to lay out at without
17+
/// overflowing.
18+
///
19+
/// The controls stop showing button labels below
20+
/// [DebuggingControls.minWidth], but the remaining icon-only content still
21+
/// did not fit below roughly 630px, which is a realistic width for DevTools
22+
/// embedded in an IDE side panel. See
23+
/// https://github.com/flutter/devtools/issues/4917.
24+
const windowWidths = [1200.0, 800.0, 600.0, 500.0, 400.0];
25+
26+
const windowHeight = 800.0;
27+
28+
late FakeServiceConnectionManager fakeServiceConnection;
29+
late MockScriptManager scriptManager;
30+
late MockDebuggerController debuggerController;
31+
32+
setUp(() {
33+
fakeServiceConnection = FakeServiceConnectionManager();
34+
scriptManager = MockScriptManager();
35+
mockConnectedApp(fakeServiceConnection.serviceManager.connectedApp!);
36+
setGlobal(ServiceConnectionManager, fakeServiceConnection);
37+
setGlobal(IdeTheme, IdeTheme());
38+
setGlobal(ScriptManager, scriptManager);
39+
setGlobal(NotificationService, NotificationService());
40+
setGlobal(BreakpointManager, BreakpointManager());
41+
setGlobal(
42+
DevToolsEnvironmentParameters,
43+
ExternalDevToolsEnvironmentParameters(),
44+
);
45+
setGlobal(PreferencesController, PreferencesController());
46+
fakeServiceConnection.consoleService.ensureServiceInitialized();
47+
when(
48+
fakeServiceConnection.errorBadgeManager.errorCountNotifier('debugger'),
49+
).thenReturn(ValueNotifier<int>(0));
50+
debuggerController = createMockDebuggerControllerWithDefaults();
51+
});
52+
53+
Future<void> pumpControls(WidgetTester tester) async {
54+
await tester.pumpWidget(
55+
wrapWithControllers(
56+
const DebuggingControls(),
57+
debugger: debuggerController,
58+
),
59+
);
60+
await tester.pump();
61+
}
62+
63+
group('DebuggingControls', () {
64+
for (final width in windowWidths) {
65+
testWidgetsWithWindowSize(
66+
'does not overflow at ${width.toInt()}px',
67+
Size(width, windowHeight),
68+
(WidgetTester tester) async {
69+
await pumpControls(tester);
70+
71+
expect(tester.takeException(), isNull);
72+
},
73+
);
74+
}
75+
76+
testWidgetsWithWindowSize(
77+
'keeps the file explorer button pinned to the right edge',
78+
const Size(1200.0, windowHeight),
79+
(WidgetTester tester) async {
80+
await pumpControls(tester);
81+
82+
final controlsRight = tester
83+
.getRect(find.byType(DebuggingControls))
84+
.right;
85+
final fileExplorerButtonRight = tester
86+
.getRect(
87+
find.widgetWithIcon(GaDevToolsButton, Icons.folder_outlined),
88+
)
89+
.right;
90+
91+
expect(fileExplorerButtonRight, equals(controlsRight));
92+
},
93+
);
94+
});
95+
}

0 commit comments

Comments
 (0)