1 /* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License 11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). 12 13 End User License Agreement: www.juce.com/juce-6-licence 14 Privacy Policy: www.juce.com/juce-privacy-policy 15 16 Or: You may also use this code under the terms of the GPL v3 (see 17 www.gnu.org/licenses). 18 19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 21 DISCLAIMED. 22 23 ============================================================================== 24 */ 25 26 namespace juce 27 { 28 29 //============================================================================== 30 /** 31 Manages a rectangle and allows geometric operations to be performed on it. 32 33 @see RectangleList, Path, Line, Point 34 35 @tags{Graphics} 36 */ 37 template <typename ValueType> 38 class Rectangle 39 { 40 public: 41 //============================================================================== 42 /** Creates a rectangle of zero size. 43 The default coordinates will be (0, 0, 0, 0). 44 */ 45 Rectangle() = default; 46 47 /** Creates a copy of another rectangle. */ 48 Rectangle (const Rectangle&) = default; 49 50 /** Creates a rectangle with a given position and size. */ Rectangle(ValueType initialX,ValueType initialY,ValueType width,ValueType height)51 Rectangle (ValueType initialX, ValueType initialY, 52 ValueType width, ValueType height) noexcept 53 : pos (initialX, initialY), 54 w (width), h (height) 55 { 56 } 57 58 /** Creates a rectangle with a given size, and a position of (0, 0). */ Rectangle(ValueType width,ValueType height)59 Rectangle (ValueType width, ValueType height) noexcept 60 : w (width), h (height) 61 { 62 } 63 64 /** Creates a Rectangle from the positions of two opposite corners. */ Rectangle(Point<ValueType> corner1,Point<ValueType> corner2)65 Rectangle (Point<ValueType> corner1, Point<ValueType> corner2) noexcept 66 : pos (jmin (corner1.x, corner2.x), 67 jmin (corner1.y, corner2.y)), 68 w (corner1.x - corner2.x), 69 h (corner1.y - corner2.y) 70 { 71 if (w < ValueType()) w = -w; 72 if (h < ValueType()) h = -h; 73 } 74 75 /** Creates a Rectangle from a set of left, right, top, bottom coordinates. 76 The right and bottom values must be larger than the left and top ones, or the resulting 77 rectangle will have a negative size. 78 */ leftTopRightBottom(ValueType left,ValueType top,ValueType right,ValueType bottom)79 static Rectangle leftTopRightBottom (ValueType left, ValueType top, 80 ValueType right, ValueType bottom) noexcept 81 { 82 return { left, top, right - left, bottom - top }; 83 } 84 85 /** Creates a copy of another rectangle. */ 86 Rectangle& operator= (const Rectangle&) = default; 87 88 /** Destructor. */ 89 ~Rectangle() = default; 90 91 //============================================================================== 92 /** Returns true if the rectangle's width or height are zero or less */ isEmpty()93 bool isEmpty() const noexcept { return w <= ValueType() || h <= ValueType(); } 94 95 /** Returns true if the rectangle's values are all finite numbers, i.e. not NaN or infinity. */ isFinite()96 inline bool isFinite() const noexcept { return pos.isFinite() && juce_isfinite (w) && juce_isfinite (h); } 97 98 /** Returns the x coordinate of the rectangle's left-hand-side. */ getX()99 inline ValueType getX() const noexcept { return pos.x; } 100 101 /** Returns the y coordinate of the rectangle's top edge. */ getY()102 inline ValueType getY() const noexcept { return pos.y; } 103 104 /** Returns the width of the rectangle. */ getWidth()105 inline ValueType getWidth() const noexcept { return w; } 106 107 /** Returns the height of the rectangle. */ getHeight()108 inline ValueType getHeight() const noexcept { return h; } 109 110 /** Returns the x coordinate of the rectangle's right-hand-side. */ getRight()111 inline ValueType getRight() const noexcept { return pos.x + w; } 112 113 /** Returns the y coordinate of the rectangle's bottom edge. */ getBottom()114 inline ValueType getBottom() const noexcept { return pos.y + h; } 115 116 /** Returns the x coordinate of the rectangle's centre. */ getCentreX()117 ValueType getCentreX() const noexcept { return pos.x + w / (ValueType) 2; } 118 119 /** Returns the y coordinate of the rectangle's centre. */ getCentreY()120 ValueType getCentreY() const noexcept { return pos.y + h / (ValueType) 2; } 121 122 /** Returns the centre point of the rectangle. */ getCentre()123 Point<ValueType> getCentre() const noexcept { return { pos.x + w / (ValueType) 2, 124 pos.y + h / (ValueType) 2 }; } 125 126 /** Returns the aspect ratio of the rectangle's width / height. 127 If widthOverHeight is true, it returns width / height; if widthOverHeight is false, 128 it returns height / width. */ 129 ValueType getAspectRatio (bool widthOverHeight = true) const noexcept { return widthOverHeight ? w / h : h / w; } 130 131 //============================================================================== 132 /** Returns the rectangle's top-left position as a Point. */ getPosition()133 inline Point<ValueType> getPosition() const noexcept { return pos; } 134 135 /** Changes the position of the rectangle's top-left corner (leaving its size unchanged). */ setPosition(Point<ValueType> newPos)136 inline void setPosition (Point<ValueType> newPos) noexcept { pos = newPos; } 137 138 /** Changes the position of the rectangle's top-left corner (leaving its size unchanged). */ setPosition(ValueType newX,ValueType newY)139 inline void setPosition (ValueType newX, ValueType newY) noexcept { pos.setXY (newX, newY); } 140 141 /** Returns the rectangle's top-left position as a Point. */ getTopLeft()142 Point<ValueType> getTopLeft() const noexcept { return pos; } 143 144 /** Returns the rectangle's top-right position as a Point. */ getTopRight()145 Point<ValueType> getTopRight() const noexcept { return { pos.x + w, pos.y }; } 146 147 /** Returns the rectangle's bottom-left position as a Point. */ getBottomLeft()148 Point<ValueType> getBottomLeft() const noexcept { return { pos.x, pos.y + h }; } 149 150 /** Returns the rectangle's bottom-right position as a Point. */ getBottomRight()151 Point<ValueType> getBottomRight() const noexcept { return { pos.x + w, pos.y + h }; } 152 153 /** Returns the rectangle's left and right positions as a Range. */ getHorizontalRange()154 Range<ValueType> getHorizontalRange() const noexcept { return Range<ValueType>::withStartAndLength (pos.x, w); } 155 156 /** Returns the rectangle's top and bottom positions as a Range. */ getVerticalRange()157 Range<ValueType> getVerticalRange() const noexcept { return Range<ValueType>::withStartAndLength (pos.y, h); } 158 159 /** Changes the rectangle's size, leaving the position of its top-left corner unchanged. */ setSize(ValueType newWidth,ValueType newHeight)160 void setSize (ValueType newWidth, ValueType newHeight) noexcept { w = newWidth; h = newHeight; } 161 162 /** Changes all the rectangle's coordinates. */ setBounds(ValueType newX,ValueType newY,ValueType newWidth,ValueType newHeight)163 void setBounds (ValueType newX, ValueType newY, 164 ValueType newWidth, ValueType newHeight) noexcept { pos.x = newX; pos.y = newY; w = newWidth; h = newHeight; } 165 166 /** Changes the rectangle's X coordinate */ setX(ValueType newX)167 inline void setX (ValueType newX) noexcept { pos.x = newX; } 168 169 /** Changes the rectangle's Y coordinate */ setY(ValueType newY)170 inline void setY (ValueType newY) noexcept { pos.y = newY; } 171 172 /** Changes the rectangle's width */ setWidth(ValueType newWidth)173 inline void setWidth (ValueType newWidth) noexcept { w = newWidth; } 174 175 /** Changes the rectangle's height */ setHeight(ValueType newHeight)176 inline void setHeight (ValueType newHeight) noexcept { h = newHeight; } 177 178 /** Changes the position of the rectangle's centre (leaving its size unchanged). */ setCentre(ValueType newCentreX,ValueType newCentreY)179 inline void setCentre (ValueType newCentreX, ValueType newCentreY) noexcept { pos.x = newCentreX - w / (ValueType) 2; 180 pos.y = newCentreY - h / (ValueType) 2; } 181 182 /** Changes the position of the rectangle's centre (leaving its size unchanged). */ setCentre(Point<ValueType> newCentre)183 inline void setCentre (Point<ValueType> newCentre) noexcept { setCentre (newCentre.x, newCentre.y); } 184 185 /** Changes the position of the rectangle's left and right edges. */ setHorizontalRange(Range<ValueType> range)186 void setHorizontalRange (Range<ValueType> range) noexcept { pos.x = range.getStart(); w = range.getLength(); } 187 188 /** Changes the position of the rectangle's top and bottom edges. */ setVerticalRange(Range<ValueType> range)189 void setVerticalRange (Range<ValueType> range) noexcept { pos.y = range.getStart(); h = range.getLength(); } 190 191 /** Returns a rectangle which has the same size and y-position as this one, but with a different x-position. */ withX(ValueType newX)192 Rectangle withX (ValueType newX) const noexcept { return { newX, pos.y, w, h }; } 193 194 /** Returns a rectangle which has the same size and x-position as this one, but with a different y-position. */ withY(ValueType newY)195 Rectangle withY (ValueType newY) const noexcept { return { pos.x, newY, w, h }; } 196 197 /** Returns a rectangle which has the same size and y-position as this one, but whose right-hand edge has the given position. */ withRightX(ValueType newRightX)198 Rectangle withRightX (ValueType newRightX) const noexcept { return { newRightX - w, pos.y, w, h }; } 199 200 /** Returns a rectangle which has the same size and x-position as this one, but whose bottom edge has the given position. */ withBottomY(ValueType newBottomY)201 Rectangle withBottomY (ValueType newBottomY) const noexcept { return { pos.x, newBottomY - h, w, h }; } 202 203 /** Returns a rectangle with the same size as this one, but a new position. */ withPosition(ValueType newX,ValueType newY)204 Rectangle withPosition (ValueType newX, ValueType newY) const noexcept { return { newX, newY, w, h }; } 205 206 /** Returns a rectangle with the same size as this one, but a new position. */ withPosition(Point<ValueType> newPos)207 Rectangle withPosition (Point<ValueType> newPos) const noexcept { return { newPos.x, newPos.y, w, h }; } 208 209 /** Returns a rectangle whose size is the same as this one, but whose top-left position is (0, 0). */ withZeroOrigin()210 Rectangle withZeroOrigin() const noexcept { return { w, h }; } 211 212 /** Returns a rectangle with the same size as this one, but a new centre position. */ withCentre(Point<ValueType> newCentre)213 Rectangle withCentre (Point<ValueType> newCentre) const noexcept { return { newCentre.x - w / (ValueType) 2, 214 newCentre.y - h / (ValueType) 2, w, h }; } 215 216 /** Returns a rectangle which has the same position and height as this one, but with a different width. */ withWidth(ValueType newWidth)217 Rectangle withWidth (ValueType newWidth) const noexcept { return { pos.x, pos.y, jmax (ValueType(), newWidth), h }; } 218 219 /** Returns a rectangle which has the same position and width as this one, but with a different height. */ withHeight(ValueType newHeight)220 Rectangle withHeight (ValueType newHeight) const noexcept { return { pos.x, pos.y, w, jmax (ValueType(), newHeight) }; } 221 222 /** Returns a rectangle with the same top-left position as this one, but a new size. */ withSize(ValueType newWidth,ValueType newHeight)223 Rectangle withSize (ValueType newWidth, ValueType newHeight) const noexcept { return { pos.x, pos.y, jmax (ValueType(), newWidth), jmax (ValueType(), newHeight) }; } 224 225 /** Returns a rectangle with the same centre position as this one, but a new size. */ withSizeKeepingCentre(ValueType newWidth,ValueType newHeight)226 Rectangle withSizeKeepingCentre (ValueType newWidth, ValueType newHeight) const noexcept { return { pos.x + (w - newWidth) / (ValueType) 2, 227 pos.y + (h - newHeight) / (ValueType) 2, newWidth, newHeight }; } 228 229 /** Moves the x position, adjusting the width so that the right-hand edge remains in the same place. 230 If the x is moved to be on the right of the current right-hand edge, the width will be set to zero. 231 @see withLeft 232 */ setLeft(ValueType newLeft)233 void setLeft (ValueType newLeft) noexcept { w = jmax (ValueType(), pos.x + w - newLeft); pos.x = newLeft; } 234 235 /** Returns a new rectangle with a different x position, but the same right-hand edge as this one. 236 If the new x is beyond the right of the current right-hand edge, the width will be set to zero. 237 @see setLeft 238 */ withLeft(ValueType newLeft)239 Rectangle withLeft (ValueType newLeft) const noexcept { return { newLeft, pos.y, jmax (ValueType(), pos.x + w - newLeft), h }; } 240 241 /** Moves the y position, adjusting the height so that the bottom edge remains in the same place. 242 If the y is moved to be below the current bottom edge, the height will be set to zero. 243 @see withTop 244 */ setTop(ValueType newTop)245 void setTop (ValueType newTop) noexcept { h = jmax (ValueType(), pos.y + h - newTop); pos.y = newTop; } 246 247 /** Returns a new rectangle with a different y position, but the same bottom edge as this one. 248 If the new y is beyond the bottom of the current rectangle, the height will be set to zero. 249 @see setTop 250 */ withTop(ValueType newTop)251 Rectangle withTop (ValueType newTop) const noexcept { return { pos.x, newTop, w, jmax (ValueType(), pos.y + h - newTop) }; } 252 253 /** Adjusts the width so that the right-hand edge of the rectangle has this new value. 254 If the new right is below the current X value, the X will be pushed down to match it. 255 @see getRight, withRight 256 */ setRight(ValueType newRight)257 void setRight (ValueType newRight) noexcept { pos.x = jmin (pos.x, newRight); w = newRight - pos.x; } 258 259 /** Returns a new rectangle with a different right-hand edge position, but the same left-hand edge as this one. 260 If the new right edge is below the current left-hand edge, the width will be set to zero. 261 @see setRight 262 */ withRight(ValueType newRight)263 Rectangle withRight (ValueType newRight) const noexcept { return { jmin (pos.x, newRight), pos.y, jmax (ValueType(), newRight - pos.x), h }; } 264 265 /** Adjusts the height so that the bottom edge of the rectangle has this new value. 266 If the new bottom is lower than the current Y value, the Y will be pushed down to match it. 267 @see getBottom, withBottom 268 */ setBottom(ValueType newBottom)269 void setBottom (ValueType newBottom) noexcept { pos.y = jmin (pos.y, newBottom); h = newBottom - pos.y; } 270 271 /** Returns a new rectangle with a different bottom edge position, but the same top edge as this one. 272 If the new y is beyond the bottom of the current rectangle, the height will be set to zero. 273 @see setBottom 274 */ withBottom(ValueType newBottom)275 Rectangle withBottom (ValueType newBottom) const noexcept { return { pos.x, jmin (pos.y, newBottom), w, jmax (ValueType(), newBottom - pos.y) }; } 276 277 /** Returns a version of this rectangle with the given amount removed from its left-hand edge. */ withTrimmedLeft(ValueType amountToRemove)278 Rectangle withTrimmedLeft (ValueType amountToRemove) const noexcept { return withLeft (pos.x + amountToRemove); } 279 280 /** Returns a version of this rectangle with the given amount removed from its right-hand edge. */ withTrimmedRight(ValueType amountToRemove)281 Rectangle withTrimmedRight (ValueType amountToRemove) const noexcept { return withWidth (w - amountToRemove); } 282 283 /** Returns a version of this rectangle with the given amount removed from its top edge. */ withTrimmedTop(ValueType amountToRemove)284 Rectangle withTrimmedTop (ValueType amountToRemove) const noexcept { return withTop (pos.y + amountToRemove); } 285 286 /** Returns a version of this rectangle with the given amount removed from its bottom edge. */ withTrimmedBottom(ValueType amountToRemove)287 Rectangle withTrimmedBottom (ValueType amountToRemove) const noexcept { return withHeight (h - amountToRemove); } 288 289 //============================================================================== 290 /** Moves the rectangle's position by adding amount to its x and y coordinates. */ translate(ValueType deltaX,ValueType deltaY)291 void translate (ValueType deltaX, 292 ValueType deltaY) noexcept 293 { 294 pos.x += deltaX; 295 pos.y += deltaY; 296 } 297 298 /** Returns a rectangle which is the same as this one moved by a given amount. */ translated(ValueType deltaX,ValueType deltaY)299 Rectangle translated (ValueType deltaX, 300 ValueType deltaY) const noexcept 301 { 302 return { pos.x + deltaX, pos.y + deltaY, w, h }; 303 } 304 305 /** Returns a rectangle which is the same as this one moved by a given amount. */ 306 Rectangle operator+ (Point<ValueType> deltaPosition) const noexcept 307 { 308 return { pos.x + deltaPosition.x, pos.y + deltaPosition.y, w, h }; 309 } 310 311 /** Moves this rectangle by a given amount. */ 312 Rectangle& operator+= (Point<ValueType> deltaPosition) noexcept 313 { 314 pos += deltaPosition; 315 return *this; 316 } 317 318 /** Returns a rectangle which is the same as this one moved by a given amount. */ 319 Rectangle operator- (Point<ValueType> deltaPosition) const noexcept 320 { 321 return { pos.x - deltaPosition.x, pos.y - deltaPosition.y, w, h }; 322 } 323 324 /** Moves this rectangle by a given amount. */ 325 Rectangle& operator-= (Point<ValueType> deltaPosition) noexcept 326 { 327 pos -= deltaPosition; 328 return *this; 329 } 330 331 /** Returns a rectangle that has been scaled by the given amount, centred around the origin. 332 Note that if the rectangle has int coordinates and it's scaled by a 333 floating-point amount, then the result will be converted back to integer 334 coordinates using getSmallestIntegerContainer(). 335 */ 336 template <typename FloatType> 337 Rectangle operator* (FloatType scaleFactor) const noexcept 338 { 339 Rectangle r (*this); 340 r *= scaleFactor; 341 return r; 342 } 343 344 /** Scales this rectangle by the given amount, centred around the origin. 345 Note that if the rectangle has int coordinates and it's scaled by a 346 floating-point amount, then the result will be converted back to integer 347 coordinates using getSmallestIntegerContainer(). 348 */ 349 template <typename FloatType> 350 Rectangle operator*= (FloatType scaleFactor) noexcept 351 { 352 Rectangle<FloatType> ((FloatType) pos.x * scaleFactor, 353 (FloatType) pos.y * scaleFactor, 354 (FloatType) w * scaleFactor, 355 (FloatType) h * scaleFactor).copyWithRounding (*this); 356 return *this; 357 } 358 359 /** Scales this rectangle by the given X and Y factors, centred around the origin. 360 Note that if the rectangle has int coordinates and it's scaled by a 361 floating-point amount, then the result will be converted back to integer 362 coordinates using getSmallestIntegerContainer(). 363 */ 364 template <typename FloatType> 365 Rectangle operator*= (Point<FloatType> scaleFactor) noexcept 366 { 367 Rectangle<FloatType> ((FloatType) pos.x * scaleFactor.x, 368 (FloatType) pos.y * scaleFactor.y, 369 (FloatType) w * scaleFactor.x, 370 (FloatType) h * scaleFactor.y).copyWithRounding (*this); 371 return *this; 372 } 373 374 /** Scales this rectangle by the given amount, centred around the origin. */ 375 template <typename FloatType> 376 Rectangle operator/ (FloatType scaleFactor) const noexcept 377 { 378 Rectangle r (*this); 379 r /= scaleFactor; 380 return r; 381 } 382 383 /** Scales this rectangle by the given amount, centred around the origin. */ 384 template <typename FloatType> 385 Rectangle operator/= (FloatType scaleFactor) noexcept 386 { 387 Rectangle<FloatType> ((FloatType) pos.x / scaleFactor, 388 (FloatType) pos.y / scaleFactor, 389 (FloatType) w / scaleFactor, 390 (FloatType) h / scaleFactor).copyWithRounding (*this); 391 return *this; 392 } 393 394 /** Scales this rectangle by the given X and Y factors, centred around the origin. */ 395 template <typename FloatType> 396 Rectangle operator/= (Point<FloatType> scaleFactor) noexcept 397 { 398 Rectangle<FloatType> ((FloatType) pos.x / scaleFactor.x, 399 (FloatType) pos.y / scaleFactor.y, 400 (FloatType) w / scaleFactor.x, 401 (FloatType) h / scaleFactor.y).copyWithRounding (*this); 402 return *this; 403 } 404 405 /** Expands the rectangle by a given amount. 406 407 Effectively, its new size is (x - deltaX, y - deltaY, w + deltaX * 2, h + deltaY * 2). 408 @see expanded, reduce, reduced 409 */ expand(ValueType deltaX,ValueType deltaY)410 void expand (ValueType deltaX, 411 ValueType deltaY) noexcept 412 { 413 auto nw = jmax (ValueType(), w + deltaX * 2); 414 auto nh = jmax (ValueType(), h + deltaY * 2); 415 setBounds (pos.x - deltaX, pos.y - deltaY, nw, nh); 416 } 417 418 /** Returns a rectangle that is larger than this one by a given amount. 419 420 Effectively, the rectangle returned is (x - deltaX, y - deltaY, w + deltaX * 2, h + deltaY * 2). 421 @see expand, reduce, reduced 422 */ expanded(ValueType deltaX,ValueType deltaY)423 Rectangle expanded (ValueType deltaX, 424 ValueType deltaY) const noexcept 425 { 426 auto nw = jmax (ValueType(), w + deltaX * 2); 427 auto nh = jmax (ValueType(), h + deltaY * 2); 428 return { pos.x - deltaX, pos.y - deltaY, nw, nh }; 429 } 430 431 /** Returns a rectangle that is larger than this one by a given amount. 432 433 Effectively, the rectangle returned is (x - delta, y - delta, w + delta * 2, h + delta * 2). 434 @see expand, reduce, reduced 435 */ expanded(ValueType delta)436 Rectangle expanded (ValueType delta) const noexcept 437 { 438 return expanded (delta, delta); 439 } 440 441 /** Shrinks the rectangle by a given amount. 442 443 Effectively, its new size is (x + deltaX, y + deltaY, w - deltaX * 2, h - deltaY * 2). 444 @see reduced, expand, expanded 445 */ reduce(ValueType deltaX,ValueType deltaY)446 void reduce (ValueType deltaX, 447 ValueType deltaY) noexcept 448 { 449 expand (-deltaX, -deltaY); 450 } 451 452 /** Returns a rectangle that is smaller than this one by a given amount. 453 454 Effectively, the rectangle returned is (x + deltaX, y + deltaY, w - deltaX * 2, h - deltaY * 2). 455 @see reduce, expand, expanded 456 */ reduced(ValueType deltaX,ValueType deltaY)457 Rectangle reduced (ValueType deltaX, 458 ValueType deltaY) const noexcept 459 { 460 return expanded (-deltaX, -deltaY); 461 } 462 463 /** Returns a rectangle that is smaller than this one by a given amount. 464 465 Effectively, the rectangle returned is (x + delta, y + delta, w - delta * 2, h - delta * 2). 466 @see reduce, expand, expanded 467 */ reduced(ValueType delta)468 Rectangle reduced (ValueType delta) const noexcept 469 { 470 return reduced (delta, delta); 471 } 472 473 /** Removes a strip from the top of this rectangle, reducing this rectangle 474 by the specified amount and returning the section that was removed. 475 476 E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will 477 return (100, 100, 300, 50) and leave this rectangle as (100, 150, 300, 250). 478 479 If amountToRemove is greater than the height of this rectangle, it'll be clipped to 480 that value. 481 */ removeFromTop(ValueType amountToRemove)482 Rectangle removeFromTop (ValueType amountToRemove) noexcept 483 { 484 const Rectangle r (pos.x, pos.y, w, jmin (amountToRemove, h)); 485 pos.y += r.h; h -= r.h; 486 return r; 487 } 488 489 /** Removes a strip from the left-hand edge of this rectangle, reducing this rectangle 490 by the specified amount and returning the section that was removed. 491 492 E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will 493 return (100, 100, 50, 300) and leave this rectangle as (150, 100, 250, 300). 494 495 If amountToRemove is greater than the width of this rectangle, it'll be clipped to 496 that value. 497 */ removeFromLeft(ValueType amountToRemove)498 Rectangle removeFromLeft (ValueType amountToRemove) noexcept 499 { 500 const Rectangle r (pos.x, pos.y, jmin (amountToRemove, w), h); 501 pos.x += r.w; w -= r.w; 502 return r; 503 } 504 505 /** Removes a strip from the right-hand edge of this rectangle, reducing this rectangle 506 by the specified amount and returning the section that was removed. 507 508 E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will 509 return (350, 100, 50, 300) and leave this rectangle as (100, 100, 250, 300). 510 511 If amountToRemove is greater than the width of this rectangle, it'll be clipped to 512 that value. 513 */ removeFromRight(ValueType amountToRemove)514 Rectangle removeFromRight (ValueType amountToRemove) noexcept 515 { 516 amountToRemove = jmin (amountToRemove, w); 517 const Rectangle r (pos.x + w - amountToRemove, pos.y, amountToRemove, h); 518 w -= amountToRemove; 519 return r; 520 } 521 522 /** Removes a strip from the bottom of this rectangle, reducing this rectangle 523 by the specified amount and returning the section that was removed. 524 525 E.g. if this rectangle is (100, 100, 300, 300) and amountToRemove is 50, this will 526 return (100, 350, 300, 50) and leave this rectangle as (100, 100, 300, 250). 527 528 If amountToRemove is greater than the height of this rectangle, it'll be clipped to 529 that value. 530 */ removeFromBottom(ValueType amountToRemove)531 Rectangle removeFromBottom (ValueType amountToRemove) noexcept 532 { 533 amountToRemove = jmin (amountToRemove, h); 534 const Rectangle r (pos.x, pos.y + h - amountToRemove, w, amountToRemove); 535 h -= amountToRemove; 536 return r; 537 } 538 539 //============================================================================== 540 /** Returns the nearest point to the specified point that lies within this rectangle. */ getConstrainedPoint(Point<ValueType> point)541 Point<ValueType> getConstrainedPoint (Point<ValueType> point) const noexcept 542 { 543 return { jlimit (pos.x, pos.x + w, point.x), 544 jlimit (pos.y, pos.y + h, point.y) }; 545 } 546 547 /** Returns a point within this rectangle, specified as proportional coordinates. 548 The relative X and Y values should be between 0 and 1, where 0 is the left or 549 top of this rectangle, and 1 is the right or bottom. (Out-of-bounds values 550 will return a point outside the rectangle). 551 */ 552 template <typename FloatType> getRelativePoint(FloatType relativeX,FloatType relativeY)553 Point<ValueType> getRelativePoint (FloatType relativeX, FloatType relativeY) const noexcept 554 { 555 return { pos.x + static_cast<ValueType> ((FloatType) w * relativeX), 556 pos.y + static_cast<ValueType> ((FloatType) h * relativeY) }; 557 } 558 559 /** Returns a proportion of the width of this rectangle. */ 560 template <typename FloatType> proportionOfWidth(FloatType proportion)561 ValueType proportionOfWidth (FloatType proportion) const noexcept 562 { 563 return static_cast<ValueType> ((FloatType) w * proportion); 564 } 565 566 /** Returns a proportion of the height of this rectangle. */ 567 template <typename FloatType> proportionOfHeight(FloatType proportion)568 ValueType proportionOfHeight (FloatType proportion) const noexcept 569 { 570 return static_cast<ValueType> ((FloatType) h * proportion); 571 } 572 573 /** Returns a rectangle based on some proportional coordinates relative to this one. 574 So for example getProportion ({ 0.25f, 0.25f, 0.5f, 0.5f }) would return a rectangle 575 of half the original size, with the same centre. 576 */ 577 template <typename FloatType> getProportion(Rectangle<FloatType> proportionalRect)578 Rectangle getProportion (Rectangle<FloatType> proportionalRect) const noexcept 579 { 580 return { pos.x + static_cast<ValueType> (w * proportionalRect.pos.x), 581 pos.y + static_cast<ValueType> (h * proportionalRect.pos.y), 582 proportionOfWidth (proportionalRect.w), 583 proportionOfHeight (proportionalRect.h) }; 584 } 585 586 //============================================================================== 587 /** Returns true if the two rectangles are identical. */ 588 bool operator== (const Rectangle& other) const noexcept { return pos == other.pos && w == other.w && h == other.h; } 589 590 /** Returns true if the two rectangles are not identical. */ 591 bool operator!= (const Rectangle& other) const noexcept { return pos != other.pos || w != other.w || h != other.h; } 592 593 /** Returns true if this coordinate is inside the rectangle. */ contains(ValueType xCoord,ValueType yCoord)594 bool contains (ValueType xCoord, ValueType yCoord) const noexcept 595 { 596 return xCoord >= pos.x && yCoord >= pos.y && xCoord < pos.x + w && yCoord < pos.y + h; 597 } 598 599 /** Returns true if this coordinate is inside the rectangle. */ contains(Point<ValueType> point)600 bool contains (Point<ValueType> point) const noexcept 601 { 602 return point.x >= pos.x && point.y >= pos.y && point.x < pos.x + w && point.y < pos.y + h; 603 } 604 605 /** Returns true if this other rectangle is completely inside this one. */ contains(Rectangle other)606 bool contains (Rectangle other) const noexcept 607 { 608 return pos.x <= other.pos.x && pos.y <= other.pos.y 609 && pos.x + w >= other.pos.x + other.w && pos.y + h >= other.pos.y + other.h; 610 } 611 612 /** Returns true if any part of another rectangle overlaps this one. */ intersects(Rectangle other)613 bool intersects (Rectangle other) const noexcept 614 { 615 return pos.x + w > other.pos.x 616 && pos.y + h > other.pos.y 617 && pos.x < other.pos.x + other.w 618 && pos.y < other.pos.y + other.h 619 && w > ValueType() && h > ValueType() 620 && other.w > ValueType() && other.h > ValueType(); 621 } 622 623 /** Returns true if any part of the given line lies inside this rectangle. */ intersects(const Line<ValueType> & line)624 bool intersects (const Line<ValueType>& line) const noexcept 625 { 626 return contains (line.getStart()) || contains (line.getEnd()) 627 || line.intersects (Line<ValueType> (getTopLeft(), getTopRight())) 628 || line.intersects (Line<ValueType> (getTopRight(), getBottomRight())) 629 || line.intersects (Line<ValueType> (getBottomRight(), getBottomLeft())) 630 || line.intersects (Line<ValueType> (getBottomLeft(), getTopLeft())); 631 } 632 633 /** Returns the region that is the overlap between this and another rectangle. 634 If the two rectangles don't overlap, the rectangle returned will be empty. 635 */ getIntersection(Rectangle other)636 Rectangle getIntersection (Rectangle other) const noexcept 637 { 638 auto nx = jmax (pos.x, other.pos.x); 639 auto ny = jmax (pos.y, other.pos.y); 640 auto nw = jmin (pos.x + w, other.pos.x + other.w) - nx; 641 642 if (nw >= ValueType()) 643 { 644 auto nh = jmin (pos.y + h, other.pos.y + other.h) - ny; 645 646 if (nh >= ValueType()) 647 return { nx, ny, nw, nh }; 648 } 649 650 return {}; 651 } 652 653 /** Clips a set of rectangle coordinates so that they lie only within this one. 654 This is a non-static version of intersectRectangles(). 655 Returns false if the two rectangles didn't overlap. 656 */ intersectRectangle(ValueType & otherX,ValueType & otherY,ValueType & otherW,ValueType & otherH)657 bool intersectRectangle (ValueType& otherX, ValueType& otherY, ValueType& otherW, ValueType& otherH) const noexcept 658 { 659 auto maxX = jmax (otherX, pos.x); 660 otherW = jmin (otherX + otherW, pos.x + w) - maxX; 661 662 if (otherW > ValueType()) 663 { 664 auto maxY = jmax (otherY, pos.y); 665 otherH = jmin (otherY + otherH, pos.y + h) - maxY; 666 667 if (otherH > ValueType()) 668 { 669 otherX = maxX; otherY = maxY; 670 return true; 671 } 672 } 673 674 return false; 675 } 676 677 /** Clips a rectangle so that it lies only within this one. 678 Returns false if the two rectangles didn't overlap. 679 */ intersectRectangle(Rectangle<ValueType> & rectangleToClip)680 bool intersectRectangle (Rectangle<ValueType>& rectangleToClip) const noexcept 681 { 682 return intersectRectangle (rectangleToClip.pos.x, rectangleToClip.pos.y, 683 rectangleToClip.w, rectangleToClip.h); 684 } 685 686 /** Returns the smallest rectangle that contains both this one and the one passed-in. 687 688 If either this or the other rectangle are empty, they will not be counted as 689 part of the resulting region. 690 */ getUnion(Rectangle other)691 Rectangle getUnion (Rectangle other) const noexcept 692 { 693 if (other.isEmpty()) return *this; 694 if (isEmpty()) return other; 695 696 auto newX = jmin (pos.x, other.pos.x); 697 auto newY = jmin (pos.y, other.pos.y); 698 699 return { newX, newY, 700 jmax (pos.x + w, other.pos.x + other.w) - newX, 701 jmax (pos.y + h, other.pos.y + other.h) - newY }; 702 } 703 704 /** If this rectangle merged with another one results in a simple rectangle, this 705 will set this rectangle to the result, and return true. 706 707 Returns false and does nothing to this rectangle if the two rectangles don't overlap, 708 or if they form a complex region. 709 */ enlargeIfAdjacent(Rectangle other)710 bool enlargeIfAdjacent (Rectangle other) noexcept 711 { 712 if (pos.x == other.pos.x && getRight() == other.getRight() 713 && (other.getBottom() >= pos.y && other.pos.y <= getBottom())) 714 { 715 auto newY = jmin (pos.y, other.pos.y); 716 h = jmax (getBottom(), other.getBottom()) - newY; 717 pos.y = newY; 718 return true; 719 } 720 721 if (pos.y == other.pos.y && getBottom() == other.getBottom() 722 && (other.getRight() >= pos.x && other.pos.x <= getRight())) 723 { 724 auto newX = jmin (pos.x, other.pos.x); 725 w = jmax (getRight(), other.getRight()) - newX; 726 pos.x = newX; 727 return true; 728 } 729 730 return false; 731 } 732 733 /** If after removing another rectangle from this one the result is a simple rectangle, 734 this will set this object's bounds to be the result, and return true. 735 736 Returns false and does nothing to this rectangle if the two rectangles don't overlap, 737 or if removing the other one would form a complex region. 738 */ reduceIfPartlyContainedIn(Rectangle other)739 bool reduceIfPartlyContainedIn (Rectangle other) noexcept 740 { 741 int inside = 0; 742 auto otherR = other.getRight(); 743 if (pos.x >= other.pos.x && pos.x < otherR) inside = 1; 744 auto otherB = other.getBottom(); 745 if (pos.y >= other.pos.y && pos.y < otherB) inside |= 2; 746 auto r = pos.x + w; 747 if (r >= other.pos.x && r < otherR) inside |= 4; 748 auto b = pos.y + h; 749 if (b >= other.pos.y && b < otherB) inside |= 8; 750 751 switch (inside) 752 { 753 case 1 + 2 + 8: w = r - otherR; pos.x = otherR; return true; 754 case 1 + 2 + 4: h = b - otherB; pos.y = otherB; return true; 755 case 2 + 4 + 8: w = other.pos.x - pos.x; return true; 756 case 1 + 4 + 8: h = other.pos.y - pos.y; return true; 757 default: break; 758 } 759 760 return false; 761 } 762 763 /** Tries to fit this rectangle within a target area, returning the result. 764 765 If this rectangle is not completely inside the target area, then it'll be 766 shifted (without changing its size) so that it lies within the target. If it 767 is larger than the target rectangle in either dimension, then that dimension 768 will be reduced to fit within the target. 769 */ constrainedWithin(Rectangle areaToFitWithin)770 Rectangle constrainedWithin (Rectangle areaToFitWithin) const noexcept 771 { 772 auto newPos = areaToFitWithin.withSize (areaToFitWithin.getWidth() - w, 773 areaToFitWithin.getHeight() - h) 774 .getConstrainedPoint (pos); 775 776 return { newPos.x, newPos.y, 777 jmin (w, areaToFitWithin.getWidth()), 778 jmin (h, areaToFitWithin.getHeight()) }; 779 } 780 781 /** Returns the smallest rectangle that can contain the shape created by applying 782 a transform to this rectangle. 783 784 This should only be used on floating point rectangles. 785 */ transformedBy(const AffineTransform & transform)786 Rectangle transformedBy (const AffineTransform& transform) const noexcept 787 { 788 using FloatType = typename TypeHelpers::SmallestFloatType<ValueType>::type; 789 790 auto x1 = static_cast<FloatType> (pos.x), y1 = static_cast<FloatType> (pos.y); 791 auto x2 = static_cast<FloatType> (pos.x + w), y2 = static_cast<FloatType> (pos.y); 792 auto x3 = static_cast<FloatType> (pos.x), y3 = static_cast<FloatType> (pos.y + h); 793 auto x4 = static_cast<FloatType> (x2), y4 = static_cast<FloatType> (y3); 794 795 transform.transformPoints (x1, y1, x2, y2); 796 transform.transformPoints (x3, y3, x4, y4); 797 798 auto rx1 = jmin (x1, x2, x3, x4); 799 auto rx2 = jmax (x1, x2, x3, x4); 800 auto ry1 = jmin (y1, y2, y3, y4); 801 auto ry2 = jmax (y1, y2, y3, y4); 802 803 Rectangle r; 804 Rectangle<FloatType> (rx1, ry1, rx2 - rx1, ry2 - ry1).copyWithRounding (r); 805 return r; 806 } 807 808 /** Returns the smallest integer-aligned rectangle that completely contains this one. 809 This is only relevant for floating-point rectangles, of course. 810 @see toFloat(), toNearestInt(), toNearestIntEdges() 811 */ getSmallestIntegerContainer()812 Rectangle<int> getSmallestIntegerContainer() const noexcept 813 { 814 return Rectangle<int>::leftTopRightBottom (floorAsInt (pos.x), 815 floorAsInt (pos.y), 816 ceilAsInt (pos.x + w), 817 ceilAsInt (pos.y + h)); 818 } 819 820 /** Casts this rectangle to a Rectangle<int>. 821 This uses roundToInt to snap x, y, width and height to the nearest integer (losing precision). 822 If the rectangle already uses integers, this will simply return a copy. 823 @see getSmallestIntegerContainer(), toNearestIntEdges() 824 */ toNearestInt()825 Rectangle<int> toNearestInt() const noexcept 826 { 827 return { roundToInt (pos.x), roundToInt (pos.y), 828 roundToInt (w), roundToInt (h) }; 829 } 830 831 /** Casts this rectangle to a Rectangle<int>. 832 This uses roundToInt to snap top, left, right and bottom to the nearest integer (losing precision). 833 If the rectangle already uses integers, this will simply return a copy. 834 @see getSmallestIntegerContainer(), toNearestInt() 835 */ toNearestIntEdges()836 Rectangle<int> toNearestIntEdges() const noexcept 837 { 838 return Rectangle<int>::leftTopRightBottom (roundToInt (pos.x), roundToInt (pos.y), 839 roundToInt (getRight()), roundToInt (getBottom())); 840 } 841 842 /** Casts this rectangle to a Rectangle<float>. 843 @see getSmallestIntegerContainer 844 */ toFloat()845 Rectangle<float> toFloat() const noexcept 846 { 847 return { static_cast<float> (pos.x), static_cast<float> (pos.y), 848 static_cast<float> (w), static_cast<float> (h) }; 849 } 850 851 /** Casts this rectangle to a Rectangle<double>. 852 @see getSmallestIntegerContainer 853 */ toDouble()854 Rectangle<double> toDouble() const noexcept 855 { 856 return { static_cast<double> (pos.x), static_cast<double> (pos.y), 857 static_cast<double> (w), static_cast<double> (h) }; 858 } 859 860 /** Casts this rectangle to a Rectangle with the given type. 861 If the target type is a conversion from float to int, then the conversion 862 will be done using getSmallestIntegerContainer(). 863 */ 864 template <typename TargetType> toType()865 Rectangle<TargetType> toType() const noexcept 866 { 867 Rectangle<TargetType> r; 868 copyWithRounding (r); 869 return r; 870 } 871 872 /** Returns the smallest Rectangle that can contain a set of points. */ findAreaContainingPoints(const Point<ValueType> * points,int numPoints)873 static Rectangle findAreaContainingPoints (const Point<ValueType>* points, int numPoints) noexcept 874 { 875 if (numPoints <= 0) 876 return {}; 877 878 auto minX = points[0].x; 879 auto maxX = minX; 880 auto minY = points[0].y; 881 auto maxY = minY; 882 883 for (int i = 1; i < numPoints; ++i) 884 { 885 minX = jmin (minX, points[i].x); 886 maxX = jmax (maxX, points[i].x); 887 minY = jmin (minY, points[i].y); 888 maxY = jmax (maxY, points[i].y); 889 } 890 891 return { minX, minY, maxX - minX, maxY - minY }; 892 } 893 894 //============================================================================== 895 /** Static utility to intersect two sets of rectangular coordinates. 896 Returns false if the two regions didn't overlap. 897 @see intersectRectangle 898 */ intersectRectangles(ValueType & x1,ValueType & y1,ValueType & w1,ValueType & h1,ValueType x2,ValueType y2,ValueType w2,ValueType h2)899 static bool intersectRectangles (ValueType& x1, ValueType& y1, ValueType& w1, ValueType& h1, 900 ValueType x2, ValueType y2, ValueType w2, ValueType h2) noexcept 901 { 902 auto x = jmax (x1, x2); 903 w1 = jmin (x1 + w1, x2 + w2) - x; 904 905 if (w1 > ValueType()) 906 { 907 auto y = jmax (y1, y2); 908 h1 = jmin (y1 + h1, y2 + h2) - y; 909 910 if (h1 > ValueType()) 911 { 912 x1 = x; y1 = y; 913 return true; 914 } 915 } 916 917 return false; 918 } 919 920 //============================================================================== 921 /** Creates a string describing this rectangle. 922 923 The string will be of the form "x y width height", e.g. "100 100 400 200". 924 925 Coupled with the fromString() method, this is very handy for things like 926 storing rectangles (particularly component positions) in XML attributes. 927 928 @see fromString 929 */ toString()930 String toString() const 931 { 932 String s; 933 s.preallocateBytes (32); 934 s << pos.x << ' ' << pos.y << ' ' << w << ' ' << h; 935 return s; 936 } 937 938 /** Parses a string containing a rectangle's details. 939 940 The string should contain 4 integer tokens, in the form "x y width height". They 941 can be comma or whitespace separated. 942 943 This method is intended to go with the toString() method, to form an easy way 944 of saving/loading rectangles as strings. 945 946 @see toString 947 */ fromString(StringRef stringVersion)948 static Rectangle fromString (StringRef stringVersion) 949 { 950 StringArray toks; 951 toks.addTokens (stringVersion.text.findEndOfWhitespace(), ",; \t\r\n", ""); 952 953 return { parseIntAfterSpace (toks[0]), 954 parseIntAfterSpace (toks[1]), 955 parseIntAfterSpace (toks[2]), 956 parseIntAfterSpace (toks[3]) }; 957 } 958 959 #ifndef DOXYGEN 960 // This has been renamed by transformedBy, in order to match the method names used in the Point class. 961 JUCE_DEPRECATED_WITH_BODY (Rectangle transformed (const AffineTransform& t) const noexcept, { return transformedBy (t); }) 962 #endif 963 964 private: 965 template <typename OtherType> friend class Rectangle; 966 967 Point<ValueType> pos; 968 ValueType w {}, h {}; 969 parseIntAfterSpace(StringRef s)970 static ValueType parseIntAfterSpace (StringRef s) noexcept 971 { return static_cast<ValueType> (s.text.findEndOfWhitespace().getIntValue32()); } 972 copyWithRounding(Rectangle<int> & result)973 void copyWithRounding (Rectangle<int>& result) const noexcept { result = getSmallestIntegerContainer(); } copyWithRounding(Rectangle<float> & result)974 void copyWithRounding (Rectangle<float>& result) const noexcept { result = toFloat(); } copyWithRounding(Rectangle<double> & result)975 void copyWithRounding (Rectangle<double>& result) const noexcept { result = toDouble(); } 976 floorAsInt(int n)977 static int floorAsInt (int n) noexcept { return n; } floorAsInt(float n)978 static int floorAsInt (float n) noexcept { return n > (float) std::numeric_limits<int>::min() ? (int) std::floor (n) : std::numeric_limits<int>::min(); } floorAsInt(double n)979 static int floorAsInt (double n) noexcept { return n > (double) std::numeric_limits<int>::min() ? (int) std::floor (n) : std::numeric_limits<int>::min(); } ceilAsInt(int n)980 static int ceilAsInt (int n) noexcept { return n; } ceilAsInt(float n)981 static int ceilAsInt (float n) noexcept { return n < (float) std::numeric_limits<int>::max() ? (int) std::ceil (n) : std::numeric_limits<int>::max(); } ceilAsInt(double n)982 static int ceilAsInt (double n) noexcept { return n < (double) std::numeric_limits<int>::max() ? (int) std::ceil (n) : std::numeric_limits<int>::max(); } 983 }; 984 985 } // namespace juce 986