sliver_padding.dart 13.5 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// Copyright 2016 The Chromium 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:math' as math;

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:vector_math/vector_math_64.dart';

import 'debug.dart';
import 'object.dart';
import 'sliver.dart';

/// Inset a [RenderSliver], applying padding on each side.
///
/// A [RenderSliverPadding] object wraps the [SliverGeometry.layoutExtent] of
/// its child. Any incoming [SliverConstraints.overlap] is ignored and not
/// passed on to the child.
///
21
/// Applying padding to anything but the most mundane sliver is likely to have
22 23 24 25
/// undesired effects. For example, wrapping a
/// [RenderSliverPinnedPersistentHeader] will cause the app bar to overlap
/// earlier slivers (contrary to the normal behavior of pinned app bars), and
/// while the app bar is pinned, the padding will scroll away.
Ian Hickson's avatar
Ian Hickson committed
26 27 28 29 30
class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
  /// Creates a render object that insets its child in a viewport.
  ///
  /// The [padding] argument must not be null and must have non-negative insets.
  RenderSliverPadding({
31 32
    @required EdgeInsetsGeometry padding,
    TextDirection textDirection,
Ian Hickson's avatar
Ian Hickson committed
33
    RenderSliver child,
34 35
  }) : assert(padding != null),
       assert(padding.isNonNegative),
36 37
       _padding = padding,
       _textDirection = textDirection {
Ian Hickson's avatar
Ian Hickson committed
38
    this.child = child;
39 40 41 42
  }

  EdgeInsets _resolvedPadding;

Ian Hickson's avatar
Ian Hickson committed
43 44 45 46 47 48 49 50 51 52
  void _resolve() {
    if (_resolvedPadding != null)
      return;
    _resolvedPadding = padding.resolve(textDirection);
    assert(_resolvedPadding.isNonNegative);
  }

  void _markNeedResolution() {
    _resolvedPadding = null;
    markNeedsLayout();
Ian Hickson's avatar
Ian Hickson committed
53 54 55
  }

  /// The amount to pad the child in each dimension.
Ian Hickson's avatar
Ian Hickson committed
56 57 58
  ///
  /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
  /// must not be null.
59 60 61
  EdgeInsetsGeometry get padding => _padding;
  EdgeInsetsGeometry _padding;
  set padding(EdgeInsetsGeometry value) {
Ian Hickson's avatar
Ian Hickson committed
62
    assert(value != null);
63
    assert(padding.isNonNegative);
Ian Hickson's avatar
Ian Hickson committed
64 65 66
    if (_padding == value)
      return;
    _padding = value;
Ian Hickson's avatar
Ian Hickson committed
67
    _markNeedResolution();
68 69 70
  }

  /// The text direction with which to resolve [padding].
Ian Hickson's avatar
Ian Hickson committed
71 72 73
  ///
  /// This may be changed to null, but only after the [padding] has been changed
  /// to a value that does not depend on the direction.
74 75 76 77 78 79
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    if (_textDirection == value)
      return;
    _textDirection = value;
Ian Hickson's avatar
Ian Hickson committed
80
    _markNeedResolution();
Ian Hickson's avatar
Ian Hickson committed
81 82 83 84 85 86 87 88 89 90
  }

  /// The padding in the scroll direction on the side nearest the 0.0 scroll direction.
  ///
  /// Only valid after layout has started, since before layout the render object
  /// doesn't know what direction it will be laid out in.
  double get beforePadding {
    assert(constraints != null);
    assert(constraints.axisDirection != null);
    assert(constraints.growthDirection != null);
Ian Hickson's avatar
Ian Hickson committed
91
    assert(_resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
92 93
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
94
        return _resolvedPadding.bottom;
Ian Hickson's avatar
Ian Hickson committed
95
      case AxisDirection.right:
96
        return _resolvedPadding.left;
Ian Hickson's avatar
Ian Hickson committed
97
      case AxisDirection.down:
98
        return _resolvedPadding.top;
Ian Hickson's avatar
Ian Hickson committed
99
      case AxisDirection.left:
100
        return _resolvedPadding.right;
Ian Hickson's avatar
Ian Hickson committed
101 102 103 104 105 106 107 108 109 110 111 112
    }
    return null;
  }

  /// The padding in the scroll direction on the side furthest from the 0.0 scroll offset.
  ///
  /// Only valid after layout has started, since before layout the render object
  /// doesn't know what direction it will be laid out in.
  double get afterPadding {
    assert(constraints != null);
    assert(constraints.axisDirection != null);
    assert(constraints.growthDirection != null);
Ian Hickson's avatar
Ian Hickson committed
113
    assert(_resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
114 115
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
116
        return _resolvedPadding.top;
Ian Hickson's avatar
Ian Hickson committed
117
      case AxisDirection.right:
118
        return _resolvedPadding.right;
Ian Hickson's avatar
Ian Hickson committed
119
      case AxisDirection.down:
120
        return _resolvedPadding.bottom;
Ian Hickson's avatar
Ian Hickson committed
121
      case AxisDirection.left:
122
        return _resolvedPadding.left;
Ian Hickson's avatar
Ian Hickson committed
123 124 125 126
    }
    return null;
  }

127 128 129
  /// The total padding in the [SliverConstraints.axisDirection]. (In other
  /// words, for a vertical downwards-growing list, the sum of the padding on
  /// the top and bottom.)
Ian Hickson's avatar
Ian Hickson committed
130 131 132 133 134 135
  ///
  /// Only valid after layout has started, since before layout the render object
  /// doesn't know what direction it will be laid out in.
  double get mainAxisPadding {
    assert(constraints != null);
    assert(constraints.axis != null);
Ian Hickson's avatar
Ian Hickson committed
136
    assert(_resolvedPadding != null);
137
    return _resolvedPadding.along(constraints.axis);
Ian Hickson's avatar
Ian Hickson committed
138 139 140 141 142 143 144 145 146 147 148
  }

  /// The total padding in the cross-axis direction. (In other words, for a
  /// vertical downwards-growing list, the sum of the padding on the left and
  /// right.)
  ///
  /// Only valid after layout has started, since before layout the render object
  /// doesn't know what direction it will be laid out in.
  double get crossAxisPadding {
    assert(constraints != null);
    assert(constraints.axis != null);
Ian Hickson's avatar
Ian Hickson committed
149
    assert(_resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
150 151
    switch (constraints.axis) {
      case Axis.horizontal:
152
        return _resolvedPadding.vertical;
Ian Hickson's avatar
Ian Hickson committed
153
      case Axis.vertical:
154
        return _resolvedPadding.horizontal;
Ian Hickson's avatar
Ian Hickson committed
155 156 157 158 159 160 161
    }
    return null;
  }

  @override
  void setupParentData(RenderObject child) {
    if (child.parentData is! SliverPhysicalParentData)
162
      child.parentData = SliverPhysicalParentData();
Ian Hickson's avatar
Ian Hickson committed
163 164 165 166
  }

  @override
  void performLayout() {
Ian Hickson's avatar
Ian Hickson committed
167 168
    _resolve();
    assert(_resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
169 170 171 172 173
    final double beforePadding = this.beforePadding;
    final double afterPadding = this.afterPadding;
    final double mainAxisPadding = this.mainAxisPadding;
    final double crossAxisPadding = this.crossAxisPadding;
    if (child == null) {
174
      geometry = SliverGeometry(
Ian Hickson's avatar
Ian Hickson committed
175 176 177 178 179 180 181 182 183
        scrollExtent: mainAxisPadding,
        paintExtent: math.min(mainAxisPadding, constraints.remainingPaintExtent),
        maxPaintExtent: mainAxisPadding,
      );
      return;
    }
    child.layout(
      constraints.copyWith(
        scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding),
184
        cacheOrigin: math.min(0.0, constraints.cacheOrigin + beforePadding),
Ian Hickson's avatar
Ian Hickson committed
185 186
        overlap: 0.0,
        remainingPaintExtent: constraints.remainingPaintExtent - calculatePaintOffset(constraints, from: 0.0, to: beforePadding),
187
        remainingCacheExtent: constraints.remainingCacheExtent - calculateCacheOffset(constraints, from: 0.0, to: beforePadding),
188
        crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding),
Ian Hickson's avatar
Ian Hickson committed
189 190 191 192
      ),
      parentUsesSize: true,
    );
    final SliverGeometry childLayoutGeometry = child.geometry;
193
    if (childLayoutGeometry.scrollOffsetCorrection != null) {
194
      geometry = SliverGeometry(
195 196 197 198
        scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection,
      );
      return;
    }
Ian Hickson's avatar
Ian Hickson committed
199 200 201 202 203 204 205 206 207 208 209
    final double beforePaddingPaintExtent = calculatePaintOffset(
      constraints,
      from: 0.0,
      to: beforePadding,
    );
    final double afterPaddingPaintExtent = calculatePaintOffset(
      constraints,
      from: beforePadding + childLayoutGeometry.scrollExtent,
      to: mainAxisPadding + childLayoutGeometry.scrollExtent,
    );
    final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent;
210 211 212 213 214 215 216 217 218 219 220
    final double beforePaddingCacheExtent = calculateCacheOffset(
      constraints,
      from: 0.0,
      to: beforePadding,
    );
    final double afterPaddingCacheExtent = calculateCacheOffset(
      constraints,
      from: beforePadding + childLayoutGeometry.scrollExtent,
      to: mainAxisPadding + childLayoutGeometry.scrollExtent,
    );
    final double mainAxisPaddingCacheExtent = afterPaddingCacheExtent + beforePaddingCacheExtent;
221 222 223 224
    final double paintExtent = math.min(
      beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent),
      constraints.remainingPaintExtent,
    );
225
    geometry = SliverGeometry(
Ian Hickson's avatar
Ian Hickson committed
226
      scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent,
227 228
      paintExtent: paintExtent,
      layoutExtent: math.min(mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, paintExtent),
229
      cacheExtent: math.min(mainAxisPaddingCacheExtent + childLayoutGeometry.cacheExtent, constraints.remainingCacheExtent),
Ian Hickson's avatar
Ian Hickson committed
230 231 232 233 234
      maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent,
      hitTestExtent: math.max(
        mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent,
        beforePaddingPaintExtent + childLayoutGeometry.hitTestExtent,
      ),
235
      hasVisualOverflow: childLayoutGeometry.hasVisualOverflow,
Ian Hickson's avatar
Ian Hickson committed
236 237 238 239 240 241 242
    );

    final SliverPhysicalParentData childParentData = child.parentData;
    assert(constraints.axisDirection != null);
    assert(constraints.growthDirection != null);
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
243
        childParentData.paintOffset = Offset(_resolvedPadding.left, calculatePaintOffset(constraints, from: _resolvedPadding.bottom + childLayoutGeometry.scrollExtent, to: _resolvedPadding.bottom + childLayoutGeometry.scrollExtent + _resolvedPadding.top));
Ian Hickson's avatar
Ian Hickson committed
244 245
        break;
      case AxisDirection.right:
246
        childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: 0.0, to: _resolvedPadding.left), _resolvedPadding.top);
Ian Hickson's avatar
Ian Hickson committed
247 248
        break;
      case AxisDirection.down:
249
        childParentData.paintOffset = Offset(_resolvedPadding.left, calculatePaintOffset(constraints, from: 0.0, to: _resolvedPadding.top));
Ian Hickson's avatar
Ian Hickson committed
250 251
        break;
      case AxisDirection.left:
252
        childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: _resolvedPadding.right + childLayoutGeometry.scrollExtent, to: _resolvedPadding.right + childLayoutGeometry.scrollExtent + _resolvedPadding.left), _resolvedPadding.top);
Ian Hickson's avatar
Ian Hickson committed
253 254 255 256 257 258 259 260 261 262
        break;
    }
    assert(childParentData.paintOffset != null);
    assert(beforePadding == this.beforePadding);
    assert(afterPadding == this.afterPadding);
    assert(mainAxisPadding == this.mainAxisPadding);
    assert(crossAxisPadding == this.crossAxisPadding);
  }

  @override
263 264 265 266 267 268 269 270 271 272 273 274
  bool hitTestChildren(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
    if (child != null && child.geometry.hitTestExtent > 0.0) {
      final SliverPhysicalParentData childParentData = child.parentData;
      result.addWithAxisOffset(
        mainAxisPosition: mainAxisPosition,
        crossAxisPosition: crossAxisPosition,
        mainAxisOffset: childMainAxisPosition(child),
        crossAxisOffset: childCrossAxisPosition(child),
        paintOffset: childParentData.paintOffset,
        hitTest: child.hitTest,
      );
    }
Ian Hickson's avatar
Ian Hickson committed
275 276 277 278
    return false;
  }

  @override
279
  double childMainAxisPosition(RenderSliver child) {
Ian Hickson's avatar
Ian Hickson committed
280 281 282 283 284
    assert(child != null);
    assert(child == this.child);
    return calculatePaintOffset(constraints, from: 0.0, to: beforePadding);
  }

285 286 287 288
  @override
  double childCrossAxisPosition(RenderSliver child) {
    assert(child != null);
    assert(child == this.child);
289 290 291
    assert(constraints != null);
    assert(constraints.axisDirection != null);
    assert(constraints.growthDirection != null);
Ian Hickson's avatar
Ian Hickson committed
292
    assert(_resolvedPadding != null);
293 294 295
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
      case AxisDirection.down:
296
        return _resolvedPadding.left;
297 298
      case AxisDirection.left:
      case AxisDirection.right:
299
        return _resolvedPadding.top;
300 301
    }
    return null;
302 303
  }

304 305 306 307 308 309
  @override
  double childScrollOffset(RenderObject child) {
    assert(child.parent == this);
    return beforePadding;
  }

Ian Hickson's avatar
Ian Hickson committed
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
  @override
  void applyPaintTransform(RenderObject child, Matrix4 transform) {
    assert(child != null);
    assert(child == this.child);
    final SliverPhysicalParentData childParentData = child.parentData;
    childParentData.applyPaintTransform(transform);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null && child.geometry.visible) {
      final SliverPhysicalParentData childParentData = child.parentData;
      context.paintChild(child, offset + childParentData.paintOffset);
    }
  }

  @override
  void debugPaint(PaintingContext context, Offset offset) {
    super.debugPaint(context, offset);
    assert(() {
      if (debugPaintSizeEnabled) {
331
        final Size parentSize = getAbsoluteSize();
Ian Hickson's avatar
Ian Hickson committed
332 333 334 335
        final Rect outerRect = offset & parentSize;
        Size childSize;
        Rect innerRect;
        if (child != null) {
336
          childSize = child.getAbsoluteSize();
Ian Hickson's avatar
Ian Hickson committed
337 338
          final SliverPhysicalParentData childParentData = child.parentData;
          innerRect = (offset + childParentData.paintOffset) & childSize;
339 340 341 342
          assert(innerRect.top >= outerRect.top);
          assert(innerRect.left >= outerRect.left);
          assert(innerRect.right <= outerRect.right);
          assert(innerRect.bottom <= outerRect.bottom);
Ian Hickson's avatar
Ian Hickson committed
343 344 345 346
        }
        debugPaintPadding(context.canvas, outerRect, innerRect);
      }
      return true;
347
    }());
Ian Hickson's avatar
Ian Hickson committed
348
  }
349 350

  @override
351 352
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
353 354
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
355
  }
Ian Hickson's avatar
Ian Hickson committed
356
}