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