proxy_sliver.dart 12.9 KB
Newer Older
1 2 3 4 5 6
// 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 'dart:ui' as ui show Color;

7
import 'package:flutter/animation.dart';
8 9 10
import 'package:flutter/foundation.dart';
import 'package:vector_math/vector_math_64.dart';

11
import 'layer.dart';
12
import 'object.dart';
13
import 'proxy_box.dart';
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
import 'sliver.dart';

/// A base class for sliver render objects that resemble their children.
///
/// A proxy sliver has a single child and simply mimics all the properties of
/// that child by calling through to the child for each function in the render
/// sliver protocol. For example, a proxy sliver determines its geometry by
/// asking its sliver child to layout with the same constraints and then
/// matching the geometry.
///
/// A proxy sliver isn't useful on its own because you might as well just
/// replace the proxy sliver with its child. However, RenderProxySliver is a
/// useful base class for render objects that wish to mimic most, but not all,
/// of the properties of their sliver child.
///
/// See also:
///
///  * [RenderProxyBox], a base class for render boxes that resemble their
///    children.
abstract class RenderProxySliver extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
  /// Creates a proxy render sliver.
  ///
  /// Proxy render slivers aren't created directly because they simply proxy
  /// the render sliver protocol to their sliver [child]. Instead, use one of
  /// the subclasses.
39
  RenderProxySliver([RenderSliver? child]) {
40 41 42 43 44
    this.child = child;
  }

  @override
  void setupParentData(RenderObject child) {
45
    if (child.parentData is! SliverPhysicalParentData) {
46
      child.parentData = SliverPhysicalParentData();
47
    }
48 49 50 51 52
  }

  @override
  void performLayout() {
    assert(child != null);
53 54
    child!.layout(constraints, parentUsesSize: true);
    geometry = child!.geometry;
55 56
  }

57 58
  @override
  void paint(PaintingContext context, Offset offset) {
59
    if (child != null) {
60
      context.paintChild(child!, offset);
61
    }
62 63
  }

64
  @override
65
  bool hitTestChildren(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
66
    return child != null
67 68
      && child!.geometry!.hitTestExtent > 0
      && child!.hitTest(
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
        result,
        mainAxisPosition: mainAxisPosition,
        crossAxisPosition: crossAxisPosition,
      );
  }

  @override
  double childMainAxisPosition(RenderSliver child) {
    assert(child != null);
    assert(child == this.child);
    return 0.0;
  }

  @override
  void applyPaintTransform(RenderObject child, Matrix4 transform) {
    assert(child != null);
85
    final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
    childParentData.applyPaintTransform(transform);
  }
}

/// Makes its sliver child partially transparent.
///
/// This class paints its sliver child into an intermediate buffer and then
/// blends the sliver child back into the scene, partially transparent.
///
/// For values of opacity other than 0.0 and 1.0, this class is relatively
/// expensive, because it requires painting the sliver child into an intermediate
/// buffer. For the value 0.0, the sliver child is simply not painted at all.
/// For the value 1.0, the sliver child is painted immediately without an
/// intermediate buffer.
class RenderSliverOpacity extends RenderProxySliver {
  /// Creates a partially transparent render object.
  ///
  /// The [opacity] argument must be between 0.0 and 1.0, inclusive.
  RenderSliverOpacity({
    double opacity = 1.0,
    bool alwaysIncludeSemantics = false,
107
    RenderSliver? sliver,
108 109 110 111 112 113 114 115 116
  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
       assert(alwaysIncludeSemantics != null),
       _opacity = opacity,
       _alwaysIncludeSemantics = alwaysIncludeSemantics,
       _alpha = ui.Color.getAlphaFromOpacity(opacity) {
    child = sliver;
  }

  @override
Dan Field's avatar
Dan Field committed
117
  bool get alwaysNeedsCompositing => child != null && (_alpha > 0);
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

  int _alpha;

  /// The fraction to scale the child's alpha value.
  ///
  /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
  /// (i.e. invisible).
  ///
  /// The opacity must not be null.
  ///
  /// Values 1.0 and 0.0 are painted with a fast path. Other values
  /// require painting the child into an intermediate buffer, which is
  /// expensive.
  double get opacity => _opacity;
  double _opacity;
  set opacity(double value) {
    assert(value != null);
    assert(value >= 0.0 && value <= 1.0);
136
    if (_opacity == value) {
137
      return;
138
    }
139 140 141 142
    final bool didNeedCompositing = alwaysNeedsCompositing;
    final bool wasVisible = _alpha != 0;
    _opacity = value;
    _alpha = ui.Color.getAlphaFromOpacity(_opacity);
143
    if (didNeedCompositing != alwaysNeedsCompositing) {
144
      markNeedsCompositingBitsUpdate();
145
    }
146
    markNeedsPaint();
147
    if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics) {
148
      markNeedsSemanticsUpdate();
149
    }
150 151 152 153 154 155 156 157 158 159
  }

  /// Whether child semantics are included regardless of the opacity.
  ///
  /// If false, semantics are excluded when [opacity] is 0.0.
  ///
  /// Defaults to false.
  bool get alwaysIncludeSemantics => _alwaysIncludeSemantics;
  bool _alwaysIncludeSemantics;
  set alwaysIncludeSemantics(bool value) {
160
    if (value == _alwaysIncludeSemantics) {
161
      return;
162
    }
163 164 165 166 167 168
    _alwaysIncludeSemantics = value;
    markNeedsSemanticsUpdate();
  }

  @override
  void paint(PaintingContext context, Offset offset) {
169
    if (child != null && child!.geometry!.visible) {
170 171 172 173 174 175 176 177 178
      if (_alpha == 0) {
        // No need to keep the layer. We'll create a new one if necessary.
        layer = null;
        return;
      }
      assert(needsCompositing);
      layer = context.pushOpacity(
        offset,
        _alpha,
179
        super.paint,
180
        oldLayer: layer as OpacityLayer?,
181
      );
182 183 184 185
      assert(() {
        layer!.debugCreator = debugCreator;
        return true;
      }());
186 187 188 189 190
    }
  }

  @override
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
191
    if (child != null && (_alpha != 0 || alwaysIncludeSemantics)) {
192
      visitor(child!);
193
    }
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DoubleProperty('opacity', opacity));
    properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics'));
  }
}

/// A render object that is invisible during hit testing.
///
/// When [ignoring] is true, this render object (and its subtree) is invisible
/// to hit testing. It still consumes space during layout and paints its sliver
/// child as usual. It just cannot be the target of located events, because its
/// render object returns false from [hitTest].
///
/// When [ignoringSemantics] is true, the subtree will be invisible to the
/// semantics layer (and thus e.g. accessibility tools). If [ignoringSemantics]
/// is null, it uses the value of [ignoring].
class RenderSliverIgnorePointer extends RenderProxySliver {
  /// Creates a render object that is invisible to hit testing.
  ///
  /// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
  /// this render object will be ignored for semantics if [ignoring] is true.
  RenderSliverIgnorePointer({
220
    RenderSliver? sliver,
221
    bool ignoring = true,
222
    bool? ignoringSemantics,
223 224 225 226 227 228 229 230 231 232 233 234 235 236
  }) : assert(ignoring != null),
       _ignoring = ignoring,
       _ignoringSemantics = ignoringSemantics {
    child = sliver;
  }

  /// Whether this render object is ignored during hit testing.
  ///
  /// Regardless of whether this render object is ignored during hit testing, it
  /// will still consume space during layout and be visible during painting.
  bool get ignoring => _ignoring;
  bool _ignoring;
  set ignoring(bool value) {
    assert(value != null);
237
    if (value == _ignoring) {
238
      return;
239
    }
240
    _ignoring = value;
241
    if (_ignoringSemantics == null || !_ignoringSemantics!) {
242
      markNeedsSemanticsUpdate();
243
    }
244 245 246 247 248 249 250 251
  }

  /// Whether the semantics of this render object is ignored when compiling the
  /// semantics tree.
  ///
  /// If null, defaults to value of [ignoring].
  ///
  /// See [SemanticsNode] for additional information about the semantics tree.
252 253 254
  bool? get ignoringSemantics => _ignoringSemantics;
  bool? _ignoringSemantics;
  set ignoringSemantics(bool? value) {
255
    if (value == _ignoringSemantics) {
256
      return;
257
    }
258 259
    final bool oldEffectiveValue = _effectiveIgnoringSemantics;
    _ignoringSemantics = value;
260
    if (oldEffectiveValue != _effectiveIgnoringSemantics) {
261
      markNeedsSemanticsUpdate();
262
    }
263 264 265 266 267
  }

  bool get _effectiveIgnoringSemantics => ignoringSemantics ?? ignoring;

  @override
268
  bool hitTest(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
269 270 271 272 273 274 275 276 277 278
    return !ignoring
      && super.hitTest(
        result,
        mainAxisPosition: mainAxisPosition,
        crossAxisPosition: crossAxisPosition,
      );
  }

  @override
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
279
    if (child != null && !_effectiveIgnoringSemantics) {
280
      visitor(child!);
281
    }
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
    properties.add(DiagnosticsProperty<bool>('ignoringSemantics', _effectiveIgnoringSemantics, description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null));
  }
}

/// Lays the sliver child out as if it was in the tree, but without painting
/// anything, without making the sliver child available for hit testing, and
/// without taking any room in the parent.
class RenderSliverOffstage extends RenderProxySliver {
  /// Creates an offstage render object.
  RenderSliverOffstage({
    bool offstage = true,
299
    RenderSliver? sliver,
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
  }) : assert(offstage != null),
       _offstage = offstage {
    child = sliver;
  }

  /// Whether the sliver child is hidden from the rest of the tree.
  ///
  /// If true, the sliver child is laid out as if it was in the tree, but
  /// without painting anything, without making the sliver child available for
  /// hit testing, and without taking any room in the parent.
  ///
  /// If false, the sliver child is included in the tree as normal.
  bool get offstage => _offstage;
  bool _offstage;

  set offstage(bool value) {
    assert(value != null);
317
    if (value == _offstage) {
318
      return;
319
    }
320 321 322 323 324 325 326
    _offstage = value;
    markNeedsLayoutForSizedByParentChange();
  }

  @override
  void performLayout() {
    assert(child != null);
327
    child!.layout(constraints, parentUsesSize: true);
328
    if (!offstage) {
329
      geometry = child!.geometry;
330
    } else {
331
      geometry = SliverGeometry.zero;
332
    }
333 334 335
  }

  @override
336
  bool hitTest(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
337 338 339 340 341 342 343 344
    return !offstage && super.hitTest(
      result,
      mainAxisPosition: mainAxisPosition,
      crossAxisPosition: crossAxisPosition,
    );
  }

  @override
345
  bool hitTestChildren(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) {
346 347
    return !offstage
      && child != null
348 349
      && child!.geometry!.hitTestExtent > 0
      && child!.hitTest(
350 351 352 353 354 355 356 357
        result,
        mainAxisPosition: mainAxisPosition,
        crossAxisPosition: crossAxisPosition,
      );
  }

  @override
  void paint(PaintingContext context, Offset offset) {
358
    if (offstage) {
359
      return;
360
    }
361
    context.paintChild(child!, offset);
362 363 364 365
  }

  @override
  void visitChildrenForSemantics(RenderObjectVisitor visitor) {
366
    if (offstage) {
367
      return;
368
    }
369 370 371 372 373 374 375 376 377 378 379
    super.visitChildrenForSemantics(visitor);
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<bool>('offstage', offstage));
  }

  @override
  List<DiagnosticsNode> debugDescribeChildren() {
380
    if (child == null) {
381
      return <DiagnosticsNode>[];
382
    }
383
    return <DiagnosticsNode>[
384
      child!.toDiagnosticsNode(
385 386 387 388 389 390
        name: 'child',
        style: offstage ? DiagnosticsTreeStyle.offstage : DiagnosticsTreeStyle.sparse,
      ),
    ];
  }
}
391 392 393 394 395

/// Makes its sliver child partially transparent, driven from an [Animation].
///
/// This is a variant of [RenderSliverOpacity] that uses an [Animation<double>]
/// rather than a [double] to control the opacity.
396
class RenderSliverAnimatedOpacity extends RenderProxySliver with RenderAnimatedOpacityMixin<RenderSliver> {
397 398 399 400
  /// Creates a partially transparent render object.
  ///
  /// The [opacity] argument must not be null.
  RenderSliverAnimatedOpacity({
401
    required Animation<double> opacity,
402
    bool alwaysIncludeSemantics = false,
403
    RenderSliver? sliver,
404 405 406 407 408 409 410
  }) : assert(opacity != null),
       assert(alwaysIncludeSemantics != null) {
    this.opacity = opacity;
    this.alwaysIncludeSemantics = alwaysIncludeSemantics;
    child = sliver;
  }
}