Factor out Border and BorderRadius into their own files. (#12055)
This will make it more tractable to convert them for RTL.
This commit is contained in:
parent
701d534ba9
commit
90841c9617
@ -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';
|
||||||
|
493
packages/flutter/lib/src/painting/border.dart
Normal file
493
packages/flutter/lib/src/painting/border.dart
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
// 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)';
|
||||||
|
}
|
157
packages/flutter/lib/src/painting/border_radius.dart
Normal file
157
packages/flutter/lib/src/painting/border_radius.dart
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// 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';
|
|||||||
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';
|
|||||||
|
|
||||||
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user