1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | /// @docImport 'package:flutter/material.dart'; |
6 | /// |
7 | /// @docImport 'box_decoration.dart'; |
8 | library; |
9 | |
10 | import 'package:flutter/foundation.dart'; |
11 | |
12 | import 'basic_types.dart'; |
13 | import 'border_radius.dart'; |
14 | import 'borders.dart'; |
15 | import 'edge_insets.dart'; |
16 | |
17 | // Examples can assume: |
18 | // late BuildContext context; |
19 | |
20 | /// The shape to use when rendering a [Border] or [BoxDecoration]. |
21 | /// |
22 | /// Consider using [ShapeBorder] subclasses directly (with [ShapeDecoration]), |
23 | /// instead of using [BoxShape] and [Border], if the shapes will need to be |
24 | /// interpolated or animated. The [Border] class cannot interpolate between |
25 | /// different shapes. |
26 | enum BoxShape { |
27 | /// An axis-aligned rectangle, optionally with rounded corners. |
28 | /// |
29 | /// The amount of corner rounding, if any, is determined by the border radius |
30 | /// specified by classes such as [BoxDecoration] or [Border]. The rectangle's |
31 | /// edges match those of the box in which it is painted. |
32 | /// |
33 | /// See also: |
34 | /// |
35 | /// * [RoundedRectangleBorder], the equivalent [ShapeBorder]. |
36 | rectangle, |
37 | |
38 | /// A circle centered in the middle of the box into which the [Border] or |
39 | /// [BoxDecoration] is painted. The diameter of the circle is the shortest |
40 | /// dimension of the box, either the width or the height, such that the circle |
41 | /// touches the edges of the box. |
42 | /// |
43 | /// See also: |
44 | /// |
45 | /// * [CircleBorder], the equivalent [ShapeBorder]. |
46 | circle, |
47 | |
48 | // Don't add more, instead create a new ShapeBorder. |
49 | } |
50 | |
51 | /// Base class for box borders that can paint as rectangles, circles, or rounded |
52 | /// rectangles. |
53 | /// |
54 | /// This class is extended by [Border] and [BorderDirectional] to provide |
55 | /// concrete versions of four-sided borders using different conventions for |
56 | /// specifying the sides. |
57 | /// |
58 | /// The only API difference that this class introduces over [ShapeBorder] is |
59 | /// that its [paint] method takes additional arguments. |
60 | /// |
61 | /// See also: |
62 | /// |
63 | /// * [BorderSide], which is used to describe each side of the box. |
64 | /// * [RoundedRectangleBorder], another way of describing a box's border. |
65 | /// * [CircleBorder], another way of describing a circle border. |
66 | /// * [BoxDecoration], which uses a [BoxBorder] to describe its borders. |
67 | abstract class BoxBorder extends ShapeBorder { |
68 | /// Abstract const constructor. This constructor enables subclasses to provide |
69 | /// const constructors so that they can be used in const expressions. |
70 | const BoxBorder(); |
71 | |
72 | /// Creates a [Border]. |
73 | /// |
74 | /// All the sides of the border default to [BorderSide.none]. |
75 | factory BoxBorder.fromLTRB({ |
76 | BorderSide top = BorderSide.none, |
77 | BorderSide right = BorderSide.none, |
78 | BorderSide bottom = BorderSide.none, |
79 | BorderSide left = BorderSide.none, |
80 | }) => Border(top: top, right: right, bottom: bottom, left: left); |
81 | |
82 | /// A uniform [Border] with all sides the same color and width. |
83 | /// |
84 | /// The sides default to black solid borders, one logical pixel wide. |
85 | factory BoxBorder.all({Color color, double width, BorderStyle style, double strokeAlign}) = |
86 | Border.all; |
87 | |
88 | /// Creates a [Border] whose sides are all the same. |
89 | const factory BoxBorder.fromBorderSide(BorderSide side) = Border.fromBorderSide; |
90 | |
91 | /// Creates a [Border] with symmetrical vertical and horizontal sides. |
92 | /// |
93 | /// The `vertical` argument applies to the [left] and [right] sides, and the |
94 | /// `horizontal` argument applies to the [top] and [bottom] sides. |
95 | /// |
96 | /// All arguments default to [BorderSide.none]. |
97 | const factory BoxBorder.symmetric({BorderSide vertical, BorderSide horizontal}) = |
98 | Border.symmetric; |
99 | |
100 | /// Creates a [BorderDirectional]. |
101 | /// |
102 | /// The [start] and [end] sides represent the horizontal sides; the start side |
103 | /// is on the leading edge given the reading direction, and the end side is on |
104 | /// the trailing edge. They are resolved during [paint]. |
105 | /// |
106 | /// All the sides of the border default to [BorderSide.none]. |
107 | factory BoxBorder.fromSTEB({ |
108 | BorderSide top = BorderSide.none, |
109 | BorderSide start = BorderSide.none, |
110 | BorderSide end = BorderSide.none, |
111 | BorderSide bottom = BorderSide.none, |
112 | }) => BorderDirectional(top: top, start: start, end: end, bottom: bottom); |
113 | |
114 | /// The top side of this border. |
115 | /// |
116 | /// This getter is available on both [Border] and [BorderDirectional]. If |
117 | /// [isUniform] is true, then this is the same style as all the other sides. |
118 | BorderSide get top; |
119 | |
120 | /// The bottom side of this border. |
121 | BorderSide get bottom; |
122 | |
123 | /// Whether all four sides of the border are identical. Uniform borders are |
124 | /// typically more efficient to paint. |
125 | /// |
126 | /// A uniform border by definition has no text direction dependency and |
127 | /// therefore could be expressed as a [Border], even if it is currently a |
128 | /// [BorderDirectional]. A uniform border can also be expressed as a |
129 | /// [RoundedRectangleBorder]. |
130 | bool get isUniform; |
131 | |
132 | // We override this to tighten the return value, so that callers can assume |
133 | // that we'll return a [BoxBorder]. |
134 | @override |
135 | BoxBorder? add(ShapeBorder other, {bool reversed = false}) => null; |
136 | |
137 | /// Linearly interpolate between two borders. |
138 | /// |
139 | /// If a border is null, it is treated as having four [BorderSide.none] |
140 | /// borders. |
141 | /// |
142 | /// This supports interpolating between [Border] and [BorderDirectional] |
143 | /// objects. If both objects are different types but both have sides on one or |
144 | /// both of their lateral edges (the two sides that aren't the top and bottom) |
145 | /// other than [BorderSide.none], then the sides are interpolated by reducing |
146 | /// `a`'s lateral edges to [BorderSide.none] over the first half of the |
147 | /// animation, and then bringing `b`'s lateral edges _from_ [BorderSide.none] |
148 | /// over the second half of the animation. |
149 | /// |
150 | /// For a more flexible approach, consider [ShapeBorder.lerp], which would |
151 | /// instead [add] the two sets of sides and interpolate them simultaneously. |
152 | /// |
153 | /// {@macro dart.ui.shadow.lerp} |
154 | static BoxBorder? lerp(BoxBorder? a, BoxBorder? b, double t) { |
155 | if (identical(a, b)) { |
156 | return a; |
157 | } |
158 | if ((a is Border?) && (b is Border?)) { |
159 | return Border.lerp(a, b, t); |
160 | } |
161 | if ((a is BorderDirectional?) && (b is BorderDirectional?)) { |
162 | return BorderDirectional.lerp(a, b, t); |
163 | } |
164 | if (b is Border && a is BorderDirectional) { |
165 | (a, b) = (b, a); |
166 | t = 1.0 - t; |
167 | // fall through to next case |
168 | } |
169 | if (a is Border && b is BorderDirectional) { |
170 | if (b.start == BorderSide.none && b.end == BorderSide.none) { |
171 | // The fact that b is a BorderDirectional really doesn't matter, it turns out. |
172 | return Border( |
173 | top: BorderSide.lerp(a.top, b.top, t), |
174 | right: BorderSide.lerp(a.right, BorderSide.none, t), |
175 | bottom: BorderSide.lerp(a.bottom, b.bottom, t), |
176 | left: BorderSide.lerp(a.left, BorderSide.none, t), |
177 | ); |
178 | } |
179 | if (a.left == BorderSide.none && a.right == BorderSide.none) { |
180 | // The fact that a is a Border really doesn't matter, it turns out. |
181 | return BorderDirectional( |
182 | top: BorderSide.lerp(a.top, b.top, t), |
183 | start: BorderSide.lerp(BorderSide.none, b.start, t), |
184 | end: BorderSide.lerp(BorderSide.none, b.end, t), |
185 | bottom: BorderSide.lerp(a.bottom, b.bottom, t), |
186 | ); |
187 | } |
188 | // Since we have to swap a visual border for a directional one, |
189 | // we speed up the horizontal sides' transitions and switch from |
190 | // one mode to the other at t=0.5. |
191 | if (t < 0.5) { |
192 | return Border( |
193 | top: BorderSide.lerp(a.top, b.top, t), |
194 | right: BorderSide.lerp(a.right, BorderSide.none, t * 2.0), |
195 | bottom: BorderSide.lerp(a.bottom, b.bottom, t), |
196 | left: BorderSide.lerp(a.left, BorderSide.none, t * 2.0), |
197 | ); |
198 | } |
199 | return BorderDirectional( |
200 | top: BorderSide.lerp(a.top, b.top, t), |
201 | start: BorderSide.lerp(BorderSide.none, b.start, (t - 0.5) * 2.0), |
202 | end: BorderSide.lerp(BorderSide.none, b.end, (t - 0.5) * 2.0), |
203 | bottom: BorderSide.lerp(a.bottom, b.bottom, t), |
204 | ); |
205 | } |
206 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
207 | ErrorSummary('BoxBorder.lerp can only interpolate Border and BorderDirectional classes.'), |
208 | ErrorDescription( |
209 | 'BoxBorder.lerp() was called with two objects of type${a.runtimeType} and${b.runtimeType} :\n' |
210 | '$a \n' |
211 | '$b \n' |
212 | 'However, only Border and BorderDirectional classes are supported by this method.', |
213 | ), |
214 | ErrorHint( |
215 | 'For a more general interpolation method, consider using ShapeBorder.lerp instead.', |
216 | ), |
217 | ]); |
218 | } |
219 | |
220 | @override |
221 | Path getInnerPath(Rect rect, {TextDirection? textDirection}) { |
222 | assert( |
223 | textDirection != null, |
224 | 'The textDirection argument to$runtimeType .getInnerPath must not be null.', |
225 | ); |
226 | return Path()..addRect(dimensions.resolve(textDirection).deflateRect(rect)); |
227 | } |
228 | |
229 | @override |
230 | Path getOuterPath(Rect rect, {TextDirection? textDirection}) { |
231 | assert( |
232 | textDirection != null, |
233 | 'The textDirection argument to$runtimeType .getOuterPath must not be null.', |
234 | ); |
235 | return Path()..addRect(rect); |
236 | } |
237 | |
238 | @override |
239 | void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { |
240 | // For `ShapeDecoration(shape: Border.all())`, a rectangle with sharp edges |
241 | // is always painted. There is no borderRadius parameter for |
242 | // ShapeDecoration or Border, only for BoxDecoration, which doesn't call |
243 | // this method. |
244 | canvas.drawRect(rect, paint); |
245 | } |
246 | |
247 | @override |
248 | bool get preferPaintInterior => true; |
249 | |
250 | /// Paints the border within the given [Rect] on the given [Canvas]. |
251 | /// |
252 | /// This is an extension of the [ShapeBorder.paint] method. It allows |
253 | /// [BoxBorder] borders to be applied to different [BoxShape]s and with |
254 | /// different [borderRadius] parameters, without changing the [BoxBorder] |
255 | /// object itself. |
256 | /// |
257 | /// The `shape` argument specifies the [BoxShape] to draw the border on. |
258 | /// |
259 | /// If the `shape` is specifies a rectangular box shape |
260 | /// ([BoxShape.rectangle]), then the `borderRadius` argument describes the |
261 | /// corners of the rectangle. |
262 | /// |
263 | /// The [getInnerPath] and [getOuterPath] methods do not know about the |
264 | /// `shape` and `borderRadius` arguments. |
265 | /// |
266 | /// See also: |
267 | /// |
268 | /// * [paintBorder], which is used if the border has non-uniform colors or styles and no borderRadius. |
269 | /// * [Border.paint], similar to this method, includes additional comments |
270 | /// and provides more details on each parameter than described here. |
271 | @override |
272 | void paint( |
273 | Canvas canvas, |
274 | Rect rect, { |
275 | TextDirection? textDirection, |
276 | BoxShape shape = BoxShape.rectangle, |
277 | BorderRadius? borderRadius, |
278 | }); |
279 | |
280 | static void _paintUniformBorderWithRadius( |
281 | Canvas canvas, |
282 | Rect rect, |
283 | BorderSide side, |
284 | BorderRadius borderRadius, |
285 | ) { |
286 | assert(side.style != BorderStyle.none); |
287 | final Paint paint = Paint()..color = side.color; |
288 | final double width = side.width; |
289 | if (width == 0.0) { |
290 | paint |
291 | ..style = PaintingStyle.stroke |
292 | ..strokeWidth = 0.0; |
293 | canvas.drawRRect(borderRadius.toRRect(rect), paint); |
294 | } else { |
295 | final RRect borderRect = borderRadius.toRRect(rect); |
296 | final RRect inner = borderRect.deflate(side.strokeInset); |
297 | final RRect outer = borderRect.inflate(side.strokeOutset); |
298 | canvas.drawDRRect(outer, inner, paint); |
299 | } |
300 | } |
301 | |
302 | /// Paints a Border with different widths, styles and strokeAligns, on any |
303 | /// borderRadius while using a single color. |
304 | /// |
305 | /// See also: |
306 | /// |
307 | /// * [paintBorder], which supports multiple colors but not borderRadius. |
308 | /// * [paint], which calls this method. |
309 | static void paintNonUniformBorder( |
310 | Canvas canvas, |
311 | Rect rect, { |
312 | required BorderRadius? borderRadius, |
313 | required TextDirection? textDirection, |
314 | BoxShape shape = BoxShape.rectangle, |
315 | BorderSide top = BorderSide.none, |
316 | BorderSide right = BorderSide.none, |
317 | BorderSide bottom = BorderSide.none, |
318 | BorderSide left = BorderSide.none, |
319 | required Color color, |
320 | }) { |
321 | final RRect borderRect; |
322 | switch (shape) { |
323 | case BoxShape.rectangle: |
324 | borderRect = (borderRadius ?? BorderRadius.zero).resolve(textDirection).toRRect(rect); |
325 | case BoxShape.circle: |
326 | assert( |
327 | borderRadius == null, |
328 | 'A borderRadius cannot be given when shape is a BoxShape.circle.', |
329 | ); |
330 | borderRect = RRect.fromRectAndRadius( |
331 | Rect.fromCircle(center: rect.center, radius: rect.shortestSide / 2.0), |
332 | Radius.circular(rect.width), |
333 | ); |
334 | } |
335 | final Paint paint = Paint()..color = color; |
336 | final RRect inner = _deflateRRect( |
337 | borderRect, |
338 | EdgeInsets.fromLTRB(left.strokeInset, top.strokeInset, right.strokeInset, bottom.strokeInset), |
339 | ); |
340 | final RRect outer = _inflateRRect( |
341 | borderRect, |
342 | EdgeInsets.fromLTRB( |
343 | left.strokeOutset, |
344 | top.strokeOutset, |
345 | right.strokeOutset, |
346 | bottom.strokeOutset, |
347 | ), |
348 | ); |
349 | canvas.drawDRRect(outer, inner, paint); |
350 | } |
351 | |
352 | static RRect _inflateRRect(RRect rect, EdgeInsets insets) { |
353 | return RRect.fromLTRBAndCorners( |
354 | rect.left - insets.left, |
355 | rect.top - insets.top, |
356 | rect.right + insets.right, |
357 | rect.bottom + insets.bottom, |
358 | topLeft: (rect.tlRadius + Radius.elliptical(insets.left, insets.top)).clamp( |
359 | minimum: Radius.zero, |
360 | ), |
361 | topRight: (rect.trRadius + Radius.elliptical(insets.right, insets.top)).clamp( |
362 | minimum: Radius.zero, |
363 | ), |
364 | bottomRight: (rect.brRadius + Radius.elliptical(insets.right, insets.bottom)).clamp( |
365 | minimum: Radius.zero, |
366 | ), |
367 | bottomLeft: (rect.blRadius + Radius.elliptical(insets.left, insets.bottom)).clamp( |
368 | minimum: Radius.zero, |
369 | ), |
370 | ); |
371 | } |
372 | |
373 | static RRect _deflateRRect(RRect rect, EdgeInsets insets) { |
374 | return RRect.fromLTRBAndCorners( |
375 | rect.left + insets.left, |
376 | rect.top + insets.top, |
377 | rect.right - insets.right, |
378 | rect.bottom - insets.bottom, |
379 | topLeft: (rect.tlRadius - Radius.elliptical(insets.left, insets.top)).clamp( |
380 | minimum: Radius.zero, |
381 | ), |
382 | topRight: (rect.trRadius - Radius.elliptical(insets.right, insets.top)).clamp( |
383 | minimum: Radius.zero, |
384 | ), |
385 | bottomRight: (rect.brRadius - Radius.elliptical(insets.right, insets.bottom)).clamp( |
386 | minimum: Radius.zero, |
387 | ), |
388 | bottomLeft: (rect.blRadius - Radius.elliptical(insets.left, insets.bottom)).clamp( |
389 | minimum: Radius.zero, |
390 | ), |
391 | ); |
392 | } |
393 | |
394 | static void _paintUniformBorderWithCircle(Canvas canvas, Rect rect, BorderSide side) { |
395 | assert(side.style != BorderStyle.none); |
396 | final double radius = (rect.shortestSide + side.strokeOffset) / 2; |
397 | canvas.drawCircle(rect.center, radius, side.toPaint()); |
398 | } |
399 | |
400 | static void _paintUniformBorderWithRectangle(Canvas canvas, Rect rect, BorderSide side) { |
401 | assert(side.style != BorderStyle.none); |
402 | canvas.drawRect(rect.inflate(side.strokeOffset / 2), side.toPaint()); |
403 | } |
404 | } |
405 | |
406 | /// A border of a box, comprised of four sides: top, right, bottom, left. |
407 | /// |
408 | /// The sides are represented by [BorderSide] objects. |
409 | /// |
410 | /// {@tool snippet} |
411 | /// |
412 | /// All four borders the same, two-pixel wide solid white: |
413 | /// |
414 | /// ```dart |
415 | /// Border.all(width: 2.0, color: const Color(0xFFFFFFFF)) |
416 | /// ``` |
417 | /// {@end-tool} |
418 | /// {@tool snippet} |
419 | /// |
420 | /// The border for a Material Design divider: |
421 | /// |
422 | /// ```dart |
423 | /// Border(bottom: BorderSide(color: Theme.of(context).dividerColor)) |
424 | /// ``` |
425 | /// {@end-tool} |
426 | /// {@tool snippet} |
427 | /// |
428 | /// A 1990s-era "OK" button: |
429 | /// |
430 | /// ```dart |
431 | /// Container( |
432 | /// decoration: const BoxDecoration( |
433 | /// border: Border( |
434 | /// top: BorderSide(color: Color(0xFFFFFFFF)), |
435 | /// left: BorderSide(color: Color(0xFFFFFFFF)), |
436 | /// right: BorderSide(), |
437 | /// bottom: BorderSide(), |
438 | /// ), |
439 | /// ), |
440 | /// child: Container( |
441 | /// padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 2.0), |
442 | /// decoration: const BoxDecoration( |
443 | /// border: Border( |
444 | /// top: BorderSide(color: Color(0xFFDFDFDF)), |
445 | /// left: BorderSide(color: Color(0xFFDFDFDF)), |
446 | /// right: BorderSide(color: Color(0xFF7F7F7F)), |
447 | /// bottom: BorderSide(color: Color(0xFF7F7F7F)), |
448 | /// ), |
449 | /// color: Color(0xFFBFBFBF), |
450 | /// ), |
451 | /// child: const Text( |
452 | /// 'OK', |
453 | /// textAlign: TextAlign.center, |
454 | /// style: TextStyle(color: Color(0xFF000000)) |
455 | /// ), |
456 | /// ), |
457 | /// ) |
458 | /// ``` |
459 | /// {@end-tool} |
460 | /// |
461 | /// See also: |
462 | /// |
463 | /// * [BoxDecoration], which uses this class to describe its edge decoration. |
464 | /// * [BorderSide], which is used to describe each side of the box. |
465 | /// * [Theme], from the material layer, which can be queried to obtain appropriate colors |
466 | /// to use for borders in a [MaterialApp], as shown in the "divider" sample above. |
467 | /// * [paint], which explains the behavior of [BoxDecoration] parameters. |
468 | /// * <https://pub.dev/packages/non_uniform_border>, a package that implements |
469 | /// a Non-Uniform Border on ShapeBorder, which is used by Material Design |
470 | /// buttons and other widgets, under the "shape" field. |
471 | class Border extends BoxBorder { |
472 | /// Creates a border. |
473 | /// |
474 | /// All the sides of the border default to [BorderSide.none]. |
475 | const Border({ |
476 | this.top = BorderSide.none, |
477 | this.right = BorderSide.none, |
478 | this.bottom = BorderSide.none, |
479 | this.left = BorderSide.none, |
480 | }); |
481 | |
482 | /// Creates a border whose sides are all the same. |
483 | const Border.fromBorderSide(BorderSide side) |
484 | : top = side, |
485 | right = side, |
486 | bottom = side, |
487 | left = side; |
488 | |
489 | /// Creates a border with symmetrical vertical and horizontal sides. |
490 | /// |
491 | /// The `vertical` argument applies to the [left] and [right] sides, and the |
492 | /// `horizontal` argument applies to the [top] and [bottom] sides. |
493 | /// |
494 | /// All arguments default to [BorderSide.none]. |
495 | const Border.symmetric({ |
496 | BorderSide vertical = BorderSide.none, |
497 | BorderSide horizontal = BorderSide.none, |
498 | }) : left = vertical, |
499 | top = horizontal, |
500 | right = vertical, |
501 | bottom = horizontal; |
502 | |
503 | /// A uniform border with all sides the same color and width. |
504 | /// |
505 | /// The sides default to black solid borders, one logical pixel wide. |
506 | factory Border.all({ |
507 | Color color = const Color(0xFF000000), |
508 | double width = 1.0, |
509 | BorderStyle style = BorderStyle.solid, |
510 | double strokeAlign = BorderSide.strokeAlignInside, |
511 | }) { |
512 | final BorderSide side = BorderSide( |
513 | color: color, |
514 | width: width, |
515 | style: style, |
516 | strokeAlign: strokeAlign, |
517 | ); |
518 | return Border.fromBorderSide(side); |
519 | } |
520 | |
521 | /// Creates a [Border] that represents the addition of the two given |
522 | /// [Border]s. |
523 | /// |
524 | /// It is only valid to call this if [BorderSide.canMerge] returns true for |
525 | /// the pairwise combination of each side on both [Border]s. |
526 | static Border merge(Border a, Border b) { |
527 | assert(BorderSide.canMerge(a.top, b.top)); |
528 | assert(BorderSide.canMerge(a.right, b.right)); |
529 | assert(BorderSide.canMerge(a.bottom, b.bottom)); |
530 | assert(BorderSide.canMerge(a.left, b.left)); |
531 | return Border( |
532 | top: BorderSide.merge(a.top, b.top), |
533 | right: BorderSide.merge(a.right, b.right), |
534 | bottom: BorderSide.merge(a.bottom, b.bottom), |
535 | left: BorderSide.merge(a.left, b.left), |
536 | ); |
537 | } |
538 | |
539 | @override |
540 | final BorderSide top; |
541 | |
542 | /// The right side of this border. |
543 | final BorderSide right; |
544 | |
545 | @override |
546 | final BorderSide bottom; |
547 | |
548 | /// The left side of this border. |
549 | final BorderSide left; |
550 | |
551 | @override |
552 | EdgeInsetsGeometry get dimensions { |
553 | return EdgeInsets.fromLTRB( |
554 | left.strokeInset, |
555 | top.strokeInset, |
556 | right.strokeInset, |
557 | bottom.strokeInset, |
558 | ); |
559 | } |
560 | |
561 | @override |
562 | bool get isUniform => |
563 | _colorIsUniform && _widthIsUniform && _styleIsUniform && _strokeAlignIsUniform; |
564 | |
565 | bool get _colorIsUniform { |
566 | final Color topColor = top.color; |
567 | return left.color == topColor && bottom.color == topColor && right.color == topColor; |
568 | } |
569 | |
570 | bool get _widthIsUniform { |
571 | final double topWidth = top.width; |
572 | return left.width == topWidth && bottom.width == topWidth && right.width == topWidth; |
573 | } |
574 | |
575 | bool get _styleIsUniform { |
576 | final BorderStyle topStyle = top.style; |
577 | return left.style == topStyle && bottom.style == topStyle && right.style == topStyle; |
578 | } |
579 | |
580 | bool get _strokeAlignIsUniform { |
581 | final double topStrokeAlign = top.strokeAlign; |
582 | return left.strokeAlign == topStrokeAlign && |
583 | bottom.strokeAlign == topStrokeAlign && |
584 | right.strokeAlign == topStrokeAlign; |
585 | } |
586 | |
587 | Set<Color> _distinctVisibleColors() { |
588 | return <Color>{ |
589 | if (top.style != BorderStyle.none) top.color, |
590 | if (right.style != BorderStyle.none) right.color, |
591 | if (bottom.style != BorderStyle.none) bottom.color, |
592 | if (left.style != BorderStyle.none) left.color, |
593 | }; |
594 | } |
595 | |
596 | // [BoxBorder.paintNonUniformBorder] is about 20% faster than [paintBorder], |
597 | // but [paintBorder] is able to draw hairline borders when width is zero |
598 | // and style is [BorderStyle.solid]. |
599 | bool get _hasHairlineBorder => |
600 | (top.style == BorderStyle.solid && top.width == 0.0) || |
601 | (right.style == BorderStyle.solid && right.width == 0.0) || |
602 | (bottom.style == BorderStyle.solid && bottom.width == 0.0) || |
603 | (left.style == BorderStyle.solid && left.width == 0.0); |
604 | |
605 | @override |
606 | Border? add(ShapeBorder other, {bool reversed = false}) { |
607 | if (other is Border && |
608 | BorderSide.canMerge(top, other.top) && |
609 | BorderSide.canMerge(right, other.right) && |
610 | BorderSide.canMerge(bottom, other.bottom) && |
611 | BorderSide.canMerge(left, other.left)) { |
612 | return Border.merge(this, other); |
613 | } |
614 | return null; |
615 | } |
616 | |
617 | @override |
618 | Border scale(double t) { |
619 | return Border( |
620 | top: top.scale(t), |
621 | right: right.scale(t), |
622 | bottom: bottom.scale(t), |
623 | left: left.scale(t), |
624 | ); |
625 | } |
626 | |
627 | @override |
628 | ShapeBorder? lerpFrom(ShapeBorder? a, double t) { |
629 | if (a is Border) { |
630 | return Border.lerp(a, this, t); |
631 | } |
632 | return super.lerpFrom(a, t); |
633 | } |
634 | |
635 | @override |
636 | ShapeBorder? lerpTo(ShapeBorder? b, double t) { |
637 | if (b is Border) { |
638 | return Border.lerp(this, b, t); |
639 | } |
640 | return super.lerpTo(b, t); |
641 | } |
642 | |
643 | /// Linearly interpolate between two borders. |
644 | /// |
645 | /// If a border is null, it is treated as having four [BorderSide.none] |
646 | /// borders. |
647 | /// |
648 | /// {@macro dart.ui.shadow.lerp} |
649 | static Border? lerp(Border? a, Border? b, double t) { |
650 | if (identical(a, b)) { |
651 | return a; |
652 | } |
653 | if (a == null) { |
654 | return b!.scale(t); |
655 | } |
656 | if (b == null) { |
657 | return a.scale(1.0 - t); |
658 | } |
659 | return Border( |
660 | top: BorderSide.lerp(a.top, b.top, t), |
661 | right: BorderSide.lerp(a.right, b.right, t), |
662 | bottom: BorderSide.lerp(a.bottom, b.bottom, t), |
663 | left: BorderSide.lerp(a.left, b.left, t), |
664 | ); |
665 | } |
666 | |
667 | /// Paints the border within the given [Rect] on the given [Canvas]. |
668 | /// |
669 | /// Uniform borders and non-uniform borders with similar colors and styles |
670 | /// are more efficient to paint than more complex borders. |
671 | /// |
672 | /// You can provide a [BoxShape] to draw the border on. If the `shape` in |
673 | /// [BoxShape.circle], there is the requirement that the border has uniform |
674 | /// color and style. |
675 | /// |
676 | /// If you specify a rectangular box shape ([BoxShape.rectangle]), then you |
677 | /// may specify a [BorderRadius]. If a `borderRadius` is specified, there is |
678 | /// the requirement that the border has uniform color and style. |
679 | /// |
680 | /// The [getInnerPath] and [getOuterPath] methods do not know about the |
681 | /// `shape` and `borderRadius` arguments. |
682 | /// |
683 | /// The `textDirection` argument is not used by this paint method. |
684 | /// |
685 | /// See also: |
686 | /// |
687 | /// * [paintBorder], which is used if the border has non-uniform colors or styles and no borderRadius. |
688 | /// * <https://pub.dev/packages/non_uniform_border>, a package that implements |
689 | /// a Non-Uniform Border on ShapeBorder, which is used by Material Design |
690 | /// buttons and other widgets, under the "shape" field. |
691 | @override |
692 | void paint( |
693 | Canvas canvas, |
694 | Rect rect, { |
695 | TextDirection? textDirection, |
696 | BoxShape shape = BoxShape.rectangle, |
697 | BorderRadius? borderRadius, |
698 | }) { |
699 | if (isUniform) { |
700 | switch (top.style) { |
701 | case BorderStyle.none: |
702 | return; |
703 | case BorderStyle.solid: |
704 | switch (shape) { |
705 | case BoxShape.circle: |
706 | assert( |
707 | borderRadius == null, |
708 | 'A borderRadius cannot be given when shape is a BoxShape.circle.', |
709 | ); |
710 | BoxBorder._paintUniformBorderWithCircle(canvas, rect, top); |
711 | case BoxShape.rectangle: |
712 | if (borderRadius != null && borderRadius != BorderRadius.zero) { |
713 | BoxBorder._paintUniformBorderWithRadius(canvas, rect, top, borderRadius); |
714 | return; |
715 | } |
716 | BoxBorder._paintUniformBorderWithRectangle(canvas, rect, top); |
717 | } |
718 | return; |
719 | } |
720 | } |
721 | |
722 | if (_styleIsUniform && top.style == BorderStyle.none) { |
723 | return; |
724 | } |
725 | |
726 | // Allow painting non-uniform borders if the visible colors are uniform. |
727 | final Set<Color> visibleColors = _distinctVisibleColors(); |
728 | final bool hasHairlineBorder = _hasHairlineBorder; |
729 | // Paint a non uniform border if a single color is visible |
730 | // and (borderRadius is present) or (border is visible and width != 0.0). |
731 | if (visibleColors.length == 1 && |
732 | !hasHairlineBorder && |
733 | (shape == BoxShape.circle || (borderRadius != null && borderRadius != BorderRadius.zero))) { |
734 | BoxBorder.paintNonUniformBorder( |
735 | canvas, |
736 | rect, |
737 | shape: shape, |
738 | borderRadius: borderRadius, |
739 | textDirection: textDirection, |
740 | top: top.style == BorderStyle.none ? BorderSide.none : top, |
741 | right: right.style == BorderStyle.none ? BorderSide.none : right, |
742 | bottom: bottom.style == BorderStyle.none ? BorderSide.none : bottom, |
743 | left: left.style == BorderStyle.none ? BorderSide.none : left, |
744 | color: visibleColors.first, |
745 | ); |
746 | return; |
747 | } |
748 | |
749 | assert(() { |
750 | if (hasHairlineBorder) { |
751 | assert( |
752 | borderRadius == null || borderRadius == BorderRadius.zero, |
753 | 'A hairline border like `BorderSide(width: 0.0, style: BorderStyle.solid)` can only be drawn when BorderRadius is zero or null.', |
754 | ); |
755 | } |
756 | if (borderRadius != null && borderRadius != BorderRadius.zero) { |
757 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
758 | ErrorSummary('A borderRadius can only be given on borders with uniform colors.'), |
759 | ErrorDescription('The following is not uniform:'), |
760 | if (!_colorIsUniform) ErrorDescription('BorderSide.color'), |
761 | ]); |
762 | } |
763 | return true; |
764 | }()); |
765 | assert(() { |
766 | if (shape != BoxShape.rectangle) { |
767 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
768 | ErrorSummary('A Border can only be drawn as a circle on borders with uniform colors.'), |
769 | ErrorDescription('The following is not uniform:'), |
770 | if (!_colorIsUniform) ErrorDescription('BorderSide.color'), |
771 | ]); |
772 | } |
773 | return true; |
774 | }()); |
775 | assert(() { |
776 | if (!_strokeAlignIsUniform || top.strokeAlign != BorderSide.strokeAlignInside) { |
777 | throw FlutterError.fromParts(<DiagnosticsNode>[ |
778 | ErrorSummary( |
779 | 'A Border can only draw strokeAlign different than BorderSide.strokeAlignInside on borders with uniform colors.', |
780 | ), |
781 | ]); |
782 | } |
783 | return true; |
784 | }()); |
785 | |
786 | paintBorder(canvas, rect, top: top, right: right, bottom: bottom, left: left); |
787 | } |
788 | |
789 | @override |
790 | bool operator ==(Object other) { |
791 | if (identical(this, other)) { |
792 | return true; |
793 | } |
794 | if (other.runtimeType != runtimeType) { |
795 | return false; |
796 | } |
797 | return other is Border && |
798 | other.top == top && |
799 | other.right == right && |
800 | other.bottom == bottom && |
801 | other.left == left; |
802 | } |
803 | |
804 | @override |
805 | int get hashCode => Object.hash(top, right, bottom, left); |
806 | |
807 | @override |
808 | String toString() { |
809 | if (isUniform) { |
810 | return '${objectRuntimeType(this, 'Border')} .all($top )'; |
811 | } |
812 | final List<String> arguments = <String>[ |
813 | if (top != BorderSide.none) 'top:$top ', |
814 | if (right != BorderSide.none) 'right:$right ', |
815 | if (bottom != BorderSide.none) 'bottom:$bottom ', |
816 | if (left != BorderSide.none) 'left:$left ', |
817 | ]; |
818 | return '${objectRuntimeType(this, 'Border')} (${arguments.join( ", ")} )'; |
819 | } |
820 | } |
821 | |
822 | /// A border of a box, comprised of four sides, the lateral sides of which |
823 | /// flip over based on the reading direction. |
824 | /// |
825 | /// The lateral sides are called [start] and [end]. When painted in |
826 | /// left-to-right environments, the [start] side will be painted on the left and |
827 | /// the [end] side on the right; in right-to-left environments, it is the |
828 | /// reverse. The other two sides are [top] and [bottom]. |
829 | /// |
830 | /// The sides are represented by [BorderSide] objects. |
831 | /// |
832 | /// If the [start] and [end] sides are the same, then it is slightly more |
833 | /// efficient to use a [Border] object rather than a [BorderDirectional] object. |
834 | /// |
835 | /// See also: |
836 | /// |
837 | /// * [BoxDecoration], which uses this class to describe its edge decoration. |
838 | /// * [BorderSide], which is used to describe each side of the box. |
839 | /// * [Theme], from the material layer, which can be queried to obtain appropriate colors |
840 | /// to use for borders in a [MaterialApp], as shown in the "divider" sample above. |
841 | /// * <https://pub.dev/packages/non_uniform_border>, a package that implements |
842 | /// a Non-Uniform Border on ShapeBorder, which is used by Material Design |
843 | /// buttons and other widgets, under the "shape" field. |
844 | class BorderDirectional extends BoxBorder { |
845 | /// Creates a border. |
846 | /// |
847 | /// The [start] and [end] sides represent the horizontal sides; the start side |
848 | /// is on the leading edge given the reading direction, and the end side is on |
849 | /// the trailing edge. They are resolved during [paint]. |
850 | /// |
851 | /// All the sides of the border default to [BorderSide.none]. |
852 | const BorderDirectional({ |
853 | this.top = BorderSide.none, |
854 | this.start = BorderSide.none, |
855 | this.end = BorderSide.none, |
856 | this.bottom = BorderSide.none, |
857 | }); |
858 | |
859 | /// Creates a [BorderDirectional] that represents the addition of the two |
860 | /// given [BorderDirectional]s. |
861 | /// |
862 | /// It is only valid to call this if [BorderSide.canMerge] returns true for |
863 | /// the pairwise combination of each side on both [BorderDirectional]s. |
864 | static BorderDirectional merge(BorderDirectional a, BorderDirectional b) { |
865 | assert(BorderSide.canMerge(a.top, b.top)); |
866 | assert(BorderSide.canMerge(a.start, b.start)); |
867 | assert(BorderSide.canMerge(a.end, b.end)); |
868 | assert(BorderSide.canMerge(a.bottom, b.bottom)); |
869 | return BorderDirectional( |
870 | top: BorderSide.merge(a.top, b.top), |
871 | start: BorderSide.merge(a.start, b.start), |
872 | end: BorderSide.merge(a.end, b.end), |
873 | bottom: BorderSide.merge(a.bottom, b.bottom), |
874 | ); |
875 | } |
876 | |
877 | @override |
878 | final BorderSide top; |
879 | |
880 | /// The start side of this border. |
881 | /// |
882 | /// This is the side on the left in left-to-right text and on the right in |
883 | /// right-to-left text. |
884 | /// |
885 | /// See also: |
886 | /// |
887 | /// * [TextDirection], which is used to describe the reading direction. |
888 | final BorderSide start; |
889 | |
890 | /// The end side of this border. |
891 | /// |
892 | /// This is the side on the right in left-to-right text and on the left in |
893 | /// right-to-left text. |
894 | /// |
895 | /// See also: |
896 | /// |
897 | /// * [TextDirection], which is used to describe the reading direction. |
898 | final BorderSide end; |
899 | |
900 | @override |
901 | final BorderSide bottom; |
902 | |
903 | @override |
904 | EdgeInsetsGeometry get dimensions { |
905 | return EdgeInsetsDirectional.fromSTEB( |
906 | start.strokeInset, |
907 | top.strokeInset, |
908 | end.strokeInset, |
909 | bottom.strokeInset, |
910 | ); |
911 | } |
912 | |
913 | @override |
914 | bool get isUniform => |
915 | _colorIsUniform && _widthIsUniform && _styleIsUniform && _strokeAlignIsUniform; |
916 | |
917 | bool get _colorIsUniform { |
918 | final Color topColor = top.color; |
919 | return start.color == topColor && bottom.color == topColor && end.color == topColor; |
920 | } |
921 | |
922 | bool get _widthIsUniform { |
923 | final double topWidth = top.width; |
924 | return start.width == topWidth && bottom.width == topWidth && end.width == topWidth; |
925 | } |
926 | |
927 | bool get _styleIsUniform { |
928 | final BorderStyle topStyle = top.style; |
929 | return start.style == topStyle && bottom.style == topStyle && end.style == topStyle; |
930 | } |
931 | |
932 | bool get _strokeAlignIsUniform { |
933 | final double topStrokeAlign = top.strokeAlign; |
934 | return start.strokeAlign == topStrokeAlign && |
935 | bottom.strokeAlign == topStrokeAlign && |
936 | end.strokeAlign == topStrokeAlign; |
937 | } |
938 | |
939 | Set<Color> _distinctVisibleColors() { |
940 | return <Color>{ |
941 | if (top.style != BorderStyle.none) top.color, |
942 | if (end.style != BorderStyle.none) end.color, |
943 | if (bottom.style != BorderStyle.none) bottom.color, |
944 | if (start.style != BorderStyle.none) start.color, |
945 | }; |
946 | } |
947 | |
948 | bool get _hasHairlineBorder => |
949 | (top.style == BorderStyle.solid && top.width == 0.0) || |
950 | (end.style == BorderStyle.solid && end.width == 0.0) || |
951 | (bottom.style == BorderStyle.solid && bottom.width == 0.0) || |
952 | (start.style == BorderStyle.solid && start.width == 0.0); |
953 | |
954 | @override |
955 | BoxBorder? add(ShapeBorder other, {bool reversed = false}) { |
956 | if (other is BorderDirectional) { |
957 | final BorderDirectional typedOther = other; |
958 | if (BorderSide.canMerge(top, typedOther.top) && |
959 | BorderSide.canMerge(start, typedOther.start) && |
960 | BorderSide.canMerge(end, typedOther.end) && |
961 | BorderSide.canMerge(bottom, typedOther.bottom)) { |
962 | return BorderDirectional.merge(this, typedOther); |
963 | } |
964 | return null; |
965 | } |
966 | if (other is Border) { |
967 | final Border typedOther = other; |
968 | if (!BorderSide.canMerge(typedOther.top, top) || |
969 | !BorderSide.canMerge(typedOther.bottom, bottom)) { |
970 | return null; |
971 | } |
972 | if (start != BorderSide.none || end != BorderSide.none) { |
973 | if (typedOther.left != BorderSide.none || typedOther.right != BorderSide.none) { |
974 | return null; |
975 | } |
976 | assert(typedOther.left == BorderSide.none); |
977 | assert(typedOther.right == BorderSide.none); |
978 | return BorderDirectional( |
979 | top: BorderSide.merge(typedOther.top, top), |
980 | start: start, |
981 | end: end, |
982 | bottom: BorderSide.merge(typedOther.bottom, bottom), |
983 | ); |
984 | } |
985 | assert(start == BorderSide.none); |
986 | assert(end == BorderSide.none); |
987 | return Border( |
988 | top: BorderSide.merge(typedOther.top, top), |
989 | right: typedOther.right, |
990 | bottom: BorderSide.merge(typedOther.bottom, bottom), |
991 | left: typedOther.left, |
992 | ); |
993 | } |
994 | return null; |
995 | } |
996 | |
997 | @override |
998 | BorderDirectional scale(double t) { |
999 | return BorderDirectional( |
1000 | top: top.scale(t), |
1001 | start: start.scale(t), |
1002 | end: end.scale(t), |
1003 | bottom: bottom.scale(t), |
1004 | ); |
1005 | } |
1006 | |
1007 | @override |
1008 | ShapeBorder? lerpFrom(ShapeBorder? a, double t) { |
1009 | if (a is BorderDirectional) { |
1010 | return BorderDirectional.lerp(a, this, t); |
1011 | } |
1012 | return super.lerpFrom(a, t); |
1013 | } |
1014 | |
1015 | @override |
1016 | ShapeBorder? lerpTo(ShapeBorder? b, double t) { |
1017 | if (b is BorderDirectional) { |
1018 | return BorderDirectional.lerp(this, b, t); |
1019 | } |
1020 | return super.lerpTo(b, t); |
1021 | } |
1022 | |
1023 | /// Linearly interpolate between two borders. |
1024 | /// |
1025 | /// If a border is null, it is treated as having four [BorderSide.none] |
1026 | /// borders. |
1027 | /// |
1028 | /// {@macro dart.ui.shadow.lerp} |
1029 | static BorderDirectional? lerp(BorderDirectional? a, BorderDirectional? b, double t) { |
1030 | if (identical(a, b)) { |
1031 | return a; |
1032 | } |
1033 | if (a == null) { |
1034 | return b!.scale(t); |
1035 | } |
1036 | if (b == null) { |
1037 | return a.scale(1.0 - t); |
1038 | } |
1039 | return BorderDirectional( |
1040 | top: BorderSide.lerp(a.top, b.top, t), |
1041 | end: BorderSide.lerp(a.end, b.end, t), |
1042 | bottom: BorderSide.lerp(a.bottom, b.bottom, t), |
1043 | start: BorderSide.lerp(a.start, b.start, t), |
1044 | ); |
1045 | } |
1046 | |
1047 | /// Paints the border within the given [Rect] on the given [Canvas]. |
1048 | /// |
1049 | /// Uniform borders are more efficient to paint than more complex borders. |
1050 | /// |
1051 | /// You can provide a [BoxShape] to draw the border on. If the `shape` in |
1052 | /// [BoxShape.circle], there is the requirement that the border [isUniform]. |
1053 | /// |
1054 | /// If you specify a rectangular box shape ([BoxShape.rectangle]), then you |
1055 | /// may specify a [BorderRadius]. If a `borderRadius` is specified, there is |
1056 | /// the requirement that the border [isUniform]. |
1057 | /// |
1058 | /// The [getInnerPath] and [getOuterPath] methods do not know about the |
1059 | /// `shape` and `borderRadius` arguments. |
1060 | /// |
1061 | /// The `textDirection` argument is used to determine which of [start] and |
1062 | /// [end] map to the left and right. For [TextDirection.ltr], the [start] is |
1063 | /// the left and the [end] is the right; for [TextDirection.rtl], it is the |
1064 | /// reverse. |
1065 | /// |
1066 | /// See also: |
1067 | /// |
1068 | /// * [paintBorder], which is used if the border has non-uniform colors or styles and no borderRadius. |
1069 | @override |
1070 | void paint( |
1071 | Canvas canvas, |
1072 | Rect rect, { |
1073 | TextDirection? textDirection, |
1074 | BoxShape shape = BoxShape.rectangle, |
1075 | BorderRadius? borderRadius, |
1076 | }) { |
1077 | if (isUniform) { |
1078 | switch (top.style) { |
1079 | case BorderStyle.none: |
1080 | return; |
1081 | case BorderStyle.solid: |
1082 | switch (shape) { |
1083 | case BoxShape.circle: |
1084 | assert( |
1085 | borderRadius == null, |
1086 | 'A borderRadius cannot be given when shape is a BoxShape.circle.', |
1087 | ); |
1088 | BoxBorder._paintUniformBorderWithCircle(canvas, rect, top); |
1089 | case BoxShape.rectangle: |
1090 | if (borderRadius != null && borderRadius != BorderRadius.zero) { |
1091 | BoxBorder._paintUniformBorderWithRadius(canvas, rect, top, borderRadius); |
1092 | return; |
1093 | } |
1094 | BoxBorder._paintUniformBorderWithRectangle(canvas, rect, top); |
1095 | } |
1096 | return; |
1097 | } |
1098 | } |
1099 | |
1100 | if (_styleIsUniform && top.style == BorderStyle.none) { |
1101 | return; |
1102 | } |
1103 | |
1104 | assert( |
1105 | textDirection != null, |
1106 | 'Non-uniform BorderDirectional objects require a TextDirection when painting.', |
1107 | ); |
1108 | final (BorderSide left, BorderSide right) = switch (textDirection!) { |
1109 | TextDirection.rtl => (end, start), |
1110 | TextDirection.ltr => (start, end), |
1111 | }; |
1112 | |
1113 | // Allow painting non-uniform borders if the visible colors are uniform. |
1114 | final Set<Color> visibleColors = _distinctVisibleColors(); |
1115 | final bool hasHairlineBorder = _hasHairlineBorder; |
1116 | if (visibleColors.length == 1 && |
1117 | !hasHairlineBorder && |
1118 | (shape == BoxShape.circle || (borderRadius != null && borderRadius != BorderRadius.zero))) { |
1119 | BoxBorder.paintNonUniformBorder( |
1120 | canvas, |
1121 | rect, |
1122 | shape: shape, |
1123 | borderRadius: borderRadius, |
1124 | textDirection: textDirection, |
1125 | top: top.style == BorderStyle.none ? BorderSide.none : top, |
1126 | right: right.style == BorderStyle.none ? BorderSide.none : right, |
1127 | bottom: bottom.style == BorderStyle.none ? BorderSide.none : bottom, |
1128 | left: left.style == BorderStyle.none ? BorderSide.none : left, |
1129 | color: visibleColors.first, |
1130 | ); |
1131 | return; |
1132 | } |
1133 | |
1134 | if (hasHairlineBorder) { |
1135 | assert( |
1136 | borderRadius == null || borderRadius == BorderRadius.zero, |
1137 | 'A side like `BorderSide(width: 0.0, style: BorderStyle.solid)` can only be drawn when BorderRadius is zero or null.', |
1138 | ); |
1139 | } |
1140 | assert( |
1141 | borderRadius == null, |
1142 | 'A borderRadius can only be given for borders with uniform colors.', |
1143 | ); |
1144 | assert( |
1145 | shape == BoxShape.rectangle, |
1146 | 'A Border can only be drawn as a circle on borders with uniform colors.', |
1147 | ); |
1148 | assert( |
1149 | _strokeAlignIsUniform && top.strokeAlign == BorderSide.strokeAlignInside, |
1150 | 'A Border can only draw strokeAlign different than strokeAlignInside on borders with uniform colors.', |
1151 | ); |
1152 | |
1153 | paintBorder(canvas, rect, top: top, left: left, bottom: bottom, right: right); |
1154 | } |
1155 | |
1156 | @override |
1157 | bool operator ==(Object other) { |
1158 | if (identical(this, other)) { |
1159 | return true; |
1160 | } |
1161 | if (other.runtimeType != runtimeType) { |
1162 | return false; |
1163 | } |
1164 | return other is BorderDirectional && |
1165 | other.top == top && |
1166 | other.start == start && |
1167 | other.end == end && |
1168 | other.bottom == bottom; |
1169 | } |
1170 | |
1171 | @override |
1172 | int get hashCode => Object.hash(top, start, end, bottom); |
1173 | |
1174 | @override |
1175 | String toString() { |
1176 | final List<String> arguments = <String>[ |
1177 | if (top != BorderSide.none) 'top:$top ', |
1178 | if (start != BorderSide.none) 'start:$start ', |
1179 | if (end != BorderSide.none) 'end:$end ', |
1180 | if (bottom != BorderSide.none) 'bottom:$bottom ', |
1181 | ]; |
1182 | return '${objectRuntimeType(this, 'BorderDirectional')} (${arguments.join( ", ")} )'; |
1183 | } |
1184 | } |
1185 |
Definitions
- BoxShape
- BoxBorder
- BoxBorder
- fromLTRB
- all
- fromBorderSide
- symmetric
- fromSTEB
- top
- bottom
- isUniform
- add
- lerp
- getInnerPath
- getOuterPath
- paintInterior
- preferPaintInterior
- paint
- _paintUniformBorderWithRadius
- paintNonUniformBorder
- _inflateRRect
- _deflateRRect
- _paintUniformBorderWithCircle
- _paintUniformBorderWithRectangle
- Border
- Border
- fromBorderSide
- symmetric
- all
- merge
- dimensions
- isUniform
- _colorIsUniform
- _widthIsUniform
- _styleIsUniform
- _strokeAlignIsUniform
- _distinctVisibleColors
- _hasHairlineBorder
- add
- scale
- lerpFrom
- lerpTo
- lerp
- paint
- ==
- hashCode
- toString
- BorderDirectional
- BorderDirectional
- merge
- dimensions
- isUniform
- _colorIsUniform
- _widthIsUniform
- _styleIsUniform
- _strokeAlignIsUniform
- _distinctVisibleColors
- _hasHairlineBorder
- add
- scale
- lerpFrom
- lerpTo
- lerp
- paint
- ==
- hashCode
Learn more about Flutter for embedded and desktop on industrialflutter.com