Commit 2afa87df authored by Ian Hickson's avatar Ian Hickson

Make BoxDecoration replaceable.

Factor out a reusable interface called Decoration from BoxDecoration.

Make all the consumers of BoxDecoration and the erstwhile BoxPainter
into consumers of Decoration.

Make a BoxPainter be something you get from a Decoration, rather than
something to which you pass a BoxDecoration.

Rename Shape to BoxShape now that it's documented specifically as
applying to boxes.

Move EdgeDims to its own file.

Move FractionalOffset up so that it's with the other helper classes in
its file rather than alone at the end.

Minor change to RenderClipOval's hit testing to avoid taking an
unnecessary square root.

Rename BoxDecorationPosition to DecorationPosition since
RenderDecoratedBox now takes any Decoration.

Implement hit testing for rounded rects.

Rename AnimatedBoxDecorationValue to AnimatedDecorationValue, and make
it support lerping across any Decoration (by deferring to the objects
involved).
parent 0fe72b17
...@@ -50,7 +50,7 @@ class Dot extends StatelessComponent { ...@@ -50,7 +50,7 @@ class Dot extends StatelessComponent {
height: size, height: size,
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: color, backgroundColor: color,
shape: Shape.circle shape: BoxShape.circle
), ),
child: child child: child
); );
......
...@@ -14,7 +14,7 @@ class Circle extends StatelessComponent { ...@@ -14,7 +14,7 @@ class Circle extends StatelessComponent {
width: 50.0, width: 50.0,
margin: margin + new EdgeDims.symmetric(horizontal: 2.0), margin: margin + new EdgeDims.symmetric(horizontal: 2.0),
decoration: new BoxDecoration( decoration: new BoxDecoration(
shape: Shape.circle, shape: BoxShape.circle,
backgroundColor: const Color(0xFF00FF00) backgroundColor: const Color(0xFF00FF00)
) )
); );
......
...@@ -15,6 +15,8 @@ library painting; ...@@ -15,6 +15,8 @@ library painting;
export 'src/painting/basic_types.dart'; export 'src/painting/basic_types.dart';
export 'src/painting/box_painter.dart'; export 'src/painting/box_painter.dart';
export 'src/painting/colors.dart'; export 'src/painting/colors.dart';
export 'src/painting/decoration.dart';
export 'src/painting/edge_dims.dart';
export 'src/painting/shadows.dart'; export 'src/painting/shadows.dart';
export 'src/painting/text_painter.dart'; export 'src/painting/text_painter.dart';
export 'src/painting/text_style.dart'; export 'src/painting/text_style.dart';
......
...@@ -35,7 +35,7 @@ class CircleAvatar extends StatelessComponent { ...@@ -35,7 +35,7 @@ class CircleAvatar extends StatelessComponent {
duration: kThemeChangeDuration, duration: kThemeChangeDuration,
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: color, backgroundColor: color,
shape: Shape.circle shape: BoxShape.circle
), ),
width: 40.0, width: 40.0,
height: 40.0, height: 40.0,
......
...@@ -226,7 +226,7 @@ class DayPicker extends StatelessComponent { ...@@ -226,7 +226,7 @@ class DayPicker extends StatelessComponent {
selectedDate.day == day) selectedDate.day == day)
decoration = new BoxDecoration( decoration = new BoxDecoration(
backgroundColor: theme.primarySwatch[100], backgroundColor: theme.primarySwatch[100],
shape: Shape.circle shape: BoxShape.circle
); );
// Use a different font color for the current day // Use a different font color for the current day
...@@ -377,7 +377,7 @@ class _YearPickerState extends ScrollableWidgetListState<YearPicker> { ...@@ -377,7 +377,7 @@ class _YearPickerState extends ScrollableWidgetListState<YearPicker> {
height: config.itemExtent, height: config.itemExtent,
decoration: year == config.selectedDate.year ? new BoxDecoration( decoration: year == config.selectedDate.year ? new BoxDecoration(
backgroundColor: Theme.of(context).primarySwatch[100], backgroundColor: Theme.of(context).primarySwatch[100],
shape: Shape.circle shape: BoxShape.circle
) : null, ) : null,
child: new Center( child: new Center(
child: new Text(label, style: style) child: new Text(label, style: style)
......
...@@ -37,11 +37,11 @@ class _DropDownMenuPainter extends CustomPainter { ...@@ -37,11 +37,11 @@ class _DropDownMenuPainter extends CustomPainter {
final RenderBox renderBox; final RenderBox renderBox;
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final BoxPainter painter = new BoxPainter(new BoxDecoration( final BoxPainter painter = new BoxDecoration(
backgroundColor: color, backgroundColor: color,
borderRadius: 2.0, borderRadius: 2.0,
boxShadow: elevationToShadow[elevation] boxShadow: elevationToShadow[elevation]
)); ).createBoxPainter();
double top = renderBox.globalToLocal(new Point(0.0, menuTop)).y; double top = renderBox.globalToLocal(new Point(0.0, menuTop)).y;
double bottom = renderBox.globalToLocal(new Point(0.0, menuBottom)).y; double bottom = renderBox.globalToLocal(new Point(0.0, menuBottom)).y;
......
...@@ -18,7 +18,7 @@ class InkResponse extends StatefulComponent { ...@@ -18,7 +18,7 @@ class InkResponse extends StatefulComponent {
this.onLongPress, this.onLongPress,
this.onHighlightChanged, this.onHighlightChanged,
this.containedInWell: false, this.containedInWell: false,
this.highlightShape: Shape.circle this.highlightShape: BoxShape.circle
}) : super(key: key); }) : super(key: key);
final Widget child; final Widget child;
...@@ -27,7 +27,7 @@ class InkResponse extends StatefulComponent { ...@@ -27,7 +27,7 @@ class InkResponse extends StatefulComponent {
final GestureLongPressCallback onLongPress; final GestureLongPressCallback onLongPress;
final ValueChanged<bool> onHighlightChanged; final ValueChanged<bool> onHighlightChanged;
final bool containedInWell; final bool containedInWell;
final Shape highlightShape; final BoxShape highlightShape;
_InkResponseState createState() => new _InkResponseState<InkResponse>(); _InkResponseState createState() => new _InkResponseState<InkResponse>();
} }
...@@ -170,6 +170,6 @@ class InkWell extends InkResponse { ...@@ -170,6 +170,6 @@ class InkWell extends InkResponse {
onLongPress: onLongPress, onLongPress: onLongPress,
onHighlightChanged: onHighlightChanged, onHighlightChanged: onHighlightChanged,
containedInWell: true, containedInWell: true,
highlightShape: Shape.rectangle highlightShape: BoxShape.rectangle
); );
} }
...@@ -65,7 +65,7 @@ abstract class MaterialInkController { ...@@ -65,7 +65,7 @@ abstract class MaterialInkController {
InkSplash splashAt({ RenderBox referenceBox, Point position, Color color, bool containedInWell, VoidCallback onRemoved }); InkSplash splashAt({ RenderBox referenceBox, Point position, Color color, bool containedInWell, VoidCallback onRemoved });
/// Begin a highlight, coincident with the referenceBox. /// Begin a highlight, coincident with the referenceBox.
InkHighlight highlightAt({ RenderBox referenceBox, Color color, Shape shape: Shape.rectangle, VoidCallback onRemoved }); InkHighlight highlightAt({ RenderBox referenceBox, Color color, BoxShape shape: BoxShape.rectangle, VoidCallback onRemoved });
/// Add an arbitrary InkFeature to this InkController. /// Add an arbitrary InkFeature to this InkController.
void addInkFeature(InkFeature feature); void addInkFeature(InkFeature feature);
...@@ -153,7 +153,7 @@ class _MaterialState extends State<Material> { ...@@ -153,7 +153,7 @@ class _MaterialState extends State<Material> {
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
borderRadius: kMaterialEdges[config.type], borderRadius: kMaterialEdges[config.type],
boxShadow: config.elevation == 0 ? null : elevationToShadow[config.elevation], boxShadow: config.elevation == 0 ? null : elevationToShadow[config.elevation],
shape: config.type == MaterialType.circle ? Shape.circle : Shape.rectangle shape: config.type == MaterialType.circle ? BoxShape.circle : BoxShape.rectangle
), ),
child: contents child: contents
); );
...@@ -217,7 +217,7 @@ class RenderInkFeatures extends RenderProxyBox implements MaterialInkController ...@@ -217,7 +217,7 @@ class RenderInkFeatures extends RenderProxyBox implements MaterialInkController
InkHighlight highlightAt({ InkHighlight highlightAt({
RenderBox referenceBox, RenderBox referenceBox,
Color color, Color color,
Shape shape: Shape.rectangle, BoxShape shape: BoxShape.rectangle,
VoidCallback onRemoved VoidCallback onRemoved
}) { }) {
_InkHighlight highlight = new _InkHighlight( _InkHighlight highlight = new _InkHighlight(
...@@ -423,7 +423,7 @@ class _InkHighlight extends InkFeature implements InkHighlight { ...@@ -423,7 +423,7 @@ class _InkHighlight extends InkFeature implements InkHighlight {
renderer.markNeedsPaint(); renderer.markNeedsPaint();
} }
final Shape shape; final BoxShape shape;
bool get active => _active; bool get active => _active;
bool _active = true; bool _active = true;
...@@ -452,7 +452,7 @@ class _InkHighlight extends InkFeature implements InkHighlight { ...@@ -452,7 +452,7 @@ class _InkHighlight extends InkFeature implements InkHighlight {
} }
void _paintHighlight(Canvas canvas, Rect rect, paint) { void _paintHighlight(Canvas canvas, Rect rect, paint) {
if (shape == Shape.rectangle) if (shape == BoxShape.rectangle)
canvas.drawRect(rect, paint); canvas.drawRect(rect, paint);
else else
canvas.drawCircle(rect.center, _kDefaultSplashRadius, paint); canvas.drawCircle(rect.center, _kDefaultSplashRadius, paint);
......
...@@ -170,7 +170,8 @@ class _RenderSwitch extends RenderToggleable { ...@@ -170,7 +170,8 @@ class _RenderSwitch extends RenderToggleable {
super.handleEvent(event, entry); super.handleEvent(event, entry);
} }
final BoxPainter _thumbPainter = new BoxPainter(const BoxDecoration()); Color _cachedThumbColor;
BoxPainter _thumbPainter;
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas; final Canvas canvas = context.canvas;
...@@ -200,11 +201,14 @@ class _RenderSwitch extends RenderToggleable { ...@@ -200,11 +201,14 @@ class _RenderSwitch extends RenderToggleable {
paintRadialReaction(canvas, thumbOffset); paintRadialReaction(canvas, thumbOffset);
_thumbPainter.decoration = new BoxDecoration( if (_cachedThumbColor != thumbColor) {
_thumbPainter = new BoxDecoration(
backgroundColor: thumbColor, backgroundColor: thumbColor,
shape: Shape.circle, shape: BoxShape.circle,
boxShadow: elevationToShadow[1] boxShadow: elevationToShadow[1]
); ).createBoxPainter();
_cachedThumbColor = thumbColor;
}
// The thumb contracts slightly during the animation // The thumb contracts slightly during the animation
double inset = 2.0 - (position.value - 0.5).abs() * 2.0; double inset = 2.0 - (position.value - 0.5).abs() * 2.0;
......
...@@ -8,150 +8,10 @@ import 'dart:ui' as ui; ...@@ -8,150 +8,10 @@ import 'dart:ui' as ui;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'basic_types.dart'; import 'basic_types.dart';
import 'decoration.dart';
import 'edge_dims.dart';
/// An immutable set of offsets in each of the four cardinal directions. export 'edge_dims.dart' show EdgeDims;
///
/// 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.
const EdgeDims.TRBL(this.top, this.right, this.bottom, this.left);
/// 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.
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.
const EdgeDims.symmetric({ double vertical: 0.0,
double horizontal: 0.0 })
: top = vertical, left = horizontal, bottom = vertical, right = horizontal;
/// The offset from the top.
final double top;
/// The offset from the right.
final double right;
/// The offset from the bottom.
final double bottom;
/// 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);
Rect inflateRect(Rect rect) {
return new Rect.fromLTRB(rect.left - left, rect.top - top, rect.right + right, rect.bottom + bottom);
}
EdgeDims operator -(EdgeDims other) {
return new EdgeDims.TRBL(
top - other.top,
right - other.right,
bottom - other.bottom,
left - other.left
);
}
EdgeDims operator +(EdgeDims other) {
return new EdgeDims.TRBL(
top + other.top,
right + other.right,
bottom + other.bottom,
left + other.left
);
}
EdgeDims operator *(double other) {
return new EdgeDims.TRBL(
top * other,
right * other,
bottom * other,
left * other
);
}
EdgeDims operator /(double other) {
return new EdgeDims.TRBL(
top / other,
right / other,
bottom / other,
left / other
);
}
EdgeDims operator ~/(double other) {
return new EdgeDims.TRBL(
(top ~/ other).toDouble(),
(right ~/ other).toDouble(),
(bottom ~/ other).toDouble(),
(left ~/ other).toDouble()
);
}
EdgeDims operator %(double other) {
return new EdgeDims.TRBL(
top % other,
right % other,
bottom % other,
left % other
);
}
/// Linearly interpolate between two EdgeDims.
///
/// If either is null, this function interpolates from [EdgeDims.zero].
static EdgeDims lerp(EdgeDims a, EdgeDims b, double t) {
if (a == null && b == null)
return null;
if (a == null)
return b * t;
if (b == null)
return a * (1.0 - t);
return new EdgeDims.TRBL(
ui.lerpDouble(a.top, b.top, t),
ui.lerpDouble(a.right, b.right, t),
ui.lerpDouble(a.bottom, b.bottom, t),
ui.lerpDouble(a.left, b.left, t)
);
}
/// 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) {
if (identical(this, other))
return true;
if (other is! EdgeDims)
return false;
final EdgeDims typedOther = other;
return top == typedOther.top &&
right == typedOther.right &&
bottom == typedOther.bottom &&
left == typedOther.left;
}
int get hashCode {
int value = 373;
value = 37 * value + top.hashCode;
value = 37 * value + left.hashCode;
value = 37 * value + bottom.hashCode;
value = 37 * value + right.hashCode;
return value;
}
String toString() => "EdgeDims($top, $right, $bottom, $left)";
}
/// A side of a border of a box /// A side of a border of a box
class BorderSide { class BorderSide {
...@@ -602,7 +462,7 @@ Iterable<Rect> _generateImageTileRects(Rect outputRect, Rect fundamentalRect, Im ...@@ -602,7 +462,7 @@ Iterable<Rect> _generateImageTileRects(Rect outputRect, Rect fundamentalRect, Im
} }
} }
/// Paint an image into the given rectangle in the canvas /// Paint an image into the given rectangle in the canvas.
void paintImage({ void paintImage({
Canvas canvas, Canvas canvas,
Rect rect, Rect rect,
...@@ -697,6 +557,48 @@ void paintImage({ ...@@ -697,6 +557,48 @@ void paintImage({
canvas.restore(); canvas.restore();
} }
/// An offset that's expressed as a fraction of a Size.
///
/// FractionalOffset(1.0, 0.0) represents the top right of the Size,
/// FractionalOffset(0.0, 1.0) represents the bottom left of the Size,
class FractionalOffset {
const FractionalOffset(this.x, this.y);
final double x;
final double y;
FractionalOffset operator -(FractionalOffset other) {
return new FractionalOffset(x - other.x, y - other.y);
}
FractionalOffset operator +(FractionalOffset other) {
return new FractionalOffset(x + other.x, y + other.y);
}
FractionalOffset operator *(double other) {
return new FractionalOffset(x * other, y * other);
}
bool operator ==(dynamic other) {
if (other is! FractionalOffset)
return false;
final FractionalOffset typedOther = other;
return x == typedOther.x &&
y == typedOther.y;
}
int get hashCode {
int value = 373;
value = 37 * value + x.hashCode;
value = 37 * value + y.hashCode;
return value;
}
static FractionalOffset lerp(FractionalOffset a, FractionalOffset b, double t) {
if (a == null && b == null)
return null;
if (a == null)
return new FractionalOffset(b.x * t, b.y * t);
if (b == null)
return new FractionalOffset(b.x * (1.0 - t), b.y * (1.0 - t));
return new FractionalOffset(ui.lerpDouble(a.x, b.x, t), ui.lerpDouble(a.y, b.y, t));
}
String toString() => '$runtimeType($x, $y)';
}
/// A background image for a box. /// A background image for a box.
class BackgroundImage { class BackgroundImage {
BackgroundImage({ BackgroundImage({
...@@ -735,11 +637,10 @@ class BackgroundImage { ...@@ -735,11 +637,10 @@ class BackgroundImage {
final ImageResource _imageResource; final ImageResource _imageResource;
final List<VoidCallback> _listeners = final List<VoidCallback> _listeners = <VoidCallback>[];
new List<VoidCallback>();
/// 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(VoidCallback listener) { void _addChangeListener(VoidCallback listener) {
// We add the listener to the _imageResource first so that the first change // We add the listener to the _imageResource first so that the first change
// listener doesn't get callback synchronously if the image resource is // listener doesn't get callback synchronously if the image resource is
// already resolved. // already resolved.
...@@ -749,7 +650,7 @@ class BackgroundImage { ...@@ -749,7 +650,7 @@ class BackgroundImage {
} }
/// No longer call listener when the background image changes. /// No longer call listener when the background image changes.
void removeChangeListener(VoidCallback listener) { void _removeChangeListener(VoidCallback listener) {
_listeners.remove(listener); _listeners.remove(listener);
// We need to remove ourselves as listeners from the _imageResource so that // We need to remove ourselves as listeners from the _imageResource so that
// we're not kept alive by the image_cache. // we're not kept alive by the image_cache.
...@@ -795,18 +696,22 @@ class BackgroundImage { ...@@ -795,18 +696,22 @@ class BackgroundImage {
String toString() => 'BackgroundImage($fit, $repeat)'; String toString() => 'BackgroundImage($fit, $repeat)';
} }
// TODO(abarth): Rename to BoxShape? /// The shape to use when rendering a BoxDecoration.
/// A 2D geometrical shape enum BoxShape {
enum Shape { /// An axis-aligned, 2D rectangle. May have rounded corners. The edges of the
/// An axis-aligned, 2D rectangle /// rectangle will match the edges of the box into which the BoxDecoration is
/// painted.
rectangle, rectangle,
/// A 2D locus of points equidistant from a single point /// A circle centered in the middle of the box into which the BoxDecoration is
/// painted. The diameter of the circle is the shortest dimension of the box,
/// either the width of the height, such that the circle touches the edges of
/// the box.
circle circle
} }
/// An immutable description of how to paint a box /// An immutable description of how to paint a box
class BoxDecoration { class BoxDecoration extends Decoration {
const BoxDecoration({ const BoxDecoration({
this.backgroundColor, // null = don't draw background color this.backgroundColor, // null = don't draw background color
this.backgroundImage, // null = don't draw background image this.backgroundImage, // null = don't draw background image
...@@ -814,9 +719,15 @@ class BoxDecoration { ...@@ -814,9 +719,15 @@ class BoxDecoration {
this.borderRadius, // null = use more efficient background drawing; note that this must be null for circles this.borderRadius, // null = use more efficient background drawing; note that this must be null for circles
this.boxShadow, // null = don't draw shadows this.boxShadow, // null = don't draw shadows
this.gradient, // null = don't allocate gradient objects this.gradient, // null = don't allocate gradient objects
this.shape: Shape.rectangle this.shape: BoxShape.rectangle
}); });
bool debugAssertValid() {
assert(shape != BoxShape.circle ||
borderRadius == null); // can't have a border radius if you're a circle
return super.debugAssertValid();
}
/// The color to fill in the background of the box /// The color to fill in the background of the box
/// ///
/// The color is filled into the shape of the box (e.g., either a rectangle, /// The color is filled into the shape of the box (e.g., either a rectangle,
...@@ -841,9 +752,9 @@ class BoxDecoration { ...@@ -841,9 +752,9 @@ class BoxDecoration {
final Gradient gradient; final Gradient gradient;
/// The shape to fill the background color into and to cast as a shadow /// The shape to fill the background color into and to cast as a shadow
final Shape shape; final BoxShape shape;
/// Returns a new box decoration that is scalled by the given factor /// Returns a new box decoration that is scaled by the given factor
BoxDecoration scale(double factor) { BoxDecoration scale(double factor) {
// TODO(abarth): Scale ALL the things. // TODO(abarth): Scale ALL the things.
return new BoxDecoration( return new BoxDecoration(
...@@ -857,7 +768,33 @@ class BoxDecoration { ...@@ -857,7 +768,33 @@ class BoxDecoration {
); );
} }
/// Linearly interpolate between two box decorations double getEffectiveBorderRadius(Rect rect) {
double shortestSide = rect.shortestSide;
// In principle, we should use shortestSide / 2.0, but we don't want to
// run into floating point rounding errors. Instead, we just use
// shortestSide and let ui.Canvas do any remaining clamping.
return borderRadius > shortestSide ? shortestSide : borderRadius;
}
bool hitTest(Size size, Point position) {
assert(shape != null);
assert((Point.origin & size).contains(position));
switch (shape) {
case BoxShape.rectangle:
if (borderRadius != null) {
ui.RRect bounds = new ui.RRect.fromRectXY(Point.origin & size, borderRadius, borderRadius);
return bounds.contains(position);
}
return true;
case BoxShape.circle:
// Circles are inscribed into our smallest dimension.
Point center = size.center(Point.origin);
double distance = (position - center).distance;
return distance <= math.min(size.width, size.height) / 2.0;
}
}
/// Linearly interpolate between two box decorations.
/// ///
/// Interpolates each parameter of the box decoration separately. /// Interpolates each parameter of the box decoration separately.
static BoxDecoration lerp(BoxDecoration a, BoxDecoration b, double t) { static BoxDecoration lerp(BoxDecoration a, BoxDecoration b, double t) {
...@@ -879,6 +816,18 @@ class BoxDecoration { ...@@ -879,6 +816,18 @@ class BoxDecoration {
); );
} }
BoxDecoration lerpFrom(Decoration a, double t) {
if (a is! BoxDecoration)
return BoxDecoration.lerp(null, this, t);
return BoxDecoration.lerp(a, this, t);
}
BoxDecoration lerpTo(Decoration b, double t) {
if (b is! BoxDecoration)
return BoxDecoration.lerp(this, null, t);
return BoxDecoration.lerp(this, b, t);
}
bool operator ==(dynamic other) { bool operator ==(dynamic other) {
if (identical(this, other)) if (identical(this, other))
return true; return true;
...@@ -920,30 +869,32 @@ class BoxDecoration { ...@@ -920,30 +869,32 @@ class BoxDecoration {
result.add('${prefix}boxShadow: ${boxShadow.map((BoxShadow shadow) => shadow.toString())}'); result.add('${prefix}boxShadow: ${boxShadow.map((BoxShadow shadow) => shadow.toString())}');
if (gradient != null) if (gradient != null)
result.add('${prefix}gradient: $gradient'); result.add('${prefix}gradient: $gradient');
if (shape != Shape.rectangle) if (shape != BoxShape.rectangle)
result.add('${prefix}shape: $shape'); result.add('${prefix}shape: $shape');
if (result.isEmpty) if (result.isEmpty)
return '$prefix<no decorations specified>'; return '$prefix<no decorations specified>';
return result.join('\n'); return result.join('\n');
} }
bool get needsListeners => backgroundImage != null;
void addChangeListener(VoidCallback listener) {
backgroundImage?._addChangeListener(listener);
}
void removeChangeListener(VoidCallback listener) {
backgroundImage?._removeChangeListener(listener);
}
_BoxDecorationPainter createBoxPainter() => new _BoxDecorationPainter(this);
} }
/// An object that paints a [BoxDecoration] into a canvas /// An object that paints a [BoxDecoration] into a canvas
class BoxPainter { class _BoxDecorationPainter extends BoxPainter {
BoxPainter(BoxDecoration decoration) : _decoration = decoration { _BoxDecorationPainter(this._decoration) {
assert(decoration != null); assert(_decoration != null);
} }
BoxDecoration _decoration; final BoxDecoration _decoration;
/// The box decoration to paint
BoxDecoration get decoration => _decoration;
void set decoration (BoxDecoration value) {
assert(value != null);
if (value == _decoration)
return;
_decoration = value;
_cachedBackgroundPaint = null;
}
Paint _cachedBackgroundPaint; Paint _cachedBackgroundPaint;
Paint get _backgroundPaint { Paint get _backgroundPaint {
...@@ -981,27 +932,19 @@ class BoxPainter { ...@@ -981,27 +932,19 @@ class BoxPainter {
return hasUniformWidth; return hasUniformWidth;
} }
double _getEffectiveBorderRadius(Rect rect) {
double shortestSide = rect.shortestSide;
// In principle, we should use shortestSide / 2.0, but we don't want to
// run into floating point rounding errors. Instead, we just use
// shortestSide and let ui.Canvas do any remaining clamping.
return _decoration.borderRadius > shortestSide ? shortestSide : _decoration.borderRadius;
}
void _paintBox(ui.Canvas canvas, Rect rect, Paint paint) { void _paintBox(ui.Canvas canvas, Rect rect, Paint paint) {
switch (_decoration.shape) { switch (_decoration.shape) {
case Shape.circle: case BoxShape.circle:
assert(_decoration.borderRadius == null); assert(_decoration.borderRadius == null);
Point center = rect.center; Point center = rect.center;
double radius = rect.shortestSide / 2.0; double radius = rect.shortestSide / 2.0;
canvas.drawCircle(center, radius, paint); canvas.drawCircle(center, radius, paint);
break; break;
case Shape.rectangle: case BoxShape.rectangle:
if (_decoration.borderRadius == null) { if (_decoration.borderRadius == null) {
canvas.drawRect(rect, paint); canvas.drawRect(rect, paint);
} else { } else {
double radius = _getEffectiveBorderRadius(rect); double radius = _decoration.getEffectiveBorderRadius(rect);
canvas.drawRRect(new ui.RRect.fromRectXY(rect, radius, radius), paint); canvas.drawRRect(new ui.RRect.fromRectXY(rect, radius, radius), paint);
} }
break; break;
...@@ -1053,14 +996,14 @@ class BoxPainter { ...@@ -1053,14 +996,14 @@ class BoxPainter {
_paintBorderWithRadius(canvas, rect); _paintBorderWithRadius(canvas, rect);
return; return;
} }
if (_decoration.shape == Shape.circle) { if (_decoration.shape == BoxShape.circle) {
_paintBorderWithCircle(canvas, rect); _paintBorderWithCircle(canvas, rect);
return; return;
} }
} }
assert(_decoration.borderRadius == null); // TODO(abarth): Support non-uniform rounded borders. assert(_decoration.borderRadius == null); // TODO(abarth): Support non-uniform rounded borders.
assert(_decoration.shape == Shape.rectangle); // TODO(ianh): Support non-uniform borders on circles. assert(_decoration.shape == BoxShape.rectangle); // TODO(ianh): Support non-uniform borders on circles.
assert(_decoration.border.top != null); assert(_decoration.border.top != null);
assert(_decoration.border.right != null); assert(_decoration.border.right != null);
...@@ -1109,10 +1052,10 @@ class BoxPainter { ...@@ -1109,10 +1052,10 @@ class BoxPainter {
void _paintBorderWithRadius(ui.Canvas canvas, Rect rect) { void _paintBorderWithRadius(ui.Canvas canvas, Rect rect) {
assert(_hasUniformBorder); assert(_hasUniformBorder);
assert(_decoration.shape == Shape.rectangle); assert(_decoration.shape == BoxShape.rectangle);
Color color = _decoration.border.top.color; Color color = _decoration.border.top.color;
double width = _decoration.border.top.width; double width = _decoration.border.top.width;
double radius = _getEffectiveBorderRadius(rect); double radius = _decoration.getEffectiveBorderRadius(rect);
ui.RRect outer = new ui.RRect.fromRectXY(rect, radius, radius); ui.RRect outer = new ui.RRect.fromRectXY(rect, radius, radius);
ui.RRect inner = new ui.RRect.fromRectXY(rect.deflate(width), radius - width, radius - width); ui.RRect inner = new ui.RRect.fromRectXY(rect.deflate(width), radius - width, radius - width);
...@@ -1121,12 +1064,11 @@ class BoxPainter { ...@@ -1121,12 +1064,11 @@ class BoxPainter {
void _paintBorderWithCircle(ui.Canvas canvas, Rect rect) { void _paintBorderWithCircle(ui.Canvas canvas, Rect rect) {
assert(_hasUniformBorder); assert(_hasUniformBorder);
assert(_decoration.shape == Shape.circle); assert(_decoration.shape == BoxShape.circle);
assert(_decoration.borderRadius == null); assert(_decoration.borderRadius == null);
double width = _decoration.border.top.width; double width = _decoration.border.top.width;
if (width <= 0.0) { if (width <= 0.0)
return; return;
}
Paint paint = new Paint() Paint paint = new Paint()
..color = _decoration.border.top.color ..color = _decoration.border.top.color
..strokeWidth = width ..strokeWidth = width
...@@ -1144,45 +1086,3 @@ class BoxPainter { ...@@ -1144,45 +1086,3 @@ class BoxPainter {
_paintBorder(canvas, rect); _paintBorder(canvas, rect);
} }
} }
/// An offset that's expressed as a fraction of a Size.
///
/// FractionalOffset(1.0, 0.0) represents the top right of the Size,
/// FractionalOffset(0.0, 1.0) represents the bottom left of the Size,
class FractionalOffset {
const FractionalOffset(this.x, this.y);
final double x;
final double y;
FractionalOffset operator -(FractionalOffset other) {
return new FractionalOffset(x - other.x, y - other.y);
}
FractionalOffset operator +(FractionalOffset other) {
return new FractionalOffset(x + other.x, y + other.y);
}
FractionalOffset operator *(double other) {
return new FractionalOffset(x * other, y * other);
}
bool operator ==(dynamic other) {
if (other is! FractionalOffset)
return false;
final FractionalOffset typedOther = other;
return x == typedOther.x &&
y == typedOther.y;
}
int get hashCode {
int value = 373;
value = 37 * value + x.hashCode;
value = 37 * value + y.hashCode;
return value;
}
static FractionalOffset lerp(FractionalOffset a, FractionalOffset b, double t) {
if (a == null && b == null)
return null;
if (a == null)
return new FractionalOffset(b.x * t, b.y * t);
if (b == null)
return new FractionalOffset(b.x * (1.0 - t), b.y * (1.0 - t));
return new FractionalOffset(ui.lerpDouble(a.x, b.x, t), ui.lerpDouble(a.y, b.y, t));
}
String toString() => '$runtimeType($x, $y)';
}
// 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 'dart:ui' as ui;
import 'edge_dims.dart';
export 'edge_dims.dart' show EdgeDims;
// This group of classes is intended for painting in cartesian coordinates.
abstract class Decoration {
const Decoration();
bool debugAssertValid() => true;
EdgeDims get padding => null;
Decoration lerpFrom(Decoration a, double t) => this;
Decoration lerpTo(Decoration b, double t) => b;
bool hitTest(ui.Size size, ui.Point position) => true;
bool get needsListeners => false;
void addChangeListener(ui.VoidCallback listener) { assert(false); }
void removeChangeListener(ui.VoidCallback listener) { assert(false); }
BoxPainter createBoxPainter();
String toString([String prefix = '']) => '$prefix$runtimeType';
}
abstract class BoxPainter {
void paint(ui.Canvas canvas, ui.Rect rect);
}
// 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 'dart:ui' as ui;
/// 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.
const EdgeDims.TRBL(this.top, this.right, this.bottom, this.left);
/// 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.
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.
const EdgeDims.symmetric({ double vertical: 0.0,
double horizontal: 0.0 })
: top = vertical, left = horizontal, bottom = vertical, right = horizontal;
/// The offset from the top.
final double top;
/// The offset from the right.
final double right;
/// The offset from the bottom.
final double bottom;
/// 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.
ui.Size get collapsedSize => new ui.Size(left + right, top + bottom);
ui.Rect inflateRect(ui.Rect rect) {
return new ui.Rect.fromLTRB(rect.left - left, rect.top - top, rect.right + right, rect.bottom + bottom);
}
EdgeDims operator -(EdgeDims other) {
return new EdgeDims.TRBL(
top - other.top,
right - other.right,
bottom - other.bottom,
left - other.left
);
}
EdgeDims operator +(EdgeDims other) {
return new EdgeDims.TRBL(
top + other.top,
right + other.right,
bottom + other.bottom,
left + other.left
);
}
EdgeDims operator *(double other) {
return new EdgeDims.TRBL(
top * other,
right * other,
bottom * other,
left * other
);
}
EdgeDims operator /(double other) {
return new EdgeDims.TRBL(
top / other,
right / other,
bottom / other,
left / other
);
}
EdgeDims operator ~/(double other) {
return new EdgeDims.TRBL(
(top ~/ other).toDouble(),
(right ~/ other).toDouble(),
(bottom ~/ other).toDouble(),
(left ~/ other).toDouble()
);
}
EdgeDims operator %(double other) {
return new EdgeDims.TRBL(
top % other,
right % other,
bottom % other,
left % other
);
}
/// Linearly interpolate between two EdgeDims.
///
/// If either is null, this function interpolates from [EdgeDims.zero].
static EdgeDims lerp(EdgeDims a, EdgeDims b, double t) {
if (a == null && b == null)
return null;
if (a == null)
return b * t;
if (b == null)
return a * (1.0 - t);
return new EdgeDims.TRBL(
ui.lerpDouble(a.top, b.top, t),
ui.lerpDouble(a.right, b.right, t),
ui.lerpDouble(a.bottom, b.bottom, t),
ui.lerpDouble(a.left, b.left, t)
);
}
/// 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) {
if (identical(this, other))
return true;
if (other is! EdgeDims)
return false;
final EdgeDims typedOther = other;
return top == typedOther.top &&
right == typedOther.right &&
bottom == typedOther.bottom &&
left == typedOther.left;
}
int get hashCode {
int value = 373;
value = 37 * value + top.hashCode;
value = 37 * value + left.hashCode;
value = 37 * value + bottom.hashCode;
value = 37 * value + right.hashCode;
return value;
}
String toString() => "EdgeDims($top, $right, $bottom, $left)";
}
...@@ -13,7 +13,7 @@ import 'package:vector_math/vector_math_64.dart'; ...@@ -13,7 +13,7 @@ import 'package:vector_math/vector_math_64.dart';
import 'debug.dart'; import 'debug.dart';
import 'object.dart'; import 'object.dart';
export 'package:flutter/painting.dart' show FractionalOffset, TextBaseline; export 'package:flutter/painting.dart' show EdgeDims, FractionalOffset, TextBaseline;
// This class should only be used in debug builds // This class should only be used in debug builds
class _DebugSize extends Size { class _DebugSize extends Size {
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
...@@ -13,13 +12,13 @@ import 'box.dart'; ...@@ -13,13 +12,13 @@ import 'box.dart';
import 'debug.dart'; import 'debug.dart';
import 'object.dart'; import 'object.dart';
export 'package:flutter/src/painting/box_painter.dart';
export 'package:flutter/gestures.dart' show export 'package:flutter/gestures.dart' show
PointerEvent, PointerEvent,
PointerDownEvent, PointerDownEvent,
PointerMoveEvent, PointerMoveEvent,
PointerUpEvent, PointerUpEvent,
PointerCancelEvent; PointerCancelEvent;
export 'package:flutter/painting.dart' show Decoration, BoxDecoration;
/// A base class for render objects that resemble their children. /// A base class for render objects that resemble their children.
/// ///
...@@ -704,9 +703,11 @@ class RenderClipOval extends _RenderCustomClip<Rect> { ...@@ -704,9 +703,11 @@ class RenderClipOval extends _RenderCustomClip<Rect> {
bool hitTest(HitTestResult result, { Point position }) { bool hitTest(HitTestResult result, { Point position }) {
Rect clipBounds = _clip; Rect clipBounds = _clip;
Point center = clipBounds.center; Point center = clipBounds.center;
// convert the position to an offset from the center of the unit circle
Offset offset = new Offset((position.x - center.x) / clipBounds.width, Offset offset = new Offset((position.x - center.x) / clipBounds.width,
(position.y - center.y) / clipBounds.height); (position.y - center.y) / clipBounds.height);
if (offset.distance > 0.5) // check if the point is outside the unit circle
if (offset.distanceSquared > 0.25) // x^2 + y^2 > r^2
return false; return false;
return super.hitTest(result, position: position); return super.hitTest(result, position: position);
} }
...@@ -720,7 +721,7 @@ class RenderClipOval extends _RenderCustomClip<Rect> { ...@@ -720,7 +721,7 @@ class RenderClipOval extends _RenderCustomClip<Rect> {
} }
/// Where to paint a box decoration. /// Where to paint a box decoration.
enum BoxDecorationPosition { enum DecorationPosition {
/// Paint the box decoration behind the children. /// Paint the box decoration behind the children.
background, background,
...@@ -728,97 +729,90 @@ enum BoxDecorationPosition { ...@@ -728,97 +729,90 @@ enum BoxDecorationPosition {
foreground, foreground,
} }
/// Paints a [BoxDecoration] either before or after its child paints. /// Paints a [Decoration] either before or after its child paints.
class RenderDecoratedBox extends RenderProxyBox { class RenderDecoratedBox extends RenderProxyBox {
RenderDecoratedBox({ RenderDecoratedBox({
BoxDecoration decoration, Decoration decoration,
RenderBox child, DecorationPosition position: DecorationPosition.background,
BoxDecorationPosition position: BoxDecorationPosition.background RenderBox child
}) : _painter = new BoxPainter(decoration), }) : _decoration = decoration,
_position = position, _position = position,
super(child) { super(child) {
assert(decoration != null); assert(decoration != null);
assert(position != null); assert(position != null);
} }
/// Where to paint the box decoration. BoxPainter _painter;
BoxDecorationPosition get position => _position;
BoxDecorationPosition _position;
void set position (BoxDecorationPosition newPosition) {
assert(newPosition != null);
if (newPosition == _position)
return;
markNeedsPaint();
}
/// What decoration to paint. /// What decoration to paint.
BoxDecoration get decoration => _painter.decoration; Decoration get decoration => _decoration;
void set decoration (BoxDecoration newDecoration) { Decoration _decoration;
void set decoration (Decoration newDecoration) {
assert(newDecoration != null); assert(newDecoration != null);
if (newDecoration == _painter.decoration) if (newDecoration == _decoration)
return; return;
_removeBackgroundImageListenerIfNeeded(); _removeListenerIfNeeded();
_painter.decoration = newDecoration; _painter = null;
_addBackgroundImageListenerIfNeeded(); _decoration = newDecoration;
_addListenerIfNeeded();
markNeedsPaint(); markNeedsPaint();
} }
final BoxPainter _painter; /// Where to paint the box decoration.
DecorationPosition get position => _position;
DecorationPosition _position;
void set position (DecorationPosition newPosition) {
assert(newPosition != null);
if (newPosition == _position)
return;
_position = newPosition;
markNeedsPaint();
}
bool get _needsBackgroundImageListener { bool get _needsListeners {
return attached && return attached && _decoration.needsListeners;
_painter.decoration != null &&
_painter.decoration.backgroundImage != null;
} }
void _addBackgroundImageListenerIfNeeded() { void _addListenerIfNeeded() {
if (_needsBackgroundImageListener) if (_needsListeners)
_painter.decoration.backgroundImage.addChangeListener(markNeedsPaint); _decoration.addChangeListener(markNeedsPaint);
} }
void _removeBackgroundImageListenerIfNeeded() { void _removeListenerIfNeeded() {
if (_needsBackgroundImageListener) if (_needsListeners)
_painter.decoration.backgroundImage.removeChangeListener(markNeedsPaint); _decoration.removeChangeListener(markNeedsPaint);
} }
void attach() { void attach() {
super.attach(); super.attach();
_addBackgroundImageListenerIfNeeded(); _addListenerIfNeeded();
} }
void detach() { void detach() {
_removeBackgroundImageListenerIfNeeded(); _removeListenerIfNeeded();
super.detach(); super.detach();
} }
bool hitTestSelf(Point position) { bool hitTestSelf(Point position) {
switch (_painter.decoration.shape) { return _decoration.hitTest(size, position);
case Shape.rectangle:
// TODO(abarth): We should check the border radius.
return true;
case Shape.circle:
// Circles are inscribed into our smallest dimension.
Point center = size.center(Point.origin);
double distance = (position - center).distance;
return distance <= math.min(size.width, size.height) / 2.0;
}
} }
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
assert(size.width != null); assert(size.width != null);
assert(size.height != null); assert(size.height != null);
if (position == BoxDecorationPosition.background) _painter ??= _decoration.createBoxPainter();
if (position == DecorationPosition.background)
_painter.paint(context.canvas, offset & size); _painter.paint(context.canvas, offset & size);
super.paint(context, offset); super.paint(context, offset);
if (position == BoxDecorationPosition.foreground) if (position == DecorationPosition.foreground)
_painter.paint(context.canvas, offset & size); _painter.paint(context.canvas, offset & size);
} }
void debugDescribeSettings(List<String> settings) { void debugDescribeSettings(List<String> settings) {
super.debugDescribeSettings(settings); super.debugDescribeSettings(settings);
settings.add('decoration:'); settings.add('decoration:');
settings.addAll(_painter.decoration.toString(" ").split('\n')); settings.addAll(_decoration.toString(" ").split('\n'));
} }
} }
......
...@@ -16,11 +16,17 @@ class AnimatedBoxConstraintsValue extends AnimatedValue<BoxConstraints> { ...@@ -16,11 +16,17 @@ class AnimatedBoxConstraintsValue extends AnimatedValue<BoxConstraints> {
BoxConstraints lerp(double t) => BoxConstraints.lerp(begin, end, t); BoxConstraints lerp(double t) => BoxConstraints.lerp(begin, end, t);
} }
class AnimatedBoxDecorationValue extends AnimatedValue<BoxDecoration> { class AnimatedDecorationValue extends AnimatedValue<Decoration> {
AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve, Curve reverseCurve }) AnimatedDecorationValue(Decoration begin, { Decoration end, Curve curve, Curve reverseCurve })
: super(begin, end: end, curve: curve, reverseCurve: reverseCurve); : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
BoxDecoration lerp(double t) => BoxDecoration.lerp(begin, end, t); Decoration lerp(double t) {
if (begin == null && end == null)
return null;
if (end == null)
return begin.lerpTo(end, t);
return end.lerpFrom(begin, t);
}
} }
class AnimatedEdgeDimsValue extends AnimatedValue<EdgeDims> { class AnimatedEdgeDimsValue extends AnimatedValue<EdgeDims> {
...@@ -62,14 +68,14 @@ class AnimatedContainer extends StatefulComponent { ...@@ -62,14 +68,14 @@ class AnimatedContainer extends StatefulComponent {
assert(margin == null || margin.isNonNegative); assert(margin == null || margin.isNonNegative);
assert(padding == null || padding.isNonNegative); assert(padding == null || padding.isNonNegative);
assert(curve != null); assert(curve != null);
assert(duration != null); assert(duration != null || decoration.debugAssertValid());
} }
final Widget child; final Widget child;
final BoxConstraints constraints; final BoxConstraints constraints;
final BoxDecoration decoration; final Decoration decoration;
final BoxDecoration foregroundDecoration; final Decoration foregroundDecoration;
final EdgeDims margin; final EdgeDims margin;
final EdgeDims padding; final EdgeDims padding;
final Matrix4 transform; final Matrix4 transform;
...@@ -84,8 +90,8 @@ class AnimatedContainer extends StatefulComponent { ...@@ -84,8 +90,8 @@ class AnimatedContainer extends StatefulComponent {
class _AnimatedContainerState extends State<AnimatedContainer> { class _AnimatedContainerState extends State<AnimatedContainer> {
AnimatedBoxConstraintsValue _constraints; AnimatedBoxConstraintsValue _constraints;
AnimatedBoxDecorationValue _decoration; AnimatedDecorationValue _decoration;
AnimatedBoxDecorationValue _foregroundDecoration; AnimatedDecorationValue _foregroundDecoration;
AnimatedEdgeDimsValue _margin; AnimatedEdgeDimsValue _margin;
AnimatedEdgeDimsValue _padding; AnimatedEdgeDimsValue _padding;
AnimatedMatrix4Value _transform; AnimatedMatrix4Value _transform;
...@@ -167,7 +173,7 @@ class _AnimatedContainerState extends State<AnimatedContainer> { ...@@ -167,7 +173,7 @@ class _AnimatedContainerState extends State<AnimatedContainer> {
} }
if (config.decoration != null) { if (config.decoration != null) {
_decoration ??= new AnimatedBoxDecorationValue(config.decoration); _decoration ??= new AnimatedDecorationValue(config.decoration);
if (_configVariable(_decoration, config.decoration)) if (_configVariable(_decoration, config.decoration))
needsAnimation = true; needsAnimation = true;
} else { } else {
...@@ -175,7 +181,7 @@ class _AnimatedContainerState extends State<AnimatedContainer> { ...@@ -175,7 +181,7 @@ class _AnimatedContainerState extends State<AnimatedContainer> {
} }
if (config.foregroundDecoration != null) { if (config.foregroundDecoration != null) {
_foregroundDecoration ??= new AnimatedBoxDecorationValue(config.foregroundDecoration); _foregroundDecoration ??= new AnimatedDecorationValue(config.foregroundDecoration);
if (_configVariable(_foregroundDecoration, config.foregroundDecoration)) if (_configVariable(_foregroundDecoration, config.foregroundDecoration))
needsAnimation = true; needsAnimation = true;
} else { } else {
......
...@@ -16,13 +16,15 @@ export 'package:flutter/rendering.dart' show ...@@ -16,13 +16,15 @@ export 'package:flutter/rendering.dart' show
BorderSide, BorderSide,
BoxConstraints, BoxConstraints,
BoxDecoration, BoxDecoration,
BoxDecorationPosition,
BoxShadow, BoxShadow,
BoxShape,
Canvas, Canvas,
Color, Color,
ColorFilter, ColorFilter,
CustomClipper, CustomClipper,
CustomPainter, CustomPainter,
Decoration,
DecorationPosition,
EdgeDims, EdgeDims,
FlexAlignItems, FlexAlignItems,
FlexDirection, FlexDirection,
...@@ -51,7 +53,6 @@ export 'package:flutter/rendering.dart' show ...@@ -51,7 +53,6 @@ export 'package:flutter/rendering.dart' show
RadialGradient, RadialGradient,
Rect, Rect,
ScrollDirection, ScrollDirection,
Shape,
Size, Size,
StyledTextSpan, StyledTextSpan,
TextAlign, TextAlign,
...@@ -135,7 +136,7 @@ class DecoratedBox extends OneChildRenderObjectWidget { ...@@ -135,7 +136,7 @@ class DecoratedBox extends OneChildRenderObjectWidget {
DecoratedBox({ DecoratedBox({
Key key, Key key,
this.decoration, this.decoration,
this.position: BoxDecorationPosition.background, this.position: DecorationPosition.background,
Widget child Widget child
}) : super(key: key, child: child) { }) : super(key: key, child: child) {
assert(decoration != null); assert(decoration != null);
...@@ -143,10 +144,10 @@ class DecoratedBox extends OneChildRenderObjectWidget { ...@@ -143,10 +144,10 @@ class DecoratedBox extends OneChildRenderObjectWidget {
} }
/// What decoration to paint. /// What decoration to paint.
final BoxDecoration decoration; final Decoration decoration;
/// Where to paint the box decoration. /// Where to paint the box decoration.
final BoxDecorationPosition position; final DecorationPosition position;
RenderDecoratedBox createRenderObject() => new RenderDecoratedBox(decoration: decoration, position: position); RenderDecoratedBox createRenderObject() => new RenderDecoratedBox(decoration: decoration, position: position);
...@@ -782,26 +783,26 @@ class Container extends StatelessComponent { ...@@ -782,26 +783,26 @@ class Container extends StatelessComponent {
}) : super(key: key) { }) : super(key: key) {
assert(margin == null || margin.isNonNegative); assert(margin == null || margin.isNonNegative);
assert(padding == null || padding.isNonNegative); assert(padding == null || padding.isNonNegative);
assert(decoration == null || decoration.shape != Shape.circle || decoration.borderRadius == null); // can't have a border radius if you're a circle assert(decoration == null || decoration.debugAssertValid());
} }
final Widget child; final Widget child;
final BoxConstraints constraints; final BoxConstraints constraints;
final BoxDecoration decoration; final Decoration decoration;
final BoxDecoration foregroundDecoration; final Decoration foregroundDecoration;
final EdgeDims margin; final EdgeDims margin;
final EdgeDims padding; final EdgeDims padding;
final Matrix4 transform; final Matrix4 transform;
final double width; final double width;
final double height; final double height;
EdgeDims get _paddingIncludingBorder { EdgeDims get _paddingIncludingDecoration {
if (decoration == null || decoration.border == null) if (decoration == null || decoration.padding == null)
return padding; return padding;
EdgeDims borderPadding = decoration.border.dimensions; EdgeDims decorationPadding = decoration.padding;
if (padding == null) if (padding == null)
return borderPadding; return decorationPadding;
return padding + borderPadding; return padding + decorationPadding;
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -810,7 +811,7 @@ class Container extends StatelessComponent { ...@@ -810,7 +811,7 @@ class Container extends StatelessComponent {
if (child == null && (width == null || height == null)) if (child == null && (width == null || height == null))
current = new ConstrainedBox(constraints: const BoxConstraints.expand()); current = new ConstrainedBox(constraints: const BoxConstraints.expand());
EdgeDims effectivePadding = _paddingIncludingBorder; EdgeDims effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null) if (effectivePadding != null)
current = new Padding(padding: effectivePadding, child: current); current = new Padding(padding: effectivePadding, child: current);
...@@ -820,7 +821,7 @@ class Container extends StatelessComponent { ...@@ -820,7 +821,7 @@ class Container extends StatelessComponent {
if (foregroundDecoration != null) { if (foregroundDecoration != null) {
current = new DecoratedBox( current = new DecoratedBox(
decoration: foregroundDecoration, decoration: foregroundDecoration,
position: BoxDecorationPosition.foreground, position: DecorationPosition.foreground,
child: current child: current
); );
} }
......
...@@ -21,6 +21,8 @@ void main() { ...@@ -21,6 +21,8 @@ void main() {
backgroundColor: new Color(0xFF0000FF) backgroundColor: new Color(0xFF0000FF)
); );
BoxDecoration actualDecoration;
tester.pumpWidget( tester.pumpWidget(
new AnimatedContainer( new AnimatedContainer(
key: key, key: key,
...@@ -30,7 +32,8 @@ void main() { ...@@ -30,7 +32,8 @@ void main() {
); );
RenderDecoratedBox box = key.currentState.context.findRenderObject(); RenderDecoratedBox box = key.currentState.context.findRenderObject();
expect(box.decoration.backgroundColor, equals(decorationA.backgroundColor)); actualDecoration = box.decoration;
expect(actualDecoration.backgroundColor, equals(decorationA.backgroundColor));
tester.pumpWidget( tester.pumpWidget(
new AnimatedContainer( new AnimatedContainer(
...@@ -41,11 +44,13 @@ void main() { ...@@ -41,11 +44,13 @@ void main() {
); );
expect(key.currentState.context.findRenderObject(), equals(box)); expect(key.currentState.context.findRenderObject(), equals(box));
expect(box.decoration.backgroundColor, equals(decorationA.backgroundColor)); actualDecoration = box.decoration;
expect(actualDecoration.backgroundColor, equals(decorationA.backgroundColor));
tester.pump(const Duration(seconds: 1)); tester.pump(const Duration(seconds: 1));
expect(box.decoration.backgroundColor, equals(decorationB.backgroundColor)); actualDecoration = box.decoration;
expect(actualDecoration.backgroundColor, equals(decorationB.backgroundColor));
}); });
}); });
......
...@@ -14,7 +14,7 @@ void main() { ...@@ -14,7 +14,7 @@ void main() {
new Container( new Container(
padding: new EdgeDims.all(50.0), padding: new EdgeDims.all(50.0),
decoration: new BoxDecoration( decoration: new BoxDecoration(
shape: Shape.circle, shape: BoxShape.circle,
border: new Border.all(width: 10.0, color: const Color(0x80FF00FF)), border: new Border.all(width: 10.0, color: const Color(0x80FF00FF)),
backgroundColor: Colors.teal[600] backgroundColor: Colors.teal[600]
) )
......
...@@ -27,7 +27,7 @@ void main() { ...@@ -27,7 +27,7 @@ void main() {
expect(element.renderObject is RenderDecoratedBox, isTrue); expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox renderObject = element.renderObject; RenderDecoratedBox renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationA)); expect(renderObject.decoration, equals(kBoxDecorationA));
expect(renderObject.position, equals(BoxDecorationPosition.background)); expect(renderObject.position, equals(DecorationPosition.background));
tester.pumpWidget(new DecoratedBox(decoration: kBoxDecorationB)); tester.pumpWidget(new DecoratedBox(decoration: kBoxDecorationB));
element = tester.findElement((Element element) => element is OneChildRenderObjectElement); element = tester.findElement((Element element) => element is OneChildRenderObjectElement);
...@@ -35,7 +35,7 @@ void main() { ...@@ -35,7 +35,7 @@ void main() {
expect(element.renderObject is RenderDecoratedBox, isTrue); expect(element.renderObject is RenderDecoratedBox, isTrue);
renderObject = element.renderObject; renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationB)); expect(renderObject.decoration, equals(kBoxDecorationB));
expect(renderObject.position, equals(BoxDecorationPosition.background)); expect(renderObject.position, equals(DecorationPosition.background));
}); });
}); });
...@@ -49,12 +49,12 @@ void main() { ...@@ -49,12 +49,12 @@ void main() {
expect(element.renderObject is RenderDecoratedBox, isTrue); expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox renderObject = element.renderObject; RenderDecoratedBox renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationA)); expect(renderObject.decoration, equals(kBoxDecorationA));
expect(renderObject.position, equals(BoxDecorationPosition.background)); expect(renderObject.position, equals(DecorationPosition.background));
expect(renderObject.child, isNotNull); expect(renderObject.child, isNotNull);
expect(renderObject.child is RenderDecoratedBox, isTrue); expect(renderObject.child is RenderDecoratedBox, isTrue);
RenderDecoratedBox child = renderObject.child; RenderDecoratedBox child = renderObject.child;
expect(child.decoration, equals(kBoxDecorationB)); expect(child.decoration, equals(kBoxDecorationB));
expect(child.position, equals(BoxDecorationPosition.background)); expect(child.position, equals(DecorationPosition.background));
expect(child.child, isNull); expect(child.child, isNull);
} }
...@@ -65,7 +65,7 @@ void main() { ...@@ -65,7 +65,7 @@ void main() {
expect(element.renderObject is RenderDecoratedBox, isTrue); expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox renderObject = element.renderObject; RenderDecoratedBox renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationA)); expect(renderObject.decoration, equals(kBoxDecorationA));
expect(renderObject.position, equals(BoxDecorationPosition.background)); expect(renderObject.position, equals(DecorationPosition.background));
expect(renderObject.child, isNull); expect(renderObject.child, isNull);
} }
......
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