blob: d561269bccfec0711644a4b6e69f83092193906c [file] [log] [blame]
// 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';
// Examples can assume:
// late Decoration myDecoration;
// 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() { }
}