| 1 | // Copyright 2014 The Flutter 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 | |
| 5 | /// @docImport 'package:flutter/semantics.dart'; |
| 6 | /// @docImport 'package:flutter/widgets.dart'; |
| 7 | /// @docImport 'package:flutter_test/flutter_test.dart'; |
| 8 | library; |
| 9 | |
| 10 | import 'dart:io' show Platform; |
| 11 | import 'dart:ui' as ui show FlutterView, Scene, SceneBuilder, SemanticsUpdate; |
| 12 | |
| 13 | import 'package:flutter/foundation.dart'; |
| 14 | import 'package:flutter/services.dart'; |
| 15 | |
| 16 | import 'binding.dart'; |
| 17 | import 'box.dart'; |
| 18 | import 'debug.dart'; |
| 19 | import 'layer.dart'; |
| 20 | import 'object.dart'; |
| 21 | |
| 22 | /// The layout constraints for the root render object. |
| 23 | @immutable |
| 24 | class ViewConfiguration { |
| 25 | /// Creates a view configuration. |
| 26 | /// |
| 27 | /// By default, the view has [logicalConstraints] and [physicalConstraints] |
| 28 | /// with all dimensions set to zero (i.e. the view is forced to [Size.zero]) |
| 29 | /// and a [devicePixelRatio] of 1.0. |
| 30 | /// |
| 31 | /// [ViewConfiguration.fromView] is a more convenient way for deriving a |
| 32 | /// [ViewConfiguration] from a given [ui.FlutterView]. |
| 33 | const ViewConfiguration({ |
| 34 | this.physicalConstraints = const BoxConstraints(maxWidth: 0, maxHeight: 0), |
| 35 | this.logicalConstraints = const BoxConstraints(maxWidth: 0, maxHeight: 0), |
| 36 | this.devicePixelRatio = 1.0, |
| 37 | }); |
| 38 | |
| 39 | /// Creates a view configuration for the provided [ui.FlutterView]. |
| 40 | factory ViewConfiguration.fromView(ui.FlutterView view) { |
| 41 | final BoxConstraints physicalConstraints = BoxConstraints.fromViewConstraints( |
| 42 | view.physicalConstraints, |
| 43 | ); |
| 44 | final double devicePixelRatio = view.devicePixelRatio; |
| 45 | return ViewConfiguration( |
| 46 | physicalConstraints: physicalConstraints, |
| 47 | logicalConstraints: physicalConstraints / devicePixelRatio, |
| 48 | devicePixelRatio: devicePixelRatio, |
| 49 | ); |
| 50 | } |
| 51 | |
| 52 | /// The constraints of the output surface in logical pixel. |
| 53 | /// |
| 54 | /// The constraints are passed to the child of the root render object. |
| 55 | final BoxConstraints logicalConstraints; |
| 56 | |
| 57 | /// The constraints of the output surface in physical pixel. |
| 58 | /// |
| 59 | /// These constraints are enforced in [toPhysicalSize] when translating |
| 60 | /// the logical size of the root render object back to physical pixels for |
| 61 | /// the [ui.FlutterView.render] method. |
| 62 | final BoxConstraints physicalConstraints; |
| 63 | |
| 64 | /// The pixel density of the output surface. |
| 65 | final double devicePixelRatio; |
| 66 | |
| 67 | /// Creates a transformation matrix that applies the [devicePixelRatio]. |
| 68 | /// |
| 69 | /// The matrix translates points from the local coordinate system of the |
| 70 | /// app (in logical pixels) to the global coordinate system of the |
| 71 | /// [ui.FlutterView] (in physical pixels). |
| 72 | Matrix4 toMatrix() { |
| 73 | return Matrix4.diagonal3Values(devicePixelRatio, devicePixelRatio, 1.0); |
| 74 | } |
| 75 | |
| 76 | /// Returns whether [toMatrix] would return a different value for this |
| 77 | /// configuration than it would for the given `oldConfiguration`. |
| 78 | bool shouldUpdateMatrix(ViewConfiguration oldConfiguration) { |
| 79 | if (oldConfiguration.runtimeType != runtimeType) { |
| 80 | // New configuration could have different logic, so we don't know |
| 81 | // whether it will need a new transform. Return a conservative result. |
| 82 | return true; |
| 83 | } |
| 84 | // For this class, the only input to toMatrix is the device pixel ratio, |
| 85 | // so we return true if they differ and false otherwise. |
| 86 | return oldConfiguration.devicePixelRatio != devicePixelRatio; |
| 87 | } |
| 88 | |
| 89 | /// Transforms the provided [Size] in logical pixels to physical pixels. |
| 90 | /// |
| 91 | /// The [ui.FlutterView.render] method accepts only sizes in physical pixels, but |
| 92 | /// the framework operates in logical pixels. This method is used to transform |
| 93 | /// the logical size calculated for a [RenderView] back to a physical size |
| 94 | /// suitable to be passed to [ui.FlutterView.render]. |
| 95 | /// |
| 96 | /// By default, this method just multiplies the provided [Size] with the |
| 97 | /// [devicePixelRatio] and constraints the results to the |
| 98 | /// [physicalConstraints]. |
| 99 | Size toPhysicalSize(Size logicalSize) { |
| 100 | return physicalConstraints.constrain(logicalSize * devicePixelRatio); |
| 101 | } |
| 102 | |
| 103 | @override |
| 104 | bool operator ==(Object other) { |
| 105 | if (other.runtimeType != runtimeType) { |
| 106 | return false; |
| 107 | } |
| 108 | return other is ViewConfiguration && |
| 109 | other.logicalConstraints == logicalConstraints && |
| 110 | other.physicalConstraints == physicalConstraints && |
| 111 | other.devicePixelRatio == devicePixelRatio; |
| 112 | } |
| 113 | |
| 114 | @override |
| 115 | int get hashCode => Object.hash(logicalConstraints, physicalConstraints, devicePixelRatio); |
| 116 | |
| 117 | @override |
| 118 | String toString() => ' $logicalConstraints at ${debugFormatDouble(devicePixelRatio)}x' ; |
| 119 | } |
| 120 | |
| 121 | /// The root of the render tree. |
| 122 | /// |
| 123 | /// The view represents the total output surface of the render tree and handles |
| 124 | /// bootstrapping the rendering pipeline. The view has a unique child |
| 125 | /// [RenderBox], which is required to fill the entire output surface. |
| 126 | /// |
| 127 | /// This object must be bootstrapped in a specific order: |
| 128 | /// |
| 129 | /// 1. First, set the [configuration] (either in the constructor or after |
| 130 | /// construction). |
| 131 | /// 2. Second, [attach] the object to a [PipelineOwner]. |
| 132 | /// 3. Third, use [prepareInitialFrame] to bootstrap the layout and paint logic. |
| 133 | /// |
| 134 | /// After the bootstrapping is complete, the [compositeFrame] method may be used |
| 135 | /// to obtain the rendered output. |
| 136 | class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> { |
| 137 | /// Creates the root of the render tree. |
| 138 | /// |
| 139 | /// Typically created by the binding (e.g., [RendererBinding]). |
| 140 | /// |
| 141 | /// Providing a [configuration] is optional, but a configuration must be set |
| 142 | /// before calling [prepareInitialFrame]. This decouples creating the |
| 143 | /// [RenderView] object from configuring it. Typically, the object is created |
| 144 | /// by the [View] widget and configured by the [RendererBinding] when the |
| 145 | /// [RenderView] is registered with it by the [View] widget. |
| 146 | RenderView({RenderBox? child, ViewConfiguration? configuration, required ui.FlutterView view}) |
| 147 | : _view = view { |
| 148 | if (configuration != null) { |
| 149 | this.configuration = configuration; |
| 150 | } |
| 151 | this.child = child; |
| 152 | } |
| 153 | |
| 154 | /// The current layout size of the view. |
| 155 | Size get size => _size; |
| 156 | Size _size = Size.zero; |
| 157 | |
| 158 | /// The constraints used for the root layout. |
| 159 | /// |
| 160 | /// Typically, this configuration is set by the [RendererBinding], when the |
| 161 | /// [RenderView] is registered with it. It will also update the configuration |
| 162 | /// if necessary. Therefore, if used in conjunction with the [RendererBinding] |
| 163 | /// this property must not be set manually as the [RendererBinding] will just |
| 164 | /// override it. |
| 165 | /// |
| 166 | /// For tests that want to change the size of the view, set |
| 167 | /// [TestFlutterView.physicalSize] on the appropriate [TestFlutterView] |
| 168 | /// (typically [WidgetTester.view]) instead of setting a configuration |
| 169 | /// directly on the [RenderView]. |
| 170 | /// |
| 171 | /// A [configuration] must be set (either directly or by passing one to the |
| 172 | /// constructor) before calling [prepareInitialFrame]. |
| 173 | ViewConfiguration get configuration => _configuration!; |
| 174 | ViewConfiguration? _configuration; |
| 175 | set configuration(ViewConfiguration value) { |
| 176 | if (_configuration == value) { |
| 177 | return; |
| 178 | } |
| 179 | final ViewConfiguration? oldConfiguration = _configuration; |
| 180 | _configuration = value; |
| 181 | if (_rootTransform == null) { |
| 182 | // [prepareInitialFrame] has not been called yet, nothing more to do for now. |
| 183 | return; |
| 184 | } |
| 185 | if (oldConfiguration == null || configuration.shouldUpdateMatrix(oldConfiguration)) { |
| 186 | replaceRootLayer(_updateMatricesAndCreateNewRootLayer()); |
| 187 | } |
| 188 | assert(_rootTransform != null); |
| 189 | markNeedsLayout(); |
| 190 | } |
| 191 | |
| 192 | /// Whether a [configuration] has been set. |
| 193 | /// |
| 194 | /// This must be true before calling [prepareInitialFrame]. |
| 195 | bool get hasConfiguration => _configuration != null; |
| 196 | |
| 197 | @override |
| 198 | BoxConstraints get constraints { |
| 199 | if (!hasConfiguration) { |
| 200 | throw StateError( |
| 201 | 'Constraints are not available because RenderView has not been given a configuration yet.' , |
| 202 | ); |
| 203 | } |
| 204 | return configuration.logicalConstraints; |
| 205 | } |
| 206 | |
| 207 | /// The [ui.FlutterView] into which this [RenderView] will render. |
| 208 | ui.FlutterView get flutterView => _view; |
| 209 | final ui.FlutterView _view; |
| 210 | |
| 211 | /// Whether Flutter should automatically compute the desired system UI. |
| 212 | /// |
| 213 | /// When this setting is enabled, Flutter will hit-test the layer tree at the |
| 214 | /// top and bottom of the screen on each frame looking for an |
| 215 | /// [AnnotatedRegionLayer] with an instance of a [SystemUiOverlayStyle]. The |
| 216 | /// hit-test result from the top of the screen provides the status bar settings |
| 217 | /// and the hit-test result from the bottom of the screen provides the system |
| 218 | /// nav bar settings. |
| 219 | /// |
| 220 | /// If there is no [AnnotatedRegionLayer] on the bottom, the hit-test result |
| 221 | /// from the top provides the system nav bar settings. If there is no |
| 222 | /// [AnnotatedRegionLayer] on the top, the hit-test result from the bottom |
| 223 | /// provides the system status bar settings. |
| 224 | /// |
| 225 | /// Setting this to false does not cause previous automatic adjustments to be |
| 226 | /// reset, nor does setting it to true cause the app to update immediately. |
| 227 | /// |
| 228 | /// If you want to imperatively set the system ui style instead, it is |
| 229 | /// recommended that [automaticSystemUiAdjustment] is set to false. |
| 230 | /// |
| 231 | /// See also: |
| 232 | /// |
| 233 | /// * [AnnotatedRegion], for placing [SystemUiOverlayStyle] in the layer tree. |
| 234 | /// * [SystemChrome.setSystemUIOverlayStyle], for imperatively setting the system ui style. |
| 235 | bool automaticSystemUiAdjustment = true; |
| 236 | |
| 237 | /// Bootstrap the rendering pipeline by preparing the first frame. |
| 238 | /// |
| 239 | /// This should only be called once. It is typically called immediately after |
| 240 | /// setting the [configuration] the first time (whether by passing one to the |
| 241 | /// constructor, or setting it directly). The [configuration] must have been |
| 242 | /// set before calling this method, and the [RenderView] must have been |
| 243 | /// attached to a [PipelineOwner] using [attach]. |
| 244 | /// |
| 245 | /// This does not actually schedule the first frame. Call |
| 246 | /// [PipelineOwner.requestVisualUpdate] on the [owner] to do that. |
| 247 | /// |
| 248 | /// This should be called before using any methods that rely on the [layer] |
| 249 | /// being initialized, such as [compositeFrame]. |
| 250 | /// |
| 251 | /// This method calls [scheduleInitialLayout] and [scheduleInitialPaint]. |
| 252 | void prepareInitialFrame() { |
| 253 | assert( |
| 254 | owner != null, |
| 255 | 'attach the RenderView to a PipelineOwner before calling prepareInitialFrame' , |
| 256 | ); |
| 257 | assert( |
| 258 | _rootTransform == null, |
| 259 | 'prepareInitialFrame must only be called once' , |
| 260 | ); // set by _updateMatricesAndCreateNewRootLayer |
| 261 | assert(hasConfiguration, 'set a configuration before calling prepareInitialFrame' ); |
| 262 | scheduleInitialLayout(); |
| 263 | scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer()); |
| 264 | assert(_rootTransform != null); |
| 265 | } |
| 266 | |
| 267 | Matrix4? _rootTransform; |
| 268 | |
| 269 | TransformLayer _updateMatricesAndCreateNewRootLayer() { |
| 270 | assert(hasConfiguration); |
| 271 | _rootTransform = configuration.toMatrix(); |
| 272 | final TransformLayer rootLayer = TransformLayer(transform: _rootTransform); |
| 273 | rootLayer.attach(this); |
| 274 | assert(_rootTransform != null); |
| 275 | return rootLayer; |
| 276 | } |
| 277 | |
| 278 | // We never call layout() on this class, so this should never get |
| 279 | // checked. (This class is laid out using scheduleInitialLayout().) |
| 280 | @override |
| 281 | void debugAssertDoesMeetConstraints() { |
| 282 | assert(false); |
| 283 | } |
| 284 | |
| 285 | @override |
| 286 | void performResize() { |
| 287 | assert(false); |
| 288 | } |
| 289 | |
| 290 | @override |
| 291 | void performLayout() { |
| 292 | assert(_rootTransform != null); |
| 293 | final bool sizedByChild = !constraints.isTight; |
| 294 | child?.layout(constraints, parentUsesSize: sizedByChild); |
| 295 | _size = sizedByChild && child != null ? child!.size : constraints.smallest; |
| 296 | assert(size.isFinite); |
| 297 | assert(constraints.isSatisfiedBy(size)); |
| 298 | } |
| 299 | |
| 300 | /// Determines the set of render objects located at the given position. |
| 301 | /// |
| 302 | /// Returns true if the given point is contained in this render object or one |
| 303 | /// of its descendants. Adds any render objects that contain the point to the |
| 304 | /// given hit test result. |
| 305 | /// |
| 306 | /// The [position] argument is in the coordinate system of the render view, |
| 307 | /// which is to say, in logical pixels. This is not necessarily the same |
| 308 | /// coordinate system as that expected by the root [Layer], which will |
| 309 | /// normally be in physical (device) pixels. |
| 310 | bool hitTest(HitTestResult result, {required Offset position}) { |
| 311 | child?.hitTest(BoxHitTestResult.wrap(result), position: position); |
| 312 | result.add(HitTestEntry(this)); |
| 313 | return true; |
| 314 | } |
| 315 | |
| 316 | @override |
| 317 | bool get isRepaintBoundary => true; |
| 318 | |
| 319 | @override |
| 320 | void paint(PaintingContext context, Offset offset) { |
| 321 | if (child != null) { |
| 322 | context.paintChild(child!, offset); |
| 323 | } |
| 324 | assert(() { |
| 325 | final List<DebugPaintCallback> localCallbacks = _debugPaintCallbacks.toList(); |
| 326 | for (final DebugPaintCallback paintCallback in localCallbacks) { |
| 327 | if (_debugPaintCallbacks.contains(paintCallback)) { |
| 328 | paintCallback(context, offset, this); |
| 329 | } |
| 330 | } |
| 331 | return true; |
| 332 | }()); |
| 333 | } |
| 334 | |
| 335 | @override |
| 336 | void applyPaintTransform(RenderBox child, Matrix4 transform) { |
| 337 | assert(_rootTransform != null); |
| 338 | transform.multiply(_rootTransform!); |
| 339 | super.applyPaintTransform(child, transform); |
| 340 | } |
| 341 | |
| 342 | /// Uploads the composited layer tree to the engine. |
| 343 | /// |
| 344 | /// Actually causes the output of the rendering pipeline to appear on screen. |
| 345 | /// |
| 346 | /// Before calling this method, the [owner] must be set by calling [attach], |
| 347 | /// the [configuration] must be set to a non-null value, and the |
| 348 | /// [prepareInitialFrame] method must have been called. |
| 349 | void compositeFrame() { |
| 350 | if (!kReleaseMode) { |
| 351 | FlutterTimeline.startSync('COMPOSITING' ); |
| 352 | } |
| 353 | try { |
| 354 | assert(hasConfiguration, 'set the RenderView configuration before calling compositeFrame' ); |
| 355 | assert(_rootTransform != null, 'call prepareInitialFrame before calling compositeFrame' ); |
| 356 | assert(layer != null, 'call prepareInitialFrame before calling compositeFrame' ); |
| 357 | final ui.SceneBuilder builder = RendererBinding.instance.createSceneBuilder(); |
| 358 | final ui.Scene scene = layer!.buildScene(builder); |
| 359 | if (automaticSystemUiAdjustment) { |
| 360 | _updateSystemChrome(); |
| 361 | } |
| 362 | assert(configuration.logicalConstraints.isSatisfiedBy(size)); |
| 363 | _view.render(scene, size: configuration.toPhysicalSize(size)); |
| 364 | scene.dispose(); |
| 365 | assert(() { |
| 366 | if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled) { |
| 367 | debugCurrentRepaintColor = debugCurrentRepaintColor.withHue( |
| 368 | (debugCurrentRepaintColor.hue + 2.0) % 360.0, |
| 369 | ); |
| 370 | } |
| 371 | return true; |
| 372 | }()); |
| 373 | } finally { |
| 374 | if (!kReleaseMode) { |
| 375 | FlutterTimeline.finishSync(); |
| 376 | } |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | /// Sends the provided [ui.SemanticsUpdate] to the [ui.FlutterView] associated with |
| 381 | /// this [RenderView]. |
| 382 | /// |
| 383 | /// A [ui.SemanticsUpdate] is produced by a [SemanticsOwner] during the |
| 384 | /// [EnginePhase.flushSemantics] phase. |
| 385 | void updateSemantics(ui.SemanticsUpdate update) { |
| 386 | _view.updateSemantics(update); |
| 387 | } |
| 388 | |
| 389 | void _updateSystemChrome() { |
| 390 | // Take overlay style from the place where a system status bar and system |
| 391 | // navigation bar are placed to update system style overlay. |
| 392 | // The center of the system navigation bar and the center of the status bar |
| 393 | // are used to get SystemUiOverlayStyle's to update system overlay appearance. |
| 394 | // |
| 395 | // Horizontal center of the screen |
| 396 | // V |
| 397 | // ++++++++++++++++++++++++++ |
| 398 | // | | |
| 399 | // | System status bar | <- Vertical center of the status bar |
| 400 | // | | |
| 401 | // ++++++++++++++++++++++++++ |
| 402 | // | | |
| 403 | // | Content | |
| 404 | // ~ ~ |
| 405 | // | | |
| 406 | // ++++++++++++++++++++++++++ |
| 407 | // | | |
| 408 | // | System navigation bar | <- Vertical center of the navigation bar |
| 409 | // | | |
| 410 | // ++++++++++++++++++++++++++ <- bounds.bottom |
| 411 | final Rect bounds = paintBounds; |
| 412 | // Center of the status bar |
| 413 | final Offset top = Offset( |
| 414 | // Horizontal center of the screen |
| 415 | bounds.center.dx, |
| 416 | // The vertical center of the system status bar. The system status bar |
| 417 | // height is kept as top window padding. |
| 418 | _view.padding.top / 2.0, |
| 419 | ); |
| 420 | // Center of the navigation bar |
| 421 | final Offset bottom = Offset( |
| 422 | // Horizontal center of the screen |
| 423 | bounds.center.dx, |
| 424 | // Vertical center of the system navigation bar. The system navigation bar |
| 425 | // height is kept as bottom window padding. The "1" needs to be subtracted |
| 426 | // from the bottom because available pixels are in (0..bottom) range. |
| 427 | // I.e. for a device with 1920 height, bound.bottom is 1920, but the most |
| 428 | // bottom drawn pixel is at 1919 position. |
| 429 | bounds.bottom - 1.0 - _view.padding.bottom / 2.0, |
| 430 | ); |
| 431 | final SystemUiOverlayStyle? upperOverlayStyle = layer!.find<SystemUiOverlayStyle>(top); |
| 432 | // Only android has a customizable system navigation bar. |
| 433 | SystemUiOverlayStyle? lowerOverlayStyle; |
| 434 | switch (defaultTargetPlatform) { |
| 435 | case TargetPlatform.android: |
| 436 | lowerOverlayStyle = layer!.find<SystemUiOverlayStyle>(bottom); |
| 437 | case TargetPlatform.fuchsia: |
| 438 | case TargetPlatform.iOS: |
| 439 | case TargetPlatform.linux: |
| 440 | case TargetPlatform.macOS: |
| 441 | case TargetPlatform.windows: |
| 442 | break; |
| 443 | } |
| 444 | // If there are no overlay style in the UI don't bother updating. |
| 445 | if (upperOverlayStyle == null && lowerOverlayStyle == null) { |
| 446 | return; |
| 447 | } |
| 448 | |
| 449 | // If both are not null, the upper provides the status bar properties and the lower provides |
| 450 | // the system navigation bar properties. This is done for advanced use cases where a widget |
| 451 | // on the top (for instance an app bar) will create an annotated region to set the status bar |
| 452 | // style and another widget on the bottom will create an annotated region to set the system |
| 453 | // navigation bar style. |
| 454 | if (upperOverlayStyle != null && lowerOverlayStyle != null) { |
| 455 | final SystemUiOverlayStyle overlayStyle = SystemUiOverlayStyle( |
| 456 | statusBarBrightness: upperOverlayStyle.statusBarBrightness, |
| 457 | statusBarIconBrightness: upperOverlayStyle.statusBarIconBrightness, |
| 458 | statusBarColor: upperOverlayStyle.statusBarColor, |
| 459 | systemStatusBarContrastEnforced: upperOverlayStyle.systemStatusBarContrastEnforced, |
| 460 | systemNavigationBarColor: lowerOverlayStyle.systemNavigationBarColor, |
| 461 | systemNavigationBarDividerColor: lowerOverlayStyle.systemNavigationBarDividerColor, |
| 462 | systemNavigationBarIconBrightness: lowerOverlayStyle.systemNavigationBarIconBrightness, |
| 463 | systemNavigationBarContrastEnforced: lowerOverlayStyle.systemNavigationBarContrastEnforced, |
| 464 | ); |
| 465 | SystemChrome.setSystemUIOverlayStyle(overlayStyle); |
| 466 | return; |
| 467 | } |
| 468 | // If only one of the upper or the lower overlay style is not null, it provides all properties. |
| 469 | // This is done for developer convenience as it allows setting both status bar style and |
| 470 | // navigation bar style using only one annotated region layer (for instance the one |
| 471 | // automatically created by an [AppBar]). |
| 472 | final bool isAndroid = defaultTargetPlatform == TargetPlatform.android; |
| 473 | final SystemUiOverlayStyle definedOverlayStyle = (upperOverlayStyle ?? lowerOverlayStyle)!; |
| 474 | final SystemUiOverlayStyle overlayStyle = SystemUiOverlayStyle( |
| 475 | statusBarBrightness: definedOverlayStyle.statusBarBrightness, |
| 476 | statusBarIconBrightness: definedOverlayStyle.statusBarIconBrightness, |
| 477 | statusBarColor: definedOverlayStyle.statusBarColor, |
| 478 | systemStatusBarContrastEnforced: definedOverlayStyle.systemStatusBarContrastEnforced, |
| 479 | systemNavigationBarColor: isAndroid ? definedOverlayStyle.systemNavigationBarColor : null, |
| 480 | systemNavigationBarDividerColor: isAndroid |
| 481 | ? definedOverlayStyle.systemNavigationBarDividerColor |
| 482 | : null, |
| 483 | systemNavigationBarIconBrightness: isAndroid |
| 484 | ? definedOverlayStyle.systemNavigationBarIconBrightness |
| 485 | : null, |
| 486 | systemNavigationBarContrastEnforced: isAndroid |
| 487 | ? definedOverlayStyle.systemNavigationBarContrastEnforced |
| 488 | : null, |
| 489 | ); |
| 490 | SystemChrome.setSystemUIOverlayStyle(overlayStyle); |
| 491 | } |
| 492 | |
| 493 | @override |
| 494 | Rect get paintBounds => Offset.zero & (size * configuration.devicePixelRatio); |
| 495 | |
| 496 | @override |
| 497 | Rect get semanticBounds { |
| 498 | assert(_rootTransform != null); |
| 499 | return MatrixUtils.transformRect(_rootTransform!, Offset.zero & size); |
| 500 | } |
| 501 | |
| 502 | @override |
| 503 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 504 | // call to ${super.debugFillProperties(description)} is omitted because the |
| 505 | // root superclasses don't include any interesting information for this |
| 506 | // class |
| 507 | assert(() { |
| 508 | properties.add( |
| 509 | DiagnosticsNode.message( |
| 510 | 'debug mode enabled - ${kIsWeb ? 'Web' : Platform.operatingSystem}' , |
| 511 | ), |
| 512 | ); |
| 513 | return true; |
| 514 | }()); |
| 515 | properties.add( |
| 516 | DiagnosticsProperty<Size>('view size' , _view.physicalSize, tooltip: 'in physical pixels' ), |
| 517 | ); |
| 518 | properties.add( |
| 519 | DoubleProperty( |
| 520 | 'device pixel ratio' , |
| 521 | _view.devicePixelRatio, |
| 522 | tooltip: 'physical pixels per logical pixel' , |
| 523 | ), |
| 524 | ); |
| 525 | properties.add( |
| 526 | DiagnosticsProperty<ViewConfiguration>( |
| 527 | 'configuration' , |
| 528 | configuration, |
| 529 | tooltip: 'in logical pixels' , |
| 530 | ), |
| 531 | ); |
| 532 | if (_view.platformDispatcher.semanticsEnabled) { |
| 533 | properties.add(DiagnosticsNode.message('semantics enabled' )); |
| 534 | } |
| 535 | } |
| 536 | |
| 537 | static final List<DebugPaintCallback> _debugPaintCallbacks = <DebugPaintCallback>[]; |
| 538 | |
| 539 | /// Registers a [DebugPaintCallback] that is called every time a [RenderView] |
| 540 | /// repaints in debug mode. |
| 541 | /// |
| 542 | /// The callback may paint a debug overlay on top of the content of the |
| 543 | /// [RenderView] provided to the callback. Callbacks are invoked in the |
| 544 | /// order they were registered in. |
| 545 | /// |
| 546 | /// Neither registering a callback nor the continued presence of a callback |
| 547 | /// changes how often [RenderView]s are repainted. It is up to the owner of |
| 548 | /// the callback to call [markNeedsPaint] on any [RenderView] for which it |
| 549 | /// wants to update the painted overlay. |
| 550 | /// |
| 551 | /// Does nothing in release mode. |
| 552 | static void debugAddPaintCallback(DebugPaintCallback callback) { |
| 553 | assert(() { |
| 554 | _debugPaintCallbacks.add(callback); |
| 555 | return true; |
| 556 | }()); |
| 557 | } |
| 558 | |
| 559 | /// Removes a callback registered with [debugAddPaintCallback]. |
| 560 | /// |
| 561 | /// It does not schedule a frame to repaint the [RenderView]s without the |
| 562 | /// overlay painted by the removed callback. It is up to the owner of the |
| 563 | /// callback to call [markNeedsPaint] on the relevant [RenderView]s to |
| 564 | /// repaint them without the overlay. |
| 565 | /// |
| 566 | /// Does nothing in release mode. |
| 567 | static void debugRemovePaintCallback(DebugPaintCallback callback) { |
| 568 | assert(() { |
| 569 | _debugPaintCallbacks.remove(callback); |
| 570 | return true; |
| 571 | }()); |
| 572 | } |
| 573 | } |
| 574 | |
| 575 | /// A callback for painting a debug overlay on top of the provided [RenderView]. |
| 576 | /// |
| 577 | /// Used by [RenderView.debugAddPaintCallback] and |
| 578 | /// [RenderView.debugRemovePaintCallback]. |
| 579 | typedef DebugPaintCallback = |
| 580 | void Function(PaintingContext context, Offset offset, RenderView renderView); |
| 581 | |