sliver_padding.dart 15 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Ian Hickson's avatar
Ian Hickson committed
2 3 4 5 6 7 8 9 10 11 12
// 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:vector_math/vector_math_64.dart';

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

13
/// Insets a [RenderSliver] by applying [resolvedPadding] on each side.
Ian Hickson's avatar
Ian Hickson committed
14
///
15 16
/// A [RenderSliverEdgeInsetsPadding] subclass wraps the [SliverGeometry.layoutExtent]
/// of its child. Any incoming [SliverConstraints.overlap] is ignored and not
Ian Hickson's avatar
Ian Hickson committed
17 18
/// passed on to the child.
///
19
/// {@template flutter.rendering.RenderSliverEdgeInsetsPadding}
20 21 22
/// Applying padding in the main extent of the viewport to slivers that have scroll effects is likely to have
/// undesired effects. For example, For example, wrapping a [SliverPersistentHeader] with
/// `pinned:true` will cause only the appbar to stay pinned while the padding will scroll away.
23 24
/// {@endtemplate}
abstract class RenderSliverEdgeInsetsPadding extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
Ian Hickson's avatar
Ian Hickson committed
25
  /// The amount to pad the child in each dimension.
Ian Hickson's avatar
Ian Hickson committed
26
  ///
27 28
  /// The offsets are specified in terms of visual edges, left, top, right, and
  /// bottom. These values are not affected by the [TextDirection].
Ian Hickson's avatar
Ian Hickson committed
29
  ///
30
  /// Must not be null or contain negative values when [performLayout] is called.
31
  EdgeInsets? get resolvedPadding;
Ian Hickson's avatar
Ian Hickson committed
32 33 34 35 36 37 38 39 40

  /// 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);
41
    assert(resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
42 43
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
44
        return resolvedPadding!.bottom;
Ian Hickson's avatar
Ian Hickson committed
45
      case AxisDirection.right:
46
        return resolvedPadding!.left;
Ian Hickson's avatar
Ian Hickson committed
47
      case AxisDirection.down:
48
        return resolvedPadding!.top;
Ian Hickson's avatar
Ian Hickson committed
49
      case AxisDirection.left:
50
        return resolvedPadding!.right;
Ian Hickson's avatar
Ian Hickson committed
51 52 53 54 55 56 57 58 59 60 61
    }
  }

  /// 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);
62
    assert(resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
63 64
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
65
        return resolvedPadding!.top;
Ian Hickson's avatar
Ian Hickson committed
66
      case AxisDirection.right:
67
        return resolvedPadding!.right;
Ian Hickson's avatar
Ian Hickson committed
68
      case AxisDirection.down:
69
        return resolvedPadding!.bottom;
Ian Hickson's avatar
Ian Hickson committed
70
      case AxisDirection.left:
71
        return resolvedPadding!.left;
Ian Hickson's avatar
Ian Hickson committed
72 73 74
    }
  }

75 76 77
  /// 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
78 79 80 81 82 83
  ///
  /// 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);
84
    assert(resolvedPadding != null);
85
    return resolvedPadding!.along(constraints.axis);
Ian Hickson's avatar
Ian Hickson committed
86 87 88 89 90 91 92 93 94 95 96
  }

  /// 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);
97
    assert(resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
98 99
    switch (constraints.axis) {
      case Axis.horizontal:
100
        return resolvedPadding!.vertical;
Ian Hickson's avatar
Ian Hickson committed
101
      case Axis.vertical:
102
        return resolvedPadding!.horizontal;
Ian Hickson's avatar
Ian Hickson committed
103 104 105 106 107
    }
  }

  @override
  void setupParentData(RenderObject child) {
108
    if (child.parentData is! SliverPhysicalParentData) {
109
      child.parentData = SliverPhysicalParentData();
110
    }
Ian Hickson's avatar
Ian Hickson committed
111 112 113 114
  }

  @override
  void performLayout() {
115
    final SliverConstraints constraints = this.constraints;
116
    assert(resolvedPadding != null);
Ian Hickson's avatar
Ian Hickson committed
117 118 119 120 121
    final double beforePadding = this.beforePadding;
    final double afterPadding = this.afterPadding;
    final double mainAxisPadding = this.mainAxisPadding;
    final double crossAxisPadding = this.crossAxisPadding;
    if (child == null) {
122 123 124 125 126 127 128 129 130 131
      final double paintExtent = calculatePaintOffset(
          constraints,
          from: 0.0,
          to: mainAxisPadding,
      );
      final double cacheExtent = calculateCacheOffset(
          constraints,
          from: 0.0,
          to: mainAxisPadding,
      );
132
      geometry = SliverGeometry(
Ian Hickson's avatar
Ian Hickson committed
133
        scrollExtent: mainAxisPadding,
134
        paintExtent: math.min(paintExtent, constraints.remainingPaintExtent),
Ian Hickson's avatar
Ian Hickson committed
135
        maxPaintExtent: mainAxisPadding,
136
        cacheExtent: cacheExtent,
Ian Hickson's avatar
Ian Hickson committed
137 138 139
      );
      return;
    }
140 141 142 143 144 145 146 147 148
    final double beforePaddingPaintExtent = calculatePaintOffset(
      constraints,
      from: 0.0,
      to: beforePadding,
    );
    double overlap = constraints.overlap;
    if (overlap > 0) {
      overlap = math.max(0.0, constraints.overlap - beforePaddingPaintExtent);
    }
149
    child!.layout(
Ian Hickson's avatar
Ian Hickson committed
150 151
      constraints.copyWith(
        scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding),
152
        cacheOrigin: math.min(0.0, constraints.cacheOrigin + beforePadding),
153
        overlap: overlap,
Ian Hickson's avatar
Ian Hickson committed
154
        remainingPaintExtent: constraints.remainingPaintExtent - calculatePaintOffset(constraints, from: 0.0, to: beforePadding),
155
        remainingCacheExtent: constraints.remainingCacheExtent - calculateCacheOffset(constraints, from: 0.0, to: beforePadding),
156
        crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding),
157
        precedingScrollExtent: beforePadding + constraints.precedingScrollExtent,
Ian Hickson's avatar
Ian Hickson committed
158 159 160
      ),
      parentUsesSize: true,
    );
161
    final SliverGeometry childLayoutGeometry = child!.geometry!;
162
    if (childLayoutGeometry.scrollOffsetCorrection != null) {
163
      geometry = SliverGeometry(
164 165 166 167
        scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection,
      );
      return;
    }
Ian Hickson's avatar
Ian Hickson committed
168 169 170 171 172 173
    final double afterPaddingPaintExtent = calculatePaintOffset(
      constraints,
      from: beforePadding + childLayoutGeometry.scrollExtent,
      to: mainAxisPadding + childLayoutGeometry.scrollExtent,
    );
    final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent;
174 175 176 177 178 179 180 181 182 183 184
    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;
185 186 187 188
    final double paintExtent = math.min(
      beforePaddingPaintExtent + math.max(childLayoutGeometry.paintExtent, childLayoutGeometry.layoutExtent + afterPaddingPaintExtent),
      constraints.remainingPaintExtent,
    );
189
    geometry = SliverGeometry(
190
      paintOrigin: childLayoutGeometry.paintOrigin,
Ian Hickson's avatar
Ian Hickson committed
191
      scrollExtent: mainAxisPadding + childLayoutGeometry.scrollExtent,
192 193
      paintExtent: paintExtent,
      layoutExtent: math.min(mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent, paintExtent),
194
      cacheExtent: math.min(mainAxisPaddingCacheExtent + childLayoutGeometry.cacheExtent, constraints.remainingCacheExtent),
Ian Hickson's avatar
Ian Hickson committed
195 196 197 198 199
      maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent,
      hitTestExtent: math.max(
        mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent,
        beforePaddingPaintExtent + childLayoutGeometry.hitTestExtent,
      ),
200
      hasVisualOverflow: childLayoutGeometry.hasVisualOverflow,
Ian Hickson's avatar
Ian Hickson committed
201 202
    );

203
    final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
Ian Hickson's avatar
Ian Hickson committed
204 205 206 207
    assert(constraints.axisDirection != null);
    assert(constraints.growthDirection != null);
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
208
        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
209 210
        break;
      case AxisDirection.right:
211
        childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: 0.0, to: resolvedPadding!.left), resolvedPadding!.top);
Ian Hickson's avatar
Ian Hickson committed
212 213
        break;
      case AxisDirection.down:
214
        childParentData.paintOffset = Offset(resolvedPadding!.left, calculatePaintOffset(constraints, from: 0.0, to: resolvedPadding!.top));
Ian Hickson's avatar
Ian Hickson committed
215 216
        break;
      case AxisDirection.left:
217
        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
218 219 220 221 222 223 224 225 226 227
        break;
    }
    assert(childParentData.paintOffset != null);
    assert(beforePadding == this.beforePadding);
    assert(afterPadding == this.afterPadding);
    assert(mainAxisPadding == this.mainAxisPadding);
    assert(crossAxisPadding == this.crossAxisPadding);
  }

  @override
228 229
  bool hitTestChildren(SliverHitTestResult result, { required double mainAxisPosition, required double crossAxisPosition }) {
    if (child != null && child!.geometry!.hitTestExtent > 0.0) {
230
      final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
231 232 233
      result.addWithAxisOffset(
        mainAxisPosition: mainAxisPosition,
        crossAxisPosition: crossAxisPosition,
234 235
        mainAxisOffset: childMainAxisPosition(child!),
        crossAxisOffset: childCrossAxisPosition(child!),
236
        paintOffset: childParentData.paintOffset,
237
        hitTest: child!.hitTest,
238 239
      );
    }
Ian Hickson's avatar
Ian Hickson committed
240 241 242 243
    return false;
  }

  @override
244
  double childMainAxisPosition(RenderSliver child) {
Ian Hickson's avatar
Ian Hickson committed
245 246 247 248 249
    assert(child != null);
    assert(child == this.child);
    return calculatePaintOffset(constraints, from: 0.0, to: beforePadding);
  }

250 251 252 253
  @override
  double childCrossAxisPosition(RenderSliver child) {
    assert(child != null);
    assert(child == this.child);
254 255 256
    assert(constraints != null);
    assert(constraints.axisDirection != null);
    assert(constraints.growthDirection != null);
257
    assert(resolvedPadding != null);
258 259 260
    switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
      case AxisDirection.up:
      case AxisDirection.down:
261
        return resolvedPadding!.left;
262 263
      case AxisDirection.left:
      case AxisDirection.right:
264
        return resolvedPadding!.top;
265
    }
266 267
  }

268
  @override
269
  double? childScrollOffset(RenderObject child) {
270 271 272 273
    assert(child.parent == this);
    return beforePadding;
  }

Ian Hickson's avatar
Ian Hickson committed
274 275 276 277
  @override
  void applyPaintTransform(RenderObject child, Matrix4 transform) {
    assert(child != null);
    assert(child == this.child);
278
    final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
Ian Hickson's avatar
Ian Hickson committed
279 280 281 282 283
    childParentData.applyPaintTransform(transform);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
284
    if (child != null && child!.geometry!.visible) {
285
      final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
286
      context.paintChild(child!, offset + childParentData.paintOffset);
Ian Hickson's avatar
Ian Hickson committed
287 288 289 290 291 292 293 294
    }
  }

  @override
  void debugPaint(PaintingContext context, Offset offset) {
    super.debugPaint(context, offset);
    assert(() {
      if (debugPaintSizeEnabled) {
295
        final Size parentSize = getAbsoluteSize();
Ian Hickson's avatar
Ian Hickson committed
296
        final Rect outerRect = offset & parentSize;
297
        Rect? innerRect;
Ian Hickson's avatar
Ian Hickson committed
298
        if (child != null) {
299
          final Size childSize = child!.getAbsoluteSize();
300
          final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
Ian Hickson's avatar
Ian Hickson committed
301
          innerRect = (offset + childParentData.paintOffset) & childSize;
302 303 304 305
          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
306 307 308 309
        }
        debugPaintPadding(context.canvas, outerRect, innerRect);
      }
      return true;
310
    }());
Ian Hickson's avatar
Ian Hickson committed
311
  }
312 313 314 315 316 317 318 319
}

/// Insets 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.
///
320
/// {@macro flutter.rendering.RenderSliverEdgeInsetsPadding}
321 322 323 324 325
class RenderSliverPadding extends RenderSliverEdgeInsetsPadding {
  /// 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({
326 327 328
    required EdgeInsetsGeometry padding,
    TextDirection? textDirection,
    RenderSliver? child,
329 330 331 332 333 334 335 336
  }) : assert(padding != null),
       assert(padding.isNonNegative),
       _padding = padding,
       _textDirection = textDirection {
    this.child = child;
  }

  @override
337 338
  EdgeInsets? get resolvedPadding => _resolvedPadding;
  EdgeInsets? _resolvedPadding;
339 340

  void _resolve() {
341
    if (resolvedPadding != null) {
342
      return;
343
    }
344
    _resolvedPadding = padding.resolve(textDirection);
345
    assert(resolvedPadding!.isNonNegative);
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
  }

  void _markNeedsResolution() {
    _resolvedPadding = null;
    markNeedsLayout();
  }

  /// The amount to pad the child in each dimension.
  ///
  /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
  /// must not be null.
  EdgeInsetsGeometry get padding => _padding;
  EdgeInsetsGeometry _padding;
  set padding(EdgeInsetsGeometry value) {
    assert(value != null);
    assert(padding.isNonNegative);
362
    if (_padding == value) {
363
      return;
364
    }
365 366 367 368 369 370 371 372
    _padding = value;
    _markNeedsResolution();
  }

  /// The text direction with which to resolve [padding].
  ///
  /// This may be changed to null, but only after the [padding] has been changed
  /// to a value that does not depend on the direction.
373 374 375
  TextDirection? get textDirection => _textDirection;
  TextDirection? _textDirection;
  set textDirection(TextDirection? value) {
376
    if (_textDirection == value) {
377
      return;
378
    }
379 380 381 382 383 384 385 386 387
    _textDirection = value;
    _markNeedsResolution();
  }

  @override
  void performLayout() {
    _resolve();
    super.performLayout();
  }
388 389

  @override
390 391
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
392 393
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
394
  }
Ian Hickson's avatar
Ian Hickson committed
395
}