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