Commit 90841c96 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Factor out Border and BorderRadius into their own files. (#12055)

This will make it more tractable to convert them for RTL.
parent 701d534b
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
library painting; library painting;
export 'src/painting/basic_types.dart'; export 'src/painting/basic_types.dart';
export 'src/painting/border.dart';
export 'src/painting/border_radius.dart';
export 'src/painting/box_fit.dart'; export 'src/painting/box_fit.dart';
export 'src/painting/box_painter.dart'; export 'src/painting/box_painter.dart';
export 'src/painting/colors.dart'; export 'src/painting/colors.dart';
......
// Copyright 2015 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 'dart:ui' as ui show lerpDouble;
import 'package:flutter/foundation.dart';
import 'basic_types.dart';
import 'border_radius.dart';
import 'decoration.dart';
import 'edge_insets.dart';
export 'edge_insets.dart' show EdgeInsets;
/// The shape to use when rendering a [Border] or [BoxDecoration].
enum BoxShape {
/// An axis-aligned, 2D rectangle. May have rounded corners (described by a
/// [BorderRadius]). The edges of the rectangle will match the edges of the box
/// into which the [Border] or [BoxDecoration] is painted.
rectangle,
/// A circle centered in the middle of the box into which the [Border] or
/// [BoxDecoration] is painted. The diameter of the circle is the shortest
/// dimension of the box, either the width or the height, such that the circle
/// touches the edges of the box.
circle,
}
/// 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 [Border] consists of four [BorderSide] objects: [Border.top],
/// [Border.left], [Border.right], and [Border.bottom].
///
/// ## Sample code
///
/// This sample shows how [BorderSide] objects can be used in a [Container], via
/// a [BoxDecoration] and a [Border], to decorate some [Text]. In this example,
/// the text has a thick bar above it that is light blue, and a thick bar below
/// it that is a darker shade of blue.
///
/// ```dart
/// new Container(
/// padding: new EdgeInsets.all(8.0),
/// decoration: new BoxDecoration(
/// border: new Border(
/// top: new BorderSide(width: 16.0, color: Colors.lightBlue.shade50),
/// bottom: new BorderSide(width: 16.0, color: Colors.lightBlue.shade900),
/// ),
/// ),
/// child: new Text('Flutter in the sky', textAlign: TextAlign.center),
/// )
/// ```
///
/// See also:
///
/// * [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.horizontalInside] and [TableBorder.verticalInside]), both
/// of which are also [BorderSide] objects.
@immutable
class BorderSide {
/// Creates the side of a border.
///
/// By default, the border is 1.0 logical pixels wide and solid black.
const BorderSide({
this.color: const Color(0xFF000000),
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, 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;
/// 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,
BorderStyle style
}) {
return new BorderSide(
color: color ?? this.color,
width: width ?? this.width,
style: style ?? this.style
);
}
/// Linearly interpolate between two border sides.
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(colorA, colorB, t),
width: ui.lerpDouble(a.width, b.width, t),
style: BorderStyle.solid,
);
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (runtimeType != other.runtimeType)
return false;
final BorderSide typedOther = other;
return color == typedOther.color &&
width == typedOther.width &&
style == typedOther.style;
}
@override
int get hashCode => hashValues(color, width, style);
@override
String toString() => 'BorderSide($color, $width, $style)';
}
/// A border of a box, comprised of four sides.
///
/// The sides are represented by [BorderSide] objects.
///
/// ## Sample code
///
/// All four borders the same, two-pixel wide solid white:
///
/// ```dart
/// new Border.all(width: 2.0, color: const Color(0xFFFFFFFF))
/// ```
///
/// The border for a material design divider:
///
/// ```dart
/// new Border(bottom: new BorderSide(color: Theme.of(context).dividerColor))
/// ```
///
/// A 1990s-era "OK" button:
///
/// ```dart
/// new Container(
/// decoration: const BoxDecoration(
/// border: const Border(
/// top: const BorderSide(width: 1.0, color: const Color(0xFFFFFFFFFF)),
/// left: const BorderSide(width: 1.0, color: const Color(0xFFFFFFFFFF)),
/// right: const BorderSide(width: 1.0, color: const Color(0xFFFF000000)),
/// bottom: const BorderSide(width: 1.0, color: const Color(0xFFFF000000)),
/// ),
/// ),
/// child: new Container(
/// padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 2.0),
/// decoration: const BoxDecoration(
/// border: const Border(
/// top: const BorderSide(width: 1.0, color: const Color(0xFFFFDFDFDF)),
/// left: const BorderSide(width: 1.0, color: const Color(0xFFFFDFDFDF)),
/// right: const BorderSide(width: 1.0, color: const Color(0xFFFF7F7F7F)),
/// bottom: const BorderSide(width: 1.0, color: const Color(0xFFFF7F7F7F)),
/// ),
/// color: const Color(0xFFBFBFBF),
/// ),
/// child: const Text(
/// 'OK',
/// textAlign: TextAlign.center,
/// style: const TextStyle(color: const Color(0xFF000000))
/// ),
/// ),
/// )
/// ```
///
/// See also:
///
/// * [BoxDecoration], which uses this class to describe its edge decoration.
/// * [BorderSide], which is used to describe each side of the box.
/// * [Theme], from the material layer, which can be queried to obtain appropriate colors
/// to use for borders in a material app, as shown in the "divider" sample above.
@immutable
class Border {
/// Creates a border.
///
/// All the sides of the border default to [BorderSide.none].
const Border({
this.top: BorderSide.none,
this.right: BorderSide.none,
this.bottom: BorderSide.none,
this.left: BorderSide.none,
});
/// A uniform border with all sides the same color and width.
///
/// The sides default to black solid borders, one logical pixel wide.
factory Border.all({
Color color: const Color(0xFF000000),
double width: 1.0,
BorderStyle style: BorderStyle.solid,
}) {
final BorderSide side = new BorderSide(color: color, width: width, style: style);
return new Border(top: side, right: side, bottom: side, left: side);
}
/// The top side of this border.
final BorderSide top;
/// The right side of this border.
final BorderSide right;
/// The bottom side of this border.
final BorderSide bottom;
/// The left side of this border.
final BorderSide left;
/// The widths of the sides of this border represented as an [EdgeInsets].
///
/// This can be used, for example, with a [Padding] widget to inset a box by
/// the size of these borders.
EdgeInsets get dimensions {
return new EdgeInsets.fromLTRB(left.width, top.width, right.width, bottom.width);
}
/// Whether all four sides of the border are identical. Uniform borders are
/// typically more efficient to paint.
bool get isUniform {
assert(top != null);
assert(right != null);
assert(bottom != null);
assert(left != null);
final Color topColor = top.color;
if (right.color != topColor ||
bottom.color != topColor ||
left.color != topColor)
return false;
final double topWidth = top.width;
if (right.width != topWidth ||
bottom.width != topWidth ||
left.width != topWidth)
return false;
final BorderStyle topStyle = top.style;
if (right.style != topStyle ||
bottom.style != topStyle ||
left.style != topStyle)
return false;
return true;
}
/// Creates a new border with the widths of this border multiplied by `t`.
Border scale(double t) {
return new Border(
top: top.copyWith(width: t * top.width),
right: right.copyWith(width: t * right.width),
bottom: bottom.copyWith(width: t * bottom.width),
left: left.copyWith(width: t * left.width)
);
}
/// Linearly interpolate between two borders.
///
/// If a border is null, it is treated as having four [BorderSide.none]
/// borders.
static Border lerp(Border a, Border b, double t) {
if (a == null && b == null)
return null;
if (a == null)
return b.scale(t);
if (b == null)
return a.scale(1.0 - t);
return new Border(
top: BorderSide.lerp(a.top, b.top, t),
right: BorderSide.lerp(a.right, b.right, t),
bottom: BorderSide.lerp(a.bottom, b.bottom, t),
left: BorderSide.lerp(a.left, b.left, t)
);
}
/// Paints the border within the given [Rect] on the given [Canvas].
///
/// Uniform borders are more efficient to paint than more complex borders.
///
/// You can provide a [BoxShape] to draw the border on. If the shape in
/// [BoxShape.circle], there is the requirement that the border [isUniform].
///
/// If you specify a rectangular box shape (BoxShape.rectangle), then you may
/// specify a [BorderRadius]. If a border radius is specified, there is the
/// requirement that the border [isUniform].
void paint(Canvas canvas, Rect rect, {
BoxShape shape: BoxShape.rectangle,
BorderRadius borderRadius,
}) {
if (isUniform) {
if (borderRadius != null) {
assert(shape == BoxShape.rectangle, 'A borderRadius can only be given for rectangular boxes.');
_paintBorderWithRadius(canvas, rect, borderRadius);
return;
}
if (shape == BoxShape.circle) {
assert(borderRadius == null);
_paintBorderWithCircle(canvas, rect);
return;
}
}
assert(borderRadius == null, 'A borderRadius can only be given for uniform borders.'); // TODO(abarth): Support non-uniform rounded borders.
assert(shape == BoxShape.rectangle, 'A border can only be drawn as a circle if it is uniform.'); // TODO(ianh): Support non-uniform borders on circles.
assert(top != null);
assert(right != null);
assert(bottom != null);
assert(left != null);
final Paint paint = new Paint()
..strokeWidth = 0.0; // used for hairline borders
Path path;
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:
break;
}
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:
break;
}
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:
break;
}
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 (left.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:
break;
}
}
void _paintBorderWithRadius(Canvas canvas, Rect rect,
BorderRadius borderRadius) {
assert(isUniform);
final Paint paint = new Paint()
..color = top.color;
final RRect outer = borderRadius.toRRect(rect);
final double width = top.width;
if (width == 0.0) {
paint
..style = PaintingStyle.stroke
..strokeWidth = 0.0;
canvas.drawRRect(outer, paint);
} else {
final RRect inner = outer.deflate(width);
canvas.drawDRRect(outer, inner, paint);
}
}
void _paintBorderWithCircle(Canvas canvas, Rect rect) {
assert(isUniform);
final double width = top.width;
final Paint paint = new Paint()
..color = top.color
..strokeWidth = width
..style = PaintingStyle.stroke;
final double radius = (rect.shortestSide - width) / 2.0;
canvas.drawCircle(rect.center, radius, paint);
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (runtimeType != other.runtimeType)
return false;
final Border typedOther = other;
return top == typedOther.top &&
right == typedOther.right &&
bottom == typedOther.bottom &&
left == typedOther.left;
}
@override
int get hashCode => hashValues(top, right, bottom, left);
@override
String toString() => 'Border($top, $right, $bottom, $left)';
}
// Copyright 2015 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/foundation.dart';
import 'basic_types.dart';
/// An immutable set of radii for each corner of a rectangle.
///
/// Used by [BoxDecoration] when the shape is a [BoxShape.rectangle].
@immutable
class BorderRadius {
/// Creates a border radius where all radii are [radius].
const BorderRadius.all(Radius radius) : this.only(
topLeft: radius,
topRight: radius,
bottomRight: radius,
bottomLeft: radius
);
/// Creates a border radius where all radii are [Radius.circular(radius)].
BorderRadius.circular(double radius) : this.all(
new Radius.circular(radius)
);
/// Creates a vertically symmetric border radius where the top and bottom
/// sides of the rectangle have the same radii.
const BorderRadius.vertical({
Radius top: Radius.zero,
Radius bottom: Radius.zero
}) : this.only(
topLeft: top,
topRight: top,
bottomRight: bottom,
bottomLeft: bottom
);
/// Creates a horizontally symmetrical border radius where the left and right
/// sides of the rectangle have the same radii.
const BorderRadius.horizontal({
Radius left: Radius.zero,
Radius right: Radius.zero
}) : this.only(
topLeft: left,
topRight: right,
bottomRight: right,
bottomLeft: left
);
/// Creates a border radius with only the given non-zero values. The other
/// corners will be right angles.
const BorderRadius.only({
this.topLeft: Radius.zero,
this.topRight: Radius.zero,
this.bottomRight: Radius.zero,
this.bottomLeft: Radius.zero
});
/// A border radius with all zero radii.
static const BorderRadius zero = const BorderRadius.all(Radius.zero);
/// The top-left [Radius].
final Radius topLeft;
/// The top-right [Radius].
final Radius topRight;
/// The bottom-right [Radius].
final Radius bottomRight;
/// The bottom-left [Radius].
final Radius bottomLeft;
/// Linearly interpolates between two [BorderRadius] objects.
///
/// If either is null, this function interpolates from [BorderRadius.zero].
static BorderRadius lerp(BorderRadius a, BorderRadius b, double t) {
if (a == null && b == null)
return null;
return new BorderRadius.only(
topLeft: Radius.lerp(a.topLeft, b.topLeft, t),
topRight: Radius.lerp(a.topRight, b.topRight, t),
bottomRight: Radius.lerp(a.bottomRight, b.bottomRight, t),
bottomLeft: Radius.lerp(a.bottomLeft, b.bottomLeft, t)
);
}
/// Creates a [RRect] from the current border radius and a [Rect].
RRect toRRect(Rect rect) {
return new RRect.fromRectAndCorners(
rect,
topLeft: topLeft,
topRight: topRight,
bottomRight: bottomRight,
bottomLeft: bottomLeft
);
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (runtimeType != other.runtimeType)
return false;
final BorderRadius typedOther = other;
return topLeft == typedOther.topLeft &&
topRight == typedOther.topRight &&
bottomRight == typedOther.bottomRight &&
bottomLeft == typedOther.bottomLeft;
}
@override
int get hashCode => hashValues(topLeft, topRight, bottomRight, bottomLeft);
@override
String toString() {
if (topLeft == topRight &&
topRight == bottomRight &&
bottomRight == bottomLeft) {
if (topLeft == Radius.zero)
return 'BorderRadius.zero';
if (topLeft.x == topLeft.y)
return 'BorderRadius.circular(${topLeft.x.toStringAsFixed(1)})';
return 'BorderRadius.all($topLeft)';
}
if (topLeft == Radius.zero ||
topRight == Radius.zero ||
bottomLeft == Radius.zero ||
bottomRight == Radius.zero) {
final StringBuffer result = new StringBuffer();
result.write('BorderRadius.only(');
bool comma = false;
if (topLeft != Radius.zero) {
result.write('topLeft: $topLeft');
comma = true;
}
if (topRight != Radius.zero) {
if (comma)
result.write(', ');
result.write('topRight: $topRight');
comma = true;
}
if (bottomLeft != Radius.zero) {
if (comma)
result.write(', ');
result.write('bottomLeft: $bottomLeft');
comma = true;
}
if (bottomRight != Radius.zero) {
if (comma)
result.write(', ');
result.write('bottomRight: $bottomRight');
}
result.write(')');
return result.toString();
}
return 'BorderRadius($topLeft, $topRight, $bottomRight, $bottomLeft)';
}
}
...@@ -9,6 +9,8 @@ import 'package:flutter/foundation.dart'; ...@@ -9,6 +9,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'basic_types.dart'; import 'basic_types.dart';
import 'border.dart';
import 'border_radius.dart';
import 'box_fit.dart'; import 'box_fit.dart';
import 'decoration.dart'; import 'decoration.dart';
import 'edge_insets.dart'; import 'edge_insets.dart';
...@@ -16,635 +18,6 @@ import 'fractional_offset.dart'; ...@@ -16,635 +18,6 @@ import 'fractional_offset.dart';
export 'edge_insets.dart' show EdgeInsets; export 'edge_insets.dart' show EdgeInsets;
/// The shape to use when rendering a BoxDecoration.
enum BoxShape {
/// An axis-aligned, 2D rectangle. May have rounded corners (described by a
/// [BorderRadius]). The edges of the rectangle will match the edges of the box
/// into which the [BoxDecoration] is painted.
rectangle,
/// A circle centered in the middle of the box into which the [BoxDecoration]
/// is painted. The diameter of the circle is the shortest dimension of the
/// box, either the width or the height, such that the circle touches the
/// edges of the box.
circle,
}
/// An immutable set of radii for each corner of a rectangle.
///
/// Used by [BoxDecoration] when the shape is a [BoxShape.rectangle].
@immutable
class BorderRadius {
/// Creates a border radius where all radii are [radius].
const BorderRadius.all(Radius radius) : this.only(
topLeft: radius,
topRight: radius,
bottomRight: radius,
bottomLeft: radius
);
/// Creates a border radius where all radii are [Radius.circular(radius)].
BorderRadius.circular(double radius) : this.all(
new Radius.circular(radius)
);
/// Creates a vertically symmetric border radius where the top and bottom
/// sides of the rectangle have the same radii.
const BorderRadius.vertical({
Radius top: Radius.zero,
Radius bottom: Radius.zero
}) : this.only(
topLeft: top,
topRight: top,
bottomRight: bottom,
bottomLeft: bottom
);
/// Creates a horizontally symmetrical border radius where the left and right
/// sides of the rectangle have the same radii.
const BorderRadius.horizontal({
Radius left: Radius.zero,
Radius right: Radius.zero
}) : this.only(
topLeft: left,
topRight: right,
bottomRight: right,
bottomLeft: left
);
/// Creates a border radius with only the given non-zero values. The other
/// corners will be right angles.
const BorderRadius.only({
this.topLeft: Radius.zero,
this.topRight: Radius.zero,
this.bottomRight: Radius.zero,
this.bottomLeft: Radius.zero
});
/// A border radius with all zero radii.
static const BorderRadius zero = const BorderRadius.all(Radius.zero);
/// The top-left [Radius].
final Radius topLeft;
/// The top-right [Radius].
final Radius topRight;
/// The bottom-right [Radius].
final Radius bottomRight;
/// The bottom-left [Radius].
final Radius bottomLeft;
/// Linearly interpolates between two [BorderRadius] objects.
///
/// If either is null, this function interpolates from [BorderRadius.zero].
static BorderRadius lerp(BorderRadius a, BorderRadius b, double t) {
if (a == null && b == null)
return null;
return new BorderRadius.only(
topLeft: Radius.lerp(a.topLeft, b.topLeft, t),
topRight: Radius.lerp(a.topRight, b.topRight, t),
bottomRight: Radius.lerp(a.bottomRight, b.bottomRight, t),
bottomLeft: Radius.lerp(a.bottomLeft, b.bottomLeft, t)
);
}
/// Creates a [RRect] from the current border radius and a [Rect].
RRect toRRect(Rect rect) {
return new RRect.fromRectAndCorners(
rect,
topLeft: topLeft,
topRight: topRight,
bottomRight: bottomRight,
bottomLeft: bottomLeft
);
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (runtimeType != other.runtimeType)
return false;
final BorderRadius typedOther = other;
return topLeft == typedOther.topLeft &&
topRight == typedOther.topRight &&
bottomRight == typedOther.bottomRight &&
bottomLeft == typedOther.bottomLeft;
}
@override
int get hashCode => hashValues(topLeft, topRight, bottomRight, bottomLeft);
@override
String toString() {
if (topLeft == topRight &&
topRight == bottomRight &&
bottomRight == bottomLeft) {
if (topLeft == Radius.zero)
return 'BorderRadius.zero';
if (topLeft.x == topLeft.y)
return 'BorderRadius.circular(${topLeft.x.toStringAsFixed(1)})';
return 'BorderRadius.all($topLeft)';
}
if (topLeft == Radius.zero ||
topRight == Radius.zero ||
bottomLeft == Radius.zero ||
bottomRight == Radius.zero) {
final StringBuffer result = new StringBuffer();
result.write('BorderRadius.only(');
bool comma = false;
if (topLeft != Radius.zero) {
result.write('topLeft: $topLeft');
comma = true;
}
if (topRight != Radius.zero) {
if (comma)
result.write(', ');
result.write('topRight: $topRight');
comma = true;
}
if (bottomLeft != Radius.zero) {
if (comma)
result.write(', ');
result.write('bottomLeft: $bottomLeft');
comma = true;
}
if (bottomRight != Radius.zero) {
if (comma)
result.write(', ');
result.write('bottomRight: $bottomRight');
}
result.write(')');
return result.toString();
}
return 'BorderRadius($topLeft, $topRight, $bottomRight, $bottomLeft)';
}
}
/// 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 [Border] consists of four [BorderSide] objects: [Border.top],
/// [Border.left], [Border.right], and [Border.bottom].
///
/// ## Sample code
///
/// This sample shows how [BorderSide] objects can be used in a [Container], via
/// a [BoxDecoration] and a [Border], to decorate some [Text]. In this example,
/// the text has a thick bar above it that is light blue, and a thick bar below
/// it that is a darker shade of blue.
///
/// ```dart
/// new Container(
/// padding: new EdgeInsets.all(8.0),
/// decoration: new BoxDecoration(
/// border: new Border(
/// top: new BorderSide(width: 16.0, color: Colors.lightBlue.shade50),
/// bottom: new BorderSide(width: 16.0, color: Colors.lightBlue.shade900),
/// ),
/// ),
/// child: new Text('Flutter in the sky', textAlign: TextAlign.center),
/// )
/// ```
///
/// See also:
///
/// * [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.horizontalInside] and [TableBorder.verticalInside]), both
/// of which are also [BorderSide] objects.
@immutable
class BorderSide {
/// Creates the side of a border.
///
/// By default, the border is 1.0 logical pixels wide and solid black.
const BorderSide({
this.color: const Color(0xFF000000),
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, 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;
/// 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,
BorderStyle style
}) {
return new BorderSide(
color: color ?? this.color,
width: width ?? this.width,
style: style ?? this.style
);
}
/// Linearly interpolate between two border sides.
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(colorA, colorB, t),
width: ui.lerpDouble(a.width, b.width, t),
style: BorderStyle.solid,
);
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (runtimeType != other.runtimeType)
return false;
final BorderSide typedOther = other;
return color == typedOther.color &&
width == typedOther.width &&
style == typedOther.style;
}
@override
int get hashCode => hashValues(color, width, style);
@override
String toString() => 'BorderSide($color, $width, $style)';
}
/// A border of a box, comprised of four sides.
///
/// The sides are represented by [BorderSide] objects.
///
/// ## Sample code
///
/// All four borders the same, two-pixel wide solid white:
///
/// ```dart
/// new Border.all(width: 2.0, color: const Color(0xFFFFFFFF))
/// ```
///
/// The border for a material design divider:
///
/// ```dart
/// new Border(bottom: new BorderSide(color: Theme.of(context).dividerColor))
/// ```
///
/// A 1990s-era "OK" button:
///
/// ```dart
/// new Container(
/// decoration: const BoxDecoration(
/// border: const Border(
/// top: const BorderSide(width: 1.0, color: const Color(0xFFFFFFFFFF)),
/// left: const BorderSide(width: 1.0, color: const Color(0xFFFFFFFFFF)),
/// right: const BorderSide(width: 1.0, color: const Color(0xFFFF000000)),
/// bottom: const BorderSide(width: 1.0, color: const Color(0xFFFF000000)),
/// ),
/// ),
/// child: new Container(
/// padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 2.0),
/// decoration: const BoxDecoration(
/// border: const Border(
/// top: const BorderSide(width: 1.0, color: const Color(0xFFFFDFDFDF)),
/// left: const BorderSide(width: 1.0, color: const Color(0xFFFFDFDFDF)),
/// right: const BorderSide(width: 1.0, color: const Color(0xFFFF7F7F7F)),
/// bottom: const BorderSide(width: 1.0, color: const Color(0xFFFF7F7F7F)),
/// ),
/// color: const Color(0xFFBFBFBF),
/// ),
/// child: const Text(
/// 'OK',
/// textAlign: TextAlign.center,
/// style: const TextStyle(color: const Color(0xFF000000))
/// ),
/// ),
/// )
/// ```
///
/// See also:
///
/// * [BoxDecoration], which uses this class to describe its edge decoration.
/// * [BorderSide], which is used to describe each side of the box.
/// * [Theme], from the material layer, which can be queried to obtain appropriate colors
/// to use for borders in a material app, as shown in the "divider" sample above.
@immutable
class Border {
/// Creates a border.
///
/// All the sides of the border default to [BorderSide.none].
const Border({
this.top: BorderSide.none,
this.right: BorderSide.none,
this.bottom: BorderSide.none,
this.left: BorderSide.none,
});
/// A uniform border with all sides the same color and width.
///
/// The sides default to black solid borders, one logical pixel wide.
factory Border.all({
Color color: const Color(0xFF000000),
double width: 1.0,
BorderStyle style: BorderStyle.solid,
}) {
final BorderSide side = new BorderSide(color: color, width: width, style: style);
return new Border(top: side, right: side, bottom: side, left: side);
}
/// The top side of this border.
final BorderSide top;
/// The right side of this border.
final BorderSide right;
/// The bottom side of this border.
final BorderSide bottom;
/// The left side of this border.
final BorderSide left;
/// The widths of the sides of this border represented as an [EdgeInsets].
///
/// This can be used, for example, with a [Padding] widget to inset a box by
/// the size of these borders.
EdgeInsets get dimensions {
return new EdgeInsets.fromLTRB(left.width, top.width, right.width, bottom.width);
}
/// Whether all four sides of the border are identical. Uniform borders are
/// typically more efficient to paint.
bool get isUniform {
assert(top != null);
assert(right != null);
assert(bottom != null);
assert(left != null);
final Color topColor = top.color;
if (right.color != topColor ||
bottom.color != topColor ||
left.color != topColor)
return false;
final double topWidth = top.width;
if (right.width != topWidth ||
bottom.width != topWidth ||
left.width != topWidth)
return false;
final BorderStyle topStyle = top.style;
if (right.style != topStyle ||
bottom.style != topStyle ||
left.style != topStyle)
return false;
return true;
}
/// Creates a new border with the widths of this border multiplied by `t`.
Border scale(double t) {
return new Border(
top: top.copyWith(width: t * top.width),
right: right.copyWith(width: t * right.width),
bottom: bottom.copyWith(width: t * bottom.width),
left: left.copyWith(width: t * left.width)
);
}
/// Linearly interpolate between two borders.
///
/// If a border is null, it is treated as having four [BorderSide.none]
/// borders.
static Border lerp(Border a, Border b, double t) {
if (a == null && b == null)
return null;
if (a == null)
return b.scale(t);
if (b == null)
return a.scale(1.0 - t);
return new Border(
top: BorderSide.lerp(a.top, b.top, t),
right: BorderSide.lerp(a.right, b.right, t),
bottom: BorderSide.lerp(a.bottom, b.bottom, t),
left: BorderSide.lerp(a.left, b.left, t)
);
}
/// Paints the border within the given [Rect] on the given [Canvas].
///
/// Uniform borders are more efficient to paint than more complex borders.
///
/// You can provide a [BoxShape] to draw the border on. If the shape in
/// [BoxShape.circle], there is the requirement that the border [isUniform].
///
/// If you specify a rectangular box shape (BoxShape.rectangle), then you may
/// specify a [BorderRadius]. If a border radius is specified, there is the
/// requirement that the border [isUniform].
void paint(Canvas canvas, Rect rect, {
BoxShape shape: BoxShape.rectangle,
BorderRadius borderRadius,
}) {
if (isUniform) {
if (borderRadius != null) {
assert(shape == BoxShape.rectangle, 'A borderRadius can only be given for rectangular boxes.');
_paintBorderWithRadius(canvas, rect, borderRadius);
return;
}
if (shape == BoxShape.circle) {
assert(borderRadius == null);
_paintBorderWithCircle(canvas, rect);
return;
}
}
assert(borderRadius == null, 'A borderRadius can only be given for uniform borders.'); // TODO(abarth): Support non-uniform rounded borders.
assert(shape == BoxShape.rectangle, 'A border can only be drawn as a circle if it is uniform.'); // TODO(ianh): Support non-uniform borders on circles.
assert(top != null);
assert(right != null);
assert(bottom != null);
assert(left != null);
final Paint paint = new Paint()
..strokeWidth = 0.0; // used for hairline borders
Path path;
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:
break;
}
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:
break;
}
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:
break;
}
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 (left.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:
break;
}
}
void _paintBorderWithRadius(Canvas canvas, Rect rect,
BorderRadius borderRadius) {
assert(isUniform);
final Paint paint = new Paint()
..color = top.color;
final RRect outer = borderRadius.toRRect(rect);
final double width = top.width;
if (width == 0.0) {
paint
..style = PaintingStyle.stroke
..strokeWidth = 0.0;
canvas.drawRRect(outer, paint);
} else {
final RRect inner = outer.deflate(width);
canvas.drawDRRect(outer, inner, paint);
}
}
void _paintBorderWithCircle(Canvas canvas, Rect rect) {
assert(isUniform);
final double width = top.width;
final Paint paint = new Paint()
..color = top.color
..strokeWidth = width
..style = PaintingStyle.stroke;
final double radius = (rect.shortestSide - width) / 2.0;
canvas.drawCircle(rect.center, radius, paint);
}
@override
bool operator ==(dynamic other) {
if (identical(this, other))
return true;
if (runtimeType != other.runtimeType)
return false;
final Border typedOther = other;
return top == typedOther.top &&
right == typedOther.right &&
bottom == typedOther.bottom &&
left == typedOther.left;
}
@override
int get hashCode => hashValues(top, right, bottom, left);
@override
String toString() => 'Border($top, $right, $bottom, $left)';
}
/// A shadow cast by a box. /// A shadow cast by a box.
/// ///
/// BoxShadow can cast non-rectangular shadows if the box is non-rectangular /// BoxShadow can cast non-rectangular shadows if the box is non-rectangular
......
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