Commit 998a066a authored by Adam Barth's avatar Adam Barth

Add a centerSlice parameter to images

This lets you draw nine-patch images.
parent 7c5092f7
// Copyright 2015 The Chromium 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/painting.dart';
import 'package:flutter/material.dart';
void main() {
runApp(new NetworkImage(
src: "http://38.media.tumblr.com/avatar_497c78dc767d_128.png",
fit: ImageFit.contain,
centerSlice: new Rect.fromLTRB(40.0, 40.0, 88.0, 88.0)
));
}
......@@ -10,43 +10,47 @@ import 'package:flutter/services.dart';
import 'shadows.dart';
/// An immutable set of offsets in each of the four cardinal directions
/// An immutable set of offsets in each of the four cardinal directions.
///
/// Typically used for an offset from each of the four sides of a box. For
/// example, the padding inside a box can be represented using this class.
class EdgeDims {
/// Constructs an EdgeDims from offsets from the top, right, bottom and left
/// Constructs an EdgeDims from offsets from the top, right, bottom and left.
const EdgeDims.TRBL(this.top, this.right, this.bottom, this.left);
/// Constructs an EdgeDims where all the offsets are value
/// Constructs an EdgeDims where all the offsets are value.
const EdgeDims.all(double value)
: top = value, right = value, bottom = value, left = value;
/// Constructs an EdgeDims with only the given values non-zero
/// Constructs an EdgeDims with only the given values non-zero.
const EdgeDims.only({ this.top: 0.0,
this.right: 0.0,
this.bottom: 0.0,
this.left: 0.0 });
/// Constructs an EdgeDims with symmetrical vertical and horizontal offsets
/// Constructs an EdgeDims with symmetrical vertical and horizontal offsets.
const EdgeDims.symmetric({ double vertical: 0.0,
double horizontal: 0.0 })
: top = vertical, left = horizontal, bottom = vertical, right = horizontal;
/// The offset from the top
/// The offset from the top.
final double top;
/// The offset from the right
/// The offset from the right.
final double right;
/// The offset from the bottom
/// The offset from the bottom.
final double bottom;
/// The offset from the left
/// The offset from the left.
final double left;
/// Whether every dimension is non-negative.
bool get isNonNegative => top >= 0.0 && right >= 0.0 && bottom >= 0.0 && left >= 0.0;
/// The size that this edge dims would occupy with an empty interior.
Size get collapsedSize => new Size(left + right, top + bottom);
EdgeDims operator-(EdgeDims other) {
return new EdgeDims.TRBL(
top - other.top,
......@@ -101,7 +105,7 @@ class EdgeDims {
);
}
/// Linearly interpolate between two EdgeDims
/// Linearly interpolate between two EdgeDims.
///
/// If either is null, this function interpolates from [EdgeDims.zero].
static EdgeDims lerp(EdgeDims a, EdgeDims b, double t) {
......@@ -119,7 +123,7 @@ class EdgeDims {
);
}
/// An EdgeDims with zero offsets in each direction
/// An EdgeDims with zero offsets in each direction.
static const EdgeDims zero = const EdgeDims.TRBL(0.0, 0.0, 0.0, 0.0);
bool operator ==(dynamic other) {
......@@ -416,88 +420,121 @@ void paintImage({
Rect rect,
ui.Image image,
ui.ColorFilter colorFilter,
fit: ImageFit.scaleDown,
ImageFit fit,
repeat: ImageRepeat.noRepeat,
Rect centerSlice,
double positionX: 0.5,
double positionY: 0.5
}) {
Size bounds = rect.size;
Size imageSize = new Size(image.width.toDouble(), image.height.toDouble());
Size outputSize = rect.size;
Size inputSize = new Size(image.width.toDouble(), image.height.toDouble());
Offset sliceBorder;
if (centerSlice != null) {
sliceBorder = new Offset(
centerSlice.left + inputSize.width - centerSlice.right,
centerSlice.top + inputSize.height - centerSlice.bottom
);
outputSize -= sliceBorder;
inputSize -= sliceBorder;
}
Size sourceSize;
Size destinationSize;
switch(fit) {
fit ??= centerSlice == null ? ImageFit.scaleDown : ImageFit.fill;
assert(centerSlice == null || (fit != ImageFit.none && fit != ImageFit.cover));
switch (fit) {
case ImageFit.fill:
sourceSize = imageSize;
destinationSize = bounds;
sourceSize = inputSize;
destinationSize = outputSize;
break;
case ImageFit.contain:
sourceSize = imageSize;
if (bounds.width / bounds.height > sourceSize.width / sourceSize.height)
destinationSize = new Size(sourceSize.width * bounds.height / sourceSize.height, bounds.height);
sourceSize = inputSize;
if (outputSize.width / outputSize.height > sourceSize.width / sourceSize.height)
destinationSize = new Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
else
destinationSize = new Size(bounds.width, sourceSize.height * bounds.width / sourceSize.width);
destinationSize = new Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
break;
case ImageFit.cover:
if (bounds.width / bounds.height > imageSize.width / imageSize.height)
sourceSize = new Size(imageSize.width, imageSize.width * bounds.height / bounds.width);
if (outputSize.width / outputSize.height > inputSize.width / inputSize.height)
sourceSize = new Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
else
sourceSize = new Size(imageSize.height * bounds.width / bounds.height, imageSize.height);
destinationSize = bounds;
sourceSize = new Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
destinationSize = outputSize;
break;
case ImageFit.none:
sourceSize = new Size(math.min(imageSize.width, bounds.width),
math.min(imageSize.height, bounds.height));
sourceSize = new Size(math.min(inputSize.width, outputSize.width),
math.min(inputSize.height, outputSize.height));
destinationSize = sourceSize;
break;
case ImageFit.scaleDown:
sourceSize = imageSize;
destinationSize = bounds;
sourceSize = inputSize;
destinationSize = outputSize;
if (sourceSize.height > destinationSize.height)
destinationSize = new Size(sourceSize.width * destinationSize.height / sourceSize.height, sourceSize.height);
if (sourceSize.width > destinationSize.width)
destinationSize = new Size(destinationSize.width, sourceSize.height * destinationSize.width / sourceSize.width);
break;
}
if (centerSlice != null) {
outputSize += sliceBorder;
destinationSize += sliceBorder;
// We don't have the ability to draw a subset of the image at the same time
// as we apply a nine-patch stretch.
assert(sourceSize == inputSize);
}
// TODO(abarth): Implement |repeat|.
Paint paint = new Paint()..isAntiAlias = false;
if (colorFilter != null)
paint.colorFilter = colorFilter;
double dx = (bounds.width - destinationSize.width) * positionX;
double dy = (bounds.height - destinationSize.height) * positionY;
double dx = (outputSize.width - destinationSize.width) * positionX;
double dy = (outputSize.height - destinationSize.height) * positionY;
Point destinationPosition = rect.topLeft + new Offset(dx, dy);
canvas.drawImageRect(image, Point.origin & sourceSize, destinationPosition & destinationSize, paint);
Rect destinationRect = destinationPosition & destinationSize;
if (centerSlice == null)
canvas.drawImageRect(image, Point.origin & sourceSize, destinationRect, paint);
else
canvas.drawImageNine(image, centerSlice, destinationRect, paint);
}
typedef void BackgroundImageChangeListener();
/// A background image for a box
/// A background image for a box.
class BackgroundImage {
/// How the background image should be inscribed into the box
/// How the background image should be inscribed into the box.
final ImageFit fit;
/// How to paint any portions of the box not covered by the background image
/// How to paint any portions of the box not covered by the background image.
final ImageRepeat repeat;
/// A color filter to apply to the background image before painting it
/// The center slice for a nine-patch image.
///
/// The region of the image inside the center slice will be stretched both
/// horizontally and vertically to fit the image into its destination. The
/// region of the image above and below the center slice will be stretched
/// only horizontally and the region of the image to the left and right of
/// the center slice will be stretched only vertically.
final Rect centerSlice;
/// A color filter to apply to the background image before painting it.
final ui.ColorFilter colorFilter;
BackgroundImage({
ImageResource image,
this.fit: ImageFit.scaleDown,
this.fit,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice,
this.colorFilter
}) : _imageResource = image;
ui.Image _image;
/// The image to be painted into the background
/// The image to be painted into the background.
ui.Image get image => _image;
ui.Image _image;
ImageResource _imageResource;
final List<BackgroundImageChangeListener> _listeners =
new List<BackgroundImageChangeListener>();
/// Call listener when the background images changes (e.g., arrives from the network)
/// Call listener when the background images changes (e.g., arrives from the network).
void addChangeListener(BackgroundImageChangeListener listener) {
// We add the listener to the _imageResource first so that the first change
// listener doesn't get callback synchronously if the image resource is
......@@ -507,7 +544,7 @@ class BackgroundImage {
_listeners.add(listener);
}
/// No longer call listener when the background image changes
/// No longer call listener when the background image changes.
void removeChangeListener(BackgroundImageChangeListener listener) {
_listeners.remove(listener);
// We need to remove ourselves as listeners from the _imageResource so that
......
......@@ -9,7 +9,7 @@ import 'package:flutter/painting.dart';
import 'box.dart';
import 'object.dart';
/// An image in the render tree
/// An image in the render tree.
///
/// The render image attempts to find a size for itself that fits in the given
/// constraints and preserves the image's intrinisc aspect ratio.
......@@ -19,18 +19,20 @@ class RenderImage extends RenderBox {
double width,
double height,
ui.ColorFilter colorFilter,
fit: ImageFit.scaleDown,
repeat: ImageRepeat.noRepeat
ImageFit fit,
repeat: ImageRepeat.noRepeat,
Rect centerSlice
}) : _image = image,
_width = width,
_height = height,
_colorFilter = colorFilter,
_fit = fit,
_repeat = repeat;
_repeat = repeat,
_centerSlice = centerSlice;
ui.Image _image;
/// The image to display
/// The image to display.
ui.Image get image => _image;
ui.Image _image;
void set image (ui.Image value) {
if (value == _image)
return;
......@@ -40,9 +42,9 @@ class RenderImage extends RenderBox {
markNeedsLayout();
}
double _width;
/// If non-null, requires the image to have this width
/// If non-null, requires the image to have this width.
double get width => _width;
double _width;
void set width (double value) {
if (value == _width)
return;
......@@ -50,9 +52,9 @@ class RenderImage extends RenderBox {
markNeedsLayout();
}
double _height;
/// If non-null, requires the image to have this height
/// If non-null, requires the image to have this height.
double get height => _height;
double _height;
void set height (double value) {
if (value == _height)
return;
......@@ -60,9 +62,9 @@ class RenderImage extends RenderBox {
markNeedsLayout();
}
ui.ColorFilter _colorFilter;
/// If non-null, apply this color filter to the image before painint.
ui.ColorFilter get colorFilter => _colorFilter;
ui.ColorFilter _colorFilter;
void set colorFilter (ui.ColorFilter value) {
if (value == _colorFilter)
return;
......@@ -70,9 +72,9 @@ class RenderImage extends RenderBox {
markNeedsPaint();
}
ImageFit _fit;
/// How to inscribe the image into the place allocated during layout
/// How to inscribe the image into the place allocated during layout.
ImageFit get fit => _fit;
ImageFit _fit;
void set fit (ImageFit value) {
if (value == _fit)
return;
......@@ -80,9 +82,9 @@ class RenderImage extends RenderBox {
markNeedsPaint();
}
ImageRepeat _repeat;
/// Not yet implemented
/// Not yet implemented.
ImageRepeat get repeat => _repeat;
ImageRepeat _repeat;
void set repeat (ImageRepeat value) {
if (value == _repeat)
return;
......@@ -90,7 +92,23 @@ class RenderImage extends RenderBox {
markNeedsPaint();
}
/// Find a size for the render image within the given constraints
/// The center slice for a nine-patch image.
///
/// The region of the image inside the center slice will be stretched both
/// horizontally and vertically to fit the image into its destination. The
/// region of the image above and below the center slice will be stretched
/// only horizontally and the region of the image to the left and right of
/// the center slice will be stretched only vertically.
Rect get centerSlice => _centerSlice;
Rect _centerSlice;
void set centerSlice (Rect value) {
if (value == _centerSlice)
return;
_centerSlice = value;
markNeedsPaint();
}
/// Find a size for the render image within the given constraints.
///
/// - The dimensions of the RenderImage must fit within the constraints.
/// - The aspect ratio of the RenderImage matches the instrinsic aspect
......@@ -170,6 +188,7 @@ class RenderImage extends RenderBox {
image: _image,
colorFilter: _colorFilter,
fit: _fit,
centerSlice: _centerSlice,
repeat: _repeat
);
}
......
......@@ -809,8 +809,9 @@ class Image extends LeafRenderObjectWidget {
this.width,
this.height,
this.colorFilter,
this.fit: ImageFit.scaleDown,
this.repeat: ImageRepeat.noRepeat
this.fit,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice
}) : super(key: key);
final ui.Image image;
......@@ -819,6 +820,7 @@ class Image extends LeafRenderObjectWidget {
final ui.ColorFilter colorFilter;
final ImageFit fit;
final ImageRepeat repeat;
final Rect centerSlice;
RenderImage createRenderObject() => new RenderImage(
image: image,
......@@ -826,7 +828,8 @@ class Image extends LeafRenderObjectWidget {
height: height,
colorFilter: colorFilter,
fit: fit,
repeat: repeat);
repeat: repeat,
centerSlice: centerSlice);
void updateRenderObject(RenderImage renderObject, Image oldWidget) {
renderObject.image = image;
......@@ -835,6 +838,7 @@ class Image extends LeafRenderObjectWidget {
renderObject.colorFilter = colorFilter;
renderObject.fit = fit;
renderObject.repeat = repeat;
renderObject.centerSlice = centerSlice;
}
}
......@@ -845,8 +849,9 @@ class ImageListener extends StatefulComponent {
this.width,
this.height,
this.colorFilter,
this.fit: ImageFit.scaleDown,
this.repeat: ImageRepeat.noRepeat
this.fit,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice
}) : super(key: key) {
assert(image != null);
}
......@@ -857,6 +862,7 @@ class ImageListener extends StatefulComponent {
final ui.ColorFilter colorFilter;
final ImageFit fit;
final ImageRepeat repeat;
final Rect centerSlice;
_ImageListenerState createState() => new _ImageListenerState();
}
......@@ -894,7 +900,8 @@ class _ImageListenerState extends State<ImageListener> {
height: config.height,
colorFilter: config.colorFilter,
fit: config.fit,
repeat: config.repeat
repeat: config.repeat,
centerSlice: config.centerSlice
);
}
}
......@@ -906,8 +913,9 @@ class NetworkImage extends StatelessComponent {
this.width,
this.height,
this.colorFilter,
this.fit: ImageFit.scaleDown,
this.repeat: ImageRepeat.noRepeat
this.fit,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice
}) : super(key: key);
final String src;
......@@ -916,6 +924,7 @@ class NetworkImage extends StatelessComponent {
final ui.ColorFilter colorFilter;
final ImageFit fit;
final ImageRepeat repeat;
final Rect centerSlice;
Widget build(BuildContext context) {
return new ImageListener(
......@@ -924,7 +933,8 @@ class NetworkImage extends StatelessComponent {
height: height,
colorFilter: colorFilter,
fit: fit,
repeat: repeat
repeat: repeat,
centerSlice: centerSlice
);
}
}
......@@ -937,8 +947,9 @@ class AssetImage extends StatelessComponent {
this.width,
this.height,
this.colorFilter,
this.fit: ImageFit.scaleDown,
this.repeat: ImageRepeat.noRepeat
this.fit,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice
}) : super(key: key);
final String name;
......@@ -948,6 +959,7 @@ class AssetImage extends StatelessComponent {
final ui.ColorFilter colorFilter;
final ImageFit fit;
final ImageRepeat repeat;
final Rect centerSlice;
Widget build(BuildContext context) {
return new ImageListener(
......@@ -956,7 +968,8 @@ class AssetImage extends StatelessComponent {
height: height,
colorFilter: colorFilter,
fit: fit,
repeat: repeat
repeat: repeat,
centerSlice: centerSlice
);
}
}
......
part of skysprites;
/// Labels are used to display a string of text in a the node tree. To align
/// the label, the textAlign property of teh [TextStyle] can be set.
/// the label, the textAlign property of the [TextStyle] can be set.
class Label extends Node {
/// Creates a new Label with the provided [_text] and [_textStyle].
Label(this._text, [this._textStyle]) {
......
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