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

Merge pull request #781 from Hixie/box_decoration

Make BoxDecoration replaceable.
parents 0fe72b17 2afa87df
......@@ -50,7 +50,7 @@ class Dot extends StatelessComponent {
height: size,
decoration: new BoxDecoration(
backgroundColor: color,
shape: Shape.circle
shape: BoxShape.circle
),
child: child
);
......
......@@ -14,7 +14,7 @@ class Circle extends StatelessComponent {
width: 50.0,
margin: margin + new EdgeDims.symmetric(horizontal: 2.0),
decoration: new BoxDecoration(
shape: Shape.circle,
shape: BoxShape.circle,
backgroundColor: const Color(0xFF00FF00)
)
);
......
......@@ -15,6 +15,8 @@ library painting;
export 'src/painting/basic_types.dart';
export 'src/painting/box_painter.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/text_painter.dart';
export 'src/painting/text_style.dart';
......
......@@ -35,7 +35,7 @@ class CircleAvatar extends StatelessComponent {
duration: kThemeChangeDuration,
decoration: new BoxDecoration(
backgroundColor: color,
shape: Shape.circle
shape: BoxShape.circle
),
width: 40.0,
height: 40.0,
......
......@@ -226,7 +226,7 @@ class DayPicker extends StatelessComponent {
selectedDate.day == day)
decoration = new BoxDecoration(
backgroundColor: theme.primarySwatch[100],
shape: Shape.circle
shape: BoxShape.circle
);
// Use a different font color for the current day
......@@ -377,7 +377,7 @@ class _YearPickerState extends ScrollableWidgetListState<YearPicker> {
height: config.itemExtent,
decoration: year == config.selectedDate.year ? new BoxDecoration(
backgroundColor: Theme.of(context).primarySwatch[100],
shape: Shape.circle
shape: BoxShape.circle
) : null,
child: new Center(
child: new Text(label, style: style)
......
......@@ -37,11 +37,11 @@ class _DropDownMenuPainter extends CustomPainter {
final RenderBox renderBox;
void paint(Canvas canvas, Size size) {
final BoxPainter painter = new BoxPainter(new BoxDecoration(
final BoxPainter painter = new BoxDecoration(
backgroundColor: color,
borderRadius: 2.0,
boxShadow: elevationToShadow[elevation]
));
).createBoxPainter();
double top = renderBox.globalToLocal(new Point(0.0, menuTop)).y;
double bottom = renderBox.globalToLocal(new Point(0.0, menuBottom)).y;
......
......@@ -18,7 +18,7 @@ class InkResponse extends StatefulComponent {
this.onLongPress,
this.onHighlightChanged,
this.containedInWell: false,
this.highlightShape: Shape.circle
this.highlightShape: BoxShape.circle
}) : super(key: key);
final Widget child;
......@@ -27,7 +27,7 @@ class InkResponse extends StatefulComponent {
final GestureLongPressCallback onLongPress;
final ValueChanged<bool> onHighlightChanged;
final bool containedInWell;
final Shape highlightShape;
final BoxShape highlightShape;
_InkResponseState createState() => new _InkResponseState<InkResponse>();
}
......@@ -170,6 +170,6 @@ class InkWell extends InkResponse {
onLongPress: onLongPress,
onHighlightChanged: onHighlightChanged,
containedInWell: true,
highlightShape: Shape.rectangle
highlightShape: BoxShape.rectangle
);
}
......@@ -65,7 +65,7 @@ abstract class MaterialInkController {
InkSplash splashAt({ RenderBox referenceBox, Point position, Color color, bool containedInWell, VoidCallback onRemoved });
/// 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.
void addInkFeature(InkFeature feature);
......@@ -153,7 +153,7 @@ class _MaterialState extends State<Material> {
backgroundColor: backgroundColor,
borderRadius: kMaterialEdges[config.type],
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
);
......@@ -217,7 +217,7 @@ class RenderInkFeatures extends RenderProxyBox implements MaterialInkController
InkHighlight highlightAt({
RenderBox referenceBox,
Color color,
Shape shape: Shape.rectangle,
BoxShape shape: BoxShape.rectangle,
VoidCallback onRemoved
}) {
_InkHighlight highlight = new _InkHighlight(
......@@ -423,7 +423,7 @@ class _InkHighlight extends InkFeature implements InkHighlight {
renderer.markNeedsPaint();
}
final Shape shape;
final BoxShape shape;
bool get active => _active;
bool _active = true;
......@@ -452,7 +452,7 @@ class _InkHighlight extends InkFeature implements InkHighlight {
}
void _paintHighlight(Canvas canvas, Rect rect, paint) {
if (shape == Shape.rectangle)
if (shape == BoxShape.rectangle)
canvas.drawRect(rect, paint);
else
canvas.drawCircle(rect.center, _kDefaultSplashRadius, paint);
......
......@@ -170,7 +170,8 @@ class _RenderSwitch extends RenderToggleable {
super.handleEvent(event, entry);
}
final BoxPainter _thumbPainter = new BoxPainter(const BoxDecoration());
Color _cachedThumbColor;
BoxPainter _thumbPainter;
void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas;
......@@ -200,11 +201,14 @@ class _RenderSwitch extends RenderToggleable {
paintRadialReaction(canvas, thumbOffset);
_thumbPainter.decoration = new BoxDecoration(
backgroundColor: thumbColor,
shape: Shape.circle,
boxShadow: elevationToShadow[1]
);
if (_cachedThumbColor != thumbColor) {
_thumbPainter = new BoxDecoration(
backgroundColor: thumbColor,
shape: BoxShape.circle,
boxShadow: elevationToShadow[1]
).createBoxPainter();
_cachedThumbColor = thumbColor;
}
// The thumb contracts slightly during the animation
double inset = 2.0 - (position.value - 0.5).abs() * 2.0;
......
......@@ -8,150 +8,10 @@ import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'basic_types.dart';
import 'decoration.dart';
import 'edge_dims.dart';
/// 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.
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)";
}
export 'edge_dims.dart' show EdgeDims;
/// A side of a border of a box
class BorderSide {
......@@ -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({
Canvas canvas,
Rect rect,
......@@ -697,6 +557,48 @@ void paintImage({
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.
class BackgroundImage {
BackgroundImage({
......@@ -735,11 +637,10 @@ class BackgroundImage {
final ImageResource _imageResource;
final List<VoidCallback> _listeners =
new List<VoidCallback>();
final List<VoidCallback> _listeners = <VoidCallback>[];
/// 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
// listener doesn't get callback synchronously if the image resource is
// already resolved.
......@@ -749,7 +650,7 @@ class BackgroundImage {
}
/// No longer call listener when the background image changes.
void removeChangeListener(VoidCallback listener) {
void _removeChangeListener(VoidCallback listener) {
_listeners.remove(listener);
// We need to remove ourselves as listeners from the _imageResource so that
// we're not kept alive by the image_cache.
......@@ -795,18 +696,22 @@ class BackgroundImage {
String toString() => 'BackgroundImage($fit, $repeat)';
}
// TODO(abarth): Rename to BoxShape?
/// A 2D geometrical shape
enum Shape {
/// An axis-aligned, 2D rectangle
/// The shape to use when rendering a BoxDecoration.
enum BoxShape {
/// An axis-aligned, 2D rectangle. May have rounded corners. The edges of the
/// rectangle will match the edges of the box into which the BoxDecoration is
/// painted.
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
}
/// An immutable description of how to paint a box
class BoxDecoration {
class BoxDecoration extends Decoration {
const BoxDecoration({
this.backgroundColor, // null = don't draw background color
this.backgroundImage, // null = don't draw background image
......@@ -814,9 +719,15 @@ class BoxDecoration {
this.borderRadius, // null = use more efficient background drawing; note that this must be null for circles
this.boxShadow, // null = don't draw shadows
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 is filled into the shape of the box (e.g., either a rectangle,
......@@ -841,9 +752,9 @@ class BoxDecoration {
final Gradient gradient;
/// 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) {
// TODO(abarth): Scale ALL the things.
return new 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.
static BoxDecoration lerp(BoxDecoration a, BoxDecoration b, double t) {
......@@ -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) {
if (identical(this, other))
return true;
......@@ -920,30 +869,32 @@ class BoxDecoration {
result.add('${prefix}boxShadow: ${boxShadow.map((BoxShadow shadow) => shadow.toString())}');
if (gradient != null)
result.add('${prefix}gradient: $gradient');
if (shape != Shape.rectangle)
if (shape != BoxShape.rectangle)
result.add('${prefix}shape: $shape');
if (result.isEmpty)
return '$prefix<no decorations specified>';
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
class BoxPainter {
BoxPainter(BoxDecoration decoration) : _decoration = decoration {
assert(decoration != null);
class _BoxDecorationPainter extends BoxPainter {
_BoxDecorationPainter(this._decoration) {
assert(_decoration != null);
}
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;
}
final BoxDecoration _decoration;
Paint _cachedBackgroundPaint;
Paint get _backgroundPaint {
......@@ -981,27 +932,19 @@ class BoxPainter {
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) {
switch (_decoration.shape) {
case Shape.circle:
case BoxShape.circle:
assert(_decoration.borderRadius == null);
Point center = rect.center;
double radius = rect.shortestSide / 2.0;
canvas.drawCircle(center, radius, paint);
break;
case Shape.rectangle:
case BoxShape.rectangle:
if (_decoration.borderRadius == null) {
canvas.drawRect(rect, paint);
} else {
double radius = _getEffectiveBorderRadius(rect);
double radius = _decoration.getEffectiveBorderRadius(rect);
canvas.drawRRect(new ui.RRect.fromRectXY(rect, radius, radius), paint);
}
break;
......@@ -1053,14 +996,14 @@ class BoxPainter {
_paintBorderWithRadius(canvas, rect);
return;
}
if (_decoration.shape == Shape.circle) {
if (_decoration.shape == BoxShape.circle) {
_paintBorderWithCircle(canvas, rect);
return;
}
}
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.right != null);
......@@ -1109,10 +1052,10 @@ class BoxPainter {
void _paintBorderWithRadius(ui.Canvas canvas, Rect rect) {
assert(_hasUniformBorder);
assert(_decoration.shape == Shape.rectangle);
assert(_decoration.shape == BoxShape.rectangle);
Color color = _decoration.border.top.color;
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 inner = new ui.RRect.fromRectXY(rect.deflate(width), radius - width, radius - width);
......@@ -1121,12 +1064,11 @@ class BoxPainter {
void _paintBorderWithCircle(ui.Canvas canvas, Rect rect) {
assert(_hasUniformBorder);
assert(_decoration.shape == Shape.circle);
assert(_decoration.shape == BoxShape.circle);
assert(_decoration.borderRadius == null);
double width = _decoration.border.top.width;
if (width <= 0.0) {
if (width <= 0.0)
return;
}
Paint paint = new Paint()
..color = _decoration.border.top.color
..strokeWidth = width
......@@ -1144,45 +1086,3 @@ class BoxPainter {
_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';
import 'debug.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
class _DebugSize extends Size {
......
......@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/painting.dart';
......@@ -13,13 +12,13 @@ import 'box.dart';
import 'debug.dart';
import 'object.dart';
export 'package:flutter/src/painting/box_painter.dart';
export 'package:flutter/gestures.dart' show
PointerEvent,
PointerDownEvent,
PointerMoveEvent,
PointerUpEvent,
PointerCancelEvent;
export 'package:flutter/painting.dart' show Decoration, BoxDecoration;
/// A base class for render objects that resemble their children.
///
......@@ -704,9 +703,11 @@ class RenderClipOval extends _RenderCustomClip<Rect> {
bool hitTest(HitTestResult result, { Point position }) {
Rect clipBounds = _clip;
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,
(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 super.hitTest(result, position: position);
}
......@@ -720,7 +721,7 @@ class RenderClipOval extends _RenderCustomClip<Rect> {
}
/// Where to paint a box decoration.
enum BoxDecorationPosition {
enum DecorationPosition {
/// Paint the box decoration behind the children.
background,
......@@ -728,97 +729,90 @@ enum BoxDecorationPosition {
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 {
RenderDecoratedBox({
BoxDecoration decoration,
RenderBox child,
BoxDecorationPosition position: BoxDecorationPosition.background
}) : _painter = new BoxPainter(decoration),
Decoration decoration,
DecorationPosition position: DecorationPosition.background,
RenderBox child
}) : _decoration = decoration,
_position = position,
super(child) {
assert(decoration != null);
assert(position != null);
}
/// Where to paint the box decoration.
BoxDecorationPosition get position => _position;
BoxDecorationPosition _position;
void set position (BoxDecorationPosition newPosition) {
assert(newPosition != null);
if (newPosition == _position)
return;
markNeedsPaint();
}
BoxPainter _painter;
/// What decoration to paint.
BoxDecoration get decoration => _painter.decoration;
void set decoration (BoxDecoration newDecoration) {
Decoration get decoration => _decoration;
Decoration _decoration;
void set decoration (Decoration newDecoration) {
assert(newDecoration != null);
if (newDecoration == _painter.decoration)
if (newDecoration == _decoration)
return;
_removeBackgroundImageListenerIfNeeded();
_painter.decoration = newDecoration;
_addBackgroundImageListenerIfNeeded();
_removeListenerIfNeeded();
_painter = null;
_decoration = newDecoration;
_addListenerIfNeeded();
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 {
return attached &&
_painter.decoration != null &&
_painter.decoration.backgroundImage != null;
bool get _needsListeners {
return attached && _decoration.needsListeners;
}
void _addBackgroundImageListenerIfNeeded() {
if (_needsBackgroundImageListener)
_painter.decoration.backgroundImage.addChangeListener(markNeedsPaint);
void _addListenerIfNeeded() {
if (_needsListeners)
_decoration.addChangeListener(markNeedsPaint);
}
void _removeBackgroundImageListenerIfNeeded() {
if (_needsBackgroundImageListener)
_painter.decoration.backgroundImage.removeChangeListener(markNeedsPaint);
void _removeListenerIfNeeded() {
if (_needsListeners)
_decoration.removeChangeListener(markNeedsPaint);
}
void attach() {
super.attach();
_addBackgroundImageListenerIfNeeded();
_addListenerIfNeeded();
}
void detach() {
_removeBackgroundImageListenerIfNeeded();
_removeListenerIfNeeded();
super.detach();
}
bool hitTestSelf(Point position) {
switch (_painter.decoration.shape) {
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;
}
return _decoration.hitTest(size, position);
}
void paint(PaintingContext context, Offset offset) {
assert(size.width != null);
assert(size.height != null);
if (position == BoxDecorationPosition.background)
_painter ??= _decoration.createBoxPainter();
if (position == DecorationPosition.background)
_painter.paint(context.canvas, offset & size);
super.paint(context, offset);
if (position == BoxDecorationPosition.foreground)
if (position == DecorationPosition.foreground)
_painter.paint(context.canvas, offset & size);
}
void debugDescribeSettings(List<String> settings) {
super.debugDescribeSettings(settings);
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> {
BoxConstraints lerp(double t) => BoxConstraints.lerp(begin, end, t);
}
class AnimatedBoxDecorationValue extends AnimatedValue<BoxDecoration> {
AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve, Curve reverseCurve })
class AnimatedDecorationValue extends AnimatedValue<Decoration> {
AnimatedDecorationValue(Decoration begin, { Decoration end, Curve curve, Curve 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> {
......@@ -62,14 +68,14 @@ class AnimatedContainer extends StatefulComponent {
assert(margin == null || margin.isNonNegative);
assert(padding == null || padding.isNonNegative);
assert(curve != null);
assert(duration != null);
assert(duration != null || decoration.debugAssertValid());
}
final Widget child;
final BoxConstraints constraints;
final BoxDecoration decoration;
final BoxDecoration foregroundDecoration;
final Decoration decoration;
final Decoration foregroundDecoration;
final EdgeDims margin;
final EdgeDims padding;
final Matrix4 transform;
......@@ -84,8 +90,8 @@ class AnimatedContainer extends StatefulComponent {
class _AnimatedContainerState extends State<AnimatedContainer> {
AnimatedBoxConstraintsValue _constraints;
AnimatedBoxDecorationValue _decoration;
AnimatedBoxDecorationValue _foregroundDecoration;
AnimatedDecorationValue _decoration;
AnimatedDecorationValue _foregroundDecoration;
AnimatedEdgeDimsValue _margin;
AnimatedEdgeDimsValue _padding;
AnimatedMatrix4Value _transform;
......@@ -167,7 +173,7 @@ class _AnimatedContainerState extends State<AnimatedContainer> {
}
if (config.decoration != null) {
_decoration ??= new AnimatedBoxDecorationValue(config.decoration);
_decoration ??= new AnimatedDecorationValue(config.decoration);
if (_configVariable(_decoration, config.decoration))
needsAnimation = true;
} else {
......@@ -175,7 +181,7 @@ class _AnimatedContainerState extends State<AnimatedContainer> {
}
if (config.foregroundDecoration != null) {
_foregroundDecoration ??= new AnimatedBoxDecorationValue(config.foregroundDecoration);
_foregroundDecoration ??= new AnimatedDecorationValue(config.foregroundDecoration);
if (_configVariable(_foregroundDecoration, config.foregroundDecoration))
needsAnimation = true;
} else {
......
......@@ -16,13 +16,15 @@ export 'package:flutter/rendering.dart' show
BorderSide,
BoxConstraints,
BoxDecoration,
BoxDecorationPosition,
BoxShadow,
BoxShape,
Canvas,
Color,
ColorFilter,
CustomClipper,
CustomPainter,
Decoration,
DecorationPosition,
EdgeDims,
FlexAlignItems,
FlexDirection,
......@@ -51,7 +53,6 @@ export 'package:flutter/rendering.dart' show
RadialGradient,
Rect,
ScrollDirection,
Shape,
Size,
StyledTextSpan,
TextAlign,
......@@ -135,7 +136,7 @@ class DecoratedBox extends OneChildRenderObjectWidget {
DecoratedBox({
Key key,
this.decoration,
this.position: BoxDecorationPosition.background,
this.position: DecorationPosition.background,
Widget child
}) : super(key: key, child: child) {
assert(decoration != null);
......@@ -143,10 +144,10 @@ class DecoratedBox extends OneChildRenderObjectWidget {
}
/// What decoration to paint.
final BoxDecoration decoration;
final Decoration decoration;
/// Where to paint the box decoration.
final BoxDecorationPosition position;
final DecorationPosition position;
RenderDecoratedBox createRenderObject() => new RenderDecoratedBox(decoration: decoration, position: position);
......@@ -782,26 +783,26 @@ class Container extends StatelessComponent {
}) : super(key: key) {
assert(margin == null || margin.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 BoxConstraints constraints;
final BoxDecoration decoration;
final BoxDecoration foregroundDecoration;
final Decoration decoration;
final Decoration foregroundDecoration;
final EdgeDims margin;
final EdgeDims padding;
final Matrix4 transform;
final double width;
final double height;
EdgeDims get _paddingIncludingBorder {
if (decoration == null || decoration.border == null)
EdgeDims get _paddingIncludingDecoration {
if (decoration == null || decoration.padding == null)
return padding;
EdgeDims borderPadding = decoration.border.dimensions;
EdgeDims decorationPadding = decoration.padding;
if (padding == null)
return borderPadding;
return padding + borderPadding;
return decorationPadding;
return padding + decorationPadding;
}
Widget build(BuildContext context) {
......@@ -810,7 +811,7 @@ class Container extends StatelessComponent {
if (child == null && (width == null || height == null))
current = new ConstrainedBox(constraints: const BoxConstraints.expand());
EdgeDims effectivePadding = _paddingIncludingBorder;
EdgeDims effectivePadding = _paddingIncludingDecoration;
if (effectivePadding != null)
current = new Padding(padding: effectivePadding, child: current);
......@@ -820,7 +821,7 @@ class Container extends StatelessComponent {
if (foregroundDecoration != null) {
current = new DecoratedBox(
decoration: foregroundDecoration,
position: BoxDecorationPosition.foreground,
position: DecorationPosition.foreground,
child: current
);
}
......
......@@ -21,6 +21,8 @@ void main() {
backgroundColor: new Color(0xFF0000FF)
);
BoxDecoration actualDecoration;
tester.pumpWidget(
new AnimatedContainer(
key: key,
......@@ -30,7 +32,8 @@ void main() {
);
RenderDecoratedBox box = key.currentState.context.findRenderObject();
expect(box.decoration.backgroundColor, equals(decorationA.backgroundColor));
actualDecoration = box.decoration;
expect(actualDecoration.backgroundColor, equals(decorationA.backgroundColor));
tester.pumpWidget(
new AnimatedContainer(
......@@ -41,11 +44,13 @@ void main() {
);
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));
expect(box.decoration.backgroundColor, equals(decorationB.backgroundColor));
actualDecoration = box.decoration;
expect(actualDecoration.backgroundColor, equals(decorationB.backgroundColor));
});
});
......
......@@ -14,7 +14,7 @@ void main() {
new Container(
padding: new EdgeDims.all(50.0),
decoration: new BoxDecoration(
shape: Shape.circle,
shape: BoxShape.circle,
border: new Border.all(width: 10.0, color: const Color(0x80FF00FF)),
backgroundColor: Colors.teal[600]
)
......
......@@ -27,7 +27,7 @@ void main() {
expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationA));
expect(renderObject.position, equals(BoxDecorationPosition.background));
expect(renderObject.position, equals(DecorationPosition.background));
tester.pumpWidget(new DecoratedBox(decoration: kBoxDecorationB));
element = tester.findElement((Element element) => element is OneChildRenderObjectElement);
......@@ -35,7 +35,7 @@ void main() {
expect(element.renderObject is RenderDecoratedBox, isTrue);
renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationB));
expect(renderObject.position, equals(BoxDecorationPosition.background));
expect(renderObject.position, equals(DecorationPosition.background));
});
});
......@@ -49,12 +49,12 @@ void main() {
expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationA));
expect(renderObject.position, equals(BoxDecorationPosition.background));
expect(renderObject.position, equals(DecorationPosition.background));
expect(renderObject.child, isNotNull);
expect(renderObject.child is RenderDecoratedBox, isTrue);
RenderDecoratedBox child = renderObject.child;
expect(child.decoration, equals(kBoxDecorationB));
expect(child.position, equals(BoxDecorationPosition.background));
expect(child.position, equals(DecorationPosition.background));
expect(child.child, isNull);
}
......@@ -65,7 +65,7 @@ void main() {
expect(element.renderObject is RenderDecoratedBox, isTrue);
RenderDecoratedBox renderObject = element.renderObject;
expect(renderObject.decoration, equals(kBoxDecorationA));
expect(renderObject.position, equals(BoxDecorationPosition.background));
expect(renderObject.position, equals(DecorationPosition.background));
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