1// Copyright 2013 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 5part of dart.ui; 6 7/// Base class for [Size] and [Offset], which are both ways to describe 8/// a distance as a two-dimensional axis-aligned vector. 9abstract class OffsetBase { 10 /// Abstract const constructor. This constructor enables subclasses to provide 11 /// const constructors so that they can be used in const expressions. 12 /// 13 /// The first argument sets the horizontal component, and the second the 14 /// vertical component. 15 const OffsetBase(this._dx, this._dy); 16 17 final double _dx; 18 final double _dy; 19 20 /// Returns true if either component is [double.infinity], and false if both 21 /// are finite (or negative infinity, or NaN). 22 /// 23 /// This is different than comparing for equality with an instance that has 24 /// _both_ components set to [double.infinity]. 25 /// 26 /// See also: 27 /// 28 /// * [isFinite], which is true if both components are finite (and not NaN). 29 bool get isInfinite => _dx >= double.infinity || _dy >= double.infinity; 30 31 /// Whether both components are finite (neither infinite nor NaN). 32 /// 33 /// See also: 34 /// 35 /// * [isInfinite], which returns true if either component is equal to 36 /// positive infinity. 37 bool get isFinite => _dx.isFinite && _dy.isFinite; 38 39 /// Less-than operator. Compares an [Offset] or [Size] to another [Offset] or 40 /// [Size], and returns true if both the horizontal and vertical values of the 41 /// left-hand-side operand are smaller than the horizontal and vertical values 42 /// of the right-hand-side operand respectively. Returns false otherwise. 43 /// 44 /// This is a partial ordering. It is possible for two values to be neither 45 /// less, nor greater than, nor equal to, another. 46 bool operator <(OffsetBase other) => _dx < other._dx && _dy < other._dy; 47 48 /// Less-than-or-equal-to operator. Compares an [Offset] or [Size] to another 49 /// [Offset] or [Size], and returns true if both the horizontal and vertical 50 /// values of the left-hand-side operand are smaller than or equal to the 51 /// horizontal and vertical values of the right-hand-side operand 52 /// respectively. Returns false otherwise. 53 /// 54 /// This is a partial ordering. It is possible for two values to be neither 55 /// less, nor greater than, nor equal to, another. 56 bool operator <=(OffsetBase other) => _dx <= other._dx && _dy <= other._dy; 57 58 /// Greater-than operator. Compares an [Offset] or [Size] to another [Offset] 59 /// or [Size], and returns true if both the horizontal and vertical values of 60 /// the left-hand-side operand are bigger than the horizontal and vertical 61 /// values of the right-hand-side operand respectively. Returns false 62 /// otherwise. 63 /// 64 /// This is a partial ordering. It is possible for two values to be neither 65 /// less, nor greater than, nor equal to, another. 66 bool operator >(OffsetBase other) => _dx > other._dx && _dy > other._dy; 67 68 /// Greater-than-or-equal-to operator. Compares an [Offset] or [Size] to 69 /// another [Offset] or [Size], and returns true if both the horizontal and 70 /// vertical values of the left-hand-side operand are bigger than or equal to 71 /// the horizontal and vertical values of the right-hand-side operand 72 /// respectively. Returns false otherwise. 73 /// 74 /// This is a partial ordering. It is possible for two values to be neither 75 /// less, nor greater than, nor equal to, another. 76 bool operator >=(OffsetBase other) => _dx >= other._dx && _dy >= other._dy; 77 78 /// Equality operator. Compares an [Offset] or [Size] to another [Offset] or 79 /// [Size], and returns true if the horizontal and vertical values of the 80 /// left-hand-side operand are equal to the horizontal and vertical values of 81 /// the right-hand-side operand respectively. Returns false otherwise. 82 @override 83 bool operator ==(dynamic other) { 84 if (other is! OffsetBase) 85 return false; 86 final OffsetBase typedOther = other; 87 return _dx == typedOther._dx && 88 _dy == typedOther._dy; 89 } 90 91 @override 92 int get hashCode => hashValues(_dx, _dy); 93 94 @override 95 String toString() => 'OffsetBase(${_dx?.toStringAsFixed(1)}, ${_dy?.toStringAsFixed(1)})'; 96} 97 98/// An immutable 2D floating-point offset. 99/// 100/// Generally speaking, Offsets can be interpreted in two ways: 101/// 102/// 1. As representing a point in Cartesian space a specified distance from a 103/// separately-maintained origin. For example, the top-left position of 104/// children in the [RenderBox] protocol is typically represented as an 105/// [Offset] from the top left of the parent box. 106/// 107/// 2. As a vector that can be applied to coordinates. For example, when 108/// painting a [RenderObject], the parent is passed an [Offset] from the 109/// screen's origin which it can add to the offsets of its children to find 110/// the [Offset] from the screen's origin to each of the children. 111/// 112/// Because a particular [Offset] can be interpreted as one sense at one time 113/// then as the other sense at a later time, the same class is used for both 114/// senses. 115/// 116/// See also: 117/// 118/// * [Size], which represents a vector describing the size of a rectangle. 119class Offset extends OffsetBase { 120 /// Creates an offset. The first argument sets [dx], the horizontal component, 121 /// and the second sets [dy], the vertical component. 122 const Offset(double dx, double dy) : super(dx, dy); 123 124 /// Creates an offset from its [direction] and [distance]. 125 /// 126 /// The direction is in radians clockwise from the positive x-axis. 127 /// 128 /// The distance can be omitted, to create a unit vector (distance = 1.0). 129 factory Offset.fromDirection(double direction, [ double distance = 1.0 ]) { 130 return Offset(distance * math.cos(direction), distance * math.sin(direction)); 131 } 132 133 /// The x component of the offset. 134 /// 135 /// The y component is given by [dy]. 136 double get dx => _dx; 137 138 /// The y component of the offset. 139 /// 140 /// The x component is given by [dx]. 141 double get dy => _dy; 142 143 /// The magnitude of the offset. 144 /// 145 /// If you need this value to compare it to another [Offset]'s distance, 146 /// consider using [distanceSquared] instead, since it is cheaper to compute. 147 double get distance => math.sqrt(dx * dx + dy * dy); 148 149 /// The square of the magnitude of the offset. 150 /// 151 /// This is cheaper than computing the [distance] itself. 152 double get distanceSquared => dx * dx + dy * dy; 153 154 /// The angle of this offset as radians clockwise from the positive x-axis, in 155 /// the range -[pi] to [pi], assuming positive values of the x-axis go to the 156 /// left and positive values of the y-axis go down. 157 /// 158 /// Zero means that [dy] is zero and [dx] is zero or positive. 159 /// 160 /// Values from zero to [pi]/2 indicate positive values of [dx] and [dy], the 161 /// bottom-right quadrant. 162 /// 163 /// Values from [pi]/2 to [pi] indicate negative values of [dx] and positive 164 /// values of [dy], the bottom-left quadrant. 165 /// 166 /// Values from zero to -[pi]/2 indicate positive values of [dx] and negative 167 /// values of [dy], the top-right quadrant. 168 /// 169 /// Values from -[pi]/2 to -[pi] indicate negative values of [dx] and [dy], 170 /// the top-left quadrant. 171 /// 172 /// When [dy] is zero and [dx] is negative, the [direction] is [pi]. 173 /// 174 /// When [dx] is zero, [direction] is [pi]/2 if [dy] is positive and -[pi]/2 175 /// if [dy] is negative. 176 /// 177 /// See also: 178 /// 179 /// * [distance], to compute the magnitude of the vector. 180 /// * [Canvas.rotate], which uses the same convention for its angle. 181 double get direction => math.atan2(dy, dx); 182 183 /// An offset with zero magnitude. 184 /// 185 /// This can be used to represent the origin of a coordinate space. 186 static const Offset zero = Offset(0.0, 0.0); 187 188 /// An offset with infinite x and y components. 189 /// 190 /// See also: 191 /// 192 /// * [isInfinite], which checks whether either component is infinite. 193 /// * [isFinite], which checks whether both components are finite. 194 // This is included for completeness, because [Size.infinite] exists. 195 static const Offset infinite = Offset(double.infinity, double.infinity); 196 197 /// Returns a new offset with the x component scaled by `scaleX` and the y 198 /// component scaled by `scaleY`. 199 /// 200 /// If the two scale arguments are the same, consider using the `*` operator 201 /// instead: 202 /// 203 /// ```dart 204 /// Offset a = const Offset(10.0, 10.0); 205 /// Offset b = a * 2.0; // same as: a.scale(2.0, 2.0) 206 /// ``` 207 /// 208 /// If the two arguments are -1, consider using the unary `-` operator 209 /// instead: 210 /// 211 /// ```dart 212 /// Offset a = const Offset(10.0, 10.0); 213 /// Offset b = -a; // same as: a.scale(-1.0, -1.0) 214 /// ``` 215 Offset scale(double scaleX, double scaleY) => Offset(dx * scaleX, dy * scaleY); 216 217 /// Returns a new offset with translateX added to the x component and 218 /// translateY added to the y component. 219 /// 220 /// If the arguments come from another [Offset], consider using the `+` or `-` 221 /// operators instead: 222 /// 223 /// ```dart 224 /// Offset a = const Offset(10.0, 10.0); 225 /// Offset b = const Offset(10.0, 10.0); 226 /// Offset c = a + b; // same as: a.translate(b.dx, b.dy) 227 /// Offset d = a - b; // same as: a.translate(-b.dx, -b.dy) 228 /// ``` 229 Offset translate(double translateX, double translateY) => Offset(dx + translateX, dy + translateY); 230 231 /// Unary negation operator. 232 /// 233 /// Returns an offset with the coordinates negated. 234 /// 235 /// If the [Offset] represents an arrow on a plane, this operator returns the 236 /// same arrow but pointing in the reverse direction. 237 Offset operator -() => Offset(-dx, -dy); 238 239 /// Binary subtraction operator. 240 /// 241 /// Returns an offset whose [dx] value is the left-hand-side operand's [dx] 242 /// minus the right-hand-side operand's [dx] and whose [dy] value is the 243 /// left-hand-side operand's [dy] minus the right-hand-side operand's [dy]. 244 /// 245 /// See also [translate]. 246 Offset operator -(Offset other) => Offset(dx - other.dx, dy - other.dy); 247 248 /// Binary addition operator. 249 /// 250 /// Returns an offset whose [dx] value is the sum of the [dx] values of the 251 /// two operands, and whose [dy] value is the sum of the [dy] values of the 252 /// two operands. 253 /// 254 /// See also [translate]. 255 Offset operator +(Offset other) => Offset(dx + other.dx, dy + other.dy); 256 257 /// Multiplication operator. 258 /// 259 /// Returns an offset whose coordinates are the coordinates of the 260 /// left-hand-side operand (an Offset) multiplied by the scalar 261 /// right-hand-side operand (a double). 262 /// 263 /// See also [scale]. 264 Offset operator *(double operand) => Offset(dx * operand, dy * operand); 265 266 /// Division operator. 267 /// 268 /// Returns an offset whose coordinates are the coordinates of the 269 /// left-hand-side operand (an Offset) divided by the scalar right-hand-side 270 /// operand (a double). 271 /// 272 /// See also [scale]. 273 Offset operator /(double operand) => Offset(dx / operand, dy / operand); 274 275 /// Integer (truncating) division operator. 276 /// 277 /// Returns an offset whose coordinates are the coordinates of the 278 /// left-hand-side operand (an Offset) divided by the scalar right-hand-side 279 /// operand (a double), rounded towards zero. 280 Offset operator ~/(double operand) => Offset((dx ~/ operand).toDouble(), (dy ~/ operand).toDouble()); 281 282 /// Modulo (remainder) operator. 283 /// 284 /// Returns an offset whose coordinates are the remainder of dividing the 285 /// coordinates of the left-hand-side operand (an Offset) by the scalar 286 /// right-hand-side operand (a double). 287 Offset operator %(double operand) => Offset(dx % operand, dy % operand); 288 289 /// Rectangle constructor operator. 290 /// 291 /// Combines an [Offset] and a [Size] to form a [Rect] whose top-left 292 /// coordinate is the point given by adding this offset, the left-hand-side 293 /// operand, to the origin, and whose size is the right-hand-side operand. 294 /// 295 /// ```dart 296 /// Rect myRect = Offset.zero & const Size(100.0, 100.0); 297 /// // same as: Rect.fromLTWH(0.0, 0.0, 100.0, 100.0) 298 /// ``` 299 Rect operator &(Size other) => Rect.fromLTWH(dx, dy, other.width, other.height); 300 301 /// Linearly interpolate between two offsets. 302 /// 303 /// If either offset is null, this function interpolates from [Offset.zero]. 304 /// 305 /// The `t` argument represents position on the timeline, with 0.0 meaning 306 /// that the interpolation has not started, returning `a` (or something 307 /// equivalent to `a`), 1.0 meaning that the interpolation has finished, 308 /// returning `b` (or something equivalent to `b`), and values in between 309 /// meaning that the interpolation is at the relevant point on the timeline 310 /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and 311 /// 1.0, so negative values and values greater than 1.0 are valid (and can 312 /// easily be generated by curves such as [Curves.elasticInOut]). 313 /// 314 /// Values for `t` are usually obtained from an [Animation<double>], such as 315 /// an [AnimationController]. 316 static Offset lerp(Offset a, Offset b, double t) { 317 assert(t != null); 318 if (a == null && b == null) 319 return null; 320 if (a == null) 321 return b * t; 322 if (b == null) 323 return a * (1.0 - t); 324 return Offset(lerpDouble(a.dx, b.dx, t), lerpDouble(a.dy, b.dy, t)); 325 } 326 327 /// Compares two Offsets for equality. 328 @override 329 bool operator ==(dynamic other) { 330 if (other is! Offset) 331 return false; 332 final Offset typedOther = other; 333 return dx == typedOther.dx && 334 dy == typedOther.dy; 335 } 336 337 @override 338 int get hashCode => hashValues(dx, dy); 339 340 @override 341 String toString() => 'Offset(${dx?.toStringAsFixed(1)}, ${dy?.toStringAsFixed(1)})'; 342} 343 344/// Holds a 2D floating-point size. 345/// 346/// You can think of this as an [Offset] from the origin. 347class Size extends OffsetBase { 348 /// Creates a [Size] with the given [width] and [height]. 349 const Size(double width, double height) : super(width, height); 350 351 /// Creates an instance of [Size] that has the same values as another. 352 // Used by the rendering library's _DebugSize hack. 353 Size.copy(Size source) : super(source.width, source.height); 354 355 /// Creates a square [Size] whose [width] and [height] are the given dimension. 356 /// 357 /// See also: 358 /// 359 /// * [Size.fromRadius], which is more convenient when the available size 360 /// is the radius of a circle. 361 const Size.square(double dimension) : super(dimension, dimension); 362 363 /// Creates a [Size] with the given [width] and an infinite [height]. 364 const Size.fromWidth(double width) : super(width, double.infinity); 365 366 /// Creates a [Size] with the given [height] and an infinite [width]. 367 const Size.fromHeight(double height) : super(double.infinity, height); 368 369 /// Creates a square [Size] whose [width] and [height] are twice the given 370 /// dimension. 371 /// 372 /// This is a square that contains a circle with the given radius. 373 /// 374 /// See also: 375 /// 376 /// * [Size.square], which creates a square with the given dimension. 377 const Size.fromRadius(double radius) : super(radius * 2.0, radius * 2.0); 378 379 /// The horizontal extent of this size. 380 double get width => _dx; 381 382 /// The vertical extent of this size. 383 double get height => _dy; 384 385 /// The aspect ratio of this size. 386 /// 387 /// This returns the [width] divided by the [height]. 388 /// 389 /// If the [width] is zero, the result will be zero. If the [height] is zero 390 /// (and the [width] is not), the result will be [double.infinity] or 391 /// [double.negativeInfinity] as determined by the sign of [width]. 392 /// 393 /// See also: 394 /// 395 /// * [AspectRatio], a widget for giving a child widget a specific aspect 396 /// ratio. 397 /// * [FittedBox], a widget that (in most modes) attempts to maintain a 398 /// child widget's aspect ratio while changing its size. 399 double get aspectRatio { 400 if (height != 0.0) 401 return width / height; 402 if (width > 0.0) 403 return double.infinity; 404 if (width < 0.0) 405 return double.negativeInfinity; 406 return 0.0; 407 } 408 409 /// An empty size, one with a zero width and a zero height. 410 static const Size zero = Size(0.0, 0.0); 411 412 /// A size whose [width] and [height] are infinite. 413 /// 414 /// See also: 415 /// 416 /// * [isInfinite], which checks whether either dimension is infinite. 417 /// * [isFinite], which checks whether both dimensions are finite. 418 static const Size infinite = Size(double.infinity, double.infinity); 419 420 /// Whether this size encloses a non-zero area. 421 /// 422 /// Negative areas are considered empty. 423 bool get isEmpty => width <= 0.0 || height <= 0.0; 424 425 /// Binary subtraction operator for [Size]. 426 /// 427 /// Subtracting a [Size] from a [Size] returns the [Offset] that describes how 428 /// much bigger the left-hand-side operand is than the right-hand-side 429 /// operand. Adding that resulting [Offset] to the [Size] that was the 430 /// right-hand-side operand would return a [Size] equal to the [Size] that was 431 /// the left-hand-side operand. (i.e. if `sizeA - sizeB -> offsetA`, then 432 /// `offsetA + sizeB -> sizeA`) 433 /// 434 /// Subtracting an [Offset] from a [Size] returns the [Size] that is smaller than 435 /// the [Size] operand by the difference given by the [Offset] operand. In other 436 /// words, the returned [Size] has a [width] consisting of the [width] of the 437 /// left-hand-side operand minus the [Offset.dx] dimension of the 438 /// right-hand-side operand, and a [height] consisting of the [height] of the 439 /// left-hand-side operand minus the [Offset.dy] dimension of the 440 /// right-hand-side operand. 441 OffsetBase operator -(OffsetBase other) { 442 if (other is Size) 443 return Offset(width - other.width, height - other.height); 444 if (other is Offset) 445 return Size(width - other.dx, height - other.dy); 446 throw ArgumentError(other); 447 } 448 449 /// Binary addition operator for adding an [Offset] to a [Size]. 450 /// 451 /// Returns a [Size] whose [width] is the sum of the [width] of the 452 /// left-hand-side operand, a [Size], and the [Offset.dx] dimension of the 453 /// right-hand-side operand, an [Offset], and whose [height] is the sum of the 454 /// [height] of the left-hand-side operand and the [Offset.dy] dimension of 455 /// the right-hand-side operand. 456 Size operator +(Offset other) => Size(width + other.dx, height + other.dy); 457 458 /// Multiplication operator. 459 /// 460 /// Returns a [Size] whose dimensions are the dimensions of the left-hand-side 461 /// operand (a [Size]) multiplied by the scalar right-hand-side operand (a 462 /// [double]). 463 Size operator *(double operand) => Size(width * operand, height * operand); 464 465 /// Division operator. 466 /// 467 /// Returns a [Size] whose dimensions are the dimensions of the left-hand-side 468 /// operand (a [Size]) divided by the scalar right-hand-side operand (a 469 /// [double]). 470 Size operator /(double operand) => Size(width / operand, height / operand); 471 472 /// Integer (truncating) division operator. 473 /// 474 /// Returns a [Size] whose dimensions are the dimensions of the left-hand-side 475 /// operand (a [Size]) divided by the scalar right-hand-side operand (a 476 /// [double]), rounded towards zero. 477 Size operator ~/(double operand) => Size((width ~/ operand).toDouble(), (height ~/ operand).toDouble()); 478 479 /// Modulo (remainder) operator. 480 /// 481 /// Returns a [Size] whose dimensions are the remainder of dividing the 482 /// left-hand-side operand (a [Size]) by the scalar right-hand-side operand (a 483 /// [double]). 484 Size operator %(double operand) => Size(width % operand, height % operand); 485 486 /// The lesser of the magnitudes of the [width] and the [height]. 487 double get shortestSide => math.min(width.abs(), height.abs()); 488 489 /// The greater of the magnitudes of the [width] and the [height]. 490 double get longestSide => math.max(width.abs(), height.abs()); 491 492 // Convenience methods that do the equivalent of calling the similarly named 493 // methods on a Rect constructed from the given origin and this size. 494 495 /// The offset to the intersection of the top and left edges of the rectangle 496 /// described by the given [Offset] (which is interpreted as the top-left corner) 497 /// and this [Size]. 498 /// 499 /// See also [Rect.topLeft]. 500 Offset topLeft(Offset origin) => origin; 501 502 /// The offset to the center of the top edge of the rectangle described by the 503 /// given offset (which is interpreted as the top-left corner) and this size. 504 /// 505 /// See also [Rect.topCenter]. 506 Offset topCenter(Offset origin) => Offset(origin.dx + width / 2.0, origin.dy); 507 508 /// The offset to the intersection of the top and right edges of the rectangle 509 /// described by the given offset (which is interpreted as the top-left corner) 510 /// and this size. 511 /// 512 /// See also [Rect.topRight]. 513 Offset topRight(Offset origin) => Offset(origin.dx + width, origin.dy); 514 515 /// The offset to the center of the left edge of the rectangle described by the 516 /// given offset (which is interpreted as the top-left corner) and this size. 517 /// 518 /// See also [Rect.centerLeft]. 519 Offset centerLeft(Offset origin) => Offset(origin.dx, origin.dy + height / 2.0); 520 521 /// The offset to the point halfway between the left and right and the top and 522 /// bottom edges of the rectangle described by the given offset (which is 523 /// interpreted as the top-left corner) and this size. 524 /// 525 /// See also [Rect.center]. 526 Offset center(Offset origin) => Offset(origin.dx + width / 2.0, origin.dy + height / 2.0); 527 528 /// The offset to the center of the right edge of the rectangle described by the 529 /// given offset (which is interpreted as the top-left corner) and this size. 530 /// 531 /// See also [Rect.centerLeft]. 532 Offset centerRight(Offset origin) => Offset(origin.dx + width, origin.dy + height / 2.0); 533 534 /// The offset to the intersection of the bottom and left edges of the 535 /// rectangle described by the given offset (which is interpreted as the 536 /// top-left corner) and this size. 537 /// 538 /// See also [Rect.bottomLeft]. 539 Offset bottomLeft(Offset origin) => Offset(origin.dx, origin.dy + height); 540 541 /// The offset to the center of the bottom edge of the rectangle described by 542 /// the given offset (which is interpreted as the top-left corner) and this 543 /// size. 544 /// 545 /// See also [Rect.bottomLeft]. 546 Offset bottomCenter(Offset origin) => Offset(origin.dx + width / 2.0, origin.dy + height); 547 548 /// The offset to the intersection of the bottom and right edges of the 549 /// rectangle described by the given offset (which is interpreted as the 550 /// top-left corner) and this size. 551 /// 552 /// See also [Rect.bottomRight]. 553 Offset bottomRight(Offset origin) => Offset(origin.dx + width, origin.dy + height); 554 555 /// Whether the point specified by the given offset (which is assumed to be 556 /// relative to the top left of the size) lies between the left and right and 557 /// the top and bottom edges of a rectangle of this size. 558 /// 559 /// Rectangles include their top and left edges but exclude their bottom and 560 /// right edges. 561 bool contains(Offset offset) { 562 return offset.dx >= 0.0 && offset.dx < width && offset.dy >= 0.0 && offset.dy < height; 563 } 564 565 /// A [Size] with the [width] and [height] swapped. 566 Size get flipped => Size(height, width); 567 568 /// Linearly interpolate between two sizes 569 /// 570 /// If either size is null, this function interpolates from [Size.zero]. 571 /// 572 /// The `t` argument represents position on the timeline, with 0.0 meaning 573 /// that the interpolation has not started, returning `a` (or something 574 /// equivalent to `a`), 1.0 meaning that the interpolation has finished, 575 /// returning `b` (or something equivalent to `b`), and values in between 576 /// meaning that the interpolation is at the relevant point on the timeline 577 /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and 578 /// 1.0, so negative values and values greater than 1.0 are valid (and can 579 /// easily be generated by curves such as [Curves.elasticInOut]). 580 /// 581 /// Values for `t` are usually obtained from an [Animation<double>], such as 582 /// an [AnimationController]. 583 static Size lerp(Size a, Size b, double t) { 584 assert(t != null); 585 if (a == null && b == null) 586 return null; 587 if (a == null) 588 return b * t; 589 if (b == null) 590 return a * (1.0 - t); 591 return Size(lerpDouble(a.width, b.width, t), lerpDouble(a.height, b.height, t)); 592 } 593 594 /// Compares two Sizes for equality. 595 // We don't compare the runtimeType because of _DebugSize in the framework. 596 @override 597 bool operator ==(dynamic other) { 598 if (other is! Size) 599 return false; 600 final Size typedOther = other; 601 return _dx == typedOther._dx && 602 _dy == typedOther._dy; 603 } 604 605 @override 606 int get hashCode => hashValues(_dx, _dy); 607 608 @override 609 String toString() => 'Size(${width?.toStringAsFixed(1)}, ${height?.toStringAsFixed(1)})'; 610} 611 612/// An immutable, 2D, axis-aligned, floating-point rectangle whose coordinates 613/// are relative to a given origin. 614/// 615/// A Rect can be created with one its constructors or from an [Offset] and a 616/// [Size] using the `&` operator: 617/// 618/// ```dart 619/// Rect myRect = const Offset(1.0, 2.0) & const Size(3.0, 4.0); 620/// ``` 621class Rect { 622 /// Construct a rectangle from its left, top, right, and bottom edges. 623 @pragma('vm:entry-point') 624 const Rect.fromLTRB(this.left, this.top, this.right, this.bottom) 625 : assert(left != null), 626 assert(top != null), 627 assert(right != null), 628 assert(bottom != null); 629 630 /// Construct a rectangle from its left and top edges, its width, and its 631 /// height. 632 /// 633 /// To construct a [Rect] from an [Offset] and a [Size], you can use the 634 /// rectangle constructor operator `&`. See [Offset.&]. 635 const Rect.fromLTWH(double left, double top, double width, double height) : this.fromLTRB(left, top, left + width, top + height); 636 637 /// Construct a rectangle that bounds the given circle. 638 /// 639 /// The `center` argument is assumed to be an offset from the origin. 640 Rect.fromCircle({ Offset center, double radius }) : this.fromCenter( 641 center: center, 642 width: radius * 2, 643 height: radius * 2, 644 ); 645 646 /// Constructs a rectangle from its center point, width, and height. 647 /// 648 /// The `center` argument is assumed to be an offset from the origin. 649 Rect.fromCenter({ Offset center, double width, double height }) : this.fromLTRB( 650 center.dx - width / 2, 651 center.dy - height / 2, 652 center.dx + width / 2, 653 center.dy + height / 2, 654 ); 655 656 /// Construct the smallest rectangle that encloses the given offsets, treating 657 /// them as vectors from the origin. 658 Rect.fromPoints(Offset a, Offset b) : this.fromLTRB( 659 math.min(a.dx, b.dx), 660 math.min(a.dy, b.dy), 661 math.max(a.dx, b.dx), 662 math.max(a.dy, b.dy), 663 ); 664 665 Float32List get _value32 => Float32List.fromList(<double>[left, top, right, bottom]); 666 667 /// The offset of the left edge of this rectangle from the x axis. 668 final double left; 669 670 /// The offset of the top edge of this rectangle from the y axis. 671 final double top; 672 673 /// The offset of the right edge of this rectangle from the x axis. 674 final double right; 675 676 /// The offset of the bottom edge of this rectangle from the y axis. 677 final double bottom; 678 679 /// The distance between the left and right edges of this rectangle. 680 double get width => right - left; 681 682 /// The distance between the top and bottom edges of this rectangle. 683 double get height => bottom - top; 684 685 /// The distance between the upper-left corner and the lower-right corner of 686 /// this rectangle. 687 Size get size => Size(width, height); 688 689 /// Whether any of the dimensions are `NaN`. 690 bool get hasNaN => left.isNaN || top.isNaN || right.isNaN || bottom.isNaN; 691 692 /// A rectangle with left, top, right, and bottom edges all at zero. 693 static const Rect zero = Rect.fromLTRB(0.0, 0.0, 0.0, 0.0); 694 695 static const double _giantScalar = 1.0E+9; // matches kGiantRect from layer.h 696 697 /// A rectangle that covers the entire coordinate space. 698 /// 699 /// This covers the space from -1e9,-1e9 to 1e9,1e9. 700 /// This is the space over which graphics operations are valid. 701 static const Rect largest = Rect.fromLTRB(-_giantScalar, -_giantScalar, _giantScalar, _giantScalar); 702 703 /// Whether any of the coordinates of this rectangle are equal to positive infinity. 704 // included for consistency with Offset and Size 705 bool get isInfinite { 706 return left >= double.infinity 707 || top >= double.infinity 708 || right >= double.infinity 709 || bottom >= double.infinity; 710 } 711 712 /// Whether all coordinates of this rectangle are finite. 713 bool get isFinite => left.isFinite && top.isFinite && right.isFinite && bottom.isFinite; 714 715 /// Whether this rectangle encloses a non-zero area. Negative areas are 716 /// considered empty. 717 bool get isEmpty => left >= right || top >= bottom; 718 719 /// Returns a new rectangle translated by the given offset. 720 /// 721 /// To translate a rectangle by separate x and y components rather than by an 722 /// [Offset], consider [translate]. 723 Rect shift(Offset offset) { 724 return Rect.fromLTRB(left + offset.dx, top + offset.dy, right + offset.dx, bottom + offset.dy); 725 } 726 727 /// Returns a new rectangle with translateX added to the x components and 728 /// translateY added to the y components. 729 /// 730 /// To translate a rectangle by an [Offset] rather than by separate x and y 731 /// components, consider [shift]. 732 Rect translate(double translateX, double translateY) { 733 return Rect.fromLTRB(left + translateX, top + translateY, right + translateX, bottom + translateY); 734 } 735 736 /// Returns a new rectangle with edges moved outwards by the given delta. 737 Rect inflate(double delta) { 738 return Rect.fromLTRB(left - delta, top - delta, right + delta, bottom + delta); 739 } 740 741 /// Returns a new rectangle with edges moved inwards by the given delta. 742 Rect deflate(double delta) => inflate(-delta); 743 744 /// Returns a new rectangle that is the intersection of the given 745 /// rectangle and this rectangle. The two rectangles must overlap 746 /// for this to be meaningful. If the two rectangles do not overlap, 747 /// then the resulting Rect will have a negative width or height. 748 Rect intersect(Rect other) { 749 return Rect.fromLTRB( 750 math.max(left, other.left), 751 math.max(top, other.top), 752 math.min(right, other.right), 753 math.min(bottom, other.bottom) 754 ); 755 } 756 757 /// Returns a new rectangle which is the bounding box containing this 758 /// rectangle and the given rectangle. 759 Rect expandToInclude(Rect other) { 760 return Rect.fromLTRB( 761 math.min(left, other.left), 762 math.min(top, other.top), 763 math.max(right, other.right), 764 math.max(bottom, other.bottom), 765 ); 766 } 767 768 /// Whether `other` has a nonzero area of overlap with this rectangle. 769 bool overlaps(Rect other) { 770 if (right <= other.left || other.right <= left) 771 return false; 772 if (bottom <= other.top || other.bottom <= top) 773 return false; 774 return true; 775 } 776 777 /// The lesser of the magnitudes of the [width] and the [height] of this 778 /// rectangle. 779 double get shortestSide => math.min(width.abs(), height.abs()); 780 781 /// The greater of the magnitudes of the [width] and the [height] of this 782 /// rectangle. 783 double get longestSide => math.max(width.abs(), height.abs()); 784 785 /// The offset to the intersection of the top and left edges of this rectangle. 786 /// 787 /// See also [Size.topLeft]. 788 Offset get topLeft => Offset(left, top); 789 790 /// The offset to the center of the top edge of this rectangle. 791 /// 792 /// See also [Size.topCenter]. 793 Offset get topCenter => Offset(left + width / 2.0, top); 794 795 /// The offset to the intersection of the top and right edges of this rectangle. 796 /// 797 /// See also [Size.topRight]. 798 Offset get topRight => Offset(right, top); 799 800 /// The offset to the center of the left edge of this rectangle. 801 /// 802 /// See also [Size.centerLeft]. 803 Offset get centerLeft => Offset(left, top + height / 2.0); 804 805 /// The offset to the point halfway between the left and right and the top and 806 /// bottom edges of this rectangle. 807 /// 808 /// See also [Size.center]. 809 Offset get center => Offset(left + width / 2.0, top + height / 2.0); 810 811 /// The offset to the center of the right edge of this rectangle. 812 /// 813 /// See also [Size.centerLeft]. 814 Offset get centerRight => Offset(right, top + height / 2.0); 815 816 /// The offset to the intersection of the bottom and left edges of this rectangle. 817 /// 818 /// See also [Size.bottomLeft]. 819 Offset get bottomLeft => Offset(left, bottom); 820 821 /// The offset to the center of the bottom edge of this rectangle. 822 /// 823 /// See also [Size.bottomLeft]. 824 Offset get bottomCenter => Offset(left + width / 2.0, bottom); 825 826 /// The offset to the intersection of the bottom and right edges of this rectangle. 827 /// 828 /// See also [Size.bottomRight]. 829 Offset get bottomRight => Offset(right, bottom); 830 831 /// Whether the point specified by the given offset (which is assumed to be 832 /// relative to the origin) lies between the left and right and the top and 833 /// bottom edges of this rectangle. 834 /// 835 /// Rectangles include their top and left edges but exclude their bottom and 836 /// right edges. 837 bool contains(Offset offset) { 838 return offset.dx >= left && offset.dx < right && offset.dy >= top && offset.dy < bottom; 839 } 840 841 /// Linearly interpolate between two rectangles. 842 /// 843 /// If either rect is null, [Rect.zero] is used as a substitute. 844 /// 845 /// The `t` argument represents position on the timeline, with 0.0 meaning 846 /// that the interpolation has not started, returning `a` (or something 847 /// equivalent to `a`), 1.0 meaning that the interpolation has finished, 848 /// returning `b` (or something equivalent to `b`), and values in between 849 /// meaning that the interpolation is at the relevant point on the timeline 850 /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and 851 /// 1.0, so negative values and values greater than 1.0 are valid (and can 852 /// easily be generated by curves such as [Curves.elasticInOut]). 853 /// 854 /// Values for `t` are usually obtained from an [Animation<double>], such as 855 /// an [AnimationController]. 856 static Rect lerp(Rect a, Rect b, double t) { 857 assert(t != null); 858 if (a == null && b == null) 859 return null; 860 if (a == null) 861 return Rect.fromLTRB(b.left * t, b.top * t, b.right * t, b.bottom * t); 862 if (b == null) { 863 final double k = 1.0 - t; 864 return Rect.fromLTRB(a.left * k, a.top * k, a.right * k, a.bottom * k); 865 } 866 return Rect.fromLTRB( 867 lerpDouble(a.left, b.left, t), 868 lerpDouble(a.top, b.top, t), 869 lerpDouble(a.right, b.right, t), 870 lerpDouble(a.bottom, b.bottom, t), 871 ); 872 } 873 874 @override 875 bool operator ==(dynamic other) { 876 if (identical(this, other)) 877 return true; 878 if (runtimeType != other.runtimeType) 879 return false; 880 final Rect typedOther = other; 881 return left == typedOther.left && 882 top == typedOther.top && 883 right == typedOther.right && 884 bottom == typedOther.bottom; 885 } 886 887 @override 888 int get hashCode => hashValues(left, top, right, bottom); 889 890 @override 891 String toString() => 'Rect.fromLTRB(${left.toStringAsFixed(1)}, ${top.toStringAsFixed(1)}, ${right.toStringAsFixed(1)}, ${bottom.toStringAsFixed(1)})'; 892} 893 894/// A radius for either circular or elliptical shapes. 895class Radius { 896 /// Constructs a circular radius. [x] and [y] will have the same radius value. 897 const Radius.circular(double radius) : this.elliptical(radius, radius); 898 899 /// Constructs an elliptical radius with the given radii. 900 const Radius.elliptical(this.x, this.y); 901 902 /// The radius value on the horizontal axis. 903 final double x; 904 905 /// The radius value on the vertical axis. 906 final double y; 907 908 /// A radius with [x] and [y] values set to zero. 909 /// 910 /// You can use [Radius.zero] with [RRect] to have right-angle corners. 911 static const Radius zero = Radius.circular(0.0); 912 913 /// Unary negation operator. 914 /// 915 /// Returns a Radius with the distances negated. 916 /// 917 /// Radiuses with negative values aren't geometrically meaningful, but could 918 /// occur as part of expressions. For example, negating a radius of one pixel 919 /// and then adding the result to another radius is equivalent to subtracting 920 /// a radius of one pixel from the other. 921 Radius operator -() => Radius.elliptical(-x, -y); 922 923 /// Binary subtraction operator. 924 /// 925 /// Returns a radius whose [x] value is the left-hand-side operand's [x] 926 /// minus the right-hand-side operand's [x] and whose [y] value is the 927 /// left-hand-side operand's [y] minus the right-hand-side operand's [y]. 928 Radius operator -(Radius other) => Radius.elliptical(x - other.x, y - other.y); 929 930 /// Binary addition operator. 931 /// 932 /// Returns a radius whose [x] value is the sum of the [x] values of the 933 /// two operands, and whose [y] value is the sum of the [y] values of the 934 /// two operands. 935 Radius operator +(Radius other) => Radius.elliptical(x + other.x, y + other.y); 936 937 /// Multiplication operator. 938 /// 939 /// Returns a radius whose coordinates are the coordinates of the 940 /// left-hand-side operand (a radius) multiplied by the scalar 941 /// right-hand-side operand (a double). 942 Radius operator *(double operand) => Radius.elliptical(x * operand, y * operand); 943 944 /// Division operator. 945 /// 946 /// Returns a radius whose coordinates are the coordinates of the 947 /// left-hand-side operand (a radius) divided by the scalar right-hand-side 948 /// operand (a double). 949 Radius operator /(double operand) => Radius.elliptical(x / operand, y / operand); 950 951 /// Integer (truncating) division operator. 952 /// 953 /// Returns a radius whose coordinates are the coordinates of the 954 /// left-hand-side operand (a radius) divided by the scalar right-hand-side 955 /// operand (a double), rounded towards zero. 956 Radius operator ~/(double operand) => Radius.elliptical((x ~/ operand).toDouble(), (y ~/ operand).toDouble()); 957 958 /// Modulo (remainder) operator. 959 /// 960 /// Returns a radius whose coordinates are the remainder of dividing the 961 /// coordinates of the left-hand-side operand (a radius) by the scalar 962 /// right-hand-side operand (a double). 963 Radius operator %(double operand) => Radius.elliptical(x % operand, y % operand); 964 965 /// Linearly interpolate between two radii. 966 /// 967 /// If either is null, this function substitutes [Radius.zero] instead. 968 /// 969 /// The `t` argument represents position on the timeline, with 0.0 meaning 970 /// that the interpolation has not started, returning `a` (or something 971 /// equivalent to `a`), 1.0 meaning that the interpolation has finished, 972 /// returning `b` (or something equivalent to `b`), and values in between 973 /// meaning that the interpolation is at the relevant point on the timeline 974 /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and 975 /// 1.0, so negative values and values greater than 1.0 are valid (and can 976 /// easily be generated by curves such as [Curves.elasticInOut]). 977 /// 978 /// Values for `t` are usually obtained from an [Animation<double>], such as 979 /// an [AnimationController]. 980 static Radius lerp(Radius a, Radius b, double t) { 981 assert(t != null); 982 if (a == null && b == null) 983 return null; 984 if (a == null) 985 return Radius.elliptical(b.x * t, b.y * t); 986 if (b == null) { 987 final double k = 1.0 - t; 988 return Radius.elliptical(a.x * k, a.y * k); 989 } 990 return Radius.elliptical( 991 lerpDouble(a.x, b.x, t), 992 lerpDouble(a.y, b.y, t), 993 ); 994 } 995 996 @override 997 bool operator ==(dynamic other) { 998 if (identical(this, other)) 999 return true; 1000 if (runtimeType != other.runtimeType) 1001 return false; 1002 final Radius typedOther = other; 1003 return typedOther.x == x && typedOther.y == y; 1004 } 1005 1006 @override 1007 int get hashCode => hashValues(x, y); 1008 1009 @override 1010 String toString() { 1011 return x == y ? 'Radius.circular(${x.toStringAsFixed(1)})' : 1012 'Radius.elliptical(${x.toStringAsFixed(1)}, ' 1013 '${y.toStringAsFixed(1)})'; 1014 } 1015} 1016 1017/// An immutable rounded rectangle with the custom radii for all four corners. 1018class RRect { 1019 /// Construct a rounded rectangle from its left, top, right, and bottom edges, 1020 /// and the same radii along its horizontal axis and its vertical axis. 1021 const RRect.fromLTRBXY(double left, double top, double right, double bottom, 1022 double radiusX, double radiusY) : this._raw( 1023 top: top, 1024 left: left, 1025 right: right, 1026 bottom: bottom, 1027 tlRadiusX: radiusX, 1028 tlRadiusY: radiusY, 1029 trRadiusX: radiusX, 1030 trRadiusY: radiusY, 1031 blRadiusX: radiusX, 1032 blRadiusY: radiusY, 1033 brRadiusX: radiusX, 1034 brRadiusY: radiusY, 1035 ); 1036 1037 /// Construct a rounded rectangle from its left, top, right, and bottom edges, 1038 /// and the same radius in each corner. 1039 RRect.fromLTRBR(double left, double top, double right, double bottom, 1040 Radius radius) 1041 : this._raw( 1042 top: top, 1043 left: left, 1044 right: right, 1045 bottom: bottom, 1046 tlRadiusX: radius.x, 1047 tlRadiusY: radius.y, 1048 trRadiusX: radius.x, 1049 trRadiusY: radius.y, 1050 blRadiusX: radius.x, 1051 blRadiusY: radius.y, 1052 brRadiusX: radius.x, 1053 brRadiusY: radius.y, 1054 ); 1055 1056 /// Construct a rounded rectangle from its bounding box and the same radii 1057 /// along its horizontal axis and its vertical axis. 1058 RRect.fromRectXY(Rect rect, double radiusX, double radiusY) 1059 : this._raw( 1060 top: rect.top, 1061 left: rect.left, 1062 right: rect.right, 1063 bottom: rect.bottom, 1064 tlRadiusX: radiusX, 1065 tlRadiusY: radiusY, 1066 trRadiusX: radiusX, 1067 trRadiusY: radiusY, 1068 blRadiusX: radiusX, 1069 blRadiusY: radiusY, 1070 brRadiusX: radiusX, 1071 brRadiusY: radiusY, 1072 ); 1073 1074 /// Construct a rounded rectangle from its bounding box and a radius that is 1075 /// the same in each corner. 1076 RRect.fromRectAndRadius(Rect rect, Radius radius) 1077 : this._raw( 1078 top: rect.top, 1079 left: rect.left, 1080 right: rect.right, 1081 bottom: rect.bottom, 1082 tlRadiusX: radius.x, 1083 tlRadiusY: radius.y, 1084 trRadiusX: radius.x, 1085 trRadiusY: radius.y, 1086 blRadiusX: radius.x, 1087 blRadiusY: radius.y, 1088 brRadiusX: radius.x, 1089 brRadiusY: radius.y, 1090 ); 1091 1092 /// Construct a rounded rectangle from its left, top, right, and bottom edges, 1093 /// and topLeft, topRight, bottomRight, and bottomLeft radii. 1094 /// 1095 /// The corner radii default to [Radius.zero], i.e. right-angled corners. 1096 RRect.fromLTRBAndCorners( 1097 double left, 1098 double top, 1099 double right, 1100 double bottom, { 1101 Radius topLeft = Radius.zero, 1102 Radius topRight = Radius.zero, 1103 Radius bottomRight = Radius.zero, 1104 Radius bottomLeft = Radius.zero, 1105 }) : this._raw( 1106 top: top, 1107 left: left, 1108 right: right, 1109 bottom: bottom, 1110 tlRadiusX: topLeft.x, 1111 tlRadiusY: topLeft.y, 1112 trRadiusX: topRight.x, 1113 trRadiusY: topRight.y, 1114 blRadiusX: bottomLeft.x, 1115 blRadiusY: bottomLeft.y, 1116 brRadiusX: bottomRight.x, 1117 brRadiusY: bottomRight.y, 1118 ); 1119 1120 /// Construct a rounded rectangle from its bounding box and and topLeft, 1121 /// topRight, bottomRight, and bottomLeft radii. 1122 /// 1123 /// The corner radii default to [Radius.zero], i.e. right-angled corners 1124 RRect.fromRectAndCorners( 1125 Rect rect, 1126 { 1127 Radius topLeft = Radius.zero, 1128 Radius topRight = Radius.zero, 1129 Radius bottomRight = Radius.zero, 1130 Radius bottomLeft = Radius.zero 1131 } 1132 ) : this._raw( 1133 top: rect.top, 1134 left: rect.left, 1135 right: rect.right, 1136 bottom: rect.bottom, 1137 tlRadiusX: topLeft.x, 1138 tlRadiusY: topLeft.y, 1139 trRadiusX: topRight.x, 1140 trRadiusY: topRight.y, 1141 blRadiusX: bottomLeft.x, 1142 blRadiusY: bottomLeft.y, 1143 brRadiusX: bottomRight.x, 1144 brRadiusY: bottomRight.y, 1145 ); 1146 1147 const RRect._raw({ 1148 this.left = 0.0, 1149 this.top = 0.0, 1150 this.right = 0.0, 1151 this.bottom = 0.0, 1152 this.tlRadiusX = 0.0, 1153 this.tlRadiusY = 0.0, 1154 this.trRadiusX = 0.0, 1155 this.trRadiusY = 0.0, 1156 this.brRadiusX = 0.0, 1157 this.brRadiusY = 0.0, 1158 this.blRadiusX = 0.0, 1159 this.blRadiusY = 0.0, 1160 }) : assert(left != null), 1161 assert(top != null), 1162 assert(right != null), 1163 assert(bottom != null), 1164 assert(tlRadiusX != null), 1165 assert(tlRadiusY != null), 1166 assert(trRadiusX != null), 1167 assert(trRadiusY != null), 1168 assert(brRadiusX != null), 1169 assert(brRadiusY != null), 1170 assert(blRadiusX != null), 1171 assert(blRadiusY != null); 1172 1173 Float32List get _value32 => Float32List.fromList(<double>[ 1174 left, 1175 top, 1176 right, 1177 bottom, 1178 tlRadiusX, 1179 tlRadiusY, 1180 trRadiusX, 1181 trRadiusY, 1182 brRadiusX, 1183 brRadiusY, 1184 blRadiusX, 1185 blRadiusY, 1186 ]); 1187 1188 /// The offset of the left edge of this rectangle from the x axis. 1189 final double left; 1190 1191 /// The offset of the top edge of this rectangle from the y axis. 1192 final double top; 1193 1194 /// The offset of the right edge of this rectangle from the x axis. 1195 final double right; 1196 1197 /// The offset of the bottom edge of this rectangle from the y axis. 1198 final double bottom; 1199 1200 /// The top-left horizontal radius. 1201 final double tlRadiusX; 1202 1203 /// The top-left vertical radius. 1204 final double tlRadiusY; 1205 1206 /// The top-left [Radius]. 1207 Radius get tlRadius => Radius.elliptical(tlRadiusX, tlRadiusY); 1208 1209 /// The top-right horizontal radius. 1210 final double trRadiusX; 1211 1212 /// The top-right vertical radius. 1213 final double trRadiusY; 1214 1215 /// The top-right [Radius]. 1216 Radius get trRadius => Radius.elliptical(trRadiusX, trRadiusY); 1217 1218 /// The bottom-right horizontal radius. 1219 final double brRadiusX; 1220 1221 /// The bottom-right vertical radius. 1222 final double brRadiusY; 1223 1224 /// The bottom-right [Radius]. 1225 Radius get brRadius => Radius.elliptical(brRadiusX, brRadiusY); 1226 1227 /// The bottom-left horizontal radius. 1228 final double blRadiusX; 1229 1230 /// The bottom-left vertical radius. 1231 final double blRadiusY; 1232 1233 /// The bottom-left [Radius]. 1234 Radius get blRadius => Radius.elliptical(blRadiusX, blRadiusY); 1235 1236 /// A rounded rectangle with all the values set to zero. 1237 static const RRect zero = RRect._raw(); 1238 1239 /// Returns a new [RRect] translated by the given offset. 1240 RRect shift(Offset offset) { 1241 return RRect._raw( 1242 left: left + offset.dx, 1243 top: top + offset.dy, 1244 right: right + offset.dx, 1245 bottom: bottom + offset.dy, 1246 tlRadiusX: tlRadiusX, 1247 tlRadiusY: tlRadiusY, 1248 trRadiusX: trRadiusX, 1249 trRadiusY: trRadiusY, 1250 blRadiusX: blRadiusX, 1251 blRadiusY: blRadiusY, 1252 brRadiusX: brRadiusX, 1253 brRadiusY: brRadiusY, 1254 ); 1255 } 1256 1257 /// Returns a new [RRect] with edges and radii moved outwards by the given 1258 /// delta. 1259 RRect inflate(double delta) { 1260 return RRect._raw( 1261 left: left - delta, 1262 top: top - delta, 1263 right: right + delta, 1264 bottom: bottom + delta, 1265 tlRadiusX: tlRadiusX + delta, 1266 tlRadiusY: tlRadiusY + delta, 1267 trRadiusX: trRadiusX + delta, 1268 trRadiusY: trRadiusY + delta, 1269 blRadiusX: blRadiusX + delta, 1270 blRadiusY: blRadiusY + delta, 1271 brRadiusX: brRadiusX + delta, 1272 brRadiusY: brRadiusY + delta, 1273 ); 1274 } 1275 1276 /// Returns a new [RRect] with edges and radii moved inwards by the given delta. 1277 RRect deflate(double delta) => inflate(-delta); 1278 1279 /// The distance between the left and right edges of this rectangle. 1280 double get width => right - left; 1281 1282 /// The distance between the top and bottom edges of this rectangle. 1283 double get height => bottom - top; 1284 1285 /// The bounding box of this rounded rectangle (the rectangle with no rounded corners). 1286 Rect get outerRect => Rect.fromLTRB(left, top, right, bottom); 1287 1288 /// The non-rounded rectangle that is constrained by the smaller of the two 1289 /// diagonals, with each diagonal traveling through the middle of the curve 1290 /// corners. The middle of a corner is the intersection of the curve with its 1291 /// respective quadrant bisector. 1292 Rect get safeInnerRect { 1293 const double kInsetFactor = 0.29289321881; // 1-cos(pi/4) 1294 1295 final double leftRadius = math.max(blRadiusX, tlRadiusX); 1296 final double topRadius = math.max(tlRadiusY, trRadiusY); 1297 final double rightRadius = math.max(trRadiusX, brRadiusX); 1298 final double bottomRadius = math.max(brRadiusY, blRadiusY); 1299 1300 return Rect.fromLTRB( 1301 left + leftRadius * kInsetFactor, 1302 top + topRadius * kInsetFactor, 1303 right - rightRadius * kInsetFactor, 1304 bottom - bottomRadius * kInsetFactor 1305 ); 1306 } 1307 1308 /// The rectangle that would be formed using the axis-aligned intersection of 1309 /// the sides of the rectangle, i.e., the rectangle formed from the 1310 /// inner-most centers of the ellipses that form the corners. This is the 1311 /// intersection of the [wideMiddleRect] and the [tallMiddleRect]. If any of 1312 /// the intersections are void, the resulting [Rect] will have negative width 1313 /// or height. 1314 Rect get middleRect { 1315 final double leftRadius = math.max(blRadiusX, tlRadiusX); 1316 final double topRadius = math.max(tlRadiusY, trRadiusY); 1317 final double rightRadius = math.max(trRadiusX, brRadiusX); 1318 final double bottomRadius = math.max(brRadiusY, blRadiusY); 1319 return Rect.fromLTRB( 1320 left + leftRadius, 1321 top + topRadius, 1322 right - rightRadius, 1323 bottom - bottomRadius 1324 ); 1325 } 1326 1327 /// The biggest rectangle that is entirely inside the rounded rectangle and 1328 /// has the full width of the rounded rectangle. If the rounded rectangle does 1329 /// not have an axis-aligned intersection of its left and right side, the 1330 /// resulting [Rect] will have negative width or height. 1331 Rect get wideMiddleRect { 1332 final double topRadius = math.max(tlRadiusY, trRadiusY); 1333 final double bottomRadius = math.max(brRadiusY, blRadiusY); 1334 return Rect.fromLTRB( 1335 left, 1336 top + topRadius, 1337 right, 1338 bottom - bottomRadius 1339 ); 1340 } 1341 1342 /// The biggest rectangle that is entirely inside the rounded rectangle and 1343 /// has the full height of the rounded rectangle. If the rounded rectangle 1344 /// does not have an axis-aligned intersection of its top and bottom side, the 1345 /// resulting [Rect] will have negative width or height. 1346 Rect get tallMiddleRect { 1347 final double leftRadius = math.max(blRadiusX, tlRadiusX); 1348 final double rightRadius = math.max(trRadiusX, brRadiusX); 1349 return Rect.fromLTRB( 1350 left + leftRadius, 1351 top, 1352 right - rightRadius, 1353 bottom 1354 ); 1355 } 1356 1357 /// Whether this rounded rectangle encloses a non-zero area. 1358 /// Negative areas are considered empty. 1359 bool get isEmpty => left >= right || top >= bottom; 1360 1361 /// Whether all coordinates of this rounded rectangle are finite. 1362 bool get isFinite => left.isFinite && top.isFinite && right.isFinite && bottom.isFinite; 1363 1364 /// Whether this rounded rectangle is a simple rectangle with zero 1365 /// corner radii. 1366 bool get isRect { 1367 return (tlRadiusX == 0.0 || tlRadiusY == 0.0) && 1368 (trRadiusX == 0.0 || trRadiusY == 0.0) && 1369 (blRadiusX == 0.0 || blRadiusY == 0.0) && 1370 (brRadiusX == 0.0 || brRadiusY == 0.0); 1371 } 1372 1373 /// Whether this rounded rectangle has a side with no straight section. 1374 bool get isStadium { 1375 return tlRadius == trRadius 1376 && trRadius == brRadius 1377 && brRadius == blRadius 1378 && (width <= 2.0 * tlRadiusX || height <= 2.0 * tlRadiusY); 1379 } 1380 1381 /// Whether this rounded rectangle has no side with a straight section. 1382 bool get isEllipse { 1383 return tlRadius == trRadius 1384 && trRadius == brRadius 1385 && brRadius == blRadius 1386 && width <= 2.0 * tlRadiusX 1387 && height <= 2.0 * tlRadiusY; 1388 } 1389 1390 /// Whether this rounded rectangle would draw as a circle. 1391 bool get isCircle => width == height && isEllipse; 1392 1393 /// The lesser of the magnitudes of the [width] and the [height] of this 1394 /// rounded rectangle. 1395 double get shortestSide => math.min(width.abs(), height.abs()); 1396 1397 /// The greater of the magnitudes of the [width] and the [height] of this 1398 /// rounded rectangle. 1399 double get longestSide => math.max(width.abs(), height.abs()); 1400 1401 /// Whether any of the dimensions are `NaN`. 1402 bool get hasNaN => left.isNaN || top.isNaN || right.isNaN || bottom.isNaN || 1403 trRadiusX.isNaN || trRadiusY.isNaN || tlRadiusX.isNaN || tlRadiusY.isNaN || 1404 brRadiusX.isNaN || brRadiusY.isNaN || blRadiusX.isNaN || blRadiusY.isNaN; 1405 1406 /// The offset to the point halfway between the left and right and the top and 1407 /// bottom edges of this rectangle. 1408 Offset get center => Offset(left + width / 2.0, top + height / 2.0); 1409 1410 // Returns the minimum between min and scale to which radius1 and radius2 1411 // should be scaled with in order not to exceed the limit. 1412 double _getMin(double min, double radius1, double radius2, double limit) { 1413 final double sum = radius1 + radius2; 1414 if (sum > limit && sum != 0.0) 1415 return math.min(min, limit / sum); 1416 return min; 1417 } 1418 1419 /// Scales all radii so that on each side their sum will not exceed the size 1420 /// of the width/height. 1421 /// 1422 /// Skia already handles RRects with radii that are too large in this way. 1423 /// Therefore, this method is only needed for RRect use cases that require 1424 /// the appropriately scaled radii values. 1425 /// 1426 /// See the [Skia scaling implementation](https://github.com/google/skia/blob/master/src/core/SkRRect.cpp) 1427 /// for more details. 1428 RRect scaleRadii() { 1429 double scale = 1.0; 1430 scale = _getMin(scale, blRadiusY, tlRadiusY, height); 1431 scale = _getMin(scale, tlRadiusX, trRadiusX, width); 1432 scale = _getMin(scale, trRadiusY, brRadiusY, height); 1433 scale = _getMin(scale, brRadiusX, blRadiusX, width); 1434 1435 if (scale < 1.0) { 1436 return RRect._raw( 1437 top: top, 1438 left: left, 1439 right: right, 1440 bottom: bottom, 1441 tlRadiusX: tlRadiusX * scale, 1442 tlRadiusY: tlRadiusY * scale, 1443 trRadiusX: trRadiusX * scale, 1444 trRadiusY: trRadiusY * scale, 1445 blRadiusX: blRadiusX * scale, 1446 blRadiusY: blRadiusY * scale, 1447 brRadiusX: brRadiusX * scale, 1448 brRadiusY: brRadiusY * scale, 1449 ); 1450 } 1451 1452 return RRect._raw( 1453 top: top, 1454 left: left, 1455 right: right, 1456 bottom: bottom, 1457 tlRadiusX: tlRadiusX, 1458 tlRadiusY: tlRadiusY, 1459 trRadiusX: trRadiusX, 1460 trRadiusY: trRadiusY, 1461 blRadiusX: blRadiusX, 1462 blRadiusY: blRadiusY, 1463 brRadiusX: brRadiusX, 1464 brRadiusY: brRadiusY, 1465 ); 1466 } 1467 1468 /// Whether the point specified by the given offset (which is assumed to be 1469 /// relative to the origin) lies inside the rounded rectangle. 1470 /// 1471 /// This method may allocate (and cache) a copy of the object with normalized 1472 /// radii the first time it is called on a particular [RRect] instance. When 1473 /// using this method, prefer to reuse existing [RRect]s rather than 1474 /// recreating the object each time. 1475 bool contains(Offset point) { 1476 if (point.dx < left || point.dx >= right || point.dy < top || point.dy >= bottom) 1477 return false; // outside bounding box 1478 1479 final RRect scaled = scaleRadii(); 1480 1481 double x; 1482 double y; 1483 double radiusX; 1484 double radiusY; 1485 // check whether point is in one of the rounded corner areas 1486 // x, y -> translate to ellipse center 1487 if (point.dx < left + scaled.tlRadiusX && 1488 point.dy < top + scaled.tlRadiusY) { 1489 x = point.dx - left - scaled.tlRadiusX; 1490 y = point.dy - top - scaled.tlRadiusY; 1491 radiusX = scaled.tlRadiusX; 1492 radiusY = scaled.tlRadiusY; 1493 } else if (point.dx > right - scaled.trRadiusX && 1494 point.dy < top + scaled.trRadiusY) { 1495 x = point.dx - right + scaled.trRadiusX; 1496 y = point.dy - top - scaled.trRadiusY; 1497 radiusX = scaled.trRadiusX; 1498 radiusY = scaled.trRadiusY; 1499 } else if (point.dx > right - scaled.brRadiusX && 1500 point.dy > bottom - scaled.brRadiusY) { 1501 x = point.dx - right + scaled.brRadiusX; 1502 y = point.dy - bottom + scaled.brRadiusY; 1503 radiusX = scaled.brRadiusX; 1504 radiusY = scaled.brRadiusY; 1505 } else if (point.dx < left + scaled.blRadiusX && 1506 point.dy > bottom - scaled.blRadiusY) { 1507 x = point.dx - left - scaled.blRadiusX; 1508 y = point.dy - bottom + scaled.blRadiusY; 1509 radiusX = scaled.blRadiusX; 1510 radiusY = scaled.blRadiusY; 1511 } else { 1512 return true; // inside and not within the rounded corner area 1513 } 1514 1515 x = x / radiusX; 1516 y = y / radiusY; 1517 // check if the point is outside the unit circle 1518 if (x * x + y * y > 1.0) 1519 return false; 1520 return true; 1521 } 1522 1523 /// Linearly interpolate between two rounded rectangles. 1524 /// 1525 /// If either is null, this function substitutes [RRect.zero] instead. 1526 /// 1527 /// The `t` argument represents position on the timeline, with 0.0 meaning 1528 /// that the interpolation has not started, returning `a` (or something 1529 /// equivalent to `a`), 1.0 meaning that the interpolation has finished, 1530 /// returning `b` (or something equivalent to `b`), and values in between 1531 /// meaning that the interpolation is at the relevant point on the timeline 1532 /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and 1533 /// 1.0, so negative values and values greater than 1.0 are valid (and can 1534 /// easily be generated by curves such as [Curves.elasticInOut]). 1535 /// 1536 /// Values for `t` are usually obtained from an [Animation<double>], such as 1537 /// an [AnimationController]. 1538 static RRect lerp(RRect a, RRect b, double t) { 1539 assert(t != null); 1540 if (a == null && b == null) 1541 return null; 1542 if (a == null) { 1543 return RRect._raw( 1544 left: b.left * t, 1545 top: b.top * t, 1546 right: b.right * t, 1547 bottom: b.bottom * t, 1548 tlRadiusX: b.tlRadiusX * t, 1549 tlRadiusY: b.tlRadiusY * t, 1550 trRadiusX: b.trRadiusX * t, 1551 trRadiusY: b.trRadiusY * t, 1552 brRadiusX: b.brRadiusX * t, 1553 brRadiusY: b.brRadiusY * t, 1554 blRadiusX: b.blRadiusX * t, 1555 blRadiusY: b.blRadiusY * t, 1556 ); 1557 } 1558 if (b == null) { 1559 final double k = 1.0 - t; 1560 return RRect._raw( 1561 left: a.left * k, 1562 top: a.top * k, 1563 right: a.right * k, 1564 bottom: a.bottom * k, 1565 tlRadiusX: a.tlRadiusX * k, 1566 tlRadiusY: a.tlRadiusY * k, 1567 trRadiusX: a.trRadiusX * k, 1568 trRadiusY: a.trRadiusY * k, 1569 brRadiusX: a.brRadiusX * k, 1570 brRadiusY: a.brRadiusY * k, 1571 blRadiusX: a.blRadiusX * k, 1572 blRadiusY: a.blRadiusY * k, 1573 ); 1574 } 1575 return RRect._raw( 1576 left: lerpDouble(a.left, b.left, t), 1577 top: lerpDouble(a.top, b.top, t), 1578 right: lerpDouble(a.right, b.right, t), 1579 bottom: lerpDouble(a.bottom, b.bottom, t), 1580 tlRadiusX: lerpDouble(a.tlRadiusX, b.tlRadiusX, t), 1581 tlRadiusY: lerpDouble(a.tlRadiusY, b.tlRadiusY, t), 1582 trRadiusX: lerpDouble(a.trRadiusX, b.trRadiusX, t), 1583 trRadiusY: lerpDouble(a.trRadiusY, b.trRadiusY, t), 1584 brRadiusX: lerpDouble(a.brRadiusX, b.brRadiusX, t), 1585 brRadiusY: lerpDouble(a.brRadiusY, b.brRadiusY, t), 1586 blRadiusX: lerpDouble(a.blRadiusX, b.blRadiusX, t), 1587 blRadiusY: lerpDouble(a.blRadiusY, b.blRadiusY, t), 1588 ); 1589 } 1590 1591 @override 1592 bool operator ==(dynamic other) { 1593 if (identical(this, other)) 1594 return true; 1595 if (runtimeType != other.runtimeType) 1596 return false; 1597 final RRect typedOther = other; 1598 return left == typedOther.left && 1599 top == typedOther.top && 1600 right == typedOther.right && 1601 bottom == typedOther.bottom && 1602 tlRadiusX == typedOther.tlRadiusX && 1603 tlRadiusY == typedOther.tlRadiusY && 1604 trRadiusX == typedOther.trRadiusX && 1605 trRadiusY == typedOther.trRadiusY && 1606 blRadiusX == typedOther.blRadiusX && 1607 blRadiusY == typedOther.blRadiusY && 1608 brRadiusX == typedOther.brRadiusX && 1609 brRadiusY == typedOther.brRadiusY; 1610 } 1611 1612 @override 1613 int get hashCode => hashValues(left, top, right, bottom, 1614 tlRadiusX, tlRadiusY, trRadiusX, trRadiusY, 1615 blRadiusX, blRadiusY, brRadiusX, brRadiusY); 1616 1617 @override 1618 String toString() { 1619 final String rect = '${left.toStringAsFixed(1)}, ' 1620 '${top.toStringAsFixed(1)}, ' 1621 '${right.toStringAsFixed(1)}, ' 1622 '${bottom.toStringAsFixed(1)}'; 1623 if (tlRadius == trRadius && 1624 trRadius == brRadius && 1625 brRadius == blRadius) { 1626 if (tlRadius.x == tlRadius.y) 1627 return 'RRect.fromLTRBR($rect, ${tlRadius.x.toStringAsFixed(1)})'; 1628 return 'RRect.fromLTRBXY($rect, ${tlRadius.x.toStringAsFixed(1)}, ${tlRadius.y.toStringAsFixed(1)})'; 1629 } 1630 return 'RRect.fromLTRBAndCorners(' 1631 '$rect, ' 1632 'topLeft: $tlRadius, ' 1633 'topRight: $trRadius, ' 1634 'bottomRight: $brRadius, ' 1635 'bottomLeft: $blRadius' 1636 ')'; 1637 } 1638} 1639 1640/// A transform consisting of a translation, a rotation, and a uniform scale. 1641/// 1642/// Used by [Canvas.drawAtlas]. This is a more efficient way to represent these 1643/// simple transformations than a full matrix. 1644// Modeled after Skia's SkRSXform. 1645class RSTransform { 1646 /// Creates an RSTransform. 1647 /// 1648 /// An [RSTransform] expresses the combination of a translation, a rotation 1649 /// around a particular point, and a scale factor. 1650 /// 1651 /// The first argument, `scos`, is the cosine of the rotation, multiplied by 1652 /// the scale factor. 1653 /// 1654 /// The second argument, `ssin`, is the sine of the rotation, multiplied by 1655 /// that same scale factor. 1656 /// 1657 /// The third argument is the x coordinate of the translation, minus the 1658 /// `scos` argument multiplied by the x-coordinate of the rotation point, plus 1659 /// the `ssin` argument multiplied by the y-coordinate of the rotation point. 1660 /// 1661 /// The fourth argument is the y coordinate of the translation, minus the `ssin` 1662 /// argument multiplied by the x-coordinate of the rotation point, minus the 1663 /// `scos` argument multiplied by the y-coordinate of the rotation point. 1664 /// 1665 /// The [RSTransform.fromComponents] method may be a simpler way to 1666 /// construct these values. However, if there is a way to factor out the 1667 /// computations of the sine and cosine of the rotation so that they can be 1668 /// reused over multiple calls to this constructor, it may be more efficient 1669 /// to directly use this constructor instead. 1670 RSTransform(double scos, double ssin, double tx, double ty) { 1671 _value 1672 ..[0] = scos 1673 ..[1] = ssin 1674 ..[2] = tx 1675 ..[3] = ty; 1676 } 1677 1678 /// Creates an RSTransform from its individual components. 1679 /// 1680 /// The `rotation` parameter gives the rotation in radians. 1681 /// 1682 /// The `scale` parameter describes the uniform scale factor. 1683 /// 1684 /// The `anchorX` and `anchorY` parameters give the coordinate of the point 1685 /// around which to rotate. 1686 /// 1687 /// The `translateX` and `translateY` parameters give the coordinate of the 1688 /// offset by which to translate. 1689 /// 1690 /// This constructor computes the arguments of the [new RSTransform] 1691 /// constructor and then defers to that constructor to actually create the 1692 /// object. If many [RSTransform] objects are being created and there is a way 1693 /// to factor out the computations of the sine and cosine of the rotation 1694 /// (which are computed each time this constructor is called) and reuse them 1695 /// over multiple [RSTransform] objects, it may be more efficient to directly 1696 /// use the more direct [new RSTransform] constructor instead. 1697 factory RSTransform.fromComponents({ 1698 double rotation, 1699 double scale, 1700 double anchorX, 1701 double anchorY, 1702 double translateX, 1703 double translateY 1704 }) { 1705 final double scos = math.cos(rotation) * scale; 1706 final double ssin = math.sin(rotation) * scale; 1707 final double tx = translateX + -scos * anchorX + ssin * anchorY; 1708 final double ty = translateY + -ssin * anchorX - scos * anchorY; 1709 return RSTransform(scos, ssin, tx, ty); 1710 } 1711 1712 final Float32List _value = Float32List(4); 1713 1714 /// The cosine of the rotation multiplied by the scale factor. 1715 double get scos => _value[0]; 1716 1717 /// The sine of the rotation multiplied by that same scale factor. 1718 double get ssin => _value[1]; 1719 1720 /// The x coordinate of the translation, minus [scos] multiplied by the 1721 /// x-coordinate of the rotation point, plus [ssin] multiplied by the 1722 /// y-coordinate of the rotation point. 1723 double get tx => _value[2]; 1724 1725 /// The y coordinate of the translation, minus [ssin] multiplied by the 1726 /// x-coordinate of the rotation point, minus [scos] multiplied by the 1727 /// y-coordinate of the rotation point. 1728 double get ty => _value[3]; 1729} 1730