| 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 | import 'dart:math'; |
| 6 | import 'dart:ui' as ui show Color; |
| 7 | |
| 8 | import 'package:flutter/animation.dart'; |
| 9 | import 'package:flutter/foundation.dart'; |
| 10 | import 'package:flutter/semantics.dart'; |
| 11 | |
| 12 | import 'layer.dart'; |
| 13 | import 'object.dart'; |
| 14 | import 'proxy_box.dart'; |
| 15 | import 'sliver.dart'; |
| 16 | |
| 17 | /// A base class for sliver render objects that resemble their children. |
| 18 | /// |
| 19 | /// A proxy sliver has a single child and mimics all the properties of |
| 20 | /// that child by calling through to the child for each function in the render |
| 21 | /// sliver protocol. For example, a proxy sliver determines its geometry by |
| 22 | /// asking its sliver child to layout with the same constraints and then |
| 23 | /// matching the geometry. |
| 24 | /// |
| 25 | /// A proxy sliver isn't useful on its own because you might as well just |
| 26 | /// replace the proxy sliver with its child. However, RenderProxySliver is a |
| 27 | /// useful base class for render objects that wish to mimic most, but not all, |
| 28 | /// of the properties of their sliver child. |
| 29 | /// |
| 30 | /// See also: |
| 31 | /// |
| 32 | /// * [RenderProxyBox], a base class for render boxes that resemble their |
| 33 | /// children. |
| 34 | abstract class RenderProxySliver extends RenderSliver |
| 35 | with RenderObjectWithChildMixin<RenderSliver> { |
| 36 | /// Creates a proxy render sliver. |
| 37 | /// |
| 38 | /// Proxy render slivers aren't created directly because they proxy |
| 39 | /// the render sliver protocol to their sliver [child]. Instead, use one of |
| 40 | /// the subclasses. |
| 41 | RenderProxySliver([RenderSliver? child]) { |
| 42 | this.child = child; |
| 43 | } |
| 44 | |
| 45 | @override |
| 46 | Rect get semanticBounds { |
| 47 | if (child != null) { |
| 48 | return child!.semanticBounds; |
| 49 | } |
| 50 | return super.semanticBounds; |
| 51 | } |
| 52 | |
| 53 | @override |
| 54 | void setupParentData(RenderObject child) { |
| 55 | if (child.parentData is! SliverPhysicalParentData) { |
| 56 | child.parentData = SliverPhysicalParentData(); |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | @override |
| 61 | void performLayout() { |
| 62 | assert(child != null); |
| 63 | child!.layout(constraints, parentUsesSize: true); |
| 64 | geometry = child!.geometry; |
| 65 | } |
| 66 | |
| 67 | @override |
| 68 | void paint(PaintingContext context, Offset offset) { |
| 69 | if (child != null) { |
| 70 | context.paintChild(child!, offset); |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | @override |
| 75 | bool hitTestChildren( |
| 76 | SliverHitTestResult result, { |
| 77 | required double mainAxisPosition, |
| 78 | required double crossAxisPosition, |
| 79 | }) { |
| 80 | return child != null && |
| 81 | child!.geometry!.hitTestExtent > 0 && |
| 82 | child!.hitTest( |
| 83 | result, |
| 84 | mainAxisPosition: mainAxisPosition, |
| 85 | crossAxisPosition: crossAxisPosition, |
| 86 | ); |
| 87 | } |
| 88 | |
| 89 | @override |
| 90 | double childMainAxisPosition(RenderSliver child) { |
| 91 | assert(child == this.child); |
| 92 | return 0.0; |
| 93 | } |
| 94 | |
| 95 | @override |
| 96 | void applyPaintTransform(RenderObject child, Matrix4 transform) { |
| 97 | final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; |
| 98 | childParentData.applyPaintTransform(transform); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | /// Makes its sliver child partially transparent. |
| 103 | /// |
| 104 | /// This class paints its sliver child into an intermediate buffer and then |
| 105 | /// blends the sliver child back into the scene, partially transparent. |
| 106 | /// |
| 107 | /// For values of opacity other than 0.0 and 1.0, this class is relatively |
| 108 | /// expensive, because it requires painting the sliver child into an intermediate |
| 109 | /// buffer. For the value 0.0, the sliver child is not painted at all. |
| 110 | /// For the value 1.0, the sliver child is painted immediately without an |
| 111 | /// intermediate buffer. |
| 112 | class RenderSliverOpacity extends RenderProxySliver { |
| 113 | /// Creates a partially transparent render object. |
| 114 | /// |
| 115 | /// The [opacity] argument must be between 0.0 and 1.0, inclusive. |
| 116 | RenderSliverOpacity({ |
| 117 | double opacity = 1.0, |
| 118 | bool alwaysIncludeSemantics = false, |
| 119 | RenderSliver? sliver, |
| 120 | }) : assert(opacity >= 0.0 && opacity <= 1.0), |
| 121 | _opacity = opacity, |
| 122 | _alwaysIncludeSemantics = alwaysIncludeSemantics, |
| 123 | _alpha = ui.Color.getAlphaFromOpacity(opacity) { |
| 124 | child = sliver; |
| 125 | } |
| 126 | |
| 127 | @override |
| 128 | bool get alwaysNeedsCompositing => child != null && (_alpha > 0); |
| 129 | |
| 130 | int _alpha; |
| 131 | |
| 132 | /// The fraction to scale the child's alpha value. |
| 133 | /// |
| 134 | /// An opacity of one is fully opaque. An opacity of zero is fully transparent |
| 135 | /// (i.e. invisible). |
| 136 | /// |
| 137 | /// Values one and zero are painted with a fast path. Other values require |
| 138 | /// painting the child into an intermediate buffer, which is expensive. |
| 139 | double get opacity => _opacity; |
| 140 | double _opacity; |
| 141 | set opacity(double value) { |
| 142 | assert(value >= 0.0 && value <= 1.0); |
| 143 | if (_opacity == value) { |
| 144 | return; |
| 145 | } |
| 146 | final bool didNeedCompositing = alwaysNeedsCompositing; |
| 147 | final bool wasVisible = _alpha != 0; |
| 148 | _opacity = value; |
| 149 | _alpha = ui.Color.getAlphaFromOpacity(_opacity); |
| 150 | if (didNeedCompositing != alwaysNeedsCompositing) { |
| 151 | markNeedsCompositingBitsUpdate(); |
| 152 | } |
| 153 | markNeedsPaint(); |
| 154 | if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics) { |
| 155 | markNeedsSemanticsUpdate(); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | /// Whether child semantics are included regardless of the opacity. |
| 160 | /// |
| 161 | /// If false, semantics are excluded when [opacity] is 0.0. |
| 162 | /// |
| 163 | /// Defaults to false. |
| 164 | bool get alwaysIncludeSemantics => _alwaysIncludeSemantics; |
| 165 | bool _alwaysIncludeSemantics; |
| 166 | set alwaysIncludeSemantics(bool value) { |
| 167 | if (value == _alwaysIncludeSemantics) { |
| 168 | return; |
| 169 | } |
| 170 | _alwaysIncludeSemantics = value; |
| 171 | markNeedsSemanticsUpdate(); |
| 172 | } |
| 173 | |
| 174 | @override |
| 175 | void paint(PaintingContext context, Offset offset) { |
| 176 | if (child != null && child!.geometry!.visible) { |
| 177 | if (_alpha == 0) { |
| 178 | // No need to keep the layer. We'll create a new one if necessary. |
| 179 | layer = null; |
| 180 | return; |
| 181 | } |
| 182 | assert(needsCompositing); |
| 183 | layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer as OpacityLayer?); |
| 184 | assert(() { |
| 185 | layer!.debugCreator = debugCreator; |
| 186 | return true; |
| 187 | }()); |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | @override |
| 192 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
| 193 | if (child != null && (_alpha != 0 || alwaysIncludeSemantics)) { |
| 194 | visitor(child!); |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | @override |
| 199 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 200 | super.debugFillProperties(properties); |
| 201 | properties.add(DoubleProperty('opacity' , opacity)); |
| 202 | properties.add( |
| 203 | FlagProperty( |
| 204 | 'alwaysIncludeSemantics' , |
| 205 | value: alwaysIncludeSemantics, |
| 206 | ifTrue: 'alwaysIncludeSemantics' , |
| 207 | ), |
| 208 | ); |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | /// A render object that is invisible during hit testing. |
| 213 | /// |
| 214 | /// When [ignoring] is true, this render object (and its subtree) is invisible |
| 215 | /// to hit testing. It still consumes space during layout and paints its sliver |
| 216 | /// child as usual. It just cannot be the target of located events, because its |
| 217 | /// render object returns false from [hitTest]. |
| 218 | /// |
| 219 | /// ## Semantics |
| 220 | /// |
| 221 | /// Using this class may also affect how the semantics subtree underneath is |
| 222 | /// collected. |
| 223 | /// |
| 224 | /// {@macro flutter.widgets.IgnorePointer.semantics} |
| 225 | /// |
| 226 | /// {@macro flutter.widgets.IgnorePointer.ignoringSemantics} |
| 227 | class RenderSliverIgnorePointer extends RenderProxySliver { |
| 228 | /// Creates a render object that is invisible to hit testing. |
| 229 | RenderSliverIgnorePointer({ |
| 230 | RenderSliver? sliver, |
| 231 | bool ignoring = true, |
| 232 | @Deprecated( |
| 233 | 'Create a custom sliver ignore pointer widget instead. ' |
| 234 | 'This feature was deprecated after v3.8.0-12.0.pre.' , |
| 235 | ) |
| 236 | bool? ignoringSemantics, |
| 237 | }) : _ignoring = ignoring, |
| 238 | _ignoringSemantics = ignoringSemantics { |
| 239 | child = sliver; |
| 240 | } |
| 241 | |
| 242 | /// Whether this render object is ignored during hit testing. |
| 243 | /// |
| 244 | /// Regardless of whether this render object is ignored during hit testing, it |
| 245 | /// will still consume space during layout and be visible during painting. |
| 246 | /// |
| 247 | /// {@macro flutter.widgets.IgnorePointer.semantics} |
| 248 | bool get ignoring => _ignoring; |
| 249 | bool _ignoring; |
| 250 | set ignoring(bool value) { |
| 251 | if (value == _ignoring) { |
| 252 | return; |
| 253 | } |
| 254 | _ignoring = value; |
| 255 | if (ignoringSemantics == null) { |
| 256 | markNeedsSemanticsUpdate(); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | /// Whether the semantics of this render object is ignored when compiling the |
| 261 | /// semantics tree. |
| 262 | /// |
| 263 | /// {@macro flutter.widgets.IgnorePointer.ignoringSemantics} |
| 264 | @Deprecated( |
| 265 | 'Create a custom sliver ignore pointer widget instead. ' |
| 266 | 'This feature was deprecated after v3.8.0-12.0.pre.' , |
| 267 | ) |
| 268 | bool? get ignoringSemantics => _ignoringSemantics; |
| 269 | bool? _ignoringSemantics; |
| 270 | set ignoringSemantics(bool? value) { |
| 271 | if (value == _ignoringSemantics) { |
| 272 | return; |
| 273 | } |
| 274 | _ignoringSemantics = value; |
| 275 | markNeedsSemanticsUpdate(); |
| 276 | } |
| 277 | |
| 278 | @override |
| 279 | bool hitTest( |
| 280 | SliverHitTestResult result, { |
| 281 | required double mainAxisPosition, |
| 282 | required double crossAxisPosition, |
| 283 | }) { |
| 284 | return !ignoring && |
| 285 | super.hitTest( |
| 286 | result, |
| 287 | mainAxisPosition: mainAxisPosition, |
| 288 | crossAxisPosition: crossAxisPosition, |
| 289 | ); |
| 290 | } |
| 291 | |
| 292 | @override |
| 293 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
| 294 | if (_ignoringSemantics ?? false) { |
| 295 | return; |
| 296 | } |
| 297 | super.visitChildrenForSemantics(visitor); |
| 298 | } |
| 299 | |
| 300 | @override |
| 301 | void describeSemanticsConfiguration(SemanticsConfiguration config) { |
| 302 | super.describeSemanticsConfiguration(config); |
| 303 | // Do not block user interactions if _ignoringSemantics is false; otherwise, |
| 304 | // delegate to absorbing |
| 305 | config.isBlockingUserActions = ignoring && (_ignoringSemantics ?? true); |
| 306 | } |
| 307 | |
| 308 | @override |
| 309 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 310 | super.debugFillProperties(properties); |
| 311 | properties.add(DiagnosticsProperty<bool>('ignoring' , ignoring)); |
| 312 | properties.add( |
| 313 | DiagnosticsProperty<bool>( |
| 314 | 'ignoringSemantics' , |
| 315 | ignoringSemantics, |
| 316 | description: ignoringSemantics == null ? null : 'implicitly $ignoringSemantics' , |
| 317 | ), |
| 318 | ); |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | /// Lays the sliver child out as if it was in the tree, but without painting |
| 323 | /// anything, without making the sliver child available for hit testing, and |
| 324 | /// without taking any room in the parent. |
| 325 | class RenderSliverOffstage extends RenderProxySliver { |
| 326 | /// Creates an offstage render object. |
| 327 | RenderSliverOffstage({bool offstage = true, RenderSliver? sliver}) : _offstage = offstage { |
| 328 | child = sliver; |
| 329 | } |
| 330 | |
| 331 | /// Whether the sliver child is hidden from the rest of the tree. |
| 332 | /// |
| 333 | /// If true, the sliver child is laid out as if it was in the tree, but |
| 334 | /// without painting anything, without making the sliver child available for |
| 335 | /// hit testing, and without taking any room in the parent. |
| 336 | /// |
| 337 | /// If false, the sliver child is included in the tree as normal. |
| 338 | bool get offstage => _offstage; |
| 339 | bool _offstage; |
| 340 | |
| 341 | set offstage(bool value) { |
| 342 | if (value == _offstage) { |
| 343 | return; |
| 344 | } |
| 345 | _offstage = value; |
| 346 | markNeedsLayoutForSizedByParentChange(); |
| 347 | } |
| 348 | |
| 349 | @override |
| 350 | void performLayout() { |
| 351 | assert(child != null); |
| 352 | child!.layout(constraints, parentUsesSize: true); |
| 353 | if (!offstage) { |
| 354 | geometry = child!.geometry; |
| 355 | } else { |
| 356 | geometry = SliverGeometry.zero; |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | @override |
| 361 | bool hitTest( |
| 362 | SliverHitTestResult result, { |
| 363 | required double mainAxisPosition, |
| 364 | required double crossAxisPosition, |
| 365 | }) { |
| 366 | return !offstage && |
| 367 | super.hitTest( |
| 368 | result, |
| 369 | mainAxisPosition: mainAxisPosition, |
| 370 | crossAxisPosition: crossAxisPosition, |
| 371 | ); |
| 372 | } |
| 373 | |
| 374 | @override |
| 375 | bool hitTestChildren( |
| 376 | SliverHitTestResult result, { |
| 377 | required double mainAxisPosition, |
| 378 | required double crossAxisPosition, |
| 379 | }) { |
| 380 | return !offstage && |
| 381 | child != null && |
| 382 | child!.geometry!.hitTestExtent > 0 && |
| 383 | child!.hitTest( |
| 384 | result, |
| 385 | mainAxisPosition: mainAxisPosition, |
| 386 | crossAxisPosition: crossAxisPosition, |
| 387 | ); |
| 388 | } |
| 389 | |
| 390 | @override |
| 391 | void paint(PaintingContext context, Offset offset) { |
| 392 | if (offstage) { |
| 393 | return; |
| 394 | } |
| 395 | context.paintChild(child!, offset); |
| 396 | } |
| 397 | |
| 398 | @override |
| 399 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { |
| 400 | if (offstage) { |
| 401 | return; |
| 402 | } |
| 403 | super.visitChildrenForSemantics(visitor); |
| 404 | } |
| 405 | |
| 406 | @override |
| 407 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 408 | super.debugFillProperties(properties); |
| 409 | properties.add(DiagnosticsProperty<bool>('offstage' , offstage)); |
| 410 | } |
| 411 | |
| 412 | @override |
| 413 | List<DiagnosticsNode> debugDescribeChildren() { |
| 414 | if (child == null) { |
| 415 | return <DiagnosticsNode>[]; |
| 416 | } |
| 417 | return <DiagnosticsNode>[ |
| 418 | child!.toDiagnosticsNode( |
| 419 | name: 'child' , |
| 420 | style: offstage ? DiagnosticsTreeStyle.offstage : DiagnosticsTreeStyle.sparse, |
| 421 | ), |
| 422 | ]; |
| 423 | } |
| 424 | } |
| 425 | |
| 426 | /// Makes its sliver child partially transparent, driven from an [Animation]. |
| 427 | /// |
| 428 | /// This is a variant of [RenderSliverOpacity] that uses an [Animation<double>] |
| 429 | /// rather than a [double] to control the opacity. |
| 430 | class RenderSliverAnimatedOpacity extends RenderProxySliver |
| 431 | with RenderAnimatedOpacityMixin<RenderSliver> { |
| 432 | /// Creates a partially transparent render object. |
| 433 | RenderSliverAnimatedOpacity({ |
| 434 | required Animation<double> opacity, |
| 435 | bool alwaysIncludeSemantics = false, |
| 436 | RenderSliver? sliver, |
| 437 | }) { |
| 438 | this.opacity = opacity; |
| 439 | this.alwaysIncludeSemantics = alwaysIncludeSemantics; |
| 440 | child = sliver; |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | /// Applies a cross-axis constraint to its sliver child. |
| 445 | /// |
| 446 | /// This render object takes a [maxExtent] parameter and uses the smaller of |
| 447 | /// [maxExtent] and the parent's [SliverConstraints.crossAxisExtent] as the |
| 448 | /// cross axis extent of the [SliverConstraints] passed to the sliver child. |
| 449 | class RenderSliverConstrainedCrossAxis extends RenderProxySliver { |
| 450 | /// Creates a render object that constrains the cross axis extent of its sliver child. |
| 451 | /// |
| 452 | /// The [maxExtent] parameter must be nonnegative. |
| 453 | RenderSliverConstrainedCrossAxis({required double maxExtent}) |
| 454 | : _maxExtent = maxExtent, |
| 455 | assert(maxExtent >= 0.0); |
| 456 | |
| 457 | /// The cross axis extent to apply to the sliver child. |
| 458 | /// |
| 459 | /// This value must be nonnegative. |
| 460 | double get maxExtent => _maxExtent; |
| 461 | double _maxExtent; |
| 462 | set maxExtent(double value) { |
| 463 | if (_maxExtent == value) { |
| 464 | return; |
| 465 | } |
| 466 | _maxExtent = value; |
| 467 | markNeedsLayout(); |
| 468 | } |
| 469 | |
| 470 | @override |
| 471 | void performLayout() { |
| 472 | assert(child != null); |
| 473 | assert(maxExtent >= 0.0); |
| 474 | child!.layout( |
| 475 | constraints.copyWith(crossAxisExtent: min(_maxExtent, constraints.crossAxisExtent)), |
| 476 | parentUsesSize: true, |
| 477 | ); |
| 478 | final SliverGeometry childLayoutGeometry = child!.geometry!; |
| 479 | geometry = childLayoutGeometry.copyWith( |
| 480 | crossAxisExtent: min(_maxExtent, constraints.crossAxisExtent), |
| 481 | ); |
| 482 | } |
| 483 | } |
| 484 | |
| 485 | /// Add annotations to the [SemanticsNode] for this subtree. |
| 486 | class RenderSliverSemanticsAnnotations extends RenderProxySliver with SemanticsAnnotationsMixin { |
| 487 | /// Creates a render object that attaches a semantic annotation. |
| 488 | /// |
| 489 | /// If the [SemanticsProperties.attributedLabel] is not null, the [textDirection] must also not be null. |
| 490 | RenderSliverSemanticsAnnotations({ |
| 491 | RenderSliver? child, |
| 492 | required SemanticsProperties properties, |
| 493 | bool container = false, |
| 494 | bool explicitChildNodes = false, |
| 495 | bool excludeSemantics = false, |
| 496 | bool blockUserActions = false, |
| 497 | Locale? localeForSubtree, |
| 498 | TextDirection? textDirection, |
| 499 | }) : super(child) { |
| 500 | initSemanticsAnnotations( |
| 501 | properties: properties, |
| 502 | container: container, |
| 503 | explicitChildNodes: explicitChildNodes, |
| 504 | excludeSemantics: excludeSemantics, |
| 505 | blockUserActions: blockUserActions, |
| 506 | localeForSubtree: localeForSubtree, |
| 507 | textDirection: textDirection, |
| 508 | ); |
| 509 | } |
| 510 | } |
| 511 | |