diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index 82bb2bd933b..6417185f8b0 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -347,12 +347,7 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture /// using `flutter run`. @protected ViewConfiguration createViewConfigurationFor(RenderView renderView) { - final FlutterView view = renderView.flutterView; - final double devicePixelRatio = view.devicePixelRatio; - return ViewConfiguration( - size: view.physicalSize / devicePixelRatio, - devicePixelRatio: devicePixelRatio, - ); + return ViewConfiguration.fromView(renderView.flutterView); } /// Called when the system metrics change. diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index 1449d8b8fbb..f01a45e621e 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'dart:math' as math; -import 'dart:ui' as ui show lerpDouble; +import 'dart:ui' as ui show ViewConstraints, lerpDouble; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -153,6 +153,13 @@ class BoxConstraints extends Constraints { minHeight = height ?? double.infinity, maxHeight = height ?? double.infinity; + /// Creates box constraints that match the given view constraints. + BoxConstraints.fromViewConstraints(ui.ViewConstraints constraints) + : minWidth = constraints.minWidth, + maxWidth = constraints.maxWidth, + minHeight = constraints.minHeight, + maxHeight = constraints.maxHeight; + /// The minimum width that satisfies the constraints. final double minWidth; diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart index 03db7f1d608..c4842cfc00a 100644 --- a/packages/flutter/lib/src/rendering/view.dart +++ b/packages/flutter/lib/src/rendering/view.dart @@ -19,14 +19,40 @@ import 'object.dart'; class ViewConfiguration { /// Creates a view configuration. /// - /// By default, the view has zero [size] and a [devicePixelRatio] of 1.0. + /// By default, the view has [logicalConstraints] and [physicalConstraints] + /// with all dimensions set to zero (i.e. the view is forced to [Size.zero]) + /// and a [devicePixelRatio] of 1.0. + /// + /// [ViewConfiguration.fromView] is a more convenient way for deriving a + /// [ViewConfiguration] from a given [FlutterView]. const ViewConfiguration({ - this.size = Size.zero, + this.physicalConstraints = const BoxConstraints(maxWidth: 0, maxHeight: 0), + this.logicalConstraints = const BoxConstraints(maxWidth: 0, maxHeight: 0), this.devicePixelRatio = 1.0, }); - /// The size of the output surface. - final Size size; + /// Creates a view configuration for the provided [FlutterView]. + factory ViewConfiguration.fromView(ui.FlutterView view) { + final BoxConstraints physicalConstraints = BoxConstraints.fromViewConstraints(view.physicalConstraints); + final double devicePixelRatio = view.devicePixelRatio; + return ViewConfiguration( + physicalConstraints: physicalConstraints, + logicalConstraints: physicalConstraints / devicePixelRatio, + devicePixelRatio: devicePixelRatio, + ); + } + + /// The constraints of the output surface in logical pixel. + /// + /// The constraints are passed to the child of the root render object. + final BoxConstraints logicalConstraints; + + /// The constraints of the output surface in physical pixel. + /// + /// These constraints are enforced in [toPhysicalSize] when translating + /// the logical size of the root render object back to physical pixels for + /// the [FlutterView.render] method. + final BoxConstraints physicalConstraints; /// The pixel density of the output surface. final double devicePixelRatio; @@ -40,21 +66,36 @@ class ViewConfiguration { return Matrix4.diagonal3Values(devicePixelRatio, devicePixelRatio, 1.0); } + /// Transforms the provided [Size] in logical pixels to physical pixels. + /// + /// The [FlutterView.render] method accepts only sizes in physical pixels, but + /// the framework operates in logical pixels. This method is used to transform + /// the logical size calculated for a [RenderView] back to a physical size + /// suitable to be passed to [FlutterView.render]. + /// + /// By default, this method just multiplies the provided [Size] with the + /// [devicePixelRatio] and constraints the results to the + /// [physicalConstraints]. + Size toPhysicalSize(Size logicalSize) { + return physicalConstraints.constrain(logicalSize * devicePixelRatio); + } + @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { return false; } return other is ViewConfiguration - && other.size == size + && other.logicalConstraints == logicalConstraints + && other.physicalConstraints == physicalConstraints && other.devicePixelRatio == devicePixelRatio; } @override - int get hashCode => Object.hash(size, devicePixelRatio); + int get hashCode => Object.hash(logicalConstraints, physicalConstraints, devicePixelRatio); @override - String toString() => '$size at ${debugFormatDouble(devicePixelRatio)}x'; + String toString() => '$logicalConstraints at ${debugFormatDouble(devicePixelRatio)}x'; } /// The root of the render tree. @@ -76,8 +117,10 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin RenderBox? child, ViewConfiguration? configuration, required ui.FlutterView view, - }) : _configuration = configuration, - _view = view { + }) : _view = view { + if (configuration != null) { + this.configuration = configuration; + } this.child = child; } @@ -119,6 +162,14 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin /// Whether a [configuration] has been set. bool get hasConfiguration => _configuration != null; + @override + BoxConstraints get constraints { + if (!hasConfiguration) { + throw StateError('Constraints are not available because RenderView has not been given a configuration yet.'); + } + return configuration.logicalConstraints; + } + /// The [FlutterView] into which this [RenderView] will render. ui.FlutterView get flutterView => _view; final ui.FlutterView _view; @@ -188,12 +239,13 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin @override void performLayout() { assert(_rootTransform != null); - _size = configuration.size; - assert(_size.isFinite); - + final bool sizedByChild = !constraints.isTight; if (child != null) { - child!.layout(BoxConstraints.tight(_size)); + child!.layout(constraints, parentUsesSize: sizedByChild); } + _size = sizedByChild && child != null ? child!.size : constraints.smallest; + assert(size.isFinite); + assert(constraints.isSatisfiedBy(size)); } /// Determines the set of render objects located at the given position. @@ -253,7 +305,8 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin if (automaticSystemUiAdjustment) { _updateSystemChrome(); } - _view.render(scene); + assert(configuration.logicalConstraints.isSatisfiedBy(size)); + _view.render(scene, size: configuration.toPhysicalSize(size)); scene.dispose(); assert(() { if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled) { diff --git a/packages/flutter/test/foundation/service_extensions_test.dart b/packages/flutter/test/foundation/service_extensions_test.dart index 88056b25897..14db86f397f 100644 --- a/packages/flutter/test/foundation/service_extensions_test.dart +++ b/packages/flutter/test/foundation/service_extensions_test.dart @@ -259,7 +259,8 @@ void main() { r' debug mode enabled - [a-zA-Z]+\n' r' view size: Size\(2400\.0, 1800\.0\) \(in physical pixels\)\n' r' device pixel ratio: 3\.0 \(physical pixels per logical pixel\)\n' - r' configuration: Size\(800\.0, 600\.0\) at 3\.0x \(in logical pixels\)\n' + r' configuration: BoxConstraints\(w=800\.0, h=600\.0\) at 3\.0x \(in\n' + r' logical pixels\)\n' r'$', ), }); diff --git a/packages/flutter/test/rendering/box_constraints_test.dart b/packages/flutter/test/rendering/box_constraints_test.dart index 48dadde8d51..6c6cbe14458 100644 --- a/packages/flutter/test/rendering/box_constraints_test.dart +++ b/packages/flutter/test/rendering/box_constraints_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:ui'; + import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -167,4 +169,17 @@ void main() { expect(copy.minHeight, 11.0); expect(copy.maxHeight, 18.0); }); + + test('BoxConstraints.fromViewConstraints', () { + final BoxConstraints unconstrained = BoxConstraints.fromViewConstraints( + const ViewConstraints(), + ); + expect(unconstrained, const BoxConstraints()); + + final BoxConstraints constraints = BoxConstraints.fromViewConstraints( + const ViewConstraints(minWidth: 1, maxWidth: 2, minHeight: 3, maxHeight: 4), + ); + expect(constraints, const BoxConstraints(minWidth: 1, maxWidth: 2, minHeight: 3, maxHeight: 4)); + }); + } diff --git a/packages/flutter/test/rendering/independent_layout_test.dart b/packages/flutter/test/rendering/independent_layout_test.dart index ce40300a529..ed2bbabaa98 100644 --- a/packages/flutter/test/rendering/independent_layout_test.dart +++ b/packages/flutter/test/rendering/independent_layout_test.dart @@ -32,8 +32,8 @@ class TestLayout { void main() { TestRenderingFlutterBinding.ensureInitialized(); - const ViewConfiguration testConfiguration = ViewConfiguration( - size: Size(800.0, 600.0), + final ViewConfiguration testConfiguration = ViewConfiguration( + logicalConstraints: BoxConstraints.tight(const Size(800.0, 600.0)), ); test('onscreen layout does not affect offscreen', () { diff --git a/packages/flutter/test/rendering/layers_test.dart b/packages/flutter/test/rendering/layers_test.dart index 20f446c214f..48d60390956 100644 --- a/packages/flutter/test/rendering/layers_test.dart +++ b/packages/flutter/test/rendering/layers_test.dart @@ -169,7 +169,8 @@ void main() { test('switching layer link of an attached leader layer should not crash', () { final LayerLink link = LayerLink(); final LeaderLayer leaderLayer = LeaderLayer(link: link); - final RenderView view = RenderView(configuration: const ViewConfiguration(), view: RendererBinding.instance.platformDispatcher.views.single); + final FlutterView flutterView = RendererBinding.instance.platformDispatcher.views.single; + final RenderView view = RenderView(configuration: ViewConfiguration.fromView(flutterView), view: flutterView); leaderLayer.attach(view); final LayerLink link2 = LayerLink(); leaderLayer.link = link2; @@ -182,7 +183,8 @@ void main() { final LayerLink link = LayerLink(); final LeaderLayer leaderLayer1 = LeaderLayer(link: link); final LeaderLayer leaderLayer2 = LeaderLayer(link: link); - final RenderView view = RenderView(configuration: const ViewConfiguration(), view: RendererBinding.instance.platformDispatcher.views.single); + final FlutterView flutterView = RendererBinding.instance.platformDispatcher.views.single; + final RenderView view = RenderView(configuration: ViewConfiguration.fromView(flutterView), view: flutterView); leaderLayer1.attach(view); leaderLayer2.attach(view); leaderLayer2.detach(); diff --git a/packages/flutter/test/rendering/multi_view_binding_test.dart b/packages/flutter/test/rendering/multi_view_binding_test.dart index 4edfbcf3129..76bd94706f2 100644 --- a/packages/flutter/test/rendering/multi_view_binding_test.dart +++ b/packages/flutter/test/rendering/multi_view_binding_test.dart @@ -18,7 +18,7 @@ void main() { binding.addRenderView(view); expect(binding.renderViews, contains(view)); expect(view.configuration.devicePixelRatio, flutterView.devicePixelRatio); - expect(view.configuration.size, flutterView.physicalSize / flutterView.devicePixelRatio); + expect(view.configuration.logicalConstraints, BoxConstraints.tight(flutterView.physicalSize) / flutterView.devicePixelRatio); binding.removeRenderView(view); expect(binding.renderViews, isEmpty); @@ -51,13 +51,17 @@ void main() { final RenderView view = RenderView(view: flutterView); binding.addRenderView(view); expect(view.configuration.devicePixelRatio, 2.5); - expect(view.configuration.size, const Size(160.0, 240.0)); + expect(view.configuration.logicalConstraints.isTight, isTrue); + expect(view.configuration.logicalConstraints.minWidth, 160.0); + expect(view.configuration.logicalConstraints.minHeight, 240.0); flutterView.devicePixelRatio = 3.0; flutterView.physicalSize = const Size(300, 300); binding.handleMetricsChanged(); expect(view.configuration.devicePixelRatio, 3.0); - expect(view.configuration.size, const Size(100.0, 100.0)); + expect(view.configuration.logicalConstraints.isTight, isTrue); + expect(view.configuration.logicalConstraints.minWidth, 100.0); + expect(view.configuration.logicalConstraints.minHeight, 100.0); binding.removeRenderView(view); }); @@ -183,6 +187,8 @@ class FakeFlutterView extends Fake implements FlutterView { @override Size physicalSize; @override + ViewConstraints get physicalConstraints => ViewConstraints.tight(physicalSize); + @override ViewPadding padding; List renderedScenes = []; diff --git a/packages/flutter/test/rendering/view_constraints_test.dart b/packages/flutter/test/rendering/view_constraints_test.dart new file mode 100644 index 00000000000..b5812819a76 --- /dev/null +++ b/packages/flutter/test/rendering/view_constraints_test.dart @@ -0,0 +1,43 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Properly constraints the physical size', (WidgetTester tester) async { + final FlutterViewSpy view = FlutterViewSpy(view: tester.view) + ..physicalConstraints = ViewConstraints.tight(const Size(1008.0, 2198.0)) + ..devicePixelRatio = 1.912500023841858; + + await pumpWidgetWithoutViewWrapper( + tester: tester, + widget: View( + view: view, + child: const SizedBox(), + ), + ); + + expect(view.sizes.single, const Size(1008.0, 2198.0)); + }); +} + +class FlutterViewSpy extends TestFlutterView { + FlutterViewSpy({required TestFlutterView super.view}) : super(platformDispatcher: view.platformDispatcher, display: view.display); + + List sizes = []; + + @override + void render(Scene scene, {Size? size}) { + sizes.add(size); + } +} + +Future pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) { + tester.binding.attachRootWidget(widget); + tester.binding.scheduleFrame(); + return tester.binding.pump(); +} diff --git a/packages/flutter/test/rendering/view_test.dart b/packages/flutter/test/rendering/view_test.dart index 9caed8c544a..c85a38a8f29 100644 --- a/packages/flutter/test/rendering/view_test.dart +++ b/packages/flutter/test/rendering/view_test.dart @@ -16,14 +16,19 @@ void main() { Size size = const Size(20, 20), double devicePixelRatio = 2.0, }) { - return ViewConfiguration(size: size, devicePixelRatio: devicePixelRatio); + final BoxConstraints constraints = BoxConstraints.tight(size); + return ViewConfiguration( + logicalConstraints: constraints, + physicalConstraints: constraints * devicePixelRatio, + devicePixelRatio: devicePixelRatio, + ); } group('RenderView', () { test('accounts for device pixel ratio in paintBounds', () { layout(RenderAspectRatio(aspectRatio: 1.0)); pumpFrame(); - final Size logicalSize = TestRenderingFlutterBinding.instance.renderView.configuration.size; + final Size logicalSize = TestRenderingFlutterBinding.instance.renderView.size; final double devicePixelRatio = TestRenderingFlutterBinding.instance.renderView.configuration.devicePixelRatio; final Size physicalSize = logicalSize * devicePixelRatio; expect(TestRenderingFlutterBinding.instance.renderView.paintBounds, Offset.zero & physicalSize); @@ -126,11 +131,40 @@ void main() { final RenderView view = RenderView( view: RendererBinding.instance.platformDispatcher.views.single, ); - view.configuration = const ViewConfiguration(size: Size(100, 200), devicePixelRatio: 3.0); - view.configuration = const ViewConfiguration(size: Size(200, 300), devicePixelRatio: 2.0); + view.configuration = ViewConfiguration(logicalConstraints: BoxConstraints.tight(const Size(100, 200)), devicePixelRatio: 3.0); + view.configuration = ViewConfiguration(logicalConstraints: BoxConstraints.tight(const Size(200, 300)), devicePixelRatio: 2.0); PipelineOwner().rootNode = view; view.prepareInitialFrame(); }); + + test('Constraints are derived from configuration', () { + const BoxConstraints constraints = BoxConstraints(minWidth: 1, maxWidth: 2, minHeight: 3, maxHeight: 4); + const double devicePixelRatio = 3.0; + final ViewConfiguration config = ViewConfiguration( + logicalConstraints: constraints, + physicalConstraints: constraints * devicePixelRatio, + devicePixelRatio: devicePixelRatio, + ); + + // Configuration set via setter. + final RenderView view = RenderView( + view: RendererBinding.instance.platformDispatcher.views.single, + ); + expect(() => view.constraints, throwsA(isA().having( + (StateError e) => e.message, + 'message', + contains('RenderView has not been given a configuration yet'), + ))); + view.configuration = config; + expect(view.constraints, constraints); + + // Configuration set in constructor. + final RenderView view2 = RenderView( + view: RendererBinding.instance.platformDispatcher.views.single, + configuration: config, + ); + expect(view2.constraints, constraints); + }); } const Color orange = Color(0xFFFF9000); diff --git a/packages/flutter/test/widgets/independent_widget_layout_test.dart b/packages/flutter/test/widgets/independent_widget_layout_test.dart index e35c5dc768c..9d91297cbf3 100644 --- a/packages/flutter/test/widgets/independent_widget_layout_test.dart +++ b/packages/flutter/test/widgets/independent_widget_layout_test.dart @@ -8,8 +8,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; -const Size _kTestViewSize = Size(800.0, 600.0); - class ScheduledFrameTrackingPlatformDispatcher extends TestPlatformDispatcher { ScheduledFrameTrackingPlatformDispatcher({ required super.platformDispatcher }); @@ -36,7 +34,7 @@ class ScheduledFrameTrackingBindings extends AutomatedTestWidgetsFlutterBinding class OffscreenRenderView extends RenderView { OffscreenRenderView({required super.view}) : super( - configuration: const ViewConfiguration(size: _kTestViewSize), + configuration: TestViewConfiguration.fromView(view: view), ); @override diff --git a/packages/flutter/test/widgets/keep_alive_test.dart b/packages/flutter/test/widgets/keep_alive_test.dart index 89a44907feb..9f02368597f 100644 --- a/packages/flutter/test/widgets/keep_alive_test.dart +++ b/packages/flutter/test/widgets/keep_alive_test.dart @@ -217,7 +217,8 @@ void main() { ' │ debug mode enabled - ${Platform.operatingSystem}\n' ' │ view size: Size(2400.0, 1800.0) (in physical pixels)\n' ' │ device pixel ratio: 3.0 (physical pixels per logical pixel)\n' - ' │ configuration: Size(800.0, 600.0) at 3.0x (in logical pixels)\n' + ' │ configuration: BoxConstraints(w=800.0, h=600.0) at 3.0x (in\n' + ' │ logical pixels)\n' ' │\n' ' └─child: RenderRepaintBoundary#00000\n' ' │ needs compositing\n' @@ -391,7 +392,8 @@ void main() { ' │ debug mode enabled - ${Platform.operatingSystem}\n' ' │ view size: Size(2400.0, 1800.0) (in physical pixels)\n' ' │ device pixel ratio: 3.0 (physical pixels per logical pixel)\n' - ' │ configuration: Size(800.0, 600.0) at 3.0x (in logical pixels)\n' + ' │ configuration: BoxConstraints(w=800.0, h=600.0) at 3.0x (in\n' + ' │ logical pixels)\n' ' │\n' ' └─child: RenderRepaintBoundary#00000\n' ' │ needs compositing\n' diff --git a/packages/flutter/test/widgets/view_test.dart b/packages/flutter/test/widgets/view_test.dart index 2177459d0b8..7f931765049 100644 --- a/packages/flutter/test/widgets/view_test.dart +++ b/packages/flutter/test/widgets/view_test.dart @@ -449,6 +449,68 @@ void main() { }); expect(children, isNot(contains(rawViewOwner))); }); + + testWidgets('RenderView does not use size of child if constraints are tight', (WidgetTester tester) async { + const Size physicalSize = Size(300, 600); + final Size logicalSize = physicalSize / tester.view.devicePixelRatio; + tester.view.physicalConstraints = ViewConstraints.tight(physicalSize); + await tester.pumpWidget(const Placeholder()); + + final RenderView renderView = tester.renderObject(find.byType(View)); + expect(renderView.constraints, BoxConstraints.tight(logicalSize)); + expect(renderView.size, logicalSize); + + final RenderBox child = renderView.child!; + expect(child.constraints, BoxConstraints.tight(logicalSize)); + expect(child.debugCanParentUseSize, isFalse); + expect(child.size, logicalSize); + }); + + testWidgets('RenderView sizes itself to child if constraints allow it (unconstrained)', (WidgetTester tester) async { + const Size size = Size(300, 600); + tester.view.physicalConstraints = const ViewConstraints(); // unconstrained + await tester.pumpWidget(SizedBox.fromSize(size: size)); + + final RenderView renderView = tester.renderObject(find.byType(View)); + expect(renderView.constraints, const BoxConstraints()); + expect(renderView.size, size); + + final RenderBox child = renderView.child!; + expect(child.constraints, const BoxConstraints()); + expect(child.debugCanParentUseSize, isTrue); + expect(child.size, size); + }); + + testWidgets('RenderView sizes itself to child if constraints allow it (constrained)', (WidgetTester tester) async { + const Size size = Size(30, 60); + const ViewConstraints viewConstraints = ViewConstraints(maxWidth: 333, maxHeight: 666); + final BoxConstraints boxConstraints = BoxConstraints.fromViewConstraints(viewConstraints / tester.view.devicePixelRatio); + tester.view.physicalConstraints = viewConstraints; + await tester.pumpWidget(SizedBox.fromSize(size: size)); + + final RenderView renderView = tester.renderObject(find.byType(View)); + expect(renderView.constraints, boxConstraints); + expect(renderView.size, size); + + final RenderBox child = renderView.child!; + expect(child.constraints, boxConstraints); + expect(child.debugCanParentUseSize, isTrue); + expect(child.size, size); + }); + + testWidgets('RenderView respects constraints when child wants to be bigger than allowed', (WidgetTester tester) async { + const Size size = Size(3000, 6000); + const ViewConstraints viewConstraints = ViewConstraints(maxWidth: 300, maxHeight: 600); + tester.view.physicalConstraints = viewConstraints; + await tester.pumpWidget(SizedBox.fromSize(size: size)); + + final RenderView renderView = tester.renderObject(find.byType(View)); + expect(renderView.size, const Size(100, 200)); // viewConstraints.biggest / devicePixelRatio + + final RenderBox child = renderView.child!; + expect(child.debugCanParentUseSize, isTrue); + expect(child.size, const Size(100, 200)); + }); } Future pumpWidgetWithoutViewWrapper({required WidgetTester tester, required Widget widget}) { diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart index 4902f310fe8..8e5345419c9 100644 --- a/packages/flutter/test/widgets/widget_inspector_test.dart +++ b/packages/flutter/test/widgets/widget_inspector_test.dart @@ -4690,7 +4690,14 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { expect(renderObject!['description'], contains('RenderView')); expect(result['parentRenderElement'], isNull); - expect(result['constraints'], isNull); + + final Map? constraints = result['constraints'] as Map?; + expect(constraints, isNotNull); + expect(constraints!['type'], equals('BoxConstraints')); + expect(constraints['minWidth'], equals('800.0')); + expect(constraints['minHeight'], equals('600.0')); + expect(constraints['maxWidth'], equals('800.0')); + expect(constraints['maxHeight'], equals('600.0')); expect(result['isBox'], isNull); final Map? size = result['size'] as Map?; diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 93a0dd59b2c..cc341e31598 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -559,8 +559,10 @@ abstract class TestWidgetsFlutterBinding extends BindingBase } final FlutterView view = renderView.flutterView; if (_surfaceSize != null && view == platformDispatcher.implicitView) { + final BoxConstraints constraints = BoxConstraints.tight(_surfaceSize!); return ViewConfiguration( - size: _surfaceSize!, + logicalConstraints: constraints, + physicalConstraints: constraints * view.devicePixelRatio, devicePixelRatio: view.devicePixelRatio, ); } @@ -1832,7 +1834,7 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { final Map? pointerIdToRecord = _renderViewToPointerIdToPointerRecord[renderView]; if (pointerIdToRecord != null && pointerIdToRecord.isNotEmpty) { - final double radius = renderView.configuration.size.shortestSide * 0.05; + final double radius = renderView.size.shortestSide * 0.05; final Path path = Path() ..addOval(Rect.fromCircle(center: Offset.zero, radius: radius)) ..moveTo(0.0, -radius * 2.0) @@ -2116,9 +2118,14 @@ class TestViewConfiguration extends ViewConfiguration { /// Creates a [TestViewConfiguration] with the given size and view. /// /// The [size] defaults to 800x600. - TestViewConfiguration.fromView({required ui.FlutterView view, super.size = _kDefaultTestViewportSize}) + TestViewConfiguration.fromView({required ui.FlutterView view, Size size = _kDefaultTestViewportSize}) : _paintMatrix = _getMatrix(size, view.devicePixelRatio, view), - super(devicePixelRatio: view.devicePixelRatio); + _physicalSize = view.physicalSize, + super( + devicePixelRatio: view.devicePixelRatio, + logicalConstraints: BoxConstraints.tight(size), + physicalConstraints: BoxConstraints.tight(size) * view.devicePixelRatio, + ); static Matrix4 _getMatrix(Size size, double devicePixelRatio, ui.FlutterView window) { final double inverseRatio = devicePixelRatio / window.devicePixelRatio; @@ -2149,6 +2156,11 @@ class TestViewConfiguration extends ViewConfiguration { @override Matrix4 toMatrix() => _paintMatrix.clone(); + final Size _physicalSize; + + @override + Size toPhysicalSize(Size logicalSize) => _physicalSize; + @override String toString() => 'TestViewConfiguration'; } diff --git a/packages/flutter_test/lib/src/window.dart b/packages/flutter_test/lib/src/window.dart index 5a38fe531a3..c4596573952 100644 --- a/packages/flutter_test/lib/src/window.dart +++ b/packages/flutter_test/lib/src/window.dart @@ -750,6 +750,9 @@ class TestFlutterView implements FlutterView { /// can only be set in a test environment to emulate different view /// configurations. A standard [FlutterView] is not mutable from the framework. /// + /// Setting this value also sets [physicalConstraints] to tight constraints + /// based on the given size. + /// /// See also: /// /// * [FlutterView.physicalSize] for the standard implementation @@ -760,12 +763,39 @@ class TestFlutterView implements FlutterView { Size? _physicalSize; set physicalSize(Size value) { _physicalSize = value; + // For backwards compatibility the constraints are set based on the provided size. + physicalConstraints = ViewConstraints.tight(value); + } + + /// Resets [physicalSize] (and implicitly also the [physicalConstraints]) to + /// the default value for this view. + void resetPhysicalSize() { + _physicalSize = null; + resetPhysicalConstraints(); + } + + /// The physical constraints to use for this test. + /// + /// Defaults to the value provided by [FlutterView.physicalConstraints]. This + /// can only be set in a test environment to emulate different view + /// configurations. A standard [FlutterView] is not mutable from the framework. + /// + /// See also: + /// + /// * [FlutterView.physicalConstraints] for the standard implementation + /// * [physicalConstraints] to reset this value specifically + /// * [reset] to reset all test values for this view + @override + ViewConstraints get physicalConstraints => _physicalConstraints ?? _view.physicalConstraints; + ViewConstraints? _physicalConstraints; + set physicalConstraints(ViewConstraints value) { + _physicalConstraints = value; platformDispatcher.onMetricsChanged?.call(); } - /// Resets [physicalSize] to the default value for this view. - void resetPhysicalSize() { - _physicalSize = null; + /// Resets [physicalConstraints] to the default value for this view. + void resetPhysicalConstraints() { + _physicalConstraints = null; platformDispatcher.onMetricsChanged?.call(); } @@ -874,8 +904,7 @@ class TestFlutterView implements FlutterView { @override void render(Scene scene, {Size? size}) { - // TODO(goderbauer): Wire through size after https://github.com/flutter/engine/pull/48090 rolled in. - _view.render(scene); + _view.render(scene, size: size); } @override @@ -900,6 +929,7 @@ class TestFlutterView implements FlutterView { resetDisplayFeatures(); resetPadding(); resetPhysicalSize(); + // resetPhysicalConstraints is implicitly called by resetPhysicalSize. resetSystemGestureInsets(); resetViewInsets(); resetViewPadding(); @@ -1636,8 +1666,7 @@ class TestWindow implements SingletonFlutterWindow { ) @override void render(Scene scene, {Size? size}) { - // TODO(goderbauer): Wire through size after https://github.com/flutter/engine/pull/48090 rolled in. - _view.render(scene); + _view.render(scene, size: size); } @Deprecated( diff --git a/packages/flutter_test/test/view_test.dart b/packages/flutter_test/test/view_test.dart index 5e14ced0edb..2b245a39303 100644 --- a/packages/flutter_test/test/view_test.dart +++ b/packages/flutter_test/test/view_test.dart @@ -124,6 +124,19 @@ void main() { ); }); + testWidgets('faking physicalSize fakes physicalConstraints', (WidgetTester tester) async { + const Size fakeSize = Size(50, 50); + verifyPropertyFaked( + tester: tester, + realValue: trueImplicitView().physicalConstraints, + fakeValue: ViewConstraints.tight(fakeSize), + propertyRetriever: () => boundImplicitView().physicalConstraints, + propertyFaker: (_, __) { + tester.view.physicalSize = fakeSize; + }, + ); + }); + testWidgets('can reset physicalSize', (WidgetTester tester) async { verifyPropertyReset( tester: tester, @@ -138,6 +151,47 @@ void main() { ); }); + testWidgets('resetting physicalSize resets physicalConstraints', (WidgetTester tester) async { + const Size fakeSize = Size(50, 50); + verifyPropertyReset( + tester: tester, + fakeValue: ViewConstraints.tight(fakeSize), + propertyRetriever: () => boundImplicitView().physicalConstraints, + propertyResetter: () { + tester.view.resetPhysicalSize(); + }, + propertyFaker: (_) { + tester.view.physicalSize = fakeSize; + }, + ); + }); + + testWidgets('can fake physicalConstraints', (WidgetTester tester) async { + verifyPropertyFaked( + tester: tester, + realValue: trueImplicitView().physicalConstraints, + fakeValue: const ViewConstraints(minWidth: 1, maxWidth: 2, minHeight: 3, maxHeight: 4), + propertyRetriever: () => boundImplicitView().physicalConstraints, + propertyFaker: (_, ViewConstraints fakeValue) { + tester.view.physicalConstraints = fakeValue; + }, + ); + }); + + testWidgets('can reset physicalConstraints', (WidgetTester tester) async { + verifyPropertyReset( + tester: tester, + fakeValue: const ViewConstraints(minWidth: 1, maxWidth: 2, minHeight: 3, maxHeight: 4), + propertyRetriever: () => boundImplicitView().physicalConstraints, + propertyResetter: () { + tester.view.resetPhysicalConstraints(); + }, + propertyFaker: (ViewConstraints fakeValue) { + tester.view.physicalConstraints = fakeValue; + }, + ); + }); + testWidgets('can fake systemGestureInsets', (WidgetTester tester) async { verifyPropertyFaked( tester: tester, diff --git a/packages/flutter_test/test/widget_tester_live_device_test.dart b/packages/flutter_test/test/widget_tester_live_device_test.dart index c6468a2f47f..18c151e9bdf 100644 --- a/packages/flutter_test/test/widget_tester_live_device_test.dart +++ b/packages/flutter_test/test/widget_tester_live_device_test.dart @@ -83,7 +83,7 @@ No widgets found at Offset(1.0, 1.0). ), ); - final Size originalSize = tester.binding.createViewConfigurationFor(tester.binding.renderView).size; // ignore: deprecated_member_use + final Size originalSize = tester.binding.renderView.size; // ignore: deprecated_member_use await tester.binding.setSurfaceSize(const Size(2000, 1800)); try { await tester.pump();