| // Copyright 2014 The Flutter 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 'package:flutter/foundation.dart'; |
| |
| import 'basic_types.dart'; |
| import 'edge_insets.dart'; |
| import 'image_provider.dart'; |
| |
| // This group of classes is intended for painting in cartesian coordinates. |
| |
| /// 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. |
| @immutable |
| abstract class Decoration with Diagnosticable { |
| /// Abstract const constructor. This constructor enables subclasses to provide |
| /// const constructors so that they can be used in const expressions. |
| const Decoration(); |
| |
| @override |
| String toStringShort() => objectRuntimeType(this, 'Decoration'); |
| |
| /// In debug mode, throws an exception if the object is not in a |
| /// valid configuration. Otherwise, returns true. |
| /// |
| /// This is intended to be used as follows: |
| /// ```dart |
| /// assert(myDecoration.debugAssertIsValid()); |
| /// ``` |
| bool debugAssertIsValid() => true; |
| |
| /// 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. |
| /// |
| /// 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. |
| /// |
| /// 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].) |
| EdgeInsetsGeometry? get padding => EdgeInsets.zero; |
| |
| /// Whether this decoration is complex enough to benefit from caching its painting. |
| bool get isComplex => false; |
| |
| /// Linearly interpolates from another [Decoration] (which may be of a |
| /// different class) to `this`. |
| /// |
| /// When implementing this method in subclasses, return null if this class |
| /// cannot interpolate from `a`. In that case, [lerp] will try `a`'s [lerpTo] |
| /// method instead. |
| /// |
| /// 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. |
| /// |
| /// 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]. |
| /// |
| /// Instead of calling this directly, use [Decoration.lerp]. |
| @protected |
| Decoration? lerpFrom(Decoration? a, double t) => null; |
| |
| /// Linearly interpolates from `this` to another [Decoration] (which may be of |
| /// a different class). |
| /// |
| /// This is called if `b`'s [lerpTo] did not know how to handle this class. |
| /// |
| /// When implementing this method in subclasses, return null if this class |
| /// cannot interpolate from `b`. In that case, [lerp] will apply a default |
| /// behavior instead. |
| /// |
| /// 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. |
| /// |
| /// 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]. |
| /// |
| /// Instead of calling this directly, use [Decoration.lerp]. |
| @protected |
| Decoration? lerpTo(Decoration? b, double t) => null; |
| |
| /// Linearly interpolates between two [Decoration]s. |
| /// |
| /// This attempts to use [lerpFrom] and [lerpTo] on `b` and `a` |
| /// 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`). |
| /// |
| /// {@macro dart.ui.shadow.lerp} |
| static Decoration? lerp(Decoration? a, Decoration? b, double t) { |
| assert(t != null); |
| if (a == null && b == null) |
| return null; |
| if (a == null) |
| return b!.lerpFrom(null, t) ?? b; |
| if (b == null) |
| return a.lerpTo(null, t) ?? a; |
| if (t == 0.0) |
| return a; |
| if (t == 1.0) |
| return b; |
| 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)); |
| } |
| |
| /// 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. |
| /// |
| /// 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 |
| /// may be omitted or set to null. |
| /// |
| /// 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). |
| bool hitTest(Size size, Offset position, { TextDirection? textDirection }) => true; |
| |
| /// Returns a [BoxPainter] that will paint this decoration. |
| /// |
| /// The `onChanged` argument configures [BoxPainter.onChanged]. It can be |
| /// omitted if there is no chance that the painter will change (for example, |
| /// if it is a [BoxDecoration] with definitely no [DecorationImage]). |
| @factory |
| BoxPainter createBoxPainter([ VoidCallback onChanged ]); |
| |
| /// Returns a closed [Path] that describes the outer edge of this decoration. |
| /// |
| /// 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.'); |
| } |
| } |
| |
| /// A stateful class that can paint a particular [Decoration]. |
| /// |
| /// [BoxPainter] objects can cache resources so that they can be used |
| /// multiple times. |
| /// |
| /// 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. |
| const BoxPainter([this.onChanged]); |
| |
| /// Paints the [Decoration] for which this object was created on the |
| /// given canvas using the given configuration. |
| /// |
| /// The [ImageConfiguration] object passed as the third argument must, at a |
| /// minimum, have a non-null [Size]. |
| /// |
| /// 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. |
| /// |
| /// 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. |
| /// |
| /// 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`. |
| /// |
| /// 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]. |
| 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. |
| final VoidCallback? onChanged; |
| |
| /// Discard any resources being held by the object. |
| /// |
| /// The [onChanged] callback will not be invoked after this method has been |
| /// called. |
| @mustCallSuper |
| void dispose() { } |
| } |