Commit ba8f6a01 authored by Adam Barth's avatar Adam Barth

Merge pull request #510 from abarth/custom_paint

Callback identity is too fragile for CustomPaint
parents ea6292e1 72329cf4
......@@ -6,6 +6,35 @@ import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
class _BaselinePainter extends CustomPainter {
const _BaselinePainter({
this.paragraph
});
final RenderParagraph paragraph;
void paint(Canvas canvas, Size size) {
double baseline = paragraph.getDistanceToBaseline(TextBaseline.alphabetic);
double w = paragraph.getMaxIntrinsicWidth(new BoxConstraints.loose(size));
double h = paragraph.getMaxIntrinsicHeight(new BoxConstraints.loose(size));
Path path = new Path();
path.moveTo(0.0, 0.0);
path.lineTo(w, 0.0);
path.moveTo(0.0, baseline);
path.lineTo(w, baseline);
path.moveTo(0.0, h);
path.lineTo(w, h);
Paint paint = new Paint()
..color = const Color(0xFFFF9000)
..style = ui.PaintingStyle.stroke
..strokeWidth = 3.0;
canvas.drawPath(path, paint);
}
// TODO(abarth): We have no good way of detecting when the paragraph's intrinsic dimensions change.
bool shouldRepaint(_BaselinePainter oldPainter) => true;
}
RenderBox getBox(double lh) {
RenderParagraph paragraph = new RenderParagraph(
new StyledTextSpan(
......@@ -36,24 +65,10 @@ RenderBox getBox(double lh) {
child: new RenderPadding(
padding: new EdgeDims.all(10.0),
child: new RenderCustomPaint(
child: paragraph,
onPaint: (PaintingCanvas canvas, Size size) {
double baseline = paragraph.getDistanceToBaseline(TextBaseline.alphabetic);
double w = paragraph.getMaxIntrinsicWidth(new BoxConstraints.loose(size));
double h = paragraph.getMaxIntrinsicHeight(new BoxConstraints.loose(size));
Path path = new Path();
path.moveTo(0.0, 0.0);
path.lineTo(w, 0.0);
path.moveTo(0.0, baseline);
path.lineTo(w, baseline);
path.moveTo(0.0, h);
path.lineTo(w, h);
Paint paint = new Paint()
..color = const Color(0xFFFF9000)
..style = ui.PaintingStyle.stroke
..strokeWidth = 3.0;
canvas.drawPath(path, paint);
}
painter: new _BaselinePainter(
paragraph: paragraph
),
child: paragraph
)
)
)
......
......@@ -4,6 +4,50 @@
part of stocks;
class StockArrowPainter extends CustomPainter {
StockArrowPainter({ this.color, this.percentChange });
final Color color;
final double percentChange;
void paint(PaintingCanvas canvas, Size size) {
Paint paint = new Paint()..color = color;
paint.strokeWidth = 1.0;
const double padding = 2.0;
assert(padding > paint.strokeWidth / 2.0); // make sure the circle remains inside the box
double r = (size - padding) / 2.0; // radius of the circle
double centerX = padding + r;
double centerY = padding + r;
// Draw the arrow.
double w = 8.0;
double h = 5.0;
double arrowY;
if (percentChange < 0.0) {
h = -h;
arrowY = centerX + 1.0;
} else {
arrowY = centerX - 1.0;
}
Path path = new Path();
path.moveTo(centerX, arrowY - h); // top of the arrow
path.lineTo(centerX + w, arrowY + h);
path.lineTo(centerX - w, arrowY + h);
path.close();
paint.style = ui.PaintingStyle.fill;
canvas.drawPath(path, paint);
// Draw a circle that circumscribes the arrow.
paint.style = ui.PaintingStyle.stroke;
canvas.drawCircle(new Point(centerX, centerY), r, paint);
}
bool shouldRepaint(StockArrowPainter oldPainter) {
return oldPainter.color != color
|| oldPainter.percentChange != percentChange;
}
}
class StockArrow extends StatelessComponent {
StockArrow({ Key key, this.percentChange }) : super(key: key);
......@@ -22,46 +66,17 @@ class StockArrow extends StatelessComponent {
}
Widget build(BuildContext context) {
// TODO(jackson): This should change colors with the theme
Color color = _colorForPercentChange(percentChange);
const double kSize = 40.0;
var arrow = new CustomPaint(onPaint: (ui.Canvas canvas, Size size) {
Paint paint = new Paint()..color = color;
paint.strokeWidth = 1.0;
const double padding = 2.0;
assert(padding > paint.strokeWidth / 2.0); // make sure the circle remains inside the box
double r = (kSize - padding) / 2.0; // radius of the circle
double centerX = padding + r;
double centerY = padding + r;
// Draw the arrow.
double w = 8.0;
double h = 5.0;
double arrowY;
if (percentChange < 0.0) {
h = -h;
arrowY = centerX + 1.0;
} else {
arrowY = centerX - 1.0;
}
Path path = new Path();
path.moveTo(centerX, arrowY - h); // top of the arrow
path.lineTo(centerX + w, arrowY + h);
path.lineTo(centerX - w, arrowY + h);
path.close();
paint.style = ui.PaintingStyle.fill;
canvas.drawPath(path, paint);
// Draw a circle that circumscribes the arrow.
paint.style = ui.PaintingStyle.stroke;
canvas.drawCircle(new Point(centerX, centerY), r, paint);
});
return new Container(
child: arrow,
width: kSize,
height: kSize,
margin: const EdgeDims.symmetric(horizontal: 5.0)
width: 40.0,
height: 40.0,
margin: const EdgeDims.symmetric(horizontal: 5.0),
child: new CustomPaint(
painter: new StockArrowPainter(
// TODO(jackson): This should change colors with the theme
color: _colorForPercentChange(percentChange),
percentChange: percentChange
)
)
);
}
}
......@@ -9,42 +9,49 @@ class ScaleApp extends StatefulComponent {
ScaleAppState createState() => new ScaleAppState();
}
class GesturesDemoPaintToken {
GesturesDemoPaintToken(this.zoom, this.offset, this.swatch, this.forward, this.flag1, this.flag2, this.flag3, this.flag4);
final Offset offset;
class _GesturePainter extends CustomPainter {
const _GesturePainter({
this.zoom,
this.offset,
this.swatch,
this.forward,
this.scaleEnabled,
this.tapEnabled,
this.doubleTapEnabled,
this.longPressEnabled
});
final double zoom;
final Offset offset;
final Map<int, Color> swatch;
final bool forward;
final bool flag1;
final bool flag2;
final bool flag3;
final bool flag4;
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (other is! GesturesDemoPaintToken)
return false;
final GesturesDemoPaintToken typedOther = other;
return offset == typedOther.offset &&
zoom == typedOther.zoom &&
identical(swatch, typedOther.swatch) &&
forward == typedOther.forward &&
flag1 == typedOther.flag1 &&
flag2 == typedOther.flag2 &&
flag3 == typedOther.flag3 &&
flag4 == typedOther.flag4;
final bool scaleEnabled;
final bool tapEnabled;
final bool doubleTapEnabled;
final bool longPressEnabled;
void paint(PaintingCanvas canvas, Size size) {
Point center = (size.center(Point.origin).toOffset() * zoom + offset).toPoint();
double radius = size.width / 2.0 * zoom;
Gradient gradient = new RadialGradient(
center: center, radius: radius,
colors: forward ? <Color>[swatch[50], swatch[900]]
: <Color>[swatch[900], swatch[50]]
);
Paint paint = new Paint()
..shader = gradient.createShader();
canvas.drawCircle(center, radius, paint);
}
int get hashCode {
int value = 373;
value = 37 * value + offset.hashCode;
value = 37 * value + zoom.hashCode;
value = 37 * value + identityHashCode(swatch);
value = 37 * value + forward.hashCode;
value = 37 * value + flag1.hashCode;
value = 37 * value + flag2.hashCode;
value = 37 * value + flag3.hashCode;
value = 37 * value + flag4.hashCode;
return value;
bool shouldRepaint(_GesturePainter oldPainter) {
return oldPainter.zoom != zoom
|| oldPainter.offset != offset
|| oldPainter.swatch != swatch
|| oldPainter.forward != forward
|| oldPainter.scaleEnabled != scaleEnabled
|| oldPainter.tapEnabled != tapEnabled
|| oldPainter.doubleTapEnabled != doubleTapEnabled
|| oldPainter.longPressEnabled != longPressEnabled;
}
}
......@@ -124,18 +131,6 @@ class ScaleAppState extends State<ScaleApp> {
});
}
void paint(PaintingCanvas canvas, Size size) {
Point center = (size.center(Point.origin).toOffset() * _zoom + _offset).toPoint();
double radius = size.width / 2.0 * _zoom;
Gradient gradient = new RadialGradient(
center: center, radius: radius,
colors: _forward ? <Color>[_swatch[50], _swatch[900]]
: <Color>[_swatch[900], _swatch[50]]
);
Paint paint = new Paint()
..shader = gradient.createShader();
canvas.drawCircle(center, radius, paint);
}
Widget build(BuildContext context) {
return new Theme(
......@@ -151,10 +146,16 @@ class ScaleAppState extends State<ScaleApp> {
onDoubleTap: _doubleTapEnabled ? _handleScaleReset : null,
onLongPress: _longPressEnabled ? _handleDirectionChange : null,
child: new CustomPaint(
onPaint: paint,
token: new GesturesDemoPaintToken(_zoom, _offset, _swatch, _forward,
_scaleEnabled, _tapEnabled, _doubleTapEnabled,
_longPressEnabled)
painter: new _GesturePainter(
zoom: _zoom,
offset: _offset,
swatch: _swatch,
forward: _forward,
scaleEnabled: _scaleEnabled,
tapEnabled: _tapEnabled,
doubleTapEnabled: _doubleTapEnabled,
longPressEnabled: _longPressEnabled
)
)
),
new Positioned(
......
......@@ -19,19 +19,16 @@ class CardModel {
enum MarkerType { topLeft, bottomRight, touch }
class Marker extends StatelessComponent {
Marker({
this.type: MarkerType.touch,
this.position,
this.size: 40.0,
Key key
}) : super(key: key);
class _MarkerPainter extends CustomPainter {
const _MarkerPainter({
this.size,
this.type
});
final Point position;
final double size;
final MarkerType type;
void paintMarker(PaintingCanvas canvas, _) {
void paint(PaintingCanvas canvas, _) {
Paint paint = new Paint()..color = const Color(0x8000FF00);
double r = size / 2.0;
canvas.drawCircle(new Point(r, r), r, paint);
......@@ -50,6 +47,24 @@ class Marker extends StatelessComponent {
}
}
bool shouldRepaint(_MarkerPainter oldPainter) {
return oldPainter.size != size
|| oldPainter.type != type;
}
}
class Marker extends StatelessComponent {
Marker({
this.type: MarkerType.touch,
this.position,
this.size: 40.0,
Key key
}) : super(key: key);
final Point position;
final double size;
final MarkerType type;
Widget build(BuildContext context) {
return new Positioned(
left: position.x - size / 2.0,
......@@ -57,7 +72,12 @@ class Marker extends StatelessComponent {
width: size,
height: size,
child: new IgnorePointer(
child: new CustomPaint(onPaint: paintMarker)
child: new CustomPaint(
painter: new _MarkerPainter(
size: size,
type: type
)
)
)
);
}
......
......@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/animation.dart';
import 'package:flutter/painting.dart';
......@@ -21,6 +20,42 @@ const EdgeDims _kMenuHorizontalPadding = const EdgeDims.only(left: 36.0, right:
const double _kBaselineOffsetFromBottom = 20.0;
const Border _kDropdownUnderline = const Border(bottom: const BorderSide(color: const Color(0xFFBDBDBD), width: 2.0));
class _DropdownMenuPainter extends CustomPainter {
const _DropdownMenuPainter({
this.color,
this.elevation,
this.menuTop,
this.menuBottom,
this.renderBox
});
final Color color;
final int elevation;
final double menuTop;
final double menuBottom;
final RenderBox renderBox;
void paint(Canvas canvas, Size size) {
final BoxPainter painter = new BoxPainter(new BoxDecoration(
backgroundColor: color,
borderRadius: 2.0,
boxShadow: elevationToShadow[elevation]
));
double top = renderBox.globalToLocal(new Point(0.0, menuTop)).y;
double bottom = renderBox.globalToLocal(new Point(0.0, menuBottom)).y;
painter.paint(canvas, new Rect.fromLTRB(0.0, top, size.width, bottom));
}
bool shouldRepaint(_DropdownMenuPainter oldPainter) {
return oldPainter.color != color
|| oldPainter.elevation != elevation
|| oldPainter.menuTop != menuTop
|| oldPainter.menuBottom != menuBottom
|| oldPainter.renderBox != renderBox;
}
}
class _DropdownMenu extends StatusTransitionComponent {
_DropdownMenu({
Key key,
......@@ -82,12 +117,6 @@ class _DropdownMenu extends StatusTransitionComponent {
reverseCurve: const Interval(0.0, 0.001)
);
final BoxPainter menuPainter = new BoxPainter(new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor,
borderRadius: 2.0,
boxShadow: elevationToShadow[route.elevation]
));
final RenderBox renderBox = Navigator.of(context).context.findRenderObject();
final Size navigatorSize = renderBox.size;
final RelativeRect menuRect = new RelativeRect.fromSize(route.rect, navigatorSize);
......@@ -105,14 +134,15 @@ class _DropdownMenu extends StatusTransitionComponent {
performance: route.performance,
variables: <AnimatedValue<double>>[menuTop, menuBottom],
builder: (BuildContext context) {
RenderBox renderBox = context.findRenderObject();
return new CustomPaint(
child: new Block(children),
onPaint: (ui.Canvas canvas, Size size) {
double top = renderBox.globalToLocal(new Point(0.0, menuTop.value)).y;
double bottom = renderBox.globalToLocal(new Point(0.0, menuBottom.value)).y;
menuPainter.paint(canvas, new Rect.fromLTRB(0.0, top, size.width, bottom));
}
painter: new _DropdownMenuPainter(
color: Theme.of(context).canvasColor,
elevation: route.elevation,
menuTop: menuTop.value,
menuBottom: menuBottom.value,
renderBox: context.findRenderObject()
),
child: new Block(children)
);
}
)
......
......@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/animation.dart';
import 'package:flutter/painting.dart';
......@@ -22,6 +21,38 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuHorizontalPadding = 16.0;
const double _kMenuVerticalPadding = 8.0;
class _PopupMenuPainter extends CustomPainter {
const _PopupMenuPainter({
this.color,
this.elevation,
this.width,
this.height
});
final Color color;
final int elevation;
final double width;
final double height;
void paint(Canvas canvas, Size size) {
double widthValue = width * size.width;
double heightValue = height * size.height;
final BoxPainter painter = new BoxPainter(new BoxDecoration(
backgroundColor: color,
borderRadius: 2.0,
boxShadow: elevationToShadow[elevation]
));
painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue));
}
bool shouldRepaint(_PopupMenuPainter oldPainter) {
return oldPainter.color != color
|| oldPainter.elevation != elevation
|| oldPainter.width != width
|| oldPainter.height != height;
}
}
class _PopupMenu extends StatelessComponent {
_PopupMenu({
Key key,
......@@ -31,12 +62,6 @@ class _PopupMenu extends StatelessComponent {
final _MenuRoute route;
Widget build(BuildContext context) {
final BoxPainter painter = new BoxPainter(new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor,
borderRadius: 2.0,
boxShadow: elevationToShadow[route.elevation]
));
double unit = 1.0 / (route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
List<Widget> children = <Widget>[];
......@@ -64,11 +89,12 @@ class _PopupMenu extends StatelessComponent {
return new Opacity(
opacity: opacity.value,
child: new CustomPaint(
onPaint: (ui.Canvas canvas, Size size) {
double widthValue = width.value * size.width;
double heightValue = height.value * size.height;
painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue));
},
painter: new _PopupMenuPainter(
color: Theme.of(context).canvasColor,
elevation: route.elevation,
width: width.value,
height: height.value
),
child: new ConstrainedBox(
constraints: new BoxConstraints(
minWidth: _kMenuMinWidth,
......
......@@ -26,7 +26,6 @@ abstract class ProgressIndicator extends StatefulComponent {
Color _getBackgroundColor(BuildContext context) => Theme.of(context).primarySwatch[200];
Color _getValueColor(BuildContext context) => Theme.of(context).primaryColor;
Object _getCustomPaintToken(double performanceValue) => value != null ? value : performanceValue;
Widget _buildIndicator(BuildContext context, double performanceValue);
......@@ -74,19 +73,26 @@ class _ProgressIndicatorState extends State<ProgressIndicator> {
}
}
class LinearProgressIndicator extends ProgressIndicator {
LinearProgressIndicator({
Key key,
double value
}) : super(key: key, value: value);
class _LinearProgressIndicatorPainter extends CustomPainter {
const _LinearProgressIndicatorPainter({
this.backgroundColor,
this.valueColor,
this.value,
this.performanceValue
});
final Color backgroundColor;
final Color valueColor;
final double value;
final double performanceValue;
void _paint(BuildContext context, double performanceValue, Canvas canvas, Size size) {
void paint(Canvas canvas, Size size) {
Paint paint = new Paint()
..color = _getBackgroundColor(context)
..color = backgroundColor
..style = ui.PaintingStyle.fill;
canvas.drawRect(Point.origin & size, paint);
paint.color = _getValueColor(context);
paint.color = valueColor;
if (value != null) {
double width = value.clamp(0.0, 1.0) * size.width;
canvas.drawRect(Point.origin & new Size(width, size.height), paint);
......@@ -99,6 +105,20 @@ class LinearProgressIndicator extends ProgressIndicator {
}
}
bool shouldRepaint(_LinearProgressIndicatorPainter oldPainter) {
return oldPainter.backgroundColor != backgroundColor
|| oldPainter.valueColor != valueColor
|| oldPainter.value != value
|| oldPainter.performanceValue != performanceValue;
}
}
class LinearProgressIndicator extends ProgressIndicator {
LinearProgressIndicator({
Key key,
double value
}) : super(key: key, value: value);
Widget _buildIndicator(BuildContext context, double performanceValue) {
return new Container(
constraints: new BoxConstraints.tightFor(
......@@ -106,30 +126,37 @@ class LinearProgressIndicator extends ProgressIndicator {
height: _kLinearProgressIndicatorHeight
),
child: new CustomPaint(
token: _getCustomPaintToken(performanceValue),
onPaint: (Canvas canvas, Size size) {
_paint(context, performanceValue, canvas, size);
}
painter: new _LinearProgressIndicatorPainter(
backgroundColor: _getBackgroundColor(context),
valueColor: _getValueColor(context),
value: value,
performanceValue: performanceValue
)
)
);
}
}
class CircularProgressIndicator extends ProgressIndicator {
class _CircularProgressIndicatorPainter extends CustomPainter {
static const _kTwoPI = math.PI * 2.0;
static const _kEpsilon = .0000001;
// Canavs.drawArc(r, 0, 2*PI) doesn't draw anything, so just get close.
static const _kSweep = _kTwoPI - _kEpsilon;
static const _kStartAngle = -math.PI / 2.0;
CircularProgressIndicator({
Key key,
double value
}) : super(key: key, value: value);
const _CircularProgressIndicatorPainter({
this.valueColor,
this.value,
this.performanceValue
});
final Color valueColor;
final double value;
final double performanceValue;
void _paint(BuildContext context, double performanceValue, Canvas canvas, Size size) {
void paint(Canvas canvas, Size size) {
Paint paint = new Paint()
..color = _getValueColor(context)
..color = valueColor
..strokeWidth = _kCircularProgressIndicatorStrokeWidth
..style = ui.PaintingStyle.stroke;
......@@ -149,6 +176,19 @@ class CircularProgressIndicator extends ProgressIndicator {
}
}
bool shouldRepaint(_LinearProgressIndicatorPainter oldPainter) {
return oldPainter.valueColor != valueColor
|| oldPainter.value != value
|| oldPainter.performanceValue != performanceValue;
}
}
class CircularProgressIndicator extends ProgressIndicator {
CircularProgressIndicator({
Key key,
double value
}) : super(key: key, value: value);
Widget _buildIndicator(BuildContext context, double performanceValue) {
return new Container(
constraints: new BoxConstraints(
......@@ -156,10 +196,11 @@ class CircularProgressIndicator extends ProgressIndicator {
minHeight: _kMinCircularProgressIndicatorSize
),
child: new CustomPaint(
token: _getCustomPaintToken(performanceValue),
onPaint: (Canvas canvas, Size size) {
_paint(context, performanceValue, canvas, size);
}
painter: new _CircularProgressIndicatorPainter(
valueColor: _getValueColor(context),
value: value,
performanceValue: performanceValue
)
)
);
}
......
......@@ -9,6 +9,42 @@ import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'theme.dart';
const double _kDiameter = 16.0;
const double _kOuterRadius = _kDiameter / 2.0;
const double _kInnerRadius = 5.0;
class _RadioPainter extends CustomPainter {
const _RadioPainter({
this.color,
this.selected
});
final Color color;
final bool selected;
void paint(Canvas canvas, Size size) {
// TODO(ianh): ink radial reaction
// Draw the outer circle
Paint paint = new Paint()
..color = color
..style = ui.PaintingStyle.stroke
..strokeWidth = 2.0;
canvas.drawCircle(const Point(_kOuterRadius, _kOuterRadius), _kOuterRadius, paint);
// Draw the inner circle
if (selected) {
paint.style = ui.PaintingStyle.fill;
canvas.drawCircle(const Point(_kOuterRadius, _kOuterRadius), _kInnerRadius, paint);
}
}
bool shouldRepaint(_RadioPainter oldPainter) {
return oldPainter.color != color
|| oldPainter.selected != selected;
}
}
class Radio<T> extends StatelessComponent {
Radio({
Key key,
......@@ -33,34 +69,17 @@ class Radio<T> extends StatelessComponent {
}
Widget build(BuildContext context) {
const double kDiameter = 16.0;
const double kOuterRadius = kDiameter / 2;
const double kInnerRadius = 5.0;
return new GestureDetector(
onTap: enabled ? () => onChanged(value) : null,
child: new Container(
margin: const EdgeDims.symmetric(horizontal: 5.0),
width: kDiameter,
height: kDiameter,
width: _kDiameter,
height: _kDiameter,
child: new CustomPaint(
onPaint: (Canvas canvas, Size size) {
// TODO(ianh): ink radial reaction
// Draw the outer circle
Paint paint = new Paint()
..color = _getColor(context)
..style = ui.PaintingStyle.stroke
..strokeWidth = 2.0;
canvas.drawCircle(const Point(kOuterRadius, kOuterRadius), kOuterRadius, paint);
// Draw the inner circle
if (value == groupValue) {
paint.style = ui.PaintingStyle.fill;
canvas.drawCircle(const Point(kOuterRadius, kOuterRadius), kInnerRadius, paint);
}
}
painter: new _RadioPainter(
color: _getColor(context),
selected: value == groupValue
)
)
)
);
......
......@@ -913,9 +913,13 @@ class RenderSizeObserver extends RenderProxyBox {
}
}
/// Called when its time to paint into the given canvas
typedef void CustomPaintCallback(PaintingCanvas canvas, Size size);
typedef bool CustomHitTestCallback(Point position);
abstract class CustomPainter {
const CustomPainter();
void paint(PaintingCanvas canvas, Size size);
bool shouldRepaint(CustomPainter oldDelegate);
bool hitTest(Point position) => true;
}
/// Delegates its painting to [onPaint]
///
......@@ -932,51 +936,47 @@ typedef bool CustomHitTestCallback(Point position);
class RenderCustomPaint extends RenderProxyBox {
RenderCustomPaint({
CustomPaintCallback onPaint,
this.onHitTest,
CustomPainter painter,
RenderBox child
}) : super(child) {
assert(onPaint != null);
_onPaint = onPaint;
}) : _painter = painter, super(child) {
assert(painter != null);
}
/// The callback to which this render object delegates its painting
///
/// The callback must be non-null whenever the render object is attached to
/// the render tree.
CustomPaintCallback get onPaint => _onPaint;
CustomPaintCallback _onPaint;
void set onPaint (CustomPaintCallback newCallback) {
assert(newCallback != null || !attached);
if (_onPaint == newCallback)
CustomPainter get painter => _painter;
CustomPainter _painter;
void set painter (CustomPainter newPainter) {
assert(newPainter != null || !attached);
if (_painter == newPainter)
return;
_onPaint = newCallback;
markNeedsPaint();
CustomPainter oldPainter = _painter;
_painter = newPainter;
if (newPainter == null)
return;
if (oldPainter == null
|| newPainter.runtimeType != oldPainter.runtimeType
|| newPainter.shouldRepaint(oldPainter))
markNeedsPaint();
}
CustomHitTestCallback onHitTest;
void attach() {
assert(_onPaint != null);
assert(_painter != null);
super.attach();
}
bool hitTestSelf(Point position) {
return onHitTest == null || onHitTest(position);
return _painter.hitTest(position);
}
void paint(PaintingContext context, Offset offset) {
assert(_onPaint != null);
assert(_painter != null);
context.canvas.translate(offset.dx, offset.dy);
_onPaint(context.canvas, size);
// TODO(abarth): We should translate back before calling super because in
// the future, super.paint might switch our compositing layer.
super.paint(context, Offset.zero);
_painter.paint(context.canvas, size);
context.canvas.translate(-offset.dx, -offset.dy);
super.paint(context, offset);
}
}
typedef void PointerEventListener(PointerInputEvent e);
typedef void PointerEventListener(PointerInputEvent event);
enum HitTestBehavior {
deferToChild,
......
......@@ -21,6 +21,7 @@ export 'package:flutter/rendering.dart' show
Canvas,
Color,
ColorFilter,
CustomPainter,
EdgeDims,
FlexAlignItems,
FlexDirection,
......@@ -136,36 +137,21 @@ class DecoratedBox extends OneChildRenderObjectWidget {
}
class CustomPaint extends OneChildRenderObjectWidget {
CustomPaint({ Key key, this.onPaint, this.onHitTest, this.token, Widget child })
CustomPaint({ Key key, this.painter, Widget child })
: super(key: key, child: child) {
assert(onPaint != null);
assert(painter != null);
}
/// This widget repaints whenver you supply a new onPaint callback.
///
/// If you use an anonymous closure for the onPaint callback, you'll trigger
/// a repaint every time you build this widget, which might not be what you
/// intend. Instead, consider passing a reference to a member function, which
/// has a more stable identity.
final CustomPaintCallback onPaint;
final CustomHitTestCallback onHitTest;
final CustomPainter painter;
/// This widget repaints whenever you supply a new token.
final Object token;
RenderCustomPaint createRenderObject() => new RenderCustomPaint(onPaint: onPaint, onHitTest: onHitTest);
RenderCustomPaint createRenderObject() => new RenderCustomPaint(painter: painter);
void updateRenderObject(RenderCustomPaint renderObject, CustomPaint oldWidget) {
if (oldWidget.token != token)
renderObject.markNeedsPaint();
renderObject.onPaint = onPaint;
renderObject.onHitTest = onHitTest;
renderObject.painter = painter;
}
void didUnmountRenderObject(RenderCustomPaint renderObject) {
renderObject.onPaint = null;
renderObject.onHitTest = null;
renderObject.painter = null;
}
}
......
......@@ -7,6 +7,34 @@ import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'framework.dart';
class _GridPaperPainter extends CustomPainter {
const _GridPaperPainter({
this.color,
this.interval
});
final Color color;
final double interval;
void paint(Canvas canvas, Size size) {
Paint linePaint = new Paint()
..color = color;
for (double x = 0.0; x <= size.width; x += interval / 10.0) {
linePaint.strokeWidth = (x % interval == 0.0) ? 1.0 : (x % (interval / 2.0) == 0.0) ? 0.5: 0.25;
canvas.drawLine(new Point(x, 0.0), new Point(x, size.height), linePaint);
}
for (double y = 0.0; y <= size.height; y += interval / 10.0) {
linePaint.strokeWidth = (y % interval == 0.0) ? 1.0 : (y % (interval / 2.0) == 0.0) ? 0.5: 0.25;
canvas.drawLine(new Point(0.0, y), new Point(size.width, y), linePaint);
}
}
bool shouldRepaint(_GridPaperPainter oldPainter) {
return oldPainter.color != color
|| oldPainter.interval != interval;
}
}
class GridPaper extends StatelessComponent {
GridPaper({
Key key,
......@@ -18,19 +46,13 @@ class GridPaper extends StatelessComponent {
final double interval;
Widget build(BuildContext context) {
return new IgnorePointer(child: new CustomPaint(
onPaint: (PaintingCanvas canvas, Size size) {
Paint linePaint = new Paint()
..color = color;
for (double x = 0.0; x <= size.width; x += interval / 10.0) {
linePaint.strokeWidth = (x % interval == 0.0) ? 1.0 : (x % (interval / 2.0) == 0.0) ? 0.5: 0.25;
canvas.drawLine(new Point(x, 0.0), new Point(x, size.height), linePaint);
}
for (double y = 0.0; y <= size.height; y += interval / 10.0) {
linePaint.strokeWidth = (y % interval == 0.0) ? 1.0 : (y % (interval / 2.0) == 0.0) ? 0.5: 0.25;
canvas.drawLine(new Point(0.0, y), new Point(size.width, y), linePaint);
}
})
return new IgnorePointer(
child: new CustomPaint(
painter: new _GridPaperPainter(
color: color,
interval: interval
)
)
);
}
}
......@@ -13,12 +13,12 @@ void main() {
RenderBox root = new RenderViewport(
child: new RenderOffStage(
child: new RenderCustomPaint(
painter: new TestCallbackPainter(
onPaint: () { painted = true; }
),
child: child = new RenderConstrainedBox(
additionalConstraints: new BoxConstraints.tightFor(height: 10.0, width: 10.0)
),
onPaint: (PaintingCanvas canvas, Size size) {
painted = true;
}
)
)
)
);
......
......@@ -12,10 +12,12 @@ void main() {
root = new RenderPositionedBox(
child: new RenderCustomPaint(
child: child = text = new RenderParagraph(new PlainTextSpan('Hello World')),
onPaint: (PaintingCanvas canvas, Size size) {
baseline1 = child.getDistanceToBaseline(TextBaseline.alphabetic);
height1 = text.size.height;
}
painter: new TestCallbackPainter(
onPaint: () {
baseline1 = child.getDistanceToBaseline(TextBaseline.alphabetic);
height1 = text.size.height;
}
)
)
);
layout(root, phase: EnginePhase.paint);
......@@ -26,10 +28,12 @@ void main() {
child: text = new RenderParagraph(new PlainTextSpan('Hello World')),
maxHeight: height1 / 2.0
),
onPaint: (PaintingCanvas canvas, Size size) {
baseline2 = child.getDistanceToBaseline(TextBaseline.alphabetic);
height2 = text.size.height;
}
painter: new TestCallbackPainter(
onPaint: () {
baseline2 = child.getDistanceToBaseline(TextBaseline.alphabetic);
height2 = text.size.height;
}
)
)
);
layout(root, phase: EnginePhase.paint);
......
......@@ -53,3 +53,15 @@ void pumpFrame({ EnginePhase phase: EnginePhase.layout }) {
return;
renderView.compositeFrame();
}
class TestCallbackPainter extends CustomPainter {
const TestCallbackPainter({ this.onPaint });
final VoidCallback onPaint;
void paint(Canvas canvas, Size size) {
onPaint();
}
bool shouldRepaint(TestCallbackPainter oldPainter) => true;
}
......@@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart';
import 'package:test/test.dart';
import 'widget_tester.dart';
import '../rendering/rendering_tester.dart';
void main() {
test('Can construct an empty Stack', () {
......@@ -150,7 +151,12 @@ void main() {
Widget buildFrame(int index) {
itemsPainted = <int>[];
List<Widget> items = new List<Widget>.generate(itemCount, (i) {
return new CustomPaint(child: new Text('$i'), onPaint: (_, __) { itemsPainted.add(i); });
return new CustomPaint(
child: new Text('$i'),
painter: new TestCallbackPainter(
onPaint: () { itemsPainted.add(i); }
)
);
});
return new Center(child: new IndexedStack(items, index: index));
}
......
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