Commit 94284074 authored by Ian Hickson's avatar Ian Hickson

CustomPaint documentation

...followed all the way back to ImageCache, ImageInfo, ImageProvider,
and friends.
parent cae847dc
...@@ -678,7 +678,7 @@ abstract class CustomClipper<T> { ...@@ -678,7 +678,7 @@ abstract class CustomClipper<T> {
/// same size as the RenderObject (e.g. it's a rounded rectangle /// same size as the RenderObject (e.g. it's a rounded rectangle
/// with very small arcs in the corners), then this may be adequate. /// with very small arcs in the corners), then this may be adequate.
Rect getApproximateClipRect(Size size) => Point.origin & size; Rect getApproximateClipRect(Size size) => Point.origin & size;
/// Returns true if the new instance will result in a different clip /// Returns `true` if the new instance will result in a different clip
/// than the oldClipper instance. /// than the oldClipper instance.
bool shouldRepaint(CustomClipper<T> oldClipper); bool shouldRepaint(CustomClipper<T> oldClipper);
} }
...@@ -992,8 +992,8 @@ class RenderTransform extends RenderProxyBox { ...@@ -992,8 +992,8 @@ class RenderTransform extends RenderProxyBox {
markNeedsPaint(); markNeedsPaint();
} }
/// When set to true, hit tests are performed based on the position of the /// When set to `true`, hit tests are performed based on the position of the
/// child as it is painted. When set to false, hit tests are performed /// child as it is painted. When set to `false`, hit tests are performed
/// ignoring the transformation. /// ignoring the transformation.
/// ///
/// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(), /// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(),
...@@ -1139,8 +1139,8 @@ class RenderFractionalTranslation extends RenderProxyBox { ...@@ -1139,8 +1139,8 @@ class RenderFractionalTranslation extends RenderProxyBox {
markNeedsPaint(); markNeedsPaint();
} }
/// When set to true, hit tests are performed based on the position of the /// When set to `true`, hit tests are performed based on the position of the
/// child as it is painted. When set to false, hit tests are performed /// child as it is painted. When set to `false`, hit tests are performed
/// ignoring the transformation. /// ignoring the transformation.
/// ///
/// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(), /// applyPaintTransform(), and therefore localToGlobal() and globalToLocal(),
...@@ -1176,13 +1176,93 @@ class RenderFractionalTranslation extends RenderProxyBox { ...@@ -1176,13 +1176,93 @@ class RenderFractionalTranslation extends RenderProxyBox {
} }
} }
/// The interface used by [CustomPaint] (in the widgets library) and
/// [RenderCustomPaint] (in the rendering library).
///
/// To implement a custom painter, subclass this interface to define your custom
/// paint delegate. [CustomPaint] subclasses must implement the [paint] and
/// [shouldRepaint] methods, and may optionally also implement the [hitTest]
/// method.
///
/// The [paint] method is called whenever the custom object needs to be repainted.
///
/// The [shouldRepaint] method is called when a new instance of the class
/// is provided, to check if the new instance actually represents different
/// information.
///
/// The [hitTest] method is invoked when the user interacts with the underlying
/// render object, to determine if the user hit the object or missed it.
abstract class CustomPainter { abstract class CustomPainter {
/// Abstract const constructor. This constructor enables subclasses to provide /// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions. /// const constructors so that they can be used in const expressions.
const CustomPainter(); const CustomPainter();
/// Called whenever the object needs to paint. The given [Canvas] has its
/// coordinate space configured such that the origin is at the top left of the
/// box. The area of the box is the size of the [size] argument.
///
/// Paint operations should remain inside the given area. Graphical operations
/// outside the bounds may be silently ignored, clipped, or not clipped.
///
/// Implementations should be wary of correctly pairing any calls to
/// [Canvas.save]/[Canvas.saveLayer] and [Canvas.restore], otherwise all
/// subsequent painting on this canvas may be affected, with potentially
/// hilarious but confusing results.
///
/// To paint text on a [Canvas], use a [TextPainter].
///
/// To paint an image on a [Canvas]:
///
/// 1. Obtain an [ImageResource], for example by using the [ImageCache.load]
/// method on the [imageCache] singleton.
///
/// 2. Whenever the [ImageResource]'s underlying [ImageInfo] object changes
/// (see [ImageResource.addListener]), create a new instance of your custom
/// paint delegate, giving it the new [ImageInfo] object.
///
/// 3. In your delegate's [paint] method, call the [Canvas.drawImage],
/// [Canvas.drawImageRect], or [Canvas.drawImageNine] methods to paint the
/// [ImageInfo.image] object, applying the [ImageInfo.scale] value to
/// obtain the correct rendering size.
void paint(Canvas canvas, Size size); void paint(Canvas canvas, Size size);
/// Called whenever a new instance of the custom painter delegate class is
/// provided to the [RenderCustomPaint] object, or any time that a new
/// [CustomPaint] object is created with a new instance of the custom painter
/// delegate class (which amounts to the same thing, since the latter is
/// implemented in terms of the former).
///
/// If the new instance represents different information than the old
/// instance, then the method should return `true`, otherwise it should return
/// `false`.
///
/// If the method returns `false`, then the paint call might be optimized away.
///
/// It's possible that the [paint] method will get invoked even if
/// [shouldRepaint] returns `false` (e.g. if an ancestor or descendant needed to
/// be repainted). It's also possible that the [paint] method will get invoked
/// without [shouldRepaint] being called at all (e.g. if the box changes
/// size).
///
/// If a custom delegate has a particularly expensive paint function such that
/// repaints should be avoided as much as possible, a [RepaintBoundary] or
/// [RenderRepaintBoundary] (or other render object with [isRepaintBoundary]
/// set to `true`) might be helpful.
bool shouldRepaint(CustomPainter oldDelegate); bool shouldRepaint(CustomPainter oldDelegate);
/// Called whenever a hit test is being performed on an object that is using
/// this custom paint delegate.
///
/// The given point is relative to the same coordinate space as the last
/// [paint] call.
///
/// The default behavior is to consider all points to be hits for
/// background painters, and no points to be hits for foreground painters.
///
/// Return `true` if the given position corresponds to a point on the drawn
/// image that should be considered a "hit", `false` if it corresponds to a
/// point that should be considered outside the painted image, and null to use
/// the default behavior.
bool hitTest(Point position) => null; bool hitTest(Point position) => null;
} }
...@@ -1206,8 +1286,23 @@ class RenderCustomPaint extends RenderProxyBox { ...@@ -1206,8 +1286,23 @@ class RenderCustomPaint extends RenderProxyBox {
RenderBox child RenderBox child
}) : _painter = painter, _foregroundPainter = foregroundPainter, super(child); }) : _painter = painter, _foregroundPainter = foregroundPainter, super(child);
/// The background custom paint delegate.
///
/// This painter, if non-null, is invoked to paint behind the children.
CustomPainter get painter => _painter; CustomPainter get painter => _painter;
CustomPainter _painter; CustomPainter _painter;
/// Set a new background custom paint delegate.
///
/// If the new delegate is the same as the previous one, this does nothing.
///
/// If the new delegate is the same class as the previous one, then the new
/// delegate has its [CustomPainter.shouldRepaint] invoked; if the result is
/// `true`, then the delegate will be invoked.
///
/// If the new delegate is a different class than the previous one, then the
/// delegate will be invoked.
///
/// If the new value is null, then there is no background custom painter.
void set painter (CustomPainter newPainter) { void set painter (CustomPainter newPainter) {
if (_painter == newPainter) if (_painter == newPainter)
return; return;
...@@ -1216,8 +1311,23 @@ class RenderCustomPaint extends RenderProxyBox { ...@@ -1216,8 +1311,23 @@ class RenderCustomPaint extends RenderProxyBox {
_checkForRepaint(_painter, oldPainter); _checkForRepaint(_painter, oldPainter);
} }
/// The foreground custom paint delegate.
///
/// This painter, if non-null, is invoked to paint in front of the children.
CustomPainter get foregroundPainter => _foregroundPainter; CustomPainter get foregroundPainter => _foregroundPainter;
CustomPainter _foregroundPainter; CustomPainter _foregroundPainter;
/// Set a new foreground custom paint delegate.
///
/// If the new delegate is the same as the previous one, this does nothing.
///
/// If the new delegate is the same class as the previous one, then the new
/// delegate has its [CustomPainter.shouldRepaint] invoked; if the result is
/// `true`, then the delegate will be invoked.
///
/// If the new delegate is a different class than the previous one, then the
/// delegate will be invoked.
///
/// If the new value is null, then there is no foreground custom painter.
void set foregroundPainter (CustomPainter newPainter) { void set foregroundPainter (CustomPainter newPainter) {
if (_foregroundPainter == newPainter) if (_foregroundPainter == newPainter)
return; return;
...@@ -1470,12 +1580,12 @@ class RenderRepaintBoundary extends RenderProxyBox { ...@@ -1470,12 +1580,12 @@ class RenderRepaintBoundary extends RenderProxyBox {
/// Is invisible during hit testing. /// Is invisible during hit testing.
/// ///
/// When [ignoring] is true, this render object (and its subtree) is invisible /// When [ignoring] is `true`, this render object (and its subtree) is invisible
/// to hit testing. It still consumes space during layout and paints its child /// to hit testing. It still consumes space during layout and paints its child
/// as usual. It just cannot be the target of located events because it returns /// as usual. It just cannot be the target of located events because it returns
/// false from [hitTest]. /// `false` from [hitTest].
/// ///
/// When [ignoringSemantics] is true, the subtree will be invisible to /// When [ignoringSemantics] is `true`, the subtree will be invisible to
/// the semantics layer (and thus e.g. accessibility tools). If /// the semantics layer (and thus e.g. accessibility tools). If
/// [ignoringSemantics] is null, it uses the value of [ignoring]. /// [ignoringSemantics] is null, it uses the value of [ignoring].
class RenderIgnorePointer extends RenderProxyBox { class RenderIgnorePointer extends RenderProxyBox {
...@@ -1693,11 +1803,11 @@ class RenderSemanticAnnotations extends RenderProxyBox { ...@@ -1693,11 +1803,11 @@ class RenderSemanticAnnotations extends RenderProxyBox {
assert(container != null); assert(container != null);
} }
/// If 'container' is true, this RenderObject will introduce a new /// If 'container' is `true`, this RenderObject will introduce a new
/// node in the semantics tree. Otherwise, the semantics will be /// node in the semantics tree. Otherwise, the semantics will be
/// merged with the semantics of any ancestors. /// merged with the semantics of any ancestors.
/// ///
/// The 'container' flag is implicitly set to true on the immediate /// The 'container' flag is implicitly set to `true` on the immediate
/// semantics-providing descendants of a node where multiple /// semantics-providing descendants of a node where multiple
/// children have semantics or have descendants providing semantics. /// children have semantics or have descendants providing semantics.
/// In other words, the semantics of siblings are not merged. To /// In other words, the semantics of siblings are not merged. To
...@@ -1713,7 +1823,7 @@ class RenderSemanticAnnotations extends RenderProxyBox { ...@@ -1713,7 +1823,7 @@ class RenderSemanticAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate(); markNeedsSemanticsUpdate();
} }
/// If non-null, sets the "hasCheckedState" semantic to true and the /// If non-null, sets the "hasCheckedState" semantic to `true` and the
/// "isChecked" semantic to the given value. /// "isChecked" semantic to the given value.
bool get checked => _checked; bool get checked => _checked;
bool _checked; bool _checked;
......
...@@ -14,8 +14,28 @@ import 'image_resource.dart'; ...@@ -14,8 +14,28 @@ import 'image_resource.dart';
/// Implements a way to retrieve an image, for example by fetching it from the /// Implements a way to retrieve an image, for example by fetching it from the
/// network. Also used as a key in the image cache. /// network. Also used as a key in the image cache.
///
/// This is the interface implemented by objects that can be used as the
/// argument to [ImageCache.loadProvider].
///
/// The [ImageCache.load] function uses an [ImageProvider] that fetches images
/// described by URLs. One could create an [ImageProvider] that used a custom
/// protocol, e.g. a direct TCP connection to a remote host, or using a
/// screenshot API from the host platform; such an image provider would then
/// share the same cache as all the other image loading codepaths that used the
/// [imageCache].
abstract class ImageProvider { // ignore: one_member_abstracts abstract class ImageProvider { // ignore: one_member_abstracts
Future<ImageInfo> loadImage(); Future<ImageInfo> loadImage();
/// Subclasses must implement the `==` operator so that the image cache can
/// distinguish identical requests.
@override
bool operator ==(dynamic other);
/// Subclasses must implement the `hashCode` operator so that the image cache
/// can efficiently store the providers in a map.
@override
int get hashCode;
} }
class _UrlFetcher implements ImageProvider { class _UrlFetcher implements ImageProvider {
...@@ -51,24 +71,75 @@ class _UrlFetcher implements ImageProvider { ...@@ -51,24 +71,75 @@ class _UrlFetcher implements ImageProvider {
const int _kDefaultSize = 1000; const int _kDefaultSize = 1000;
/// Class for the [imageCache] object.
///
/// Implements a least-recently-used cache of up to 1000 images. The maximum
/// size can be adjusted using [maximumSize]. Images that are actively in use
/// (i.e. to which the application is holding references, either via
/// [ImageResource] objects, [ImageInfo] objects, or raw [ui.Image] objects) may
/// get evicted from the cache (and thus need to be refetched from the network
/// if they are referenced in the [load] method), but the raw bits are kept in
/// memory for as long as the application is using them.
///
/// The [load] method fetches the image with the given URL and scale.
///
/// For more complicated use cases, the [loadProvider] method can be used with a
/// custom [ImageProvider].
class ImageCache { class ImageCache {
ImageCache._(); ImageCache._();
final LruMap<ImageProvider, ImageResource> _cache = final LruMap<ImageProvider, ImageResource> _cache =
new LruMap<ImageProvider, ImageResource>(maximumSize: _kDefaultSize); new LruMap<ImageProvider, ImageResource>(maximumSize: _kDefaultSize);
/// Maximum number of entries to store in the cache.
///
/// Once this many entries have been cached, the least-recently-used entry is
/// evicted when adding a new entry.
int get maximumSize => _cache.maximumSize; int get maximumSize => _cache.maximumSize;
/// Changes the maximum cache size.
///
/// If the new size is smaller than the current number of elements, the
/// extraneous elements are evicted immediately. Setting this to zero and then
/// returning it to its original value will therefore immediately clear the
/// cache. However, doing this is not very efficient.
// (the quiver library does it one at a time rather than using clear())
void set maximumSize(int value) { _cache.maximumSize = value; } void set maximumSize(int value) { _cache.maximumSize = value; }
/// Calls the [ImageProvider.loadImage] method on the given image provider, if
/// necessary, and returns an [ImageResource] that encapsulates a [Future] for
/// the given image.
///
/// If the given [ImageProvider] has already been used and is still in the
/// cache, then the [ImageResource] object is immediately usable and the
/// provider is not invoked.
ImageResource loadProvider(ImageProvider provider) { ImageResource loadProvider(ImageProvider provider) {
return _cache.putIfAbsent(provider, () { return _cache.putIfAbsent(provider, () {
return new ImageResource(provider.loadImage()); return new ImageResource(provider.loadImage());
}); });
} }
/// Fetches the given URL, associating it with the given scale.
///
/// The return value is an [ImageResource], which encapsulates a [Future] for
/// the given image.
///
/// If the given URL has already been fetched for the given scale, and it is
/// still in the cache, then the [ImageResource] object is immediately usable.
ImageResource load(String url, { double scale: 1.0 }) { ImageResource load(String url, { double scale: 1.0 }) {
assert(url != null);
assert(scale != null);
return loadProvider(new _UrlFetcher(url, scale)); return loadProvider(new _UrlFetcher(url, scale));
} }
} }
/// The singleton that implements the Flutter framework's image cache.
///
/// The simplest use of this object is as follows:
///
/// ```dart
/// imageCache.load(myImageUrl).first.then(myImageHandler);
/// ```
///
/// ...where `myImageHandler` is a function with one argument, an [ImageInfo]
/// object.
final ImageCache imageCache = new ImageCache._(); final ImageCache imageCache = new ImageCache._();
...@@ -7,24 +7,55 @@ import 'dart:ui' as ui show Image; ...@@ -7,24 +7,55 @@ import 'dart:ui' as ui show Image;
import 'print.dart'; import 'print.dart';
/// A [ui.Image] object with its corresponding scale.
///
/// ImageInfo objects are used by [ImageResource] objects to represent the
/// actual data of the image once it has been obtained.
class ImageInfo { class ImageInfo {
ImageInfo({ this.image, this.scale: 1.0 }); /// Creates an [ImageInfo] object for the given image and scale.
///
/// Both the image and the scale must be non-null.
ImageInfo({ this.image, this.scale: 1.0 }) {
assert(image != null);
assert(scale != null);
}
/// The raw image pixels.
///
/// This is the object to pass to the [Canvas.drawImage],
/// [Canvas.drawImageRect], or [Canvas.drawImageNine] methods when painting
/// the image.
final ui.Image image; final ui.Image image;
/// The linear scale factor for drawing this image at its intended size.
///
/// The scale factor applies to the width and the height.
///
/// For example, if this is 2.0 it means that there are four image pixels for
/// every one logical pixel, and the image's actual width and height (as given
/// by the [ui.Image.width] and [ui.Image.height] properties) are double the
/// height and width that should be used when painting the image (e.g. in the
/// arguments given to [Canvas.drawImage]).
final double scale; final double scale;
@override @override
String toString() => '$image @ ${scale}x'; String toString() => '$image @ ${scale}x';
} }
/// A callback for when the image is available. /// Signature for callbacks reporting that an image is available.
///
/// Used by [ImageResource].
typedef void ImageListener(ImageInfo image); typedef void ImageListener(ImageInfo image);
/// A handle to an image resource /// A handle to an image resource.
///
/// ImageResource represents a handle to a [ui.Image] object and its scale
/// (together represented by an [ImageInfo] object). The underlying image object
/// might change over time, either because the image is animating or because the
/// underlying image resource was mutated.
/// ///
/// ImageResource represents a handle to a [ui.Image] object. The underlying /// ImageResource objects can also represent an image that hasn't finished
/// image object might change over time, either because the image is animating /// loading.
/// or because the underlying image resource was mutated.
class ImageResource { class ImageResource {
ImageResource(this._futureImage) { ImageResource(this._futureImage) {
_futureImage.then( _futureImage.then(
...@@ -40,15 +71,15 @@ class ImageResource { ...@@ -40,15 +71,15 @@ class ImageResource {
ImageInfo _image; ImageInfo _image;
final List<ImageListener> _listeners = new List<ImageListener>(); final List<ImageListener> _listeners = new List<ImageListener>();
/// The first concrete [ui.Image] object represented by this handle. /// The first concrete [ImageInfo] object represented by this handle.
/// ///
/// Instead of receivingly only the first image, most clients will want to /// Instead of receivingly only the first image, most clients will want to
/// [addListener] to be notified whenever a a concrete image is available. /// [addListener] to be notified whenever a a concrete image is available.
Future<ImageInfo> get first => _futureImage; Future<ImageInfo> get first => _futureImage;
/// Adds a listener callback that is called whenever a concrete [ui.Image] /// Adds a listener callback that is called whenever a concrete [ImageInfo]
/// object is available. Note: If a concrete image is available currently, /// object is available. If a concrete image is already available, this object
/// this object will call the listener synchronously. /// will call the listener synchronously.
void addListener(ImageListener listener) { void addListener(ImageListener listener) {
_listeners.add(listener); _listeners.add(listener);
if (_resolved) { if (_resolved) {
...@@ -60,7 +91,7 @@ class ImageResource { ...@@ -60,7 +91,7 @@ class ImageResource {
} }
} }
/// Stop listening for new concrete [ui.Image] objects. /// Stop listening for new concrete [ImageInfo] objects.
void removeListener(ImageListener listener) { void removeListener(ImageListener listener) {
_listeners.remove(listener); _listeners.remove(listener);
} }
......
...@@ -147,19 +147,25 @@ class DecoratedBox extends SingleChildRenderObjectWidget { ...@@ -147,19 +147,25 @@ class DecoratedBox extends SingleChildRenderObjectWidget {
} }
} }
/// Delegates its painting. /// Provides a canvas on which to draw during the paint phase.
/// ///
/// When asked to paint, custom paint first asks painter to paint with the /// When asked to paint, [CustomPaint] objects first ask their [painter] to
/// current canvas and then paints its children. After painting its children, /// paint on the current canvas, then they paint their children, and then, after
/// custom paint asks foregroundPainter to paint. The coodinate system of the /// painting their children, ask their [foregroundPainter] to paint. The
/// canvas matches the coordinate system of the custom paint object. The /// coodinate system of the canvas matches the coordinate system of the
/// painters are expected to paint within a rectangle starting at the origin /// [CustomPaint] object. The painters are expected to paint within a rectangle
/// and encompassing a region of the given size. If the painters paints outside /// starting at the origin and encompassing a region of the given size. (If the
/// those bounds, there might be insufficient memory allocated to rasterize the /// painters paints outside those bounds, there might be insufficient memory
/// painting commands and the resulting behavior is undefined. /// allocated to rasterize the painting commands and the resulting behavior is
/// undefined.)
/// ///
/// Because custom paint calls its painters during paint, you cannot dirty /// Painters are implemented by subclassing [CustomPainter].
/// layout or paint information during the callback. ///
/// Because custom paint calls its painters during paint, you cannot mark the
/// tree as needing a new layout during the callback (the layout for this frame
/// has already happened).
///
/// See: [CustomPainter], [Canvas].
class CustomPaint extends SingleChildRenderObjectWidget { class CustomPaint extends SingleChildRenderObjectWidget {
CustomPaint({ Key key, this.painter, this.foregroundPainter, Widget child }) CustomPaint({ Key key, this.painter, this.foregroundPainter, Widget child })
: super(key: key, child: child); : super(key: key, child: child);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment