sliver_padding.dart 12.4 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 21
// 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 'binding.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.
///
22
/// Applying padding to anything but the most mundane sliver is likely to have
23 24 25 26
/// 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
27 28 29 30 31
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({
32 33
    @required EdgeInsetsGeometry padding,
    TextDirection textDirection,
Ian Hickson's avatar
Ian Hickson committed
34
    RenderSliver child,
35 36
  }) : assert(padding != null),
       assert(padding.isNonNegative),
37 38
       _padding = padding,
       _textDirection = textDirection {
Ian Hickson's avatar
Ian Hickson committed
39
    this.child = child;
40 41 42 43
  }

  EdgeInsets _resolvedPadding;

Ian Hickson's avatar
Ian Hickson committed
44 45 46 47 48 49 50 51 52 53
  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
54 55 56
  }

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

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

  /// 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
92
    assert(_resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
93 94
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
95
        return _resolvedPadding.bottom;
Ian Hickson's avatar
Ian Hickson committed
96
      case AxisDirection.right:
97
        return _resolvedPadding.left;
Ian Hickson's avatar
Ian Hickson committed
98
      case AxisDirection.down:
99
        return _resolvedPadding.top;
Ian Hickson's avatar
Ian Hickson committed
100
      case AxisDirection.left:
101
        return _resolvedPadding.right;
Ian Hickson's avatar
Ian Hickson committed
102 103 104 105 106 107 108 109 110 111 112 113
    }
    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
114
    assert(_resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
115 116
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
117
        return _resolvedPadding.top;
Ian Hickson's avatar
Ian Hickson committed
118
      case AxisDirection.right:
119
        return _resolvedPadding.right;
Ian Hickson's avatar
Ian Hickson committed
120
      case AxisDirection.down:
121
        return _resolvedPadding.bottom;
Ian Hickson's avatar
Ian Hickson committed
122
      case AxisDirection.left:
123
        return _resolvedPadding.left;
Ian Hickson's avatar
Ian Hickson committed
124 125 126 127
    }
    return null;
  }

128 129 130
  /// 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
131 132 133 134 135 136
  ///
  /// 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
137
    assert(_resolvedPadding != null);
138
    return _resolvedPadding.along(constraints.axis);
Ian Hickson's avatar
Ian Hickson committed
139 140 141 142 143 144 145 146 147 148 149
  }

  /// 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
150
    assert(_resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
151 152
    switch (constraints.axis) {
      case Axis.horizontal:
153
        return _resolvedPadding.vertical;
Ian Hickson's avatar
Ian Hickson committed
154
      case Axis.vertical:
155
        return _resolvedPadding.horizontal;
Ian Hickson's avatar
Ian Hickson committed
156 157 158 159 160 161 162 163 164 165 166 167
    }
    return null;
  }

  @override
  void setupParentData(RenderObject child) {
    if (child.parentData is! SliverPhysicalParentData)
      child.parentData = new SliverPhysicalParentData();
  }

  @override
  void performLayout() {
Ian Hickson's avatar
Ian Hickson committed
168 169
    _resolve();
    assert(_resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
    final double beforePadding = this.beforePadding;
    final double afterPadding = this.afterPadding;
    final double mainAxisPadding = this.mainAxisPadding;
    final double crossAxisPadding = this.crossAxisPadding;
    if (child == null) {
      geometry = new SliverGeometry(
        scrollExtent: mainAxisPadding,
        paintExtent: math.min(mainAxisPadding, constraints.remainingPaintExtent),
        maxPaintExtent: mainAxisPadding,
      );
      return;
    }
    child.layout(
      constraints.copyWith(
        scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding),
        overlap: 0.0,
        remainingPaintExtent: constraints.remainingPaintExtent - calculatePaintOffset(constraints, from: 0.0, to: beforePadding),
187
        crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding),
Ian Hickson's avatar
Ian Hickson committed
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
      ),
      parentUsesSize: true,
    );
    final SliverGeometry childLayoutGeometry = child.geometry;
    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;
203 204 205 206
    final double paintExtent = math.min(
      beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent),
      constraints.remainingPaintExtent,
    );
Ian Hickson's avatar
Ian Hickson committed
207 208
    geometry = new SliverGeometry(
      scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent,
209 210
      paintExtent: paintExtent,
      layoutExtent: math.min(mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, paintExtent),
Ian Hickson's avatar
Ian Hickson committed
211 212 213 214 215
      maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent,
      hitTestExtent: math.max(
        mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent,
        beforePaddingPaintExtent + childLayoutGeometry.hitTestExtent,
      ),
216
      hasVisualOverflow: childLayoutGeometry.hasVisualOverflow,
Ian Hickson's avatar
Ian Hickson committed
217 218 219 220 221 222 223
    );

    final SliverPhysicalParentData childParentData = child.parentData;
    assert(constraints.axisDirection != null);
    assert(constraints.growthDirection != null);
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
224
        childParentData.paintOffset = new Offset(_resolvedPadding.left, calculatePaintOffset(constraints, from: _resolvedPadding.bottom + childLayoutGeometry.scrollExtent, to: _resolvedPadding.bottom + childLayoutGeometry.scrollExtent + _resolvedPadding.top));
Ian Hickson's avatar
Ian Hickson committed
225 226
        break;
      case AxisDirection.right:
227
        childParentData.paintOffset = new Offset(calculatePaintOffset(constraints, from: 0.0, to: _resolvedPadding.left), _resolvedPadding.top);
Ian Hickson's avatar
Ian Hickson committed
228 229
        break;
      case AxisDirection.down:
230
        childParentData.paintOffset = new Offset(_resolvedPadding.left, calculatePaintOffset(constraints, from: 0.0, to: _resolvedPadding.top));
Ian Hickson's avatar
Ian Hickson committed
231 232
        break;
      case AxisDirection.left:
233
        childParentData.paintOffset = new Offset(calculatePaintOffset(constraints, from: _resolvedPadding.right + childLayoutGeometry.scrollExtent, to: _resolvedPadding.right + childLayoutGeometry.scrollExtent + _resolvedPadding.left), _resolvedPadding.top);
Ian Hickson's avatar
Ian Hickson committed
234 235 236 237 238 239 240 241 242 243 244 245
        break;
    }
    assert(childParentData.paintOffset != null);
    assert(beforePadding == this.beforePadding);
    assert(afterPadding == this.afterPadding);
    assert(mainAxisPadding == this.mainAxisPadding);
    assert(crossAxisPadding == this.crossAxisPadding);
  }

  @override
  bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
    if (child.geometry.hitTestExtent > 0.0)
246
      return child.hitTest(result, mainAxisPosition: mainAxisPosition - childMainAxisPosition(child), crossAxisPosition: crossAxisPosition - childCrossAxisPosition(child));
Ian Hickson's avatar
Ian Hickson committed
247 248 249 250
    return false;
  }

  @override
251
  double childMainAxisPosition(RenderSliver child) {
Ian Hickson's avatar
Ian Hickson committed
252 253 254 255 256
    assert(child != null);
    assert(child == this.child);
    return calculatePaintOffset(constraints, from: 0.0, to: beforePadding);
  }

257 258 259 260
  @override
  double childCrossAxisPosition(RenderSliver child) {
    assert(child != null);
    assert(child == this.child);
261 262 263
    assert(constraints != null);
    assert(constraints.axisDirection != null);
    assert(constraints.growthDirection != null);
Ian Hickson's avatar
Ian Hickson committed
264
    assert(_resolvedPadding != null);
265 266 267
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
      case AxisDirection.down:
268
        return _resolvedPadding.left;
269 270
      case AxisDirection.left:
      case AxisDirection.right:
271
        return _resolvedPadding.top;
272 273
    }
    return null;
274 275
  }

276 277 278 279 280 281
  @override
  double childScrollOffset(RenderObject child) {
    assert(child.parent == this);
    return beforePadding;
  }

Ian Hickson's avatar
Ian Hickson committed
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
  @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) {
        final Size parentSize = getAbsoluteSizeRelativeToOrigin();
        final Rect outerRect = offset & parentSize;
        Size childSize;
        Rect innerRect;
        if (child != null) {
          childSize = child.getAbsoluteSizeRelativeToOrigin();
          final SliverPhysicalParentData childParentData = child.parentData;
          innerRect = (offset + childParentData.paintOffset) & childSize;
311 312 313 314
          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
315 316 317 318
        }
        debugPaintPadding(context.canvas, outerRect, innerRect);
      }
      return true;
319
    }());
Ian Hickson's avatar
Ian Hickson committed
320
  }
321 322 323 324 325 326 327

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
    super.debugFillProperties(description);
    description.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
    description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
  }
Ian Hickson's avatar
Ian Hickson committed
328
}