• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:developer';
6import 'dart:io' show Platform;
7import 'dart:ui' as ui show Scene, SceneBuilder, Window;
8
9import 'package:flutter/foundation.dart';
10import 'package:flutter/gestures.dart' show MouseTrackerAnnotation;
11import 'package:flutter/services.dart';
12import 'package:vector_math/vector_math_64.dart';
13
14import 'binding.dart';
15import 'box.dart';
16import 'debug.dart';
17import 'layer.dart';
18import 'object.dart';
19
20/// The layout constraints for the root render object.
21@immutable
22class ViewConfiguration {
23  /// Creates a view configuration.
24  ///
25  /// By default, the view has zero [size] and a [devicePixelRatio] of 1.0.
26  const ViewConfiguration({
27    this.size = Size.zero,
28    this.devicePixelRatio = 1.0,
29  });
30
31  /// The size of the output surface.
32  final Size size;
33
34  /// The pixel density of the output surface.
35  final double devicePixelRatio;
36
37  /// Creates a transformation matrix that applies the [devicePixelRatio].
38  Matrix4 toMatrix() {
39    return Matrix4.diagonal3Values(devicePixelRatio, devicePixelRatio, 1.0);
40  }
41
42  @override
43  String toString() => '$size at ${debugFormatDouble(devicePixelRatio)}x';
44}
45
46/// The root of the render tree.
47///
48/// The view represents the total output surface of the render tree and handles
49/// bootstrapping the rendering pipeline. The view has a unique child
50/// [RenderBox], which is required to fill the entire output surface.
51class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> {
52  /// Creates the root of the render tree.
53  ///
54  /// Typically created by the binding (e.g., [RendererBinding]).
55  ///
56  /// The [configuration] must not be null.
57  RenderView({
58    RenderBox child,
59    @required ViewConfiguration configuration,
60    @required ui.Window window,
61  }) : assert(configuration != null),
62       _configuration = configuration,
63       _window = window {
64    this.child = child;
65  }
66
67  /// The current layout size of the view.
68  Size get size => _size;
69  Size _size = Size.zero;
70
71  /// The constraints used for the root layout.
72  ViewConfiguration get configuration => _configuration;
73  ViewConfiguration _configuration;
74  /// The configuration is initially set by the `configuration` argument
75  /// passed to the constructor.
76  ///
77  /// Always call [scheduleInitialFrame] before changing the configuration.
78  set configuration(ViewConfiguration value) {
79    assert(value != null);
80    if (configuration == value)
81      return;
82    _configuration = value;
83    replaceRootLayer(_updateMatricesAndCreateNewRootLayer());
84    assert(_rootTransform != null);
85    markNeedsLayout();
86  }
87
88  final ui.Window _window;
89
90  /// Whether Flutter should automatically compute the desired system UI.
91  ///
92  /// When this setting is enabled, Flutter will hit-test the layer tree at the
93  /// top and bottom of the screen on each frame looking for an
94  /// [AnnotatedRegionLayer] with an instance of a [SystemUiOverlayStyle]. The
95  /// hit-test result from the top of the screen provides the status bar settings
96  /// and the hit-test result from the bottom of the screen provides the system
97  /// nav bar settings.
98  ///
99  /// Setting this to false does not cause previous automatic adjustments to be
100  /// reset, nor does setting it to true cause the app to update immediately.
101  ///
102  /// If you want to imperatively set the system ui style instead, it is
103  /// recommended that [automaticSystemUiAdjustment] is set to false.
104  ///
105  /// See also:
106  ///
107  ///  * [AnnotatedRegion], for placing [SystemUiOverlayStyle] in the layer tree.
108  ///  * [SystemChrome.setSystemUIOverlayStyle], for imperatively setting the system ui style.
109  bool automaticSystemUiAdjustment = true;
110
111  /// Bootstrap the rendering pipeline by scheduling the first frame.
112  ///
113  /// This should only be called once, and must be called before changing
114  /// [configuration]. It is typically called immediately after calling the
115  /// constructor.
116  void scheduleInitialFrame() {
117    assert(owner != null);
118    assert(_rootTransform == null);
119    scheduleInitialLayout();
120    scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
121    assert(_rootTransform != null);
122    owner.requestVisualUpdate();
123  }
124
125  Matrix4 _rootTransform;
126
127  Layer _updateMatricesAndCreateNewRootLayer() {
128    _rootTransform = configuration.toMatrix();
129    final ContainerLayer rootLayer = TransformLayer(transform: _rootTransform);
130    rootLayer.attach(this);
131    assert(_rootTransform != null);
132    return rootLayer;
133  }
134
135  // We never call layout() on this class, so this should never get
136  // checked. (This class is laid out using scheduleInitialLayout().)
137  @override
138  void debugAssertDoesMeetConstraints() { assert(false); }
139
140  @override
141  void performResize() {
142    assert(false);
143  }
144
145  @override
146  void performLayout() {
147    assert(_rootTransform != null);
148    _size = configuration.size;
149    assert(_size.isFinite);
150
151    if (child != null)
152      child.layout(BoxConstraints.tight(_size));
153  }
154
155  @override
156  void rotate({ int oldAngle, int newAngle, Duration time }) {
157    assert(false); // nobody tells the screen to rotate, the whole rotate() dance is started from our performResize()
158  }
159
160  /// Determines the set of render objects located at the given position.
161  ///
162  /// Returns true if the given point is contained in this render object or one
163  /// of its descendants. Adds any render objects that contain the point to the
164  /// given hit test result.
165  ///
166  /// The [position] argument is in the coordinate system of the render view,
167  /// which is to say, in logical pixels. This is not necessarily the same
168  /// coordinate system as that expected by the root [Layer], which will
169  /// normally be in physical (device) pixels.
170  bool hitTest(HitTestResult result, { Offset position }) {
171    if (child != null)
172      child.hitTest(BoxHitTestResult.wrap(result), position: position);
173    result.add(HitTestEntry(this));
174    return true;
175  }
176
177  /// Determines the set of mouse tracker annotations at the given position.
178  ///
179  /// See also:
180  ///
181  /// * [Layer.findAll], which is used by this method to find all
182  ///   [AnnotatedRegionLayer]s annotated for mouse tracking.
183  Iterable<MouseTrackerAnnotation> hitTestMouseTrackers(Offset position) {
184    // Layer hit testing is done using device pixels, so we have to convert
185    // the logical coordinates of the event location back to device pixels
186    // here.
187    return layer.findAll<MouseTrackerAnnotation>(position * configuration.devicePixelRatio);
188  }
189
190  @override
191  bool get isRepaintBoundary => true;
192
193  @override
194  void paint(PaintingContext context, Offset offset) {
195    if (child != null)
196      context.paintChild(child, offset);
197  }
198
199  @override
200  void applyPaintTransform(RenderBox child, Matrix4 transform) {
201    assert(_rootTransform != null);
202    transform.multiply(_rootTransform);
203    super.applyPaintTransform(child, transform);
204  }
205
206  /// Uploads the composited layer tree to the engine.
207  ///
208  /// Actually causes the output of the rendering pipeline to appear on screen.
209  void compositeFrame() {
210    Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
211    try {
212      final ui.SceneBuilder builder = ui.SceneBuilder();
213      final ui.Scene scene = layer.buildScene(builder);
214      if (automaticSystemUiAdjustment)
215        _updateSystemChrome();
216      _window.render(scene);
217      scene.dispose();
218      assert(() {
219        if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
220          debugCurrentRepaintColor = debugCurrentRepaintColor.withHue((debugCurrentRepaintColor.hue + 2.0) % 360.0);
221        return true;
222      }());
223    } finally {
224      Timeline.finishSync();
225    }
226  }
227
228  void _updateSystemChrome() {
229    final Rect bounds = paintBounds;
230    final Offset top = Offset(bounds.center.dx, _window.padding.top / _window.devicePixelRatio);
231    final Offset bottom = Offset(bounds.center.dx, bounds.center.dy - _window.padding.bottom / _window.devicePixelRatio);
232    final SystemUiOverlayStyle upperOverlayStyle = layer.find<SystemUiOverlayStyle>(top);
233    // Only android has a customizable system navigation bar.
234    SystemUiOverlayStyle lowerOverlayStyle;
235    switch (defaultTargetPlatform) {
236      case TargetPlatform.android:
237        lowerOverlayStyle = layer.find<SystemUiOverlayStyle>(bottom);
238        break;
239      case TargetPlatform.iOS:
240      case TargetPlatform.fuchsia:
241        break;
242    }
243    // If there are no overlay styles in the UI don't bother updating.
244    if (upperOverlayStyle != null || lowerOverlayStyle != null) {
245      final SystemUiOverlayStyle overlayStyle = SystemUiOverlayStyle(
246        statusBarBrightness: upperOverlayStyle?.statusBarBrightness,
247        statusBarIconBrightness: upperOverlayStyle?.statusBarIconBrightness,
248        statusBarColor: upperOverlayStyle?.statusBarColor,
249        systemNavigationBarColor: lowerOverlayStyle?.systemNavigationBarColor,
250        systemNavigationBarDividerColor: lowerOverlayStyle?.systemNavigationBarDividerColor,
251        systemNavigationBarIconBrightness: lowerOverlayStyle?.systemNavigationBarIconBrightness,
252      );
253      SystemChrome.setSystemUIOverlayStyle(overlayStyle);
254    }
255  }
256
257  @override
258  Rect get paintBounds => Offset.zero & (size * configuration.devicePixelRatio);
259
260  @override
261  Rect get semanticBounds {
262    assert(_rootTransform != null);
263    return MatrixUtils.transformRect(_rootTransform, Offset.zero & size);
264  }
265
266  @override
267  // ignore: MUST_CALL_SUPER
268  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
269    // call to ${super.debugFillProperties(description)} is omitted because the
270    // root superclasses don't include any interesting information for this
271    // class
272    assert(() {
273      properties.add(DiagnosticsNode.message('debug mode enabled - ${kIsWeb ? 'Web' :  Platform.operatingSystem}'));
274      return true;
275    }());
276    properties.add(DiagnosticsProperty<Size>('window size', _window.physicalSize, tooltip: 'in physical pixels'));
277    properties.add(DoubleProperty('device pixel ratio', _window.devicePixelRatio, tooltip: 'physical pixels per logical pixel'));
278    properties.add(DiagnosticsProperty<ViewConfiguration>('configuration', configuration, tooltip: 'in logical pixels'));
279    if (_window.semanticsEnabled)
280      properties.add(DiagnosticsNode.message('semantics enabled'));
281  }
282}
283