diff --git a/dev/manual_tests/drag_and_drop.dart b/dev/manual_tests/drag_and_drop.dart index f7c5a761bf..a8fd7b7d24 100644 --- a/dev/manual_tests/drag_and_drop.dart +++ b/dev/manual_tests/drag_and_drop.dart @@ -64,7 +64,7 @@ class DotState extends State { height: config.size, decoration: new BoxDecoration( backgroundColor: config.color, - border: new Border.all(color: const Color(0xFF000000), width: taps.toDouble()), + border: new Border.all(width: taps.toDouble()), shape: BoxShape.circle ), child: config.child diff --git a/examples/layers/widgets/sectors.dart b/examples/layers/widgets/sectors.dart index 33ac58437e..7e5fc9f8eb 100644 --- a/examples/layers/widgets/sectors.dart +++ b/examples/layers/widgets/sectors.dart @@ -134,7 +134,7 @@ class SectorAppState extends State { child: new Container( margin: new EdgeInsets.all(8.0), decoration: new BoxDecoration( - border: new Border.all(color: new Color(0xFF000000)) + border: new Border.all() ), padding: new EdgeInsets.all(8.0), child: new WidgetToRenderBoxAdapter( diff --git a/examples/material_gallery/lib/demo/list_demo.dart b/examples/material_gallery/lib/demo/list_demo.dart index daa92ff82d..903226e24b 100644 --- a/examples/material_gallery/lib/demo/list_demo.dart +++ b/examples/material_gallery/lib/demo/list_demo.dart @@ -42,7 +42,7 @@ class ListDemoState extends State { _bottomSheet = scaffoldKey.currentState.showBottomSheet((BuildContext bottomSheetContext) { return new Container( decoration: new BoxDecoration( - border: new Border(top: new BorderSide(color: Colors.black26, width: 1.0)) + border: new Border(top: new BorderSide(color: Colors.black26)) ), child: new Column( mainAxisAlignment: MainAxisAlignment.collapse, diff --git a/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart b/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart index 5709593f8d..0d11a95d3a 100644 --- a/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart +++ b/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart @@ -16,7 +16,7 @@ class PersistentBottomSheetDemo extends StatelessWidget { Scaffold.of(context).showBottomSheet((_) { return new Container( decoration: new BoxDecoration( - border: new Border(top: new BorderSide(color: Colors.black26, width: 1.0)) + border: new Border(top: new BorderSide(color: Colors.black26)) ), child: new Padding( padding: const EdgeInsets.all(32.0), diff --git a/examples/stocks/lib/stock_symbol_viewer.dart b/examples/stocks/lib/stock_symbol_viewer.dart index 5f2ff07bd0..6812fedc12 100644 --- a/examples/stocks/lib/stock_symbol_viewer.dart +++ b/examples/stocks/lib/stock_symbol_viewer.dart @@ -100,7 +100,7 @@ class StockSymbolBottomSheet extends StatelessWidget { return new Container( padding: new EdgeInsets.all(10.0), decoration: new BoxDecoration( - border: new Border(top: new BorderSide(color: Colors.black26, width: 1.0)) + border: new Border(top: new BorderSide(color: Colors.black26)) ), child: new StockSymbolView(stock: stock) ); diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart index 50cd3981a3..3823188e16 100644 --- a/packages/flutter/lib/src/painting/box_painter.dart +++ b/packages/flutter/lib/src/painting/box_painter.dart @@ -25,30 +25,52 @@ double _getEffectiveBorderRadius(Rect rect, double borderRadius) { return borderRadius > shortestSide ? shortestSide : borderRadius; } +/// The style of line to draw for a [BorderSide] in a [Border]. +enum BorderStyle { + /// Skip the border. + none, + + /// Draw the border as a solid line. + solid, + + // if you add more, think about how they will lerp +} + /// A side of a border of a box. class BorderSide { const BorderSide({ this.color: const Color(0xFF000000), - this.width: 1.0 + this.width: 1.0, + this.style: BorderStyle.solid }); /// The color of this side of the border. final Color color; - /// The width of this side of the border. + /// The width of this side of the border, in logical pixels. A + /// zero-width border is a hairline border. To omit the border + /// entirely, set the [style] to [BorderStyle.none]. final double width; - /// A black border side of zero width. - static const BorderSide none = const BorderSide(width: 0.0); + /// The style of this side of the border. + /// + /// To omit a side, set [style] to [BorderStyle.none]. This skips + /// painting the border, but the border still has a [width]. + final BorderStyle style; + + /// A hairline black border that is not rendered. + static const BorderSide none = const BorderSide(width: 0.0, style: BorderStyle.none); /// Creates a copy of this border but with the given fields replaced with the new values. BorderSide copyWith({ Color color, - double width + double width, + BorderStyle style }) { return new BorderSide( color: color ?? this.color, - width: width ?? this.width + width: width ?? this.width, + style: style ?? this.style ); } @@ -56,9 +78,38 @@ class BorderSide { static BorderSide lerp(BorderSide a, BorderSide b, double t) { assert(a != null); assert(b != null); + if (t == 0.0) + return a; + if (t == 1.0) + return b; + if (a.style == b.style) { + return new BorderSide( + color: Color.lerp(a.color, b.color, t), + width: ui.lerpDouble(a.width, b.width, t), + style: a.style // == b.style + ); + } + Color colorA, colorB; + switch (a.style) { + case BorderStyle.solid: + colorA = a.color; + break; + case BorderStyle.none: + colorA = a.color.withAlpha(0x00); + break; + } + switch (b.style) { + case BorderStyle.solid: + colorB = b.color; + break; + case BorderStyle.none: + colorB = b.color.withAlpha(0x00); + break; + } return new BorderSide( - color: Color.lerp(a.color, b.color, t), - width: ui.lerpDouble(a.width, b.width, t) + color: Color.lerp(colorA, colorB, t), + width: ui.lerpDouble(a.width, b.width, t), + style: BorderStyle.solid ); } @@ -70,14 +121,15 @@ class BorderSide { return false; final BorderSide typedOther = other; return color == typedOther.color && - width == typedOther.width; + width == typedOther.width && + style == typedOther.style; } @override - int get hashCode => hashValues(color, width); + int get hashCode => hashValues(color, width, style); @override - String toString() => 'BorderSide($color, $width)'; + String toString() => 'BorderSide($color, $width, $style)'; } /// A border of a box, comprised of four sides. @@ -92,9 +144,10 @@ class Border { /// A uniform border with all sides the same color and width. factory Border.all({ Color color: const Color(0xFF000000), - double width: 1.0 + double width: 1.0, + BorderStyle style: BorderStyle.solid }) { - final BorderSide side = new BorderSide(color: color, width: width); + final BorderSide side = new BorderSide(color: color, width: width, style: style); return new Border(top: side, right: side, bottom: side, left: side); } @@ -134,6 +187,12 @@ class Border { left.width != topWidth) return false; + final BorderStyle topStyle = top.style; + if (right.style != topStyle || + bottom.style != topStyle || + left.style != topStyle) + return false; + return true; } @@ -186,57 +245,99 @@ class Border { assert(bottom != null); assert(left != null); - Paint paint = new Paint(); + Paint paint = new Paint() + ..strokeWidth = 0.0; // used for hairline borders Path path; - // TODO(ianh): Handle hairline border by drawing a single line instead of a wedge + switch (top.style) { + case BorderStyle.solid: + paint.color = top.color; + path = new Path(); + path.moveTo(rect.left, rect.top); + path.lineTo(rect.right, rect.top); + if (top.width == 0.0) { + paint.style = PaintingStyle.stroke; + } else { + paint.style = PaintingStyle.fill; + path.lineTo(rect.right - right.width, rect.top + top.width); + path.lineTo(rect.left + left.width, rect.top + top.width); + } + canvas.drawPath(path, paint); + break; + case BorderStyle.none: ; + } - paint.color = top.color; - path = new Path(); - path.moveTo(rect.left, rect.top); - path.lineTo(rect.left + left.width, rect.top + top.width); - path.lineTo(rect.right - right.width, rect.top + top.width); - path.lineTo(rect.right, rect.top); - path.close(); - canvas.drawPath(path, paint); + switch (right.style) { + case BorderStyle.solid: + paint.color = right.color; + path = new Path(); + path.moveTo(rect.right, rect.top); + path.lineTo(rect.right, rect.bottom); + if (right.width == 0.0) { + paint.style = PaintingStyle.stroke; + } else { + paint.style = PaintingStyle.fill; + path.lineTo(rect.right - right.width, rect.bottom - bottom.width); + path.lineTo(rect.right - right.width, rect.top + top.width); + } + canvas.drawPath(path, paint); + break; + case BorderStyle.none: ; + } - paint.color = right.color; - path = new Path(); - path.moveTo(rect.right, rect.top); - path.lineTo(rect.right - right.width, rect.top + top.width); - path.lineTo(rect.right - right.width, rect.bottom - bottom.width); - path.lineTo(rect.right, rect.bottom); - path.close(); - canvas.drawPath(path, paint); + switch (bottom.style) { + case BorderStyle.solid: + paint.color = bottom.color; + path = new Path(); + path.moveTo(rect.right, rect.bottom); + path.lineTo(rect.left, rect.bottom); + if (bottom.width == 0.0) { + paint.style = PaintingStyle.stroke; + } else { + paint.style = PaintingStyle.fill; + path.lineTo(rect.left + left.width, rect.bottom - bottom.width); + path.lineTo(rect.right - right.width, rect.bottom - bottom.width); + } + canvas.drawPath(path, paint); + break; + case BorderStyle.none: ; + } - paint.color = bottom.color; - path = new Path(); - path.moveTo(rect.right, rect.bottom); - path.lineTo(rect.right - right.width, rect.bottom - bottom.width); - path.lineTo(rect.left + left.width, rect.bottom - bottom.width); - path.lineTo(rect.left, rect.bottom); - path.close(); - canvas.drawPath(path, paint); - - paint.color = left.color; - path = new Path(); - path.moveTo(rect.left, rect.bottom); - path.lineTo(rect.left + left.width, rect.bottom - bottom.width); - path.lineTo(rect.left + left.width, rect.top + top.width); - path.lineTo(rect.left, rect.top); - path.close(); - canvas.drawPath(path, paint); + switch (left.style) { + case BorderStyle.solid: + paint.color = left.color; + path = new Path(); + path.moveTo(rect.left, rect.bottom); + path.lineTo(rect.left, rect.top); + if (right.width == 0.0) { + paint.style = PaintingStyle.stroke; + } else { + paint.style = PaintingStyle.fill; + path.lineTo(rect.left + left.width, rect.top + top.width); + path.lineTo(rect.left + left.width, rect.bottom - bottom.width); + } + canvas.drawPath(path, paint); + break; + case BorderStyle.none: ; + } } void _paintBorderWithRadius(Canvas canvas, Rect rect, double borderRadius) { assert(isUniform); - Color color = top.color; - double width = top.width; + Paint paint = new Paint() + ..color = top.color; double radius = _getEffectiveBorderRadius(rect, borderRadius); - // TODO(ianh): Handle hairline borders by just drawing an RRect instead RRect outer = new RRect.fromRectXY(rect, radius, radius); - RRect inner = new RRect.fromRectXY(rect.deflate(width), radius - width, radius - width); - canvas.drawDRRect(outer, inner, new Paint()..color = color); + double width = top.width; + if (width == 0.0) { + paint + ..style = PaintingStyle.stroke + ..strokeWidth = 0.0; + canvas.drawRRect(outer, paint); + } else { + RRect inner = new RRect.fromRectXY(rect.deflate(width), radius - width, radius - width); + canvas.drawDRRect(outer, inner, paint); + } } void _paintBorderWithCircle(Canvas canvas, Rect rect) {