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.
This commit is contained in:
parent
7711b1f660
commit
9fc29dbbb8
@ -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> {
|
|||||||
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> {
|
|||||||
_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 {
|
|||||||
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 {
|
|||||||
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) {
|
|||||||
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 {
|
|||||||
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(
|
||||||
|
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(
|
return new BorderSide(
|
||||||
color: Color.lerp(a.color, b.color, t),
|
color: Color.lerp(colorA, colorB, t),
|
||||||
width: ui.lerpDouble(a.width, b.width, t)
|
width: ui.lerpDouble(a.width, b.width, t),
|
||||||
|
style: BorderStyle.solid
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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 {
|
|||||||
/// 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 {
|
|||||||
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 {
|
|||||||
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;
|
||||||
|
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;
|
switch (right.style) {
|
||||||
path = new Path();
|
case BorderStyle.solid:
|
||||||
path.moveTo(rect.left, rect.top);
|
paint.color = right.color;
|
||||||
path.lineTo(rect.left + left.width, rect.top + top.width);
|
path = new Path();
|
||||||
path.lineTo(rect.right - right.width, rect.top + top.width);
|
path.moveTo(rect.right, rect.top);
|
||||||
path.lineTo(rect.right, rect.top);
|
path.lineTo(rect.right, rect.bottom);
|
||||||
path.close();
|
if (right.width == 0.0) {
|
||||||
canvas.drawPath(path, paint);
|
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;
|
switch (bottom.style) {
|
||||||
path = new Path();
|
case BorderStyle.solid:
|
||||||
path.moveTo(rect.right, rect.top);
|
paint.color = bottom.color;
|
||||||
path.lineTo(rect.right - right.width, rect.top + top.width);
|
path = new Path();
|
||||||
path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
|
path.moveTo(rect.right, rect.bottom);
|
||||||
path.lineTo(rect.right, rect.bottom);
|
path.lineTo(rect.left, rect.bottom);
|
||||||
path.close();
|
if (bottom.width == 0.0) {
|
||||||
canvas.drawPath(path, paint);
|
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;
|
switch (left.style) {
|
||||||
path = new Path();
|
case BorderStyle.solid:
|
||||||
path.moveTo(rect.right, rect.bottom);
|
paint.color = left.color;
|
||||||
path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
|
path = new Path();
|
||||||
path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
|
path.moveTo(rect.left, rect.bottom);
|
||||||
path.lineTo(rect.left, rect.bottom);
|
path.lineTo(rect.left, rect.top);
|
||||||
path.close();
|
if (right.width == 0.0) {
|
||||||
canvas.drawPath(path, paint);
|
paint.style = PaintingStyle.stroke;
|
||||||
|
} else {
|
||||||
paint.color = left.color;
|
paint.style = PaintingStyle.fill;
|
||||||
path = new Path();
|
path.lineTo(rect.left + left.width, rect.top + top.width);
|
||||||
path.moveTo(rect.left, rect.bottom);
|
path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
|
||||||
path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
|
}
|
||||||
path.lineTo(rect.left + left.width, rect.top + top.width);
|
canvas.drawPath(path, paint);
|
||||||
path.lineTo(rect.left, rect.top);
|
break;
|
||||||
path.close();
|
case BorderStyle.none: ;
|
||||||
canvas.drawPath(path, paint);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
RRect inner = new RRect.fromRectXY(rect.deflate(width), radius - width, radius - width);
|
double width = top.width;
|
||||||
canvas.drawDRRect(outer, inner, new Paint()..color = color);
|
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) {
|
void _paintBorderWithCircle(Canvas canvas, Rect rect) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user