From ce930db3b41ce3a02287eb9df549846fe4f51871 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Mon, 2 Oct 2017 17:06:57 -0700 Subject: [PATCH] Move Border into its own file (#12364) I'm about to add the BoxBorder and BorderDirectional classes to this new file, but figured it would make review easier if the move of the existing class happened first. --- packages/flutter/lib/painting.dart | 1 + .../flutter/lib/src/painting/borders.dart | 356 ----------------- .../flutter/lib/src/painting/box_border.dart | 363 ++++++++++++++++++ .../lib/src/painting/box_decoration.dart | 2 +- 4 files changed, 365 insertions(+), 357 deletions(-) create mode 100644 packages/flutter/lib/src/painting/box_border.dart diff --git a/packages/flutter/lib/painting.dart b/packages/flutter/lib/painting.dart index d01cb4ab13..8297e76811 100644 --- a/packages/flutter/lib/painting.dart +++ b/packages/flutter/lib/painting.dart @@ -21,6 +21,7 @@ export 'src/painting/alignment.dart'; export 'src/painting/basic_types.dart'; export 'src/painting/border_radius.dart'; export 'src/painting/borders.dart'; +export 'src/painting/box_border.dart'; export 'src/painting/box_decoration.dart'; export 'src/painting/box_fit.dart'; export 'src/painting/box_shadow.dart'; diff --git a/packages/flutter/lib/src/painting/borders.dart b/packages/flutter/lib/src/painting/borders.dart index 06a1cc57b5..3c4fab6188 100644 --- a/packages/flutter/lib/src/painting/borders.dart +++ b/packages/flutter/lib/src/painting/borders.dart @@ -8,23 +8,8 @@ import 'dart:ui' as ui show lerpDouble; import 'package:flutter/foundation.dart'; import 'basic_types.dart'; -import 'border_radius.dart'; import 'edge_insets.dart'; -/// 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. @@ -571,347 +556,6 @@ class _CompoundBorder extends ShapeBorder { } } -/// 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. -class Border extends ShapeBorder { - /// 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); - } - - /// Creates a [Border] that represents the addition of the two given - /// [Border]s. - /// - /// It is only valid to call this if [BorderSide.canMerge] returns true for - /// the pairwise combination of each side on both [Border]s. - /// - /// The arguments must not be null. - static Border merge(Border a, Border b) { - assert(a != null); - assert(b != null); - assert(BorderSide.canMerge(a.top, b.top)); - assert(BorderSide.canMerge(a.right, b.right)); - assert(BorderSide.canMerge(a.bottom, b.bottom)); - assert(BorderSide.canMerge(a.left, b.left)); - return new Border( - top: BorderSide.merge(a.top, b.top), - right: BorderSide.merge(a.right, b.right), - bottom: BorderSide.merge(a.bottom, b.bottom), - left: BorderSide.merge(a.left, b.left), - ); - } - - /// 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; - - @override - EdgeInsetsGeometry 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; - } - - @override - Border add(ShapeBorder other, { bool reversed: false }) { - if (other is! Border) - return null; - final Border typedOther = other; - if (BorderSide.canMerge(top, typedOther.top) && - BorderSide.canMerge(right, typedOther.right) && - BorderSide.canMerge(bottom, typedOther.bottom) && - BorderSide.canMerge(left, typedOther.left)) { - return Border.merge(this, typedOther); - } - return null; - } - - /// Creates a new border with the widths of this border multiplied by `t`. - @override - Border scale(double t) { - return new Border( - top: top.scale(t), - right: right.scale(t), - bottom: bottom.scale(t), - left: left.scale(t), - ); - } - - /// Linearly interpolates from `a` to [this]. - /// - /// If `a` is null, this defers to [scale]. - /// - /// If `a` is also a [Border], this uses [Border.lerp]. - /// - /// Otherwise, it defers to [ShapeBorder.lerpFrom]. - @override - ShapeBorder lerpFrom(ShapeBorder a, double t) { - if (a is Border) - return Border.lerp(a, this, t); - return super.lerpFrom(a, t); - } - - /// Linearly interpolates from [this] to `b`. - /// - /// If `b` is null, this defers to [scale]. - /// - /// If `b` is also a [Border], this uses [Border.lerp]. - /// - /// Otherwise, it defers to [ShapeBorder.lerpTo]. - @override - ShapeBorder lerpTo(ShapeBorder b, double t) { - if (b is Border) - return Border.lerp(this, b, t); - return super.lerpTo(b, t); - } - - /// 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) - ); - } - - @override - Path getInnerPath(Rect rect, { TextDirection textDirection }) { - return new Path() - ..addRect(dimensions.resolve(textDirection).deflateRect(rect)); - } - - @override - Path getOuterPath(Rect rect, { TextDirection textDirection }) { - return new Path() - ..addRect(rect); - } - - /// 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 `borderRadius` is specified, there is - /// the requirement that the border [isUniform]. - /// - /// The [getInnerPath] and [getOuterPath] methods do not know about the - /// `shape` and `borderRadius` arguments. - /// - /// The `textDirection` argument is not used by this paint method. - /// - /// See also: - /// - /// * [paintBorder], which is used if the border is not uniform. - @override - void paint(Canvas canvas, Rect rect, { - TextDirection textDirection, - BoxShape shape: BoxShape.rectangle, - BorderRadius borderRadius, - }) { - if (isUniform) { - switch (top.style) { - case BorderStyle.none: - return; - case BorderStyle.solid: - if (shape == BoxShape.circle) { - assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.'); - _paintUniformBorderWithCircle(canvas, rect); - return; - } - if (borderRadius != null) { - _paintUniformBorderWithRadius(canvas, rect, borderRadius); - return; - } - _paintUniformBorderWithRectangle(canvas, rect); - return; - } - } - - assert(borderRadius == null, 'A borderRadius can only be given for uniform borders.'); - assert(shape == BoxShape.rectangle, 'A border can only be drawn as a circle if it is uniform.'); - - paintBorder(canvas, rect, top: top, right: right, bottom: bottom, left: left); - } - - void _paintUniformBorderWithRadius(Canvas canvas, Rect rect, - BorderRadius borderRadius) { - assert(isUniform); - assert(top.style != BorderStyle.none); - 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 _paintUniformBorderWithCircle(Canvas canvas, Rect rect) { - assert(isUniform); - assert(top.style != BorderStyle.none); - final double width = top.width; - final Paint paint = top.toPaint(); - final double radius = (rect.shortestSide - width) / 2.0; - canvas.drawCircle(rect.center, radius, paint); - } - - void _paintUniformBorderWithRectangle(Canvas canvas, Rect rect) { - assert(isUniform); - assert(top.style != BorderStyle.none); - final double width = top.width; - final Paint paint = top.toPaint(); - canvas.drawRect(rect.deflate(width / 2.0), 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() { - if (isUniform) - return 'Border.all($top)'; - return 'Border($top, $right, $bottom, $left)'; - } -} - /// Paints a border around the given rectangle on the canvas. /// /// The four sides can be independently specified. They are painted in the order diff --git a/packages/flutter/lib/src/painting/box_border.dart b/packages/flutter/lib/src/painting/box_border.dart new file mode 100644 index 0000000000..e9b5876e43 --- /dev/null +++ b/packages/flutter/lib/src/painting/box_border.dart @@ -0,0 +1,363 @@ +// 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 'basic_types.dart'; +import 'border_radius.dart'; +import 'borders.dart'; +import 'edge_insets.dart'; + +/// 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, +} + +/// 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. +class Border extends ShapeBorder { + /// 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); + } + + /// Creates a [Border] that represents the addition of the two given + /// [Border]s. + /// + /// It is only valid to call this if [BorderSide.canMerge] returns true for + /// the pairwise combination of each side on both [Border]s. + /// + /// The arguments must not be null. + static Border merge(Border a, Border b) { + assert(a != null); + assert(b != null); + assert(BorderSide.canMerge(a.top, b.top)); + assert(BorderSide.canMerge(a.right, b.right)); + assert(BorderSide.canMerge(a.bottom, b.bottom)); + assert(BorderSide.canMerge(a.left, b.left)); + return new Border( + top: BorderSide.merge(a.top, b.top), + right: BorderSide.merge(a.right, b.right), + bottom: BorderSide.merge(a.bottom, b.bottom), + left: BorderSide.merge(a.left, b.left), + ); + } + + /// 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; + + @override + EdgeInsetsGeometry 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; + } + + @override + Border add(ShapeBorder other, { bool reversed: false }) { + if (other is! Border) + return null; + final Border typedOther = other; + if (BorderSide.canMerge(top, typedOther.top) && + BorderSide.canMerge(right, typedOther.right) && + BorderSide.canMerge(bottom, typedOther.bottom) && + BorderSide.canMerge(left, typedOther.left)) { + return Border.merge(this, typedOther); + } + return null; + } + + /// Creates a new border with the widths of this border multiplied by `t`. + @override + Border scale(double t) { + return new Border( + top: top.scale(t), + right: right.scale(t), + bottom: bottom.scale(t), + left: left.scale(t), + ); + } + + /// Linearly interpolates from `a` to [this]. + /// + /// If `a` is null, this defers to [scale]. + /// + /// If `a` is also a [Border], this uses [Border.lerp]. + /// + /// Otherwise, it defers to [ShapeBorder.lerpFrom]. + @override + ShapeBorder lerpFrom(ShapeBorder a, double t) { + if (a is Border) + return Border.lerp(a, this, t); + return super.lerpFrom(a, t); + } + + /// Linearly interpolates from [this] to `b`. + /// + /// If `b` is null, this defers to [scale]. + /// + /// If `b` is also a [Border], this uses [Border.lerp]. + /// + /// Otherwise, it defers to [ShapeBorder.lerpTo]. + @override + ShapeBorder lerpTo(ShapeBorder b, double t) { + if (b is Border) + return Border.lerp(this, b, t); + return super.lerpTo(b, t); + } + + /// 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) + ); + } + + @override + Path getInnerPath(Rect rect, { TextDirection textDirection }) { + return new Path() + ..addRect(dimensions.resolve(textDirection).deflateRect(rect)); + } + + @override + Path getOuterPath(Rect rect, { TextDirection textDirection }) { + return new Path() + ..addRect(rect); + } + + /// 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 `borderRadius` is specified, there is + /// the requirement that the border [isUniform]. + /// + /// The [getInnerPath] and [getOuterPath] methods do not know about the + /// `shape` and `borderRadius` arguments. + /// + /// The `textDirection` argument is not used by this paint method. + /// + /// See also: + /// + /// * [paintBorder], which is used if the border is not uniform. + @override + void paint(Canvas canvas, Rect rect, { + TextDirection textDirection, + BoxShape shape: BoxShape.rectangle, + BorderRadius borderRadius, + }) { + if (isUniform) { + switch (top.style) { + case BorderStyle.none: + return; + case BorderStyle.solid: + if (shape == BoxShape.circle) { + assert(borderRadius == null, 'A borderRadius can only be given for rectangular boxes.'); + _paintUniformBorderWithCircle(canvas, rect); + return; + } + if (borderRadius != null) { + _paintUniformBorderWithRadius(canvas, rect, borderRadius); + return; + } + _paintUniformBorderWithRectangle(canvas, rect); + return; + } + } + + assert(borderRadius == null, 'A borderRadius can only be given for uniform borders.'); + assert(shape == BoxShape.rectangle, 'A border can only be drawn as a circle if it is uniform.'); + + paintBorder(canvas, rect, top: top, right: right, bottom: bottom, left: left); + } + + void _paintUniformBorderWithRadius(Canvas canvas, Rect rect, + BorderRadius borderRadius) { + assert(isUniform); + assert(top.style != BorderStyle.none); + 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 _paintUniformBorderWithCircle(Canvas canvas, Rect rect) { + assert(isUniform); + assert(top.style != BorderStyle.none); + final double width = top.width; + final Paint paint = top.toPaint(); + final double radius = (rect.shortestSide - width) / 2.0; + canvas.drawCircle(rect.center, radius, paint); + } + + void _paintUniformBorderWithRectangle(Canvas canvas, Rect rect) { + assert(isUniform); + assert(top.style != BorderStyle.none); + final double width = top.width; + final Paint paint = top.toPaint(); + canvas.drawRect(rect.deflate(width / 2.0), 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() { + if (isUniform) + return 'Border.all($top)'; + return 'Border($top, $right, $bottom, $left)'; + } +} diff --git a/packages/flutter/lib/src/painting/box_decoration.dart b/packages/flutter/lib/src/painting/box_decoration.dart index 2760c621a8..00db38df77 100644 --- a/packages/flutter/lib/src/painting/box_decoration.dart +++ b/packages/flutter/lib/src/painting/box_decoration.dart @@ -10,7 +10,7 @@ import 'package:flutter/services.dart'; import 'basic_types.dart'; import 'border_radius.dart'; -import 'borders.dart'; +import 'box_border.dart'; import 'box_shadow.dart'; import 'decoration.dart'; import 'edge_insets.dart';