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
5import 'object.dart';
6import 'proxy_box.dart';
7import 'proxy_sliver.dart';
8import 'sliver.dart';
9
10/// Paints a [Decoration] either before or after its child paints.
11///
12/// If the child has infinite scroll extent, then the [Decoration] paints itself up to the
13/// bottom cache extent.
14class RenderDecoratedSliver extends RenderProxySliver {
15 /// Creates a decorated sliver.
16 ///
17 /// The [decoration], [position], and [configuration] arguments must not be
18 /// null. By default the decoration paints behind the child.
19 ///
20 /// The [ImageConfiguration] will be passed to the decoration (with the size
21 /// filled in) to let it resolve images.
22 RenderDecoratedSliver({
23 required Decoration decoration,
24 DecorationPosition position = DecorationPosition.background,
25 ImageConfiguration configuration = ImageConfiguration.empty,
26 }) : _decoration = decoration,
27 _position = position,
28 _configuration = configuration;
29
30 /// What decoration to paint.
31 ///
32 /// Commonly a [BoxDecoration].
33 Decoration get decoration => _decoration;
34 Decoration _decoration;
35 set decoration(Decoration value) {
36 if (value == decoration) {
37 return;
38 }
39 _decoration = value;
40 _painter?.dispose();
41 _painter = decoration.createBoxPainter(markNeedsPaint);
42 markNeedsPaint();
43 }
44
45 /// Whether to paint the box decoration behind or in front of the child.
46 DecorationPosition get position => _position;
47 DecorationPosition _position;
48 set position(DecorationPosition value) {
49 if (value == position) {
50 return;
51 }
52 _position = value;
53 markNeedsPaint();
54 }
55
56 /// The settings to pass to the decoration when painting, so that it can
57 /// resolve images appropriately. See [ImageProvider.resolve] and
58 /// [BoxPainter.paint].
59 ///
60 /// The [ImageConfiguration.textDirection] field is also used by
61 /// direction-sensitive [Decoration]s for painting and hit-testing.
62 ImageConfiguration get configuration => _configuration;
63 ImageConfiguration _configuration;
64 set configuration(ImageConfiguration value) {
65 if (value == configuration) {
66 return;
67 }
68 _configuration = value;
69 markNeedsPaint();
70 }
71
72 BoxPainter? _painter;
73
74 @override
75 void attach(covariant PipelineOwner owner) {
76 _painter = decoration.createBoxPainter(markNeedsPaint);
77 super.attach(owner);
78 }
79
80 @override
81 void detach() {
82 _painter?.dispose();
83 _painter = null;
84 super.detach();
85 }
86
87 @override
88 void dispose() {
89 _painter?.dispose();
90 _painter = null;
91 super.dispose();
92 }
93
94 @override
95 void paint(PaintingContext context, Offset offset) {
96 if (child != null && child!.geometry!.visible) {
97 final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
98 final Size childSize;
99 final Offset scrollOffset;
100
101 // In the case where the child sliver has infinite scroll extent, the decoration
102 // should only extend down to the bottom cache extent.
103 final double cappedMainAxisExtent = child!.geometry!.scrollExtent.isInfinite
104 ? constraints.scrollOffset + child!.geometry!.cacheExtent + constraints.cacheOrigin
105 : child!.geometry!.scrollExtent;
106 switch (constraints.axis) {
107 case Axis.vertical:
108 childSize = Size(constraints.crossAxisExtent, cappedMainAxisExtent);
109 scrollOffset = Offset(0.0, -constraints.scrollOffset);
110 case Axis.horizontal:
111 childSize = Size(cappedMainAxisExtent, constraints.crossAxisExtent);
112 scrollOffset = Offset(-constraints.scrollOffset, 0.0);
113 }
114 final Offset childOffset = offset + childParentData.paintOffset;
115 if (position == DecorationPosition.background) {
116 _painter!.paint(context.canvas, childOffset + scrollOffset, configuration.copyWith(size: childSize));
117 }
118 context.paintChild(child!, childOffset);
119 if (position == DecorationPosition.foreground) {
120 _painter!.paint(context.canvas, childOffset + scrollOffset, configuration.copyWith(size: childSize));
121 }
122 }
123 }
124}
125