Commit 9fc29dbb authored by Hixie's avatar Hixie

Support hairline borders

Previously, border with '0' was ambiguous. Sometimes we treated it as
hairline borders, sometimes as "don't show the border", though even in
the latter case we did some graphics work sometimes. Now we have an
explicit BorderStyle.none flag to not draw the border efficiently.
parent 7711b1f6
...@@ -64,7 +64,7 @@ class DotState extends State<Dot> { ...@@ -64,7 +64,7 @@ class DotState extends State<Dot> {
height: config.size, height: config.size,
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: config.color, backgroundColor: config.color,
border: new Border.all(color: const Color(0xFF000000), width: taps.toDouble()), border: new Border.all(width: taps.toDouble()),
shape: BoxShape.circle shape: BoxShape.circle
), ),
child: config.child child: config.child
......
...@@ -134,7 +134,7 @@ class SectorAppState extends State<SectorApp> { ...@@ -134,7 +134,7 @@ class SectorAppState extends State<SectorApp> {
child: new Container( child: new Container(
margin: new EdgeInsets.all(8.0), margin: new EdgeInsets.all(8.0),
decoration: new BoxDecoration( decoration: new BoxDecoration(
border: new Border.all(color: new Color(0xFF000000)) border: new Border.all()
), ),
padding: new EdgeInsets.all(8.0), padding: new EdgeInsets.all(8.0),
child: new WidgetToRenderBoxAdapter( child: new WidgetToRenderBoxAdapter(
......
...@@ -42,7 +42,7 @@ class ListDemoState extends State<ListDemo> { ...@@ -42,7 +42,7 @@ class ListDemoState extends State<ListDemo> {
_bottomSheet = scaffoldKey.currentState.showBottomSheet((BuildContext bottomSheetContext) { _bottomSheet = scaffoldKey.currentState.showBottomSheet((BuildContext bottomSheetContext) {
return new Container( return new Container(
decoration: new BoxDecoration( 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( child: new Column(
mainAxisAlignment: MainAxisAlignment.collapse, mainAxisAlignment: MainAxisAlignment.collapse,
......
...@@ -16,7 +16,7 @@ class PersistentBottomSheetDemo extends StatelessWidget { ...@@ -16,7 +16,7 @@ class PersistentBottomSheetDemo extends StatelessWidget {
Scaffold.of(context).showBottomSheet((_) { Scaffold.of(context).showBottomSheet((_) {
return new Container( return new Container(
decoration: new BoxDecoration( 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( child: new Padding(
padding: const EdgeInsets.all(32.0), padding: const EdgeInsets.all(32.0),
......
...@@ -100,7 +100,7 @@ class StockSymbolBottomSheet extends StatelessWidget { ...@@ -100,7 +100,7 @@ class StockSymbolBottomSheet extends StatelessWidget {
return new Container( return new Container(
padding: new EdgeInsets.all(10.0), padding: new EdgeInsets.all(10.0),
decoration: new BoxDecoration( 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) child: new StockSymbolView(stock: stock)
); );
......
...@@ -25,30 +25,52 @@ double _getEffectiveBorderRadius(Rect rect, double borderRadius) { ...@@ -25,30 +25,52 @@ double _getEffectiveBorderRadius(Rect rect, double borderRadius) {
return borderRadius > shortestSide ? shortestSide : 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. /// A side of a border of a box.
class BorderSide { class BorderSide {
const BorderSide({ const BorderSide({
this.color: const Color(0xFF000000), 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. /// The color of this side of the border.
final Color color; 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; final double width;
/// A black border side of zero width. /// The style of this side of the border.
static const BorderSide none = const BorderSide(width: 0.0); ///
/// 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. /// Creates a copy of this border but with the given fields replaced with the new values.
BorderSide copyWith({ BorderSide copyWith({
Color color, Color color,
double width double width,
BorderStyle style
}) { }) {
return new BorderSide( return new BorderSide(
color: color ?? this.color, color: color ?? this.color,
width: width ?? this.width width: width ?? this.width,
style: style ?? this.style
); );
} }
...@@ -56,9 +78,38 @@ class BorderSide { ...@@ -56,9 +78,38 @@ class BorderSide {
static BorderSide lerp(BorderSide a, BorderSide b, double t) { static BorderSide lerp(BorderSide a, BorderSide b, double t) {
assert(a != null); assert(a != null);
assert(b != null); assert(b != null);
if (t == 0.0)
return a;
if (t == 1.0)
return b;
if (a.style == b.style) {
return new BorderSide( return new BorderSide(
color: Color.lerp(a.color, b.color, t), color: Color.lerp(a.color, b.color, t),
width: ui.lerpDouble(a.width, b.width, 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(colorA, colorB, t),
width: ui.lerpDouble(a.width, b.width, t),
style: BorderStyle.solid
); );
} }
...@@ -70,14 +121,15 @@ class BorderSide { ...@@ -70,14 +121,15 @@ class BorderSide {
return false; return false;
final BorderSide typedOther = other; final BorderSide typedOther = other;
return color == typedOther.color && return color == typedOther.color &&
width == typedOther.width; width == typedOther.width &&
style == typedOther.style;
} }
@override @override
int get hashCode => hashValues(color, width); int get hashCode => hashValues(color, width, style);
@override @override
String toString() => 'BorderSide($color, $width)'; String toString() => 'BorderSide($color, $width, $style)';
} }
/// A border of a box, comprised of four sides. /// A border of a box, comprised of four sides.
...@@ -92,9 +144,10 @@ class Border { ...@@ -92,9 +144,10 @@ class Border {
/// A uniform border with all sides the same color and width. /// A uniform border with all sides the same color and width.
factory Border.all({ factory Border.all({
Color color: const Color(0xFF000000), 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); return new Border(top: side, right: side, bottom: side, left: side);
} }
...@@ -134,6 +187,12 @@ class Border { ...@@ -134,6 +187,12 @@ class Border {
left.width != topWidth) left.width != topWidth)
return false; return false;
final BorderStyle topStyle = top.style;
if (right.style != topStyle ||
bottom.style != topStyle ||
left.style != topStyle)
return false;
return true; return true;
} }
...@@ -186,57 +245,99 @@ class Border { ...@@ -186,57 +245,99 @@ class Border {
assert(bottom != null); assert(bottom != null);
assert(left != null); assert(left != null);
Paint paint = new Paint(); Paint paint = new Paint()
..strokeWidth = 0.0; // used for hairline borders
Path path; 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; paint.color = top.color;
path = new Path(); path = new Path();
path.moveTo(rect.left, rect.top); 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.lineTo(rect.right, rect.top);
path.close(); 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); canvas.drawPath(path, paint);
break;
case BorderStyle.none: ;
}
switch (right.style) {
case BorderStyle.solid:
paint.color = right.color; paint.color = right.color;
path = new Path(); path = new Path();
path.moveTo(rect.right, rect.top); 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.lineTo(rect.right, rect.bottom);
path.close(); 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); canvas.drawPath(path, paint);
break;
case BorderStyle.none: ;
}
switch (bottom.style) {
case BorderStyle.solid:
paint.color = bottom.color; paint.color = bottom.color;
path = new Path(); path = new Path();
path.moveTo(rect.right, rect.bottom); 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.lineTo(rect.left, rect.bottom);
path.close(); 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); canvas.drawPath(path, paint);
break;
case BorderStyle.none: ;
}
switch (left.style) {
case BorderStyle.solid:
paint.color = left.color; paint.color = left.color;
path = new Path(); path = new Path();
path.moveTo(rect.left, rect.bottom); 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.lineTo(rect.left, rect.top);
path.close(); 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); canvas.drawPath(path, paint);
break;
case BorderStyle.none: ;
}
} }
void _paintBorderWithRadius(Canvas canvas, Rect rect, double borderRadius) { void _paintBorderWithRadius(Canvas canvas, Rect rect, double borderRadius) {
assert(isUniform); assert(isUniform);
Color color = top.color; Paint paint = new Paint()
double width = top.width; ..color = top.color;
double radius = _getEffectiveBorderRadius(rect, borderRadius); 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 outer = new RRect.fromRectXY(rect, radius, radius);
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); RRect inner = new RRect.fromRectXY(rect.deflate(width), radius - width, radius - width);
canvas.drawDRRect(outer, inner, new Paint()..color = color); canvas.drawDRRect(outer, inner, paint);
}
} }
void _paintBorderWithCircle(Canvas canvas, Rect rect) { void _paintBorderWithCircle(Canvas canvas, Rect rect) {
......
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