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