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/// @docImport 'package:flutter/widgets.dart';
6library;
7
8import 'dart:math' as math;
9
10import 'package:vector_math/vector_math_64.dart';
11
12import 'debug.dart';
13import 'object.dart';
14import 'sliver.dart';
15
16/// Insets a [RenderSliver] by applying [resolvedPadding] on each side.
17///
18/// A [RenderSliverEdgeInsetsPadding] subclass wraps the [SliverGeometry.layoutExtent]
19/// of its child. Any incoming [SliverConstraints.overlap] is ignored and not
20/// passed on to the child.
21///
22/// {@template flutter.rendering.RenderSliverEdgeInsetsPadding}
23/// Applying padding in the main extent of the viewport to slivers that have scroll effects is likely to have
24/// undesired effects. For example, wrapping a [SliverPersistentHeader] with
25/// `pinned:true` will cause only the appbar to stay pinned while the padding will scroll away.
26/// {@endtemplate}
27abstract class RenderSliverEdgeInsetsPadding extends RenderSliver
28 with RenderObjectWithChildMixin<RenderSliver> {
29 /// The amount to pad the child in each dimension.
30 ///
31 /// The offsets are specified in terms of visual edges, left, top, right, and
32 /// bottom. These values are not affected by the [TextDirection].
33 ///
34 /// Must not be null or contain negative values when [performLayout] is called.
35 EdgeInsets? get resolvedPadding;
36
37 /// The padding in the scroll direction on the side nearest the 0.0 scroll direction.
38 ///
39 /// Only valid after layout has started, since before layout the render object
40 /// doesn't know what direction it will be laid out in.
41 double get beforePadding {
42 assert(resolvedPadding != null);
43 return switch (applyGrowthDirectionToAxisDirection(
44 constraints.axisDirection,
45 constraints.growthDirection,
46 )) {
47 AxisDirection.up => resolvedPadding!.bottom,
48 AxisDirection.right => resolvedPadding!.left,
49 AxisDirection.down => resolvedPadding!.top,
50 AxisDirection.left => resolvedPadding!.right,
51 };
52 }
53
54 /// The padding in the scroll direction on the side furthest from the 0.0 scroll offset.
55 ///
56 /// Only valid after layout has started, since before layout the render object
57 /// doesn't know what direction it will be laid out in.
58 double get afterPadding {
59 assert(resolvedPadding != null);
60 return switch (applyGrowthDirectionToAxisDirection(
61 constraints.axisDirection,
62 constraints.growthDirection,
63 )) {
64 AxisDirection.up => resolvedPadding!.top,
65 AxisDirection.right => resolvedPadding!.right,
66 AxisDirection.down => resolvedPadding!.bottom,
67 AxisDirection.left => resolvedPadding!.left,
68 };
69 }
70
71 /// The total padding in the [SliverConstraints.axisDirection]. (In other
72 /// words, for a vertical downwards-growing list, the sum of the padding on
73 /// the top and bottom.)
74 ///
75 /// Only valid after layout has started, since before layout the render object
76 /// doesn't know what direction it will be laid out in.
77 double get mainAxisPadding {
78 assert(resolvedPadding != null);
79 return resolvedPadding!.along(constraints.axis);
80 }
81
82 /// The total padding in the cross-axis direction. (In other words, for a
83 /// vertical downwards-growing list, the sum of the padding on the left and
84 /// right.)
85 ///
86 /// Only valid after layout has started, since before layout the render object
87 /// doesn't know what direction it will be laid out in.
88 double get crossAxisPadding {
89 assert(resolvedPadding != null);
90 return switch (constraints.axis) {
91 Axis.horizontal => resolvedPadding!.vertical,
92 Axis.vertical => resolvedPadding!.horizontal,
93 };
94 }
95
96 @override
97 void setupParentData(RenderObject child) {
98 if (child.parentData is! SliverPhysicalParentData) {
99 child.parentData = SliverPhysicalParentData();
100 }
101 }
102
103 @override
104 void performLayout() {
105 final SliverConstraints constraints = this.constraints;
106 double paintOffset({required double from, required double to}) =>
107 calculatePaintOffset(constraints, from: from, to: to);
108 double cacheOffset({required double from, required double to}) =>
109 calculateCacheOffset(constraints, from: from, to: to);
110
111 assert(this.resolvedPadding != null);
112 final EdgeInsets resolvedPadding = this.resolvedPadding!;
113 final double beforePadding = this.beforePadding;
114 final double afterPadding = this.afterPadding;
115 final double mainAxisPadding = this.mainAxisPadding;
116 final double crossAxisPadding = this.crossAxisPadding;
117 if (child == null) {
118 final double paintExtent = paintOffset(from: 0.0, to: mainAxisPadding);
119 final double cacheExtent = cacheOffset(from: 0.0, to: mainAxisPadding);
120 geometry = SliverGeometry(
121 scrollExtent: mainAxisPadding,
122 paintExtent: math.min(paintExtent, constraints.remainingPaintExtent),
123 maxPaintExtent: mainAxisPadding,
124 cacheExtent: cacheExtent,
125 );
126 return;
127 }
128 final double beforePaddingPaintExtent = paintOffset(from: 0.0, to: beforePadding);
129 double overlap = constraints.overlap;
130 if (overlap > 0) {
131 overlap = math.max(0.0, constraints.overlap - beforePaddingPaintExtent);
132 }
133 child!.layout(
134 constraints.copyWith(
135 scrollOffset: math.max(0.0, constraints.scrollOffset - beforePadding),
136 cacheOrigin: math.min(0.0, constraints.cacheOrigin + beforePadding),
137 overlap: overlap,
138 remainingPaintExtent:
139 constraints.remainingPaintExtent - paintOffset(from: 0.0, to: beforePadding),
140 remainingCacheExtent:
141 constraints.remainingCacheExtent - cacheOffset(from: 0.0, to: beforePadding),
142 crossAxisExtent: math.max(0.0, constraints.crossAxisExtent - crossAxisPadding),
143 precedingScrollExtent: beforePadding + constraints.precedingScrollExtent,
144 ),
145 parentUsesSize: true,
146 );
147 final SliverGeometry childLayoutGeometry = child!.geometry!;
148 if (childLayoutGeometry.scrollOffsetCorrection != null) {
149 geometry = SliverGeometry(scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection);
150 return;
151 }
152 final double scrollExtent = childLayoutGeometry.scrollExtent;
153 final double beforePaddingCacheExtent = cacheOffset(from: 0.0, to: beforePadding);
154 final double afterPaddingCacheExtent = cacheOffset(
155 from: beforePadding + scrollExtent,
156 to: mainAxisPadding + scrollExtent,
157 );
158 final double afterPaddingPaintExtent = paintOffset(
159 from: beforePadding + scrollExtent,
160 to: mainAxisPadding + scrollExtent,
161 );
162 final double mainAxisPaddingCacheExtent = beforePaddingCacheExtent + afterPaddingCacheExtent;
163 final double mainAxisPaddingPaintExtent = beforePaddingPaintExtent + afterPaddingPaintExtent;
164 final double paintExtent = math.min(
165 beforePaddingPaintExtent +
166 math.max(
167 childLayoutGeometry.paintExtent,
168 childLayoutGeometry.layoutExtent + afterPaddingPaintExtent,
169 ),
170 constraints.remainingPaintExtent,
171 );
172 geometry = SliverGeometry(
173 paintOrigin: childLayoutGeometry.paintOrigin,
174 scrollExtent: mainAxisPadding + scrollExtent,
175 paintExtent: paintExtent,
176 layoutExtent: math.min(
177 mainAxisPaddingPaintExtent + childLayoutGeometry.layoutExtent,
178 paintExtent,
179 ),
180 cacheExtent: math.min(
181 mainAxisPaddingCacheExtent + childLayoutGeometry.cacheExtent,
182 constraints.remainingCacheExtent,
183 ),
184 maxPaintExtent: mainAxisPadding + childLayoutGeometry.maxPaintExtent,
185 hitTestExtent: math.max(
186 mainAxisPaddingPaintExtent + childLayoutGeometry.paintExtent,
187 beforePaddingPaintExtent + childLayoutGeometry.hitTestExtent,
188 ),
189 hasVisualOverflow: childLayoutGeometry.hasVisualOverflow,
190 );
191 final double calculatedOffset = switch (applyGrowthDirectionToAxisDirection(
192 constraints.axisDirection,
193 constraints.growthDirection,
194 )) {
195 AxisDirection.up => paintOffset(
196 from: resolvedPadding.bottom + scrollExtent,
197 to: resolvedPadding.vertical + scrollExtent,
198 ),
199 AxisDirection.left => paintOffset(
200 from: resolvedPadding.right + scrollExtent,
201 to: resolvedPadding.horizontal + scrollExtent,
202 ),
203 AxisDirection.right => paintOffset(from: 0.0, to: resolvedPadding.left),
204 AxisDirection.down => paintOffset(from: 0.0, to: resolvedPadding.top),
205 };
206 final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
207 childParentData.paintOffset = switch (constraints.axis) {
208 Axis.horizontal => Offset(calculatedOffset, resolvedPadding.top),
209 Axis.vertical => Offset(resolvedPadding.left, calculatedOffset),
210 };
211 assert(beforePadding == this.beforePadding);
212 assert(afterPadding == this.afterPadding);
213 assert(mainAxisPadding == this.mainAxisPadding);
214 assert(crossAxisPadding == this.crossAxisPadding);
215 }
216
217 @override
218 bool hitTestChildren(
219 SliverHitTestResult result, {
220 required double mainAxisPosition,
221 required double crossAxisPosition,
222 }) {
223 if (child != null && child!.geometry!.hitTestExtent > 0.0) {
224 final SliverPhysicalParentData childParentData =
225 child!.parentData! as SliverPhysicalParentData;
226 return result.addWithAxisOffset(
227 mainAxisPosition: mainAxisPosition,
228 crossAxisPosition: crossAxisPosition,
229 mainAxisOffset: childMainAxisPosition(child!),
230 crossAxisOffset: childCrossAxisPosition(child!),
231 paintOffset: childParentData.paintOffset,
232 hitTest: child!.hitTest,
233 );
234 }
235 return false;
236 }
237
238 @override
239 double childMainAxisPosition(RenderSliver child) {
240 assert(child == this.child);
241 return calculatePaintOffset(constraints, from: 0.0, to: beforePadding);
242 }
243
244 @override
245 double childCrossAxisPosition(RenderSliver child) {
246 assert(child == this.child);
247 assert(resolvedPadding != null);
248 return switch (constraints.axis) {
249 Axis.horizontal => resolvedPadding!.top,
250 Axis.vertical => resolvedPadding!.left,
251 };
252 }
253
254 @override
255 double? childScrollOffset(RenderObject child) {
256 assert(child.parent == this);
257 return beforePadding;
258 }
259
260 @override
261 void applyPaintTransform(RenderObject child, Matrix4 transform) {
262 assert(child == this.child);
263 final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
264 childParentData.applyPaintTransform(transform);
265 }
266
267 @override
268 void paint(PaintingContext context, Offset offset) {
269 if (child != null && child!.geometry!.visible) {
270 final SliverPhysicalParentData childParentData =
271 child!.parentData! as SliverPhysicalParentData;
272 context.paintChild(child!, offset + childParentData.paintOffset);
273 }
274 }
275
276 @override
277 void debugPaint(PaintingContext context, Offset offset) {
278 super.debugPaint(context, offset);
279 assert(() {
280 if (debugPaintSizeEnabled) {
281 final Size parentSize = getAbsoluteSize();
282 final Rect outerRect = offset & parentSize;
283 Rect? innerRect;
284 if (child != null) {
285 final Size childSize = child!.getAbsoluteSize();
286 final SliverPhysicalParentData childParentData =
287 child!.parentData! as SliverPhysicalParentData;
288 innerRect = (offset + childParentData.paintOffset) & childSize;
289 assert(innerRect.top >= outerRect.top);
290 assert(innerRect.left >= outerRect.left);
291 assert(innerRect.right <= outerRect.right);
292 assert(innerRect.bottom <= outerRect.bottom);
293 }
294 debugPaintPadding(context.canvas, outerRect, innerRect);
295 }
296 return true;
297 }());
298 }
299}
300
301/// Insets a [RenderSliver], applying padding on each side.
302///
303/// A [RenderSliverPadding] object wraps the [SliverGeometry.layoutExtent] of
304/// its child. Any incoming [SliverConstraints.overlap] is ignored and not
305/// passed on to the child.
306///
307/// {@macro flutter.rendering.RenderSliverEdgeInsetsPadding}
308class RenderSliverPadding extends RenderSliverEdgeInsetsPadding {
309 /// Creates a render object that insets its child in a viewport.
310 ///
311 /// The [padding] argument must have non-negative insets.
312 RenderSliverPadding({
313 required EdgeInsetsGeometry padding,
314 TextDirection? textDirection,
315 RenderSliver? child,
316 }) : assert(padding.isNonNegative),
317 _padding = padding,
318 _textDirection = textDirection {
319 this.child = child;
320 }
321
322 @override
323 EdgeInsets? get resolvedPadding => _resolvedPadding;
324 EdgeInsets? _resolvedPadding;
325
326 void _resolve() {
327 if (resolvedPadding != null) {
328 return;
329 }
330 _resolvedPadding = padding.resolve(textDirection);
331 assert(resolvedPadding!.isNonNegative);
332 }
333
334 void _markNeedsResolution() {
335 _resolvedPadding = null;
336 markNeedsLayout();
337 }
338
339 /// The amount to pad the child in each dimension.
340 ///
341 /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
342 /// must not be null.
343 EdgeInsetsGeometry get padding => _padding;
344 EdgeInsetsGeometry _padding;
345 set padding(EdgeInsetsGeometry value) {
346 assert(padding.isNonNegative);
347 if (_padding == value) {
348 return;
349 }
350 _padding = value;
351 _markNeedsResolution();
352 }
353
354 /// The text direction with which to resolve [padding].
355 ///
356 /// This may be changed to null, but only after the [padding] has been changed
357 /// to a value that does not depend on the direction.
358 TextDirection? get textDirection => _textDirection;
359 TextDirection? _textDirection;
360 set textDirection(TextDirection? value) {
361 if (_textDirection == value) {
362 return;
363 }
364 _textDirection = value;
365 _markNeedsResolution();
366 }
367
368 @override
369 void performLayout() {
370 _resolve();
371 super.performLayout();
372 }
373
374 @override
375 void debugFillProperties(DiagnosticPropertiesBuilder properties) {
376 super.debugFillProperties(properties);
377 properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
378 properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
379 }
380}
381

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com