Unverified Commit 4cf89cc2 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Remove LiveTestRenderView (#127882)

In the multi view world, `RenderViews` are created by the `View` widget and no longer owned by the binding. Prior to this change, the `LiveTestWidgetsFlutterBinding` owned and managed a special subclass of `RenderView`, the `_LiveTestRenderView`. In the new world, where `RenderView`s can be created anywhere in the widget tree where a `View` widget is used, this setup is no longer feasible. This change removes this special `_LiveTestRenderView` and instead adds debug hocks to `RenderView` to allow the `LiveTestWidgetsFlutterBinding` to draw a debug overlay on top of the content of any `RenderView`.
parent 6f8ef1a1
...@@ -33,6 +33,10 @@ class ViewConfiguration { ...@@ -33,6 +33,10 @@ class ViewConfiguration {
final double devicePixelRatio; final double devicePixelRatio;
/// Creates a transformation matrix that applies the [devicePixelRatio]. /// Creates a transformation matrix that applies the [devicePixelRatio].
///
/// The matrix translates points from the local coordinate system of the
/// app (in logical pixels) to the global coordinate system of the
/// [FlutterView] (in physical pixels).
Matrix4 toMatrix() { Matrix4 toMatrix() {
return Matrix4.diagonal3Values(devicePixelRatio, devicePixelRatio, 1.0); return Matrix4.diagonal3Values(devicePixelRatio, devicePixelRatio, 1.0);
} }
...@@ -212,6 +216,15 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> ...@@ -212,6 +216,15 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
if (child != null) { if (child != null) {
context.paintChild(child!, offset); context.paintChild(child!, offset);
} }
assert(() {
final List<DebugPaintCallback> localCallbacks = _debugPaintCallbacks.toList();
for (final DebugPaintCallback paintCallback in localCallbacks) {
if (_debugPaintCallbacks.contains(paintCallback)) {
paintCallback(context, offset, this);
}
}
return true;
}());
} }
@override @override
...@@ -381,4 +394,47 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> ...@@ -381,4 +394,47 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
properties.add(DiagnosticsNode.message('semantics enabled')); properties.add(DiagnosticsNode.message('semantics enabled'));
} }
} }
static final List<DebugPaintCallback> _debugPaintCallbacks = <DebugPaintCallback>[];
/// Registers a [DebugPaintCallback] that is called every time a [RenderView]
/// repaints in debug mode.
///
/// The callback may paint a debug overlay on top of the content of the
/// [RenderView] provided to the callback. Callbacks are invoked in the
/// order they were registered in.
///
/// Neither registering a callback nor the continued presence of a callback
/// changes how often [RenderView]s are repainted. It is up to the owner of
/// the callback to call [markNeedsPaint] on any [RenderView] for which it
/// wants to update the painted overlay.
///
/// Does nothing in release mode.
static void debugAddPaintCallback(DebugPaintCallback callback) {
assert(() {
_debugPaintCallbacks.add(callback);
return true;
}());
}
/// Removes a callback registered with [debugAddPaintCallback].
///
/// It does not schedule a frame to repaint the [RenderView]s without the
/// overlay painted by the removed callback. It is up to the owner of the
/// callback to call [markNeedsPaint] on the relevant [RenderView]s to
/// repaint them without the overlay.
///
/// Does nothing in release mode.
static void debugRemovePaintCallback(DebugPaintCallback callback) {
assert(() {
_debugPaintCallbacks.remove(callback);
return true;
}());
}
} }
/// A callback for painting a debug overlay on top of the provided [RenderView].
///
/// Used by [RenderView.debugAddPaintCallback] and
/// [RenderView.debugRemovePaintCallback].
typedef DebugPaintCallback = void Function(PaintingContext context, Offset offset, RenderView renderView);
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'mock_canvas.dart';
import 'rendering_tester.dart'; import 'rendering_tester.dart';
void main() { void main() {
...@@ -69,4 +70,76 @@ void main() { ...@@ -69,4 +70,76 @@ void main() {
expect(viewConfigurationA.hashCode, viewConfigurationB.hashCode); expect(viewConfigurationA.hashCode, viewConfigurationB.hashCode);
expect(viewConfigurationA.hashCode != viewConfigurationC.hashCode, true); expect(viewConfigurationA.hashCode != viewConfigurationC.hashCode, true);
}); });
test('invokes DebugPaintCallback', () {
final PaintPattern paintsOrangeRect = paints..rect(
color: orange,
rect: orangeRect,
);
final PaintPattern paintsGreenRect = paints..rect(
color: green,
rect: greenRect,
);
final PaintPattern paintOrangeAndGreenRect = paints
..rect(
color: orange,
rect: orangeRect,
)
..rect(
color: green,
rect: greenRect,
);
void paintCallback(PaintingContext context, Offset offset, RenderView renderView) {
context.canvas.drawRect(
greenRect,
Paint()..color = green,
);
}
layout(TestRenderObject());
expect(
TestRenderingFlutterBinding.instance.renderView,
paintsOrangeRect,
);
expect(
TestRenderingFlutterBinding.instance.renderView,
isNot(paintsGreenRect),
);
RenderView.debugAddPaintCallback(paintCallback);
expect(
TestRenderingFlutterBinding.instance.renderView,
paintOrangeAndGreenRect,
);
RenderView.debugRemovePaintCallback(paintCallback);
expect(
TestRenderingFlutterBinding.instance.renderView,
paintsOrangeRect,
);
expect(
TestRenderingFlutterBinding.instance.renderView,
isNot(paintsGreenRect),
);
});
}
const Color orange = Color(0xFFFF9000);
const Color green = Color(0xFF0FF900);
const Rect orangeRect = Rect.fromLTWH(10, 10, 50, 75);
const Rect greenRect = Rect.fromLTWH(20, 20, 100, 150);
class TestRenderObject extends RenderBox {
@override
void performLayout() {
size = constraints.biggest;
}
@override
void paint(PaintingContext context, Offset offset) {
context.canvas.drawRect(
orangeRect,
Paint()..color = orange,
);
}
} }
This diff is collapsed.
...@@ -834,7 +834,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker ...@@ -834,7 +834,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
@override @override
HitTestResult hitTestOnBinding(Offset location) { HitTestResult hitTestOnBinding(Offset location) {
location = binding.localToGlobal(location); location = binding.localToGlobal(location, binding.renderView);
return super.hitTestOnBinding(location); return super.hitTestOnBinding(location);
} }
......
// 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 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
final LiveTestWidgetsFlutterBinding binding = LiveTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('localToGlobal and globalToLocal calculate correct results', (WidgetTester tester) async {
tester.view.physicalSize = const Size(2400, 1800);
tester.view.devicePixelRatio = 3.0;
final RenderView renderView = RenderView(
view: tester.view,
configuration: TestViewConfiguration.fromView(
view: tester.view,
size: const Size(400, 200),
)
);
// The configuration above defines a view with a resolution of 2400x1800
// physical pixels. With a device pixel ratio of 3x, this yields a
// resolution of 800x600 logical pixels. In this view, a RenderView sized
// 400x200 (in logical pixels) is fitted using the BoxFit.contain
// algorithm (as documented on TestViewConfiguration. To fit 400x200 into
// 800x600 the RenderView is scaled up by 2 to fill the full width and then
// vertically positioned in the middle. The origin of the RenderView is
// located at (0, 100) in the logical coordinate space of the view:
//
// View: 800 logical pixels wide (or 2400 physical pixels)
// +---------------------------------------+
// | |
// | 100px |
// | |
// +---------------------------------------+
// | |
// | RenderView (400x200px) |
// | 400px scaled to 800x400px | View: 600 logical pixels high (or 1800 physical pixels)
// | |
// | |
// +---------------------------------------+
// | |
// | 200px |
// | |
// +---------------------------------------+
//
// All values in logical pixels until otherwise noted.
//
// A point can be translated from the local coordinate space of the
// RenderView (in logical pixels) to the global coordinate space of the View
// (in logical pixels) by multiplying each coordinate by 2 and adding 100 to
// the y coordinate. This is what the localToGlobal/globalToLocal methods
// do:
expect(binding.localToGlobal(const Offset(0, -50), renderView), Offset.zero);
expect(binding.localToGlobal(Offset.zero, renderView), const Offset(0, 100));
expect(binding.localToGlobal(const Offset(200, 100), renderView), const Offset(400, 300));
expect(binding.localToGlobal(const Offset(150, 75), renderView), const Offset(300, 250));
expect(binding.localToGlobal(const Offset(400, 200), renderView), const Offset(800, 500));
expect(binding.localToGlobal(const Offset(400, 400), renderView), const Offset(800, 900));
expect(binding.globalToLocal(Offset.zero, renderView), const Offset(0, -50));
expect(binding.globalToLocal(const Offset(0, 100), renderView), Offset.zero);
expect(binding.globalToLocal(const Offset(400, 300), renderView), const Offset(200, 100));
expect(binding.globalToLocal(const Offset(300, 250), renderView), const Offset(150, 75));
expect(binding.globalToLocal(const Offset(800, 500), renderView), const Offset(400, 200));
expect(binding.globalToLocal(const Offset(800, 900), renderView), const Offset(400, 400));
});
}
...@@ -126,7 +126,7 @@ class _MockLiveTestWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding { ...@@ -126,7 +126,7 @@ class _MockLiveTestWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding {
// real devices touches sends event in the global coordinate system. // real devices touches sends event in the global coordinate system.
// See the documentation of [handlePointerEventForSource] for details. // See the documentation of [handlePointerEventForSource] for details.
if (source == TestBindingEventSource.test) { if (source == TestBindingEventSource.test) {
final PointerEvent globalEvent = event.copyWith(position: localToGlobal(event.position)); final PointerEvent globalEvent = event.copyWith(position: localToGlobal(event.position, renderView));
return super.handlePointerEventForSource(globalEvent); return super.handlePointerEventForSource(globalEvent);
} }
return super.handlePointerEventForSource(event, source: source); return super.handlePointerEventForSource(event, source: source);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment