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 'box_border.dart'; |
6 | /// @docImport 'box_decoration.dart'; |
7 | /// @docImport 'shape_decoration.dart'; |
8 | library; |
9 | |
10 | import 'dart:ui' as ui show lerpDouble; |
11 | |
12 | import 'package:flutter/foundation.dart'; |
13 | |
14 | import 'basic_types.dart'; |
15 | import 'border_radius.dart'; |
16 | import 'borders.dart'; |
17 | import 'circle_border.dart'; |
18 | |
19 | // A common interface for [RoundedRectangleBorder] and [RoundedSuperellipseBorder]. |
20 | mixin _RRectLikeBorder on OutlinedBorder { |
21 | BorderRadiusGeometry get borderRadius; |
22 | } |
23 | |
24 | /// A rectangular border with rounded corners. |
25 | /// |
26 | /// Typically used with [ShapeDecoration] to draw a box with a rounded |
27 | /// rectangle. |
28 | /// |
29 | /// This shape can interpolate to and from [CircleBorder]. |
30 | /// |
31 | /// See also: |
32 | /// |
33 | /// * [BorderSide], which is used to describe each side of the box. |
34 | /// * [Border], which, when used with [BoxDecoration], can also |
35 | /// describe a rounded rectangle. |
36 | /// * [RoundedSuperellipseBorder], which uses a smoother shape similar to the one |
37 | /// used in iOS design. |
38 | class RoundedRectangleBorder extends OutlinedBorder with _RRectLikeBorder { |
39 | /// Creates a rounded rectangle border. |
40 | const RoundedRectangleBorder({super.side, this.borderRadius = BorderRadius.zero}); |
41 | |
42 | /// The radii for each corner. |
43 | @override |
44 | final BorderRadiusGeometry borderRadius; |
45 | |
46 | @override |
47 | ShapeBorder scale(double t) { |
48 | return RoundedRectangleBorder(side: side.scale(t), borderRadius: borderRadius * t); |
49 | } |
50 | |
51 | @override |
52 | ShapeBorder? lerpFrom(ShapeBorder? a, double t) { |
53 | if (a is RoundedRectangleBorder) { |
54 | return RoundedRectangleBorder( |
55 | side: BorderSide.lerp(a.side, side, t), |
56 | borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!, |
57 | ); |
58 | } |
59 | if (a is CircleBorder) { |
60 | return _RoundedRectangleToCircleBorder( |
61 | side: BorderSide.lerp(a.side, side, t), |
62 | borderRadius: borderRadius, |
63 | circularity: 1.0 - t, |
64 | eccentricity: a.eccentricity, |
65 | ); |
66 | } |
67 | return super.lerpFrom(a, t); |
68 | } |
69 | |
70 | @override |
71 | ShapeBorder? lerpTo(ShapeBorder? b, double t) { |
72 | if (b is RoundedRectangleBorder) { |
73 | return RoundedRectangleBorder( |
74 | side: BorderSide.lerp(side, b.side, t), |
75 | borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!, |
76 | ); |
77 | } |
78 | if (b is CircleBorder) { |
79 | return _RoundedRectangleToCircleBorder( |
80 | side: BorderSide.lerp(side, b.side, t), |
81 | borderRadius: borderRadius, |
82 | circularity: t, |
83 | eccentricity: b.eccentricity, |
84 | ); |
85 | } |
86 | return super.lerpTo(b, t); |
87 | } |
88 | |
89 | /// Returns a copy of this RoundedRectangleBorder with the given fields |
90 | /// replaced with the new values. |
91 | @override |
92 | RoundedRectangleBorder copyWith({BorderSide? side, BorderRadiusGeometry? borderRadius}) { |
93 | return RoundedRectangleBorder( |
94 | side: side ?? this.side, |
95 | borderRadius: borderRadius ?? this.borderRadius, |
96 | ); |
97 | } |
98 | |
99 | @override |
100 | Path getInnerPath(Rect rect, {TextDirection? textDirection}) { |
101 | final RRect borderRect = borderRadius.resolve(textDirection).toRRect(rect); |
102 | final RRect adjustedRect = borderRect.deflate(side.strokeInset); |
103 | return Path()..addRRect(adjustedRect); |
104 | } |
105 | |
106 | @override |
107 | Path getOuterPath(Rect rect, {TextDirection? textDirection}) { |
108 | return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect)); |
109 | } |
110 | |
111 | @override |
112 | void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { |
113 | if (borderRadius == BorderRadius.zero) { |
114 | canvas.drawRect(rect, paint); |
115 | } else { |
116 | canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), paint); |
117 | } |
118 | } |
119 | |
120 | @override |
121 | bool get preferPaintInterior => true; |
122 | |
123 | @override |
124 | void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { |
125 | switch (side.style) { |
126 | case BorderStyle.none: |
127 | break; |
128 | case BorderStyle.solid: |
129 | if (side.width == 0.0) { |
130 | canvas.drawRRect(borderRadius.resolve(textDirection).toRRect(rect), side.toPaint()); |
131 | } else { |
132 | final Paint paint = Paint()..color = side.color; |
133 | final RRect borderRect = borderRadius.resolve(textDirection).toRRect(rect); |
134 | final RRect inner = borderRect.deflate(side.strokeInset); |
135 | final RRect outer = borderRect.inflate(side.strokeOutset); |
136 | canvas.drawDRRect(outer, inner, paint); |
137 | } |
138 | } |
139 | } |
140 | |
141 | @override |
142 | bool operator ==(Object other) { |
143 | if (other.runtimeType != runtimeType) { |
144 | return false; |
145 | } |
146 | return other is RoundedRectangleBorder && |
147 | other.side == side && |
148 | other.borderRadius == borderRadius; |
149 | } |
150 | |
151 | @override |
152 | int get hashCode => Object.hash(side, borderRadius); |
153 | |
154 | @override |
155 | String toString() { |
156 | return '${objectRuntimeType(this, 'RoundedRectangleBorder')} ($side ,$borderRadius )'; |
157 | } |
158 | } |
159 | |
160 | class _RoundedRectangleToCircleBorder extends _ShapeToCircleBorder<RoundedRectangleBorder> { |
161 | const _RoundedRectangleToCircleBorder({ |
162 | super.side, |
163 | super.borderRadius = BorderRadius.zero, |
164 | required super.circularity, |
165 | required super.eccentricity, |
166 | }); |
167 | |
168 | @override |
169 | void drawShape(Canvas canvas, Rect rect, BorderRadius radius, Paint paint, [double? inflation]) { |
170 | RRect rrect = radius.toRRect(rect); |
171 | if (inflation != null) { |
172 | rrect = rrect.inflate(inflation); |
173 | } |
174 | canvas.drawRRect(rrect, paint); |
175 | } |
176 | |
177 | @override |
178 | Path buildPath(Rect rect, BorderRadius radius, [double? inflation]) { |
179 | RRect rrect = radius.toRRect(rect); |
180 | if (inflation != null) { |
181 | rrect = rrect.inflate(inflation); |
182 | } |
183 | return Path()..addRRect(rrect); |
184 | } |
185 | |
186 | @override |
187 | _RoundedRectangleToCircleBorder copyWith({ |
188 | BorderSide? side, |
189 | BorderRadiusGeometry? borderRadius, |
190 | double? circularity, |
191 | double? eccentricity, |
192 | }) { |
193 | return _RoundedRectangleToCircleBorder( |
194 | side: side ?? this.side, |
195 | borderRadius: borderRadius ?? this.borderRadius, |
196 | circularity: circularity ?? this.circularity, |
197 | eccentricity: eccentricity ?? this.eccentricity, |
198 | ); |
199 | } |
200 | } |
201 | |
202 | /// A rectangular border with rounded corners following the shape of an |
203 | /// [RSuperellipse]. |
204 | /// |
205 | /// Typically used with [ShapeDecoration] to draw a box that mimics the rounded |
206 | /// rectangle style commonly seen in iOS design. |
207 | /// |
208 | /// See also: |
209 | /// |
210 | /// * [RSuperellipse], which defines the shape. |
211 | /// * [RoundedRectangleBorder], which uses the traditional [RRect] shape. |
212 | class RoundedSuperellipseBorder extends OutlinedBorder with _RRectLikeBorder { |
213 | /// Creates a rounded rectangle border. |
214 | const RoundedSuperellipseBorder({super.side, this.borderRadius = BorderRadius.zero}); |
215 | |
216 | /// The radii for each corner. |
217 | @override |
218 | final BorderRadiusGeometry borderRadius; |
219 | |
220 | @override |
221 | ShapeBorder scale(double t) { |
222 | return RoundedSuperellipseBorder(side: side.scale(t), borderRadius: borderRadius * t); |
223 | } |
224 | |
225 | @override |
226 | ShapeBorder? lerpFrom(ShapeBorder? a, double t) { |
227 | if (a is RoundedSuperellipseBorder) { |
228 | return RoundedSuperellipseBorder( |
229 | side: BorderSide.lerp(a.side, side, t), |
230 | borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t)!, |
231 | ); |
232 | } |
233 | if (a is CircleBorder) { |
234 | return _RoundedSuperellipseToCircleBorder( |
235 | side: BorderSide.lerp(a.side, side, t), |
236 | borderRadius: borderRadius, |
237 | circularity: 1.0 - t, |
238 | eccentricity: a.eccentricity, |
239 | ); |
240 | } |
241 | return super.lerpFrom(a, t); |
242 | } |
243 | |
244 | @override |
245 | ShapeBorder? lerpTo(ShapeBorder? b, double t) { |
246 | if (b is RoundedSuperellipseBorder) { |
247 | return RoundedSuperellipseBorder( |
248 | side: BorderSide.lerp(side, b.side, t), |
249 | borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t)!, |
250 | ); |
251 | } |
252 | if (b is CircleBorder) { |
253 | return _RoundedSuperellipseToCircleBorder( |
254 | side: BorderSide.lerp(side, b.side, t), |
255 | borderRadius: borderRadius, |
256 | circularity: t, |
257 | eccentricity: b.eccentricity, |
258 | ); |
259 | } |
260 | return super.lerpTo(b, t); |
261 | } |
262 | |
263 | /// Returns a copy of this RoundedSuperellipseBorder with the given fields |
264 | /// replaced with the new values. |
265 | @override |
266 | RoundedSuperellipseBorder copyWith({BorderSide? side, BorderRadiusGeometry? borderRadius}) { |
267 | return RoundedSuperellipseBorder( |
268 | side: side ?? this.side, |
269 | borderRadius: borderRadius ?? this.borderRadius, |
270 | ); |
271 | } |
272 | |
273 | @override |
274 | Path getInnerPath(Rect rect, {TextDirection? textDirection}) { |
275 | final RSuperellipse borderRect = borderRadius.resolve(textDirection).toRSuperellipse(rect); |
276 | final RSuperellipse adjustedRect = borderRect.deflate(side.strokeInset); |
277 | return Path()..addRSuperellipse(adjustedRect); |
278 | } |
279 | |
280 | @override |
281 | Path getOuterPath(Rect rect, {TextDirection? textDirection}) { |
282 | return Path()..addRSuperellipse(borderRadius.resolve(textDirection).toRSuperellipse(rect)); |
283 | } |
284 | |
285 | @override |
286 | void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { |
287 | if (borderRadius == BorderRadius.zero) { |
288 | canvas.drawRect(rect, paint); |
289 | } else { |
290 | canvas.drawRSuperellipse(borderRadius.resolve(textDirection).toRSuperellipse(rect), paint); |
291 | } |
292 | } |
293 | |
294 | @override |
295 | bool get preferPaintInterior => true; |
296 | |
297 | @override |
298 | void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { |
299 | switch (side.style) { |
300 | case BorderStyle.none: |
301 | break; |
302 | case BorderStyle.solid: |
303 | if (side.width == 0.0) { |
304 | canvas.drawRSuperellipse( |
305 | borderRadius.resolve(textDirection).toRSuperellipse(rect), |
306 | side.toPaint(), |
307 | ); |
308 | } else { |
309 | final double strokeOffset = (side.strokeOutset - side.strokeInset) / 2; |
310 | final RSuperellipse base = borderRadius |
311 | .resolve(textDirection) |
312 | .toRSuperellipse(rect) |
313 | .inflate(strokeOffset); |
314 | canvas.drawRSuperellipse(base, side.toPaint()); |
315 | } |
316 | } |
317 | } |
318 | |
319 | @override |
320 | bool operator ==(Object other) { |
321 | if (other.runtimeType != runtimeType) { |
322 | return false; |
323 | } |
324 | return other is RoundedSuperellipseBorder && |
325 | other.side == side && |
326 | other.borderRadius == borderRadius; |
327 | } |
328 | |
329 | @override |
330 | int get hashCode => Object.hash(side, borderRadius); |
331 | |
332 | @override |
333 | String toString() { |
334 | return '${objectRuntimeType(this, 'RoundedSuperellipseBorder')} ($side ,$borderRadius )'; |
335 | } |
336 | } |
337 | |
338 | class _RoundedSuperellipseToCircleBorder extends _ShapeToCircleBorder<RoundedSuperellipseBorder> { |
339 | const _RoundedSuperellipseToCircleBorder({ |
340 | super.side, |
341 | super.borderRadius = BorderRadius.zero, |
342 | required super.circularity, |
343 | required super.eccentricity, |
344 | }); |
345 | |
346 | @override |
347 | void drawShape(Canvas canvas, Rect rect, BorderRadius radius, Paint paint, [double? inflation]) { |
348 | RSuperellipse rsuperellipse = radius.toRSuperellipse(rect); |
349 | if (inflation != null) { |
350 | rsuperellipse = rsuperellipse.inflate(inflation); |
351 | } |
352 | canvas.drawRSuperellipse(rsuperellipse, paint); |
353 | } |
354 | |
355 | @override |
356 | Path buildPath(Rect rect, BorderRadius radius, [double? inflation]) { |
357 | RSuperellipse rsuperellipse = radius.toRSuperellipse(rect); |
358 | if (inflation != null) { |
359 | rsuperellipse = rsuperellipse.inflate(inflation); |
360 | } |
361 | return Path()..addRSuperellipse(rsuperellipse); |
362 | } |
363 | |
364 | @override |
365 | _RoundedSuperellipseToCircleBorder copyWith({ |
366 | BorderSide? side, |
367 | BorderRadiusGeometry? borderRadius, |
368 | double? circularity, |
369 | double? eccentricity, |
370 | }) { |
371 | return _RoundedSuperellipseToCircleBorder( |
372 | side: side ?? this.side, |
373 | borderRadius: borderRadius ?? this.borderRadius, |
374 | circularity: circularity ?? this.circularity, |
375 | eccentricity: eccentricity ?? this.eccentricity, |
376 | ); |
377 | } |
378 | } |
379 | |
380 | abstract class _ShapeToCircleBorder<T extends _RRectLikeBorder> extends OutlinedBorder { |
381 | const _ShapeToCircleBorder({ |
382 | super.side, |
383 | this.borderRadius = BorderRadius.zero, |
384 | required this.circularity, |
385 | required this.eccentricity, |
386 | }); |
387 | |
388 | void drawShape(Canvas canvas, Rect rect, BorderRadius radius, Paint paint, [double? inflation]); |
389 | Path buildPath(Rect rect, BorderRadius radius, [double? inflation]); |
390 | |
391 | final BorderRadiusGeometry borderRadius; |
392 | final double circularity; |
393 | final double eccentricity; |
394 | |
395 | @override |
396 | ShapeBorder scale(double t) { |
397 | return copyWith( |
398 | side: side.scale(t), |
399 | borderRadius: borderRadius * t, |
400 | circularity: t, |
401 | eccentricity: eccentricity, |
402 | ); |
403 | } |
404 | |
405 | @override |
406 | ShapeBorder? lerpFrom(ShapeBorder? a, double t) { |
407 | if (a is T) { |
408 | return copyWith( |
409 | side: BorderSide.lerp(a.side, side, t), |
410 | borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t), |
411 | circularity: circularity * t, |
412 | eccentricity: eccentricity, |
413 | ); |
414 | } |
415 | if (a is CircleBorder) { |
416 | return copyWith( |
417 | side: BorderSide.lerp(a.side, side, t), |
418 | borderRadius: borderRadius, |
419 | circularity: circularity + (1.0 - circularity) * (1.0 - t), |
420 | eccentricity: a.eccentricity, |
421 | ); |
422 | } |
423 | if (a is _ShapeToCircleBorder<T>) { |
424 | return copyWith( |
425 | side: BorderSide.lerp(a.side, side, t), |
426 | borderRadius: BorderRadiusGeometry.lerp(a.borderRadius, borderRadius, t), |
427 | circularity: ui.lerpDouble(a.circularity, circularity, t), |
428 | eccentricity: eccentricity, |
429 | ); |
430 | } |
431 | return super.lerpFrom(a, t); |
432 | } |
433 | |
434 | @override |
435 | ShapeBorder? lerpTo(ShapeBorder? b, double t) { |
436 | if (b is T) { |
437 | return copyWith( |
438 | side: BorderSide.lerp(side, b.side, t), |
439 | borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t), |
440 | circularity: circularity * (1.0 - t), |
441 | eccentricity: eccentricity, |
442 | ); |
443 | } |
444 | if (b is CircleBorder) { |
445 | return copyWith( |
446 | side: BorderSide.lerp(side, b.side, t), |
447 | borderRadius: borderRadius, |
448 | circularity: circularity + (1.0 - circularity) * t, |
449 | eccentricity: b.eccentricity, |
450 | ); |
451 | } |
452 | if (b is _ShapeToCircleBorder<T>) { |
453 | return copyWith( |
454 | side: BorderSide.lerp(side, b.side, t), |
455 | borderRadius: BorderRadiusGeometry.lerp(borderRadius, b.borderRadius, t), |
456 | circularity: ui.lerpDouble(circularity, b.circularity, t), |
457 | eccentricity: eccentricity, |
458 | ); |
459 | } |
460 | return super.lerpTo(b, t); |
461 | } |
462 | |
463 | Rect _adjustRect(Rect rect) { |
464 | if (circularity == 0.0 || rect.width == rect.height) { |
465 | return rect; |
466 | } |
467 | if (rect.width < rect.height) { |
468 | final double partialDelta = (rect.height - rect.width) / 2; |
469 | final double delta = circularity * partialDelta * (1.0 - eccentricity); |
470 | return Rect.fromLTRB(rect.left, rect.top + delta, rect.right, rect.bottom - delta); |
471 | } else { |
472 | final double partialDelta = (rect.width - rect.height) / 2; |
473 | final double delta = circularity * partialDelta * (1.0 - eccentricity); |
474 | return Rect.fromLTRB(rect.left + delta, rect.top, rect.right - delta, rect.bottom); |
475 | } |
476 | } |
477 | |
478 | BorderRadius _adjustBorderRadius(Rect rect, TextDirection? textDirection) { |
479 | final BorderRadius resolvedRadius = borderRadius.resolve(textDirection); |
480 | if (circularity == 0.0) { |
481 | return resolvedRadius; |
482 | } |
483 | if (eccentricity != 0.0) { |
484 | if (rect.width < rect.height) { |
485 | return BorderRadius.lerp( |
486 | resolvedRadius, |
487 | BorderRadius.all( |
488 | Radius.elliptical(rect.width / 2, (0.5 + eccentricity / 2) * rect.height / 2), |
489 | ), |
490 | circularity, |
491 | )!; |
492 | } else { |
493 | return BorderRadius.lerp( |
494 | resolvedRadius, |
495 | BorderRadius.all( |
496 | Radius.elliptical((0.5 + eccentricity / 2) * rect.width / 2, rect.height / 2), |
497 | ), |
498 | circularity, |
499 | )!; |
500 | } |
501 | } |
502 | return BorderRadius.lerp( |
503 | resolvedRadius, |
504 | BorderRadius.circular(rect.shortestSide / 2), |
505 | circularity, |
506 | )!; |
507 | } |
508 | |
509 | @override |
510 | Path getInnerPath(Rect rect, {TextDirection? textDirection}) { |
511 | return buildPath( |
512 | _adjustRect(rect), |
513 | _adjustBorderRadius(rect, textDirection), |
514 | -ui.lerpDouble(side.width, 0, side.strokeAlign)!, |
515 | ); |
516 | } |
517 | |
518 | @override |
519 | Path getOuterPath(Rect rect, {TextDirection? textDirection}) { |
520 | return buildPath(_adjustRect(rect), _adjustBorderRadius(rect, textDirection)); |
521 | } |
522 | |
523 | @override |
524 | void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) { |
525 | final BorderRadius adjustedBorderRadius = _adjustBorderRadius(rect, textDirection); |
526 | if (adjustedBorderRadius == BorderRadius.zero) { |
527 | canvas.drawRect(_adjustRect(rect), paint); |
528 | } else { |
529 | drawShape(canvas, _adjustRect(rect), adjustedBorderRadius, paint); |
530 | } |
531 | } |
532 | |
533 | @override |
534 | bool get preferPaintInterior => true; |
535 | @override |
536 | _ShapeToCircleBorder<T> copyWith({ |
537 | BorderSide? side, |
538 | BorderRadiusGeometry? borderRadius, |
539 | double? circularity, |
540 | double? eccentricity, |
541 | }); |
542 | |
543 | @override |
544 | void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) { |
545 | switch (side.style) { |
546 | case BorderStyle.none: |
547 | break; |
548 | case BorderStyle.solid: |
549 | drawShape( |
550 | canvas, |
551 | _adjustRect(rect), |
552 | _adjustBorderRadius(rect, textDirection), |
553 | side.toPaint(), |
554 | side.strokeOffset / 2, |
555 | ); |
556 | } |
557 | } |
558 | |
559 | @override |
560 | bool operator ==(Object other) { |
561 | if (other.runtimeType != runtimeType) { |
562 | return false; |
563 | } |
564 | return other is _ShapeToCircleBorder<T> && |
565 | other.side == side && |
566 | other.borderRadius == borderRadius && |
567 | other.circularity == circularity; |
568 | } |
569 | |
570 | @override |
571 | int get hashCode => Object.hash(side, borderRadius, circularity); |
572 | |
573 | @override |
574 | String toString() { |
575 | if (eccentricity != 0.0) { |
576 | return '$T ($side ,$borderRadius ,${(circularity * 100).toStringAsFixed(1)} % of the way to being a CircleBorder that is${(eccentricity * 100).toStringAsFixed(1)} % oval)'; |
577 | } |
578 | return '$T ($side ,$borderRadius ,${(circularity * 100).toStringAsFixed(1)} % of the way to being a CircleBorder)'; |
579 | } |
580 | } |
581 |
Definitions
- _RRectLikeBorder
- borderRadius
- RoundedRectangleBorder
- RoundedRectangleBorder
- scale
- lerpFrom
- lerpTo
- copyWith
- getInnerPath
- getOuterPath
- paintInterior
- preferPaintInterior
- paint
- ==
- hashCode
- toString
- _RoundedRectangleToCircleBorder
- _RoundedRectangleToCircleBorder
- drawShape
- buildPath
- copyWith
- RoundedSuperellipseBorder
- RoundedSuperellipseBorder
- scale
- lerpFrom
- lerpTo
- copyWith
- getInnerPath
- getOuterPath
- paintInterior
- preferPaintInterior
- paint
- ==
- hashCode
- toString
- _RoundedSuperellipseToCircleBorder
- _RoundedSuperellipseToCircleBorder
- drawShape
- buildPath
- copyWith
- _ShapeToCircleBorder
- _ShapeToCircleBorder
- drawShape
- buildPath
- scale
- lerpFrom
- lerpTo
- _adjustRect
- _adjustBorderRadius
- getInnerPath
- getOuterPath
- paintInterior
- preferPaintInterior
- copyWith
- paint
- ==
- hashCode
Learn more about Flutter for embedded and desktop on industrialflutter.com