Commit 9646e1fb authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

BorderSide improvements (#12294)

parent 9909e773
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' as ui show lerpDouble;
import 'package:flutter/foundation.dart';
......@@ -64,7 +65,7 @@ enum BorderStyle {
///
/// * [Border], which uses [BorderSide] objects to represent its sides.
/// * [BoxDecoration], which optionally takes a [Border] object.
/// * [TableBorder], which extends [Border] to have two more sides
/// * [TableBorder], which is similar to [Border] but has two more sides
/// ([TableBorder.horizontalInside] and [TableBorder.verticalInside]), both
/// of which are also [BorderSide] objects.
@immutable
......@@ -75,8 +76,34 @@ class BorderSide {
const BorderSide({
this.color: const Color(0xFF000000),
this.width: 1.0,
this.style: BorderStyle.solid
});
this.style: BorderStyle.solid,
}) : assert(color != null),
assert(width != null),
assert(width >= 0.0),
assert(style != null);
/// Creates a [BorderSide] that represents the addition of the two given
/// [BorderSide]s.
///
/// It is only valid to call this if [canMerge] returns true for the two
/// sides.
///
/// If both sides are null, then this will return null. If one of the two
/// sides is null, then the other side is returned as-is.
static BorderSide merge(BorderSide a, BorderSide b) {
assert(canMerge(a, b));
if (a == null)
return b; // might return null
if (b == null)
return a;
assert(a.color == b.color);
assert(a.style == b.style);
return new BorderSide(
color: a.color, // == b.color
width: a.width + b.width,
style: a.style, // == b.style
);
}
/// The color of this side of the border.
final Color color;
......@@ -101,17 +128,69 @@ class BorderSide {
double width,
BorderStyle style
}) {
assert(width == null || width >= 0.0);
return new BorderSide(
color: color ?? this.color,
width: width ?? this.width,
style: style ?? this.style
style: style ?? this.style,
);
}
/// Creates a copy of this border but with the width scaled by the given factor.
///
/// Since a zero width is painted as a hairline width rather than no border at
/// all, the zero factor is special-cased to instead change the style no
/// [BorderStyle.none].
BorderSide scale(double t) {
assert(t != null);
return new BorderSide(
color: color,
width: math.max(0.0, width * t),
style: t <= 0.0 ? BorderStyle.none : style,
);
}
/// Create a [Paint] object that, if used to stroke a line, will draw the line
/// in this border's style.
///
/// Not all borders use this method to paint their border sides. For example,
/// non-uniform rectangular [Border]s have beveled edges and so paint their
/// border sides as filled shapes rather than using a stroke.
Paint toPaint() {
switch (style) {
case BorderStyle.solid:
return new Paint()
..color = color
..strokeWidth = width
..style = PaintingStyle.stroke;
case BorderStyle.none:
return new Paint()
..color = const Color(0x00000000)
..strokeWidth = 0.0
..style = PaintingStyle.stroke;
}
return null;
}
/// Whether the two given [BorderSide]s can be merged using [new
/// BorderSide.merge].
///
/// Two sides can be merged if one or both are null, or if they both have the
/// same color and style.
static bool canMerge(BorderSide a, BorderSide b) {
if (a == null || b == null)
return true;
return a.style == b.style
&& a.color == b.color;
}
/// Linearly interpolate between two border sides.
///
/// The arguments must not be null.
static BorderSide lerp(BorderSide a, BorderSide b, double t) {
assert(a != null);
assert(b != null);
assert(t != null);
if (t == 0.0)
return a;
if (t == 1.0)
......@@ -119,8 +198,8 @@ class BorderSide {
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
width: math.max(0.0, ui.lerpDouble(a.width, b.width, t)),
style: a.style, // == b.style
);
}
Color colorA, colorB;
......@@ -142,7 +221,7 @@ class BorderSide {
}
return new BorderSide(
color: Color.lerp(colorA, colorB, t),
width: ui.lerpDouble(a.width, b.width, t),
width: math.max(0.0, ui.lerpDouble(a.width, b.width, t)),
style: BorderStyle.solid,
);
}
......@@ -163,7 +242,7 @@ class BorderSide {
int get hashCode => hashValues(color, width, style);
@override
String toString() => 'BorderSide($color, $width, $style)';
String toString() => '$runtimeType($color, ${width.toStringAsFixed(1)}, $style)';
}
/// A border of a box, comprised of four sides.
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/painting.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('BorderSide - asserts when constructed incorrectly', () {
expect(
const BorderSide(),
const BorderSide(
color: const Color(0xFF000000),
width: 1.0,
style: BorderStyle.solid,
),
);
// so that we can use `new` below, we use these:
final Null $null = null;
final double $minus1 = -1.0;
expect(() => new BorderSide(color: $null), throwsAssertionError);
expect(() => new BorderSide(width: $null), throwsAssertionError);
expect(() => new BorderSide(style: $null), throwsAssertionError);
expect(() => new BorderSide(width: $minus1), throwsAssertionError);
expect(
const BorderSide(width: -0.0),
const BorderSide(
color: const Color(0xFF000000),
width: 0.0,
style: BorderStyle.solid,
),
);
});
test('BorderSide - merging', () {
final BorderSide side2 = const BorderSide(width: 2.0);
final BorderSide side3 = const BorderSide(width: 3.0);
final BorderSide side5 = const BorderSide(width: 5.0);
final BorderSide green = const BorderSide(color: const Color(0xFF00FF00));
final BorderSide blue = const BorderSide(color: const Color(0xFF0000FF));
final BorderSide solid = const BorderSide(style: BorderStyle.solid);
final BorderSide none = const BorderSide(style: BorderStyle.none);
expect(BorderSide.merge(null, null), isNull);
expect(BorderSide.merge(null, side2), side2);
expect(BorderSide.merge(side2, null), side2);
expect(() => BorderSide.merge(green, blue), throwsAssertionError);
expect(() => BorderSide.merge(solid, none), throwsAssertionError);
expect(BorderSide.merge(side2, side3), side5);
expect(BorderSide.merge(side3, side2), side5);
});
test('BorderSide - asserts when copied incorrectly', () {
final BorderSide green2 = const BorderSide(color: const Color(0xFF00FF00), width: 2.0);
final BorderSide blue3 = const BorderSide(color: const Color(0xFF0000FF), width: 3.0);
final BorderSide blue2 = const BorderSide(color: const Color(0xFF0000FF), width: 2.0);
final BorderSide green3 = const BorderSide(color: const Color(0xFF00FF00), width: 3.0);
final BorderSide none2 = const BorderSide(color: const Color(0xFF00FF00), width: 2.0, style: BorderStyle.none);
expect(green2.copyWith(color: const Color(0xFF0000FF), width: 3.0), blue3);
expect(green2.copyWith(width: 3.0), green3);
expect(green2.copyWith(color: const Color(0xFF0000FF)), blue2);
expect(green2.copyWith(style: BorderStyle.none), none2);
});
test('BorderSide - scale', () {
final BorderSide side3 = const BorderSide(width: 3.0, color: const Color(0xFF0000FF));
final BorderSide side6 = const BorderSide(width: 6.0, color: const Color(0xFF0000FF));
final BorderSide none = const BorderSide(style: BorderStyle.none, width: 0.0, color: const Color(0xFF0000FF));
expect(side3.scale(2.0), side6);
expect(side6.scale(0.5), side3);
expect(side6.scale(0.0), none);
expect(side6.scale(-1.0), none);
expect(none.scale(2.0), none);
});
test('BorderSide - toPaint', () {
final Paint paint1 = const BorderSide(width: 2.5, color: const Color(0xFFFFFF00), style: BorderStyle.solid).toPaint();
expect(paint1.strokeWidth, 2.5);
expect(paint1.style, PaintingStyle.stroke);
expect(paint1.color, const Color(0xFFFFFF00));
expect(paint1.blendMode, BlendMode.srcOver);
final Paint paint2 = const BorderSide(width: 2.5, color: const Color(0xFFFFFF00), style: BorderStyle.none).toPaint();
expect(paint2.strokeWidth, 0.0);
expect(paint2.style, PaintingStyle.stroke);
expect(paint2.color, const Color(0x00000000));
expect(paint2.blendMode, BlendMode.srcOver);
});
test('BorderSide - canMerge', () {
final BorderSide green2 = const BorderSide(color: const Color(0xFF00FF00), width: 2.0);
final BorderSide green3 = const BorderSide(color: const Color(0xFF00FF00), width: 3.0);
final BorderSide blue2 = const BorderSide(color: const Color(0xFF0000FF), width: 2.0);
final BorderSide none2 = const BorderSide(color: const Color(0xFF0000FF), width: 2.0, style: BorderStyle.none);
expect(BorderSide.canMerge(green2, green3), isTrue);
expect(BorderSide.canMerge(green2, blue2), isFalse);
expect(BorderSide.canMerge(green2, none2), isFalse);
expect(BorderSide.canMerge(blue2, null), isTrue);
expect(BorderSide.canMerge(null, null), isTrue);
expect(BorderSide.canMerge(null, blue2), isTrue);
});
test('BorderSide - won\'t lerp into negative widths', () {
final BorderSide side0 = const BorderSide(width: 0.0);
final BorderSide side1 = const BorderSide(width: 1.0);
final BorderSide side2 = const BorderSide(width: 2.0);
expect(BorderSide.lerp(side2, side1, 10.0), side0);
expect(BorderSide.lerp(side1, side2, -10.0), side0);
expect(BorderSide.lerp(side0, side1, 2.0), side2);
expect(BorderSide.lerp(side1, side0, 2.0), side0);
});
test('BorderSide - toString', () {
expect(
const BorderSide(color: const Color(0xFFAABBCC), width: 1.2345).toString(),
'BorderSide(Color(0xffaabbcc), 1.2, BorderStyle.solid)',
);
});
}
\ No newline at end of file
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