decoration.dart 10.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/foundation.dart';
6

7
import 'basic_types.dart';
8
import 'edge_insets.dart';
9
import 'image_provider.dart';
10

11 12 13
// Examples can assume:
// late Decoration myDecoration;

14 15
// This group of classes is intended for painting in cartesian coordinates.

16 17 18 19 20 21 22 23 24
/// A description of a box decoration (a decoration applied to a [Rect]).
///
/// This class presents the abstract interface for all decorations.
/// See [BoxDecoration] for a concrete example.
///
/// To actually paint a [Decoration], use the [createBoxPainter]
/// method to obtain a [BoxPainter]. [Decoration] objects can be
/// shared between boxes; [BoxPainter] objects can cache resources to
/// make painting on a particular surface faster.
25
@immutable
26
abstract class Decoration with Diagnosticable {
27 28
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
29
  const Decoration();
30

31
  @override
32
  String toStringShort() => objectRuntimeType(this, 'Decoration');
33

34
  /// In debug mode, throws an exception if the object is not in a
35 36 37 38
  /// valid configuration. Otherwise, returns true.
  ///
  /// This is intended to be used as follows:
  /// ```dart
39
  /// assert(myDecoration.debugAssertIsValid());
40
  /// ```
41
  bool debugAssertIsValid() => true;
42 43 44 45 46 47

  /// Returns the insets to apply when using this decoration on a box
  /// that has contents, so that the contents do not overlap the edges
  /// of the decoration. For example, if the decoration draws a frame
  /// around its edge, the padding would return the distance by which
  /// to inset the children so as to not overlap the frame.
48 49 50 51 52 53 54 55 56
  ///
  /// This only works for decorations that have absolute sizes. If the padding
  /// needed would change based on the size at which the decoration is drawn,
  /// then this will return incorrect padding values.
  ///
  /// For example, when a [BoxDecoration] has [BoxShape.circle], the padding
  /// does not take into account that the circle is drawn in the center of the
  /// box regardless of the ratio of the box; it does not provide the extra
  /// padding that is implied by changing the ratio.
Ian Hickson's avatar
Ian Hickson committed
57 58 59 60 61
  ///
  /// The value returned by this getter must be resolved (using
  /// [EdgeInsetsGeometry.resolve] to obtain an absolute [EdgeInsets]. (For
  /// example, [BorderDirectional] will return an [EdgeInsetsDirectional] for
  /// its [padding].)
62
  EdgeInsetsGeometry get padding => EdgeInsets.zero;
63

64 65 66
  /// Whether this decoration is complex enough to benefit from caching its painting.
  bool get isComplex => false;

67 68
  /// Linearly interpolates from another [Decoration] (which may be of a
  /// different class) to `this`.
69 70 71
  ///
  /// When implementing this method in subclasses, return null if this class
  /// cannot interpolate from `a`. In that case, [lerp] will try `a`'s [lerpTo]
72
  /// method instead. Classes should implement both [lerpFrom] and [lerpTo].
73 74 75 76 77
  ///
  /// Supporting interpolating from null is recommended as the [Decoration.lerp]
  /// method uses this as a fallback when two classes can't interpolate between
  /// each other.
  ///
78 79 80 81 82 83 84 85 86 87 88 89 90
  /// The `t` argument represents position on the timeline, with 0.0 meaning
  /// that the interpolation has not started, returning `a` (or something
  /// equivalent to `a`), 1.0 meaning that the interpolation has finished,
  /// returning `this` (or something equivalent to `this`), and values in
  /// between meaning that the interpolation is at the relevant point on the
  /// timeline between `a` and `this`. The interpolation can be extrapolated
  /// beyond 0.0 and 1.0, so negative values and values greater than 1.0 are
  /// valid (and can easily be generated by curves such as
  /// [Curves.elasticInOut]).
  ///
  /// Values for `t` are usually obtained from an [Animation<double>], such as
  /// an [AnimationController].
  ///
91 92
  /// Instead of calling this directly, use [Decoration.lerp].
  @protected
93
  Decoration? lerpFrom(Decoration? a, double t) => null;
94

95 96
  /// Linearly interpolates from `this` to another [Decoration] (which may be of
  /// a different class).
97
  ///
98
  /// This is called if `b`'s [lerpFrom] did not know how to handle this class.
99 100 101
  ///
  /// When implementing this method in subclasses, return null if this class
  /// cannot interpolate from `b`. In that case, [lerp] will apply a default
102
  /// behavior instead. Classes should implement both [lerpFrom] and [lerpTo].
103 104 105 106 107
  ///
  /// Supporting interpolating to null is recommended as the [Decoration.lerp]
  /// method uses this as a fallback when two classes can't interpolate between
  /// each other.
  ///
108 109 110 111 112 113 114 115 116 117 118 119
  /// The `t` argument represents position on the timeline, with 0.0 meaning
  /// that the interpolation has not started, returning `this` (or something
  /// equivalent to `this`), 1.0 meaning that the interpolation has finished,
  /// returning `b` (or something equivalent to `b`), and values in between
  /// meaning that the interpolation is at the relevant point on the timeline
  /// between `this` and `b`. The interpolation can be extrapolated beyond 0.0
  /// and 1.0, so negative values and values greater than 1.0 are valid (and can
  /// easily be generated by curves such as [Curves.elasticInOut]).
  ///
  /// Values for `t` are usually obtained from an [Animation<double>], such as
  /// an [AnimationController].
  ///
120 121
  /// Instead of calling this directly, use [Decoration.lerp].
  @protected
122
  Decoration? lerpTo(Decoration? b, double t) => null;
123

124
  /// Linearly interpolates between two [Decoration]s.
125
  ///
126
  /// This attempts to use [lerpFrom] and [lerpTo] on `b` and `a`
127 128 129
  /// respectively to find a solution. If the two values can't directly be
  /// interpolated, then the interpolation is done via null (at `t == 0.5`).
  ///
130
  /// {@macro dart.ui.shadow.lerp}
131
  static Decoration? lerp(Decoration? a, Decoration? b, double t) {
132 133
    if (identical(a, b)) {
      return a;
134 135
    }
    if (a == null) {
136
      return b!.lerpFrom(null, t) ?? b;
137 138
    }
    if (b == null) {
139
      return a.lerpTo(null, t) ?? a;
140 141
    }
    if (t == 0.0) {
142
      return a;
143 144
    }
    if (t == 1.0) {
145
      return b;
146
    }
147 148 149
    return b.lerpFrom(a, t)
        ?? a.lerpTo(b, t)
        ?? (t < 0.5 ? (a.lerpTo(null, t * 2.0) ?? a) : (b.lerpFrom(null, (t - 0.5) * 2.0) ?? b));
150 151 152 153 154 155 156
  }

  /// Tests whether the given point, on a rectangle of a given size,
  /// would be considered to hit the decoration or not. For example,
  /// if the decoration only draws a circle, this function might
  /// return true if the point was inside the circle and false
  /// otherwise.
157 158 159 160
  ///
  /// The decoration may be sensitive to the [TextDirection]. The
  /// `textDirection` argument should therefore be provided. If it is known that
  /// the decoration is not affected by the text direction, then the argument
161
  /// may be omitted or set to null.
162 163 164 165 166
  ///
  /// When a [Decoration] is painted in a [Container] or [DecoratedBox] (which
  /// is what [Container] uses), the `textDirection` parameter will be populated
  /// based on the ambient [Directionality] (by way of the [RenderDecoratedBox]
  /// renderer).
167
  bool hitTest(Size size, Offset position, { TextDirection? textDirection }) => true;
168 169

  /// Returns a [BoxPainter] that will paint this decoration.
170 171 172
  ///
  /// The `onChanged` argument configures [BoxPainter.onChanged]. It can be
  /// omitted if there is no chance that the painter will change (for example,
173
  /// if it is a [BoxDecoration] with definitely no [DecorationImage]).
174
  @factory
175
  BoxPainter createBoxPainter([ VoidCallback onChanged ]);
176 177

  /// Returns a closed [Path] that describes the outer edge of this decoration.
178 179 180 181 182 183 184 185 186 187 188 189
  ///
  /// The default implementation throws. Subclasses must override this implementation
  /// to describe the clip path that should be applied to the decoration when it is
  /// used in a [Container] with an explicit [Clip] behavior.
  ///
  /// See also:
  ///
  ///  * [Container.clipBehavior], which, if set, uses this method to determine
  ///    the clip path to use.
  Path getClipPath(Rect rect, TextDirection textDirection) {
    throw UnsupportedError('${objectRuntimeType(this, 'This Decoration subclass')} does not expect to be used for clipping.');
  }
190 191
}

192 193 194 195
/// A stateful class that can paint a particular [Decoration].
///
/// [BoxPainter] objects can cache resources so that they can be used
/// multiple times.
196 197 198 199 200 201 202
///
/// Some resources used by [BoxPainter] may load asynchronously. When this
/// happens, the [onChanged] callback will be invoked. To stop this callback
/// from being called after the painter has been discarded, call [dispose].
abstract class BoxPainter {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
203
  const BoxPainter([this.onChanged]);
204 205

  /// Paints the [Decoration] for which this object was created on the
206 207 208 209
  /// given canvas using the given configuration.
  ///
  /// The [ImageConfiguration] object passed as the third argument must, at a
  /// minimum, have a non-null [Size].
210
  ///
211 212 213 214 215
  /// If this object caches resources for painting (e.g. [Paint] objects), the
  /// cache may be flushed when [paint] is called with a new configuration. For
  /// this reason, it may be more efficient to call
  /// [Decoration.createBoxPainter] for each different rectangle that is being
  /// painted in a particular frame.
216 217 218 219 220 221 222
  ///
  /// For example, if a decoration's owner wants to paint a particular
  /// decoration once for its whole size, and once just in the bottom
  /// right, it might get two [BoxPainter] instances, one for each.
  /// However, when its size changes, it could continue using those
  /// same instances, since the previous resources would no longer be
  /// relevant and thus losing them would not be an issue.
223 224 225 226
  ///
  /// Implementations should paint their decorations on the canvas in a
  /// rectangle whose top left corner is at the given `offset` and whose size is
  /// given by `configuration.size`.
227 228 229 230
  ///
  /// When a [Decoration] is painted in a [Container] or [DecoratedBox] (which
  /// is what [Container] uses), the [ImageConfiguration.textDirection] property
  /// will be populated based on the ambient [Directionality].
231 232 233 234 235 236 237 238
  void paint(Canvas canvas, Offset offset, ImageConfiguration configuration);

  /// Callback that is invoked if an asynchronously-loading resource used by the
  /// decoration finishes loading. For example, an image. When this is invoked,
  /// the [paint] method should be called again.
  ///
  /// Resources might not start to load until after [paint] has been called,
  /// because they might depend on the configuration.
239
  final VoidCallback? onChanged;
240

241 242 243 244
  /// Discard any resources being held by the object.
  ///
  /// The [onChanged] callback will not be invoked after this method has been
  /// called.
245
  @mustCallSuper
246
  void dispose() { }
247
}