Unverified Commit 3a181e49 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Added LinearBorder, an OutlinedBorder like BoxBorder (#116940)

parent fb1a1510
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Examples of LinearBorder and LinearBorderEdge.
import 'package:flutter/material.dart';
void main() {
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({ super.key });
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(useMaterial3: true),
home: const Directionality(
textDirection: TextDirection.ltr, // Or try rtl.
child: Home(),
),
);
}
}
class SampleCard extends StatelessWidget {
const SampleCard({ super.key, required this.title, required this.subtitle, required this.children });
final String title;
final String subtitle;
final List<Widget> children;
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final TextTheme textTheme = theme.textTheme;
final ColorScheme colorScheme = theme.colorScheme;
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(title, style: textTheme.titleMedium),
Text(subtitle, style: textTheme.bodyMedium!.copyWith(color: colorScheme.secondary)),
const SizedBox(height: 16),
Row(
children: List<Widget>.generate(children.length * 2 - 1, (int index) {
return index.isEven ? children[index ~/2] : const SizedBox(width: 16);
}),
),
],
),
),
);
}
}
class Home extends StatefulWidget {
const Home({ super.key });
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
final LinearBorder shape0 = LinearBorder.top();
final LinearBorder shape1 = LinearBorder.top(size: 0);
late LinearBorder shape = shape0;
@override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final BorderSide primarySide0 = BorderSide(width: 0, color: colorScheme.inversePrimary); // hairline
final BorderSide primarySide2 = BorderSide(width: 2, color: colorScheme.onPrimaryContainer);
final BorderSide primarySide3 = BorderSide(width: 3, color: colorScheme.inversePrimary);
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
// Demonstrates using LinearBorder.bottom() to define
// an underline border for the standard button types.
// The underline's color and width is defined by the ButtonStyle's
// side parameter. The side can also be specified as a
// LinearBorder parameter and if both are specified then the
// ButtonStyle's side is used. This set up makes it possible
// for a button theme to specify the shape and for indidividual
// buttons to specify the shape border's color and width.
SampleCard(
title: 'LinearBorder.bottom()',
subtitle: 'Standard button widgets',
children: <Widget>[
TextButton(
style: TextButton.styleFrom(
side: primarySide3,
shape: LinearBorder.bottom(),
),
onPressed: () { },
child: const Text('Text'),
),
OutlinedButton(
style: OutlinedButton.styleFrom(
side: primarySide3,
shape: LinearBorder.bottom(),
),
onPressed: () { },
child: const Text('Outlined'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
side: primarySide3,
shape: LinearBorder.bottom(),
),
onPressed: () { },
child: const Text('Elevated'),
),
],
),
const SizedBox(height: 32),
// Demonstrates creating LinearBorders with a single edge
// by using the convenience constructors like LinearBorder.start().
// The edges are drawn with a BorderSide with width:0, which
// means that a "hairline" line is stroked. Wider borders are
// drawn with filled rectangles.
SampleCard(
title: 'LinearBorder',
subtitle: 'Convenience constructors',
children: <Widget>[
TextButton(
style: TextButton.styleFrom(
side: primarySide0,
shape: LinearBorder.start(),
),
onPressed: () { },
child: const Text('Start()'),
),
TextButton(
style: TextButton.styleFrom(
side: primarySide0,
shape: LinearBorder.end(),
),
onPressed: () { },
child: const Text('End()'),
),
TextButton(
style: TextButton.styleFrom(
side: primarySide0,
shape: LinearBorder.top(),
),
onPressed: () { },
child: const Text('Top()'),
),
TextButton(
style: TextButton.styleFrom(
side: primarySide0,
shape: LinearBorder.bottom(),
),
onPressed: () { },
child: const Text('Bottom()'),
),
],
),
const SizedBox(height: 32),
// Demonstrates creating LinearBorders with a single edge
// that's smaller than the button's bounding box. The size
// parameter specifies a percentage of the available space
// and alignment is -1 for start-alignment, 0 for centered,
// and 1 for end-alignment.
SampleCard(
title: 'LinearBorder',
subtitle: 'Size and alignment parameters',
children: <Widget>[
TextButton(
style: TextButton.styleFrom(
side: primarySide2,
shape: LinearBorder.bottom(
size: 0.5,
),
),
onPressed: () { },
child: const Text('Center'),
),
TextButton(
style: TextButton.styleFrom(
side: primarySide2,
shape: LinearBorder.bottom(
size: 0.75,
alignment: -1,
),
),
onPressed: () { },
child: const Text('Start'),
),
TextButton(
style: TextButton.styleFrom(
side: primarySide2,
shape: LinearBorder.bottom(
size: 0.75,
alignment: 1,
),
),
onPressed: () { },
child: const Text('End'),
),
],
),
const SizedBox(height: 32),
// Demonstrates creating LinearBorders with more than one edge.
// In these cases the default constructor is used and each edge
// is defined with one LinearBorderEdge object.
SampleCard(
title: 'LinearBorder',
subtitle: 'LinearBorderEdge parameters',
children: <Widget>[
TextButton(
style: TextButton.styleFrom(
side: primarySide0,
shape: const LinearBorder(
top: LinearBorderEdge(),
bottom: LinearBorderEdge(),
),
),
onPressed: () { },
child: const Text('Horizontal'),
),
TextButton(
style: TextButton.styleFrom(
side: primarySide0,
shape: const LinearBorder(
start: LinearBorderEdge(),
end: LinearBorderEdge(),
),
),
onPressed: () { },
child: const Text('Vertical'),
),
TextButton(
style: TextButton.styleFrom(
side: primarySide0,
shape: const LinearBorder(
start: LinearBorderEdge(),
bottom: LinearBorderEdge(),
),
),
onPressed: () { },
child: const Text('Corner'),
),
],
),
const SizedBox(height: 32),
// Demonstrates that changing properties of LinearBorders
// causes them to animate to their new configuration.
SampleCard(
title: 'Interpolation',
subtitle: 'LinearBorder.top() => LinearBorder.top(size: 0)',
children: <Widget>[
IconButton(
icon: const Icon(Icons.play_arrow),
onPressed: () {
setState(() {
shape = shape == shape0 ? shape1 : shape0;
});
},
),
TextButton(
style: TextButton.styleFrom(
side: primarySide3,
shape: shape,
),
onPressed: () { },
child: const Text('Press Play'),
),
TextButton(
style: ButtonStyle(
side: MaterialStateProperty.resolveWith<BorderSide?>((Set <MaterialState> states) {
return states.contains(MaterialState.hovered) ? primarySide3 : null;
}),
shape: MaterialStateProperty.resolveWith<OutlinedBorder>((Set <MaterialState> states) {
return states.contains(MaterialState.hovered) ? shape0 : shape1;
}),
),
onPressed: () { },
child: const Text('Hover'),
),
],
),
],
),
),
),
);
}
}
// Copyright 2014 The Flutter 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/material.dart';
import 'package:flutter_api_samples/painting/linear_border/linear_border.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Smoke Test', (WidgetTester tester) async {
await tester.pumpWidget(
const example.ExampleApp(),
);
expect(find.byType(example.Home), findsOneWidget);
// Scroll the interpolation example into view
await tester.scrollUntilVisible(
find.byIcon(Icons.play_arrow),
500.0,
scrollable: find.byType(Scrollable),
);
expect(find.byIcon(Icons.play_arrow), findsOneWidget);
// Run the interpolation example
await tester.tap(find.byIcon(Icons.play_arrow));
await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.play_arrow));
await tester.pumpAndSettle();
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Interpolation')));
await gesture.moveTo(tester.getCenter(find.text('Hover')));
await tester.pumpAndSettle();
await gesture.moveTo(tester.getCenter(find.text('Interpolation')));
await tester.pumpAndSettle();
});
}
......@@ -47,6 +47,7 @@ export 'src/painting/image_provider.dart';
export 'src/painting/image_resolution.dart';
export 'src/painting/image_stream.dart';
export 'src/painting/inline_span.dart';
export 'src/painting/linear_border.dart';
export 'src/painting/matrix_utils.dart';
export 'src/painting/notched_shapes.dart';
export 'src/painting/oval_border.dart';
......
// Copyright 2014 The Flutter 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' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'basic_types.dart';
import 'borders.dart';
import 'edge_insets.dart';
/// Defines the relative size and alignment of one <LinearBorder> edge.
///
/// A [LinearBorder] defines a box outline as zero to four edges, each
/// of which is rendered as a single line. The width and color of the
/// lines is defined by [LinearBorder.side].
///
/// Each line's length is defined by [size], a value between 0.0 and 1.0
/// (the default) which defines the length as a percentage of the
/// length of a box edge.
///
/// When [size] is less than 1.0, the line is aligned within the
/// available space according to [alignment], a value between -1.0 and
/// 1.0. The default is 0.0, which means centered, -1.0 means align on the
/// "start" side, and 1.0 means align on the "end" side. The meaning of
/// start and end depend on the current [TextDirection], see
/// [Directionality].
@immutable
class LinearBorderEdge {
/// Defines one side of a [LinearBorder].
///
/// The values of [size] and [alignment] must be between
/// 0.0 and 1.0, and -1.0 and 1.0 respectively.
const LinearBorderEdge({
this.size = 1.0,
this.alignment = 0.0,
}) : assert(size >= 0.0 && size <= 1.0);
/// A value between 0.0 and 1.0 that defines the length of the edge as a
/// percentage of the length of the corresponding box
/// edge. Default is 1.0.
final double size;
/// A value between -1.0 and 1.0 that defines how edges for which [size]
/// is less than 1.0 are aligned relative to the corresponding box edge.
///
/// * -1.0, aligned in the "start" direction. That's left
/// for [TextDirection.ltr] and right for [TextDirection.rtl].
/// * 0.0, centered.
/// * 1.0, aligned in the "end" direction. That's right
/// for [TextDirection.ltr] and left for [TextDirection.rtl].
final double alignment;
/// Linearly interpolates between two [LinearBorder]s.
///
/// If both `a` and `b` are null then null is returned. If `a` is null
/// then we interpolate to `b` varying [size] from 0.0 to `b.size`. If `b`
/// is null then we interpolate from `a` varying size from `a.size` to zero.
/// Otherwise both values are interpolated.
static LinearBorderEdge? lerp(LinearBorderEdge? a, LinearBorderEdge? b, double t) {
if (a == null && b == null) {
return null;
}
a ??= LinearBorderEdge(alignment: b!.alignment, size: 0);
b ??= LinearBorderEdge(alignment: a.alignment, size: 0);
return LinearBorderEdge(
size: lerpDouble(a.size, b.size, t)!,
alignment: lerpDouble(a.alignment, b.alignment, t)!,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is LinearBorderEdge
&& other.size == size
&& other.alignment == alignment;
}
@override
int get hashCode => Object.hash(size, alignment);
@override
String toString() {
final StringBuffer s = StringBuffer('${objectRuntimeType(this, 'LinearBorderEdge')}(');
if (size != 1.0 ) {
s.write('size: $size');
}
if (alignment != 0) {
final String comma = size != 1.0 ? ', ' : '';
s.write('${comma}alignment: $alignment');
}
s.write(')');
return s.toString();
}
}
/// An [OutlinedBorder] like [BoxBorder] that allows one to define a rectangular (box) border
/// in terms of zero to four [LinearBorderEdge]s, each of which is rendered as a single line.
///
/// The color and width of each line are defined by [side]. When [LinearBorder] is used
/// with a class whose border sides and shape are defined by a [ButtonStyle], then a non-null
/// [ButtonStyle.side] will override the one specified here. For example the [LinearBorder]
/// in the [TextButton] example below adds a red underline to the button. This is because
/// TextButton's `side` parameter overrides the `side` property of its [ButtonStyle.shape].
///
/// ```dart
/// TextButton(
/// style: TextButton.styleFrom(
/// side: const BorderSide(color: Colors.red),
/// shape: const LinearBorder(
/// side: BorderSide(color: Colors.blue),
/// bottom: LinearBorderEdge(),
/// ),
/// ),
/// onPressed: () { },
/// child: const Text('Red LinearBorder'),
/// )
///```
///
/// This class resolves itself against the current [TextDirection] (see [Directionality]).
/// Start and end values resolve to left and right for [TextDirection.ltr] and to
/// right and left for [TextDirection.rtl].
///
/// Convenience constructors are included for the common case where just one edge is specified:
/// [LinearBorder.start], [LinearBorder.end], [LinearBorder.top], [LinearBorder.bottom].
class LinearBorder extends OutlinedBorder {
/// Creates a rectangular box border that's rendered as zero to four lines.
const LinearBorder({
super.side,
this.start,
this.end,
this.top,
this.bottom,
});
/// Creates a rectangular box border with an edge on the left for [TextDirection.ltr]
/// or on the right for [TextDirection.rtl].
LinearBorder.start({
super.side,
double alignment = 0.0,
double size = 1.0
}) : start = LinearBorderEdge(alignment: alignment, size: size),
end = null,
top = null,
bottom = null;
/// Creates a rectangular box border with an edge on the right for [TextDirection.ltr]
/// or on the left for [TextDirection.rtl].
LinearBorder.end({
super.side,
double alignment = 0.0,
double size = 1.0
}) : start = null,
end = LinearBorderEdge(alignment: alignment, size: size),
top = null,
bottom = null;
/// Creates a rectangular box border with an edge on the top.
LinearBorder.top({
super.side,
double alignment = 0.0,
double size = 1.0
}) : start = null,
end = null,
top = LinearBorderEdge(alignment: alignment, size: size),
bottom = null;
/// Creates a rectangular box border with an edge on the bottom.
LinearBorder.bottom({
super.side,
double alignment = 0.0,
double size = 1.0
}) : start = null,
end = null,
top = null,
bottom = LinearBorderEdge(alignment: alignment, size: size);
/// No border.
static const LinearBorder none = LinearBorder();
/// Defines the left edge for [TextDirection.ltr] or the right
/// for [TextDirection.rtl].
final LinearBorderEdge? start;
/// Defines the right edge for [TextDirection.ltr] or the left
/// for [TextDirection.rtl].
final LinearBorderEdge? end;
/// Defines the top edge.
final LinearBorderEdge? top;
/// Defines the bottom edge.
final LinearBorderEdge? bottom;
@override
LinearBorder scale(double t) {
return LinearBorder(
side: side.scale(t),
);
}
@override
EdgeInsetsGeometry get dimensions {
final double width = side.width;
return EdgeInsetsDirectional.fromSTEB(
start == null ? 0.0 : width,
top == null ? 0.0 : width,
end == null ? 0.0 : width,
bottom == null ? 0.0 : width,
);
}
@override
ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
if (a is LinearBorder) {
return LinearBorder(
side: BorderSide.lerp(a.side, side, t),
start: LinearBorderEdge.lerp(a.start, start, t),
end: LinearBorderEdge.lerp(a.end, end, t),
top: LinearBorderEdge.lerp(a.top, top, t),
bottom: LinearBorderEdge.lerp(a.bottom, bottom, t),
);
}
return super.lerpFrom(a, t);
}
@override
ShapeBorder? lerpTo(ShapeBorder? b, double t) {
if (b is LinearBorder) {
return LinearBorder(
side: BorderSide.lerp(side, b.side, t),
start: LinearBorderEdge.lerp(start, b.start, t),
end: LinearBorderEdge.lerp(end, b.end, t),
top: LinearBorderEdge.lerp(top, b.top, t),
bottom: LinearBorderEdge.lerp(bottom, b.bottom, t),
);
}
return super.lerpTo(b, t);
}
/// Returns a copy of this LinearBorder with the given fields replaced with
/// the new values.
@override
LinearBorder copyWith({
BorderSide? side,
LinearBorderEdge? start,
LinearBorderEdge? end,
LinearBorderEdge? top,
LinearBorderEdge? bottom,
}) {
return LinearBorder(
side: side ?? this.side,
start: start ?? this.start,
end: end ?? this.end,
top: top ?? this.top,
bottom: bottom ?? this.bottom,
);
}
@override
Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
final Rect adjustedRect = dimensions.resolve(textDirection).deflateRect(rect);
return Path()
..addRect(adjustedRect);
}
@override
Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
return Path()
..addRect(rect);
}
@override
void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
final EdgeInsets insets = dimensions.resolve(textDirection);
final bool rtl = textDirection == TextDirection.rtl;
final Path path = Path();
final Paint paint = Paint()
..strokeWidth = 0.0;
void drawEdge(Rect rect, Color color) {
paint.color = color;
path.reset();
path.moveTo(rect.left, rect.top);
if (rect.width == 0.0) {
paint.style = PaintingStyle.stroke;
path.lineTo(rect.left, rect.bottom);
} else if (rect.height == 0.0) {
paint.style = PaintingStyle.stroke;
path.lineTo(rect.right, rect.top);
} else {
paint.style = PaintingStyle.fill;
path.lineTo(rect.right, rect.top);
path.lineTo(rect.right, rect.bottom);
path.lineTo(rect.left, rect.bottom);
}
canvas.drawPath(path, paint);
}
if (start != null && start!.size != 0.0 && side.style != BorderStyle.none) {
final Rect insetRect = Rect.fromLTWH(rect.left, rect.top + insets.top, rect.width, rect.height - insets.vertical);
final double x = rtl ? rect.right - insets.right : rect.left;
final double width = rtl ? insets.right : insets.left;
final double height = insetRect.height * start!.size;
final double y = (insetRect.height - height) * ((start!.alignment + 1.0) / 2.0);
final Rect r = Rect.fromLTWH(x, y, width, height);
drawEdge(r, side.color);
}
if (end != null && end!.size != 0.0 && side.style != BorderStyle.none) {
final Rect insetRect = Rect.fromLTWH(rect.left, rect.top + insets.top, rect.width, rect.height - insets.vertical);
final double x = rtl ? rect.left : rect.right - insets.right;
final double width = rtl ? insets.left : insets.right;
final double height = insetRect.height * end!.size;
final double y = (insetRect.height - height) * ((end!.alignment + 1.0) / 2.0);
final Rect r = Rect.fromLTWH(x, y, width, height);
drawEdge(r, side.color);
}
if (top != null && top!.size != 0.0 && side.style != BorderStyle.none) {
final double width = rect.width * top!.size;
final double startX = (rect.width - width) * ((top!.alignment + 1.0) / 2.0);
final double x = rtl ? rect.width - startX - width : startX;
final Rect r = Rect.fromLTWH(x, rect.top, width, insets.top);
drawEdge(r, side.color);
}
if (bottom != null && bottom!.size != 0.0 && side.style != BorderStyle.none) {
final double width = rect.width * bottom!.size;
final double startX = (rect.width - width) * ((bottom!.alignment + 1.0) / 2.0);
final double x = rtl ? rect.width - startX - width: startX;
final Rect r = Rect.fromLTWH(x, rect.bottom - insets.bottom, width, side.width);
drawEdge(r, side.color);
}
}
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is LinearBorder
&& other.side == side
&& other.start == start
&& other.end == end
&& other.top == top
&& other.bottom == bottom;
}
@override
int get hashCode => Object.hash(side, start, end, top, bottom);
@override
String toString() {
if (this == LinearBorder.none) {
return 'LinearBorder.none';
}
final StringBuffer s = StringBuffer('${objectRuntimeType(this, 'LinearBorder')}(side: $side');
if (start != null ) {
s.write(', start: $start');
}
if (end != null ) {
s.write(', end: $end');
}
if (top != null ) {
s.write(', top: $top');
}
if (bottom != null ) {
s.write(', bottom: $bottom');
}
s.write(')');
return s.toString();
}
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/painting.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
const Rect canvasRect = Rect.fromLTWH(0, 0, 100, 100);
const BorderSide borderSide = BorderSide(width: 4, color: Color(0x0f00ff00));
// Test points for rectangular filled paths based on a BorderSide with width 4 and
// a 100x100 bounding rectangle (canvasRect).
List<Offset> rectIncludes(Rect r) {
return <Offset>[r.topLeft, r.topRight, r.bottomLeft, r.bottomRight, r.center];
}
final List<Offset> leftRectIncludes = rectIncludes(const Rect.fromLTWH(0, 0, 4, 100));
final List<Offset> rightRectIncludes = rectIncludes(const Rect.fromLTWH(96, 0, 4, 100));
final List<Offset> topRectIncludes = rectIncludes(const Rect.fromLTWH(0, 0, 100, 4));
final List<Offset> bottomRectIncludes = rectIncludes(const Rect.fromLTWH(0, 96, 100, 4));
void main() {
test('LinearBorderEdge defaults', () {
expect(const LinearBorderEdge().size, 1);
expect(const LinearBorderEdge().alignment, 0);
});
test('LinearBorder defaults', () {
void expectEmptyBorder(LinearBorder border) {
expect(border.side, BorderSide.none);
expect(border.dimensions, EdgeInsets.zero);
expect(border.preferPaintInterior, false);
expect(border.start, null);
expect(border.end, null);
expect(border.top, null);
expect(border.bottom, null);
}
expectEmptyBorder(LinearBorder.none);
expect(LinearBorder.start().side, BorderSide.none);
expect(LinearBorder.start().start, const LinearBorderEdge());
expect(LinearBorder.start().end, null);
expect(LinearBorder.start().top, null);
expect(LinearBorder.start().bottom, null);
expect(LinearBorder.end().side, BorderSide.none);
expect(LinearBorder.end().start, null);
expect(LinearBorder.end().end, const LinearBorderEdge());
expect(LinearBorder.end().top, null);
expect(LinearBorder.end().bottom, null);
expect(LinearBorder.top().side, BorderSide.none);
expect(LinearBorder.top().start, null);
expect(LinearBorder.top().end, null);
expect(LinearBorder.top().top, const LinearBorderEdge());
expect(LinearBorder.top().bottom, null);
expect(LinearBorder.bottom().side, BorderSide.none);
expect(LinearBorder.bottom().start, null);
expect(LinearBorder.bottom().end, null);
expect(LinearBorder.bottom().top, null);
expect(LinearBorder.bottom().bottom, const LinearBorderEdge());
});
test('LinearBorder copyWith, ==, hashCode', () {
expect(LinearBorder.none, LinearBorder.none.copyWith());
expect(LinearBorder.none.hashCode, LinearBorder.none.copyWith().hashCode);
const BorderSide side = BorderSide(width: 10.0, color: Color(0xff123456));
expect(LinearBorder.none.copyWith(side: side), const LinearBorder(side: side));
});
test('LinearBorderEdge, LinearBorder toString()', () {
expect(const LinearBorderEdge(size: 0.5, alignment: -0.5).toString(), 'LinearBorderEdge(size: 0.5, alignment: -0.5)');
expect(LinearBorder.none.toString(), 'LinearBorder.none');
const BorderSide side = BorderSide(width: 10.0, color: Color(0xff123456));
expect(const LinearBorder(side: side).toString(), 'LinearBorder(side: BorderSide(color: Color(0xff123456), width: 10.0))');
expect(
const LinearBorder(
side: side,
start: LinearBorderEdge(size: 0, alignment: -0.75),
end: LinearBorderEdge(size: 0.25, alignment: -0.5),
top: LinearBorderEdge(size: 0.5, alignment: 0.5),
bottom: LinearBorderEdge(size: 0.75, alignment: 0.75),
).toString(),
'LinearBorder('
'side: BorderSide(color: Color(0xff123456), width: 10.0), '
'start: LinearBorderEdge(size: 0.0, alignment: -0.75), '
'end: LinearBorderEdge(size: 0.25, alignment: -0.5), '
'top: LinearBorderEdge(size: 0.5, alignment: 0.5), '
'bottom: LinearBorderEdge(size: 0.75, alignment: 0.75))',
);
},
skip: isBrowser, // [intended] see https://github.com/flutter/flutter/issues/118207
);
test('LinearBorder.start()', () {
final LinearBorder border = LinearBorder.start(side: borderSide);
expect(
(Canvas canvas) => border.paint(canvas, canvasRect, textDirection: TextDirection.ltr),
paints
..path(
includes: leftRectIncludes,
excludes: rightRectIncludes,
color: borderSide.color,
),
);
expect(
(Canvas canvas) => border.paint(canvas, canvasRect, textDirection: TextDirection.rtl),
paints
..path(
includes: rightRectIncludes,
excludes: leftRectIncludes,
color: borderSide.color,
),
);
});
test('LinearBorder.end()', () {
final LinearBorder border = LinearBorder.end(side: borderSide);
expect(
(Canvas canvas) => border.paint(canvas, canvasRect, textDirection: TextDirection.ltr),
paints
..path(
includes: rightRectIncludes,
excludes: leftRectIncludes,
color: borderSide.color,
),
);
expect(
(Canvas canvas) => border.paint(canvas, canvasRect, textDirection: TextDirection.rtl),
paints
..path(
includes: leftRectIncludes,
excludes: rightRectIncludes,
color: borderSide.color,
),
);
});
test('LinearBorder.top()', () {
final LinearBorder border = LinearBorder.top(side: borderSide);
expect(
(Canvas canvas) => border.paint(canvas, canvasRect, textDirection: TextDirection.ltr),
paints
..path(
includes: topRectIncludes,
excludes: bottomRectIncludes,
color: borderSide.color,
),
);
});
test('LinearBorder.bottom()', () {
final LinearBorder border = LinearBorder.bottom(side: borderSide);
expect(
(Canvas canvas) => border.paint(canvas, canvasRect, textDirection: TextDirection.ltr),
paints
..path(
includes: bottomRectIncludes,
excludes: topRectIncludes,
color: borderSide.color,
),
);
});
}
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