Commit 72329cf4 authored by Adam Barth's avatar Adam Barth

Callback identity is too fragile for CustomPaint

Many of the widgets that use CustomPaint were spamming repaints because
CustomPaint repaints when the identity of the onPaint callback changes, which
it does every build for StatelessComponents.

This patch changes CustomPaint to use a CustomPainter, similar to the new
custom layout widgets. The CustomPainter has a `shouldRepaint` function along
with its `paint` function. This function gives clients explicit control over
when the custom paint object repaints.
parent ea6292e1
......@@ -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