commonMain.io.nacular.doodle.geometry.Rectangle.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core-jvm Show documentation
Show all versions of core-jvm Show documentation
A pure Kotlin, UI framework for the Web and Desktop
package io.nacular.doodle.geometry
import io.nacular.doodle.geometry.Point.Companion.Origin
import io.nacular.doodle.layout.Insets
import kotlin.math.max
import kotlin.math.min
/**
* A rectangular [ConvexPolygon] with 4 sides at right-angles.
*
* @author Nicholas Eddy
*
* ```
* x
*(0,0) ──▶
* │
* y ▼
* ┌──────────────────────────┐
* │ │
* │ │
* │ │
* │ │ height
* │ │
* │ │
* │ │
* └──────────────────────────┘
* width
*```
*
* @constructor
* @property position The top-left corner (x,y)
* @property size The width-height
*/
public class Rectangle(public val position: Point = Origin, public val size: Size = Size.Empty): ConvexPolygon() {
/** Creates a Rectangle at the [Origin]: `[0, 0, width, height]` */
public constructor(width: Int, height: Int = width): this(Origin, Size(width, height))
/** Creates a Rectangle at the [Origin]: `[0, 0, width, height]` */
public constructor(width: Float, height: Float = width): this(Origin, Size(width, height))
/** Creates a Rectangle at the [Origin]: `[0, 0, width, height]` */
public constructor(width: Double, height: Double = width): this(Origin, Size(width, height))
/** Creates a Rectangle */
public constructor(x: Int = 0, y: Int = 0, width: Int = 0, height: Int = 0): this(Point(x, y), Size(width, height))
/** Creates a Rectangle */
public constructor(x: Double = 0.0, y: Double = 0.0, width: Double = 0.0, height: Double = 0.0): this(Point(x, y), Size(width, height))
/** Creates a Rectangle */
public constructor(x: Float = 0f, y: Float = 0f, width: Float = 0f, height: Float = 0f): this(Point(x, y), Size(width, height))
/** Left edge */
public val x: Double get() = position.x
/** Top edge */
public val y: Double get() = position.y
/** Horizontal extent */
public val width: Double get() = size.width
/** Vertical extent */
public val height: Double get() = size.height
/**
* Bottom edge
*
* ```
*(0,0) │
* │
* │
* │ ┌──────────────────────────┐
* │ │ │
* │ │ │
* bottom │ │ │
* │ │ │
* │ │ │
* │ │ │
* │ │ │
* ▼ └──────────────────────────┘
*```
*/
public val bottom: Double get() = y + height
/**
* Right edge
*
* ```
* right
*(0,0) ───────────────────────────────▶
*
* ┌──────────────────────────┐
* │ │
* │ │
* │ │
* │ │
* │ │
* │ │
* │ │
* └──────────────────────────┘
*```
*/
public val right: Double get() = x + width
/**
* Point at the center of the rectangle
*
* ```
* cx
*(0,0) ────────────────▶
* │
* │ ┌──────────────────────────┐
* │ │ │
* │ │ │
* │ │ │
* cy ▼ │ C │
* │ │
* │ │
* │ │
* └──────────────────────────┘
*```
*/
public val center: Point get() = position + Point(width / 2, height / 2)
override val points: List by lazy {
listOf(position, Point(x+width, y), Point(x+width, y+height), Point(x, y+height))
}
override val area: Double get() = size.area
override val empty: Boolean get() = size.empty
override val boundingRectangle: Rectangle get() = this
@Suppress("PrivatePropertyName")
private val hashCode_ by lazy { 31 * position.hashCode() + size.hashCode() }
/**
* Returns a Rectangle that is the intersection of this and the given one. The result is [Empty] if there is no intersection.
*
* @param rectangle to intersect with
* @return a Rectangle representing the intersection of the 2 rectangles
*/
public infix fun intersect(rectangle: Rectangle): Rectangle {
if (rectangle === this) {
return this
}
val vertical = y..bottom
val horizontal = x..right
val otherVertical = rectangle.y..rectangle.bottom
val otherHorizontal = rectangle.x..rectangle.right
val x1 = when {
x in otherHorizontal -> x
rectangle.x in horizontal -> rectangle.x
else -> 0.0
}
val y1 = when {
y in otherVertical -> y
rectangle.y in vertical -> rectangle.y
else -> 0.0
}
val x2 = when {
right in otherHorizontal -> right
rectangle.right in horizontal -> rectangle.right
else -> 0.0
}
val y2 = when {
bottom in otherVertical -> bottom
rectangle.bottom in vertical -> rectangle.bottom
else -> 0.0
}
return Rectangle(x1, y1, x2 - x1, y2 - y1)
}
/**
* Returns a Rectangle that is the union of this and the given one.
*
* @param rectangle to union with
* @return a Rectangle representing the union of the 2 rectangles
*/
public infix fun union(rectangle: Rectangle): Rectangle {
if (rectangle === this) {
return this
}
val newX = min(x, rectangle.x)
val newY = min(y, rectangle.y)
return Rectangle(newX, newY, max(right, rectangle.right) - newX, max(bottom, rectangle.bottom) - newY)
}
/** Rectangle with the same width/height but positioned at 0,0 */
public val atOrigin: Rectangle get() = at(0.0, 0.0)
/**
* Returns a rectangle with the same width/height but positioned at the given x,y
*
* @return adjusted Rectangle
*/
public fun at(x: Double = this.x, y: Double = this.y): Rectangle = if (this.x != x || this.y != y) Rectangle(x, y, width, height) else this
/**
* Returns a rectangle with the same width/height but positioned at the given point
*
* @return adjusted Rectangle
*/
public infix fun at(position: Point): Rectangle = at(position.x, position.y)
/**
* Rectangle that has been adjusted as follows `[x + i, y + i, w - 2i, h - 2i]`, where `i` is the inset
*
* @param inset amount to resize by
* @return adjusted Rectangle
*/
public infix fun inset(inset: Double): Rectangle = if (inset == 0.0) this else Rectangle(x + inset, y + inset, max(0.0, width - inset * 2), max(0.0, height - inset * 2))
/**
* Rectangle that has been adjusted as follows `[x + left, y + top, w - (left + right), h - (top + bottom)]`, where
* the adjustments are from the inset
*
* @param inset amount to resize by
* @return adjusted Rectangle
*/
public infix fun inset(inset: Insets): Rectangle = Rectangle(x + inset.left, y + inset.top, max(0.0, width - (inset.left + inset.right)), max(0.0, height - (inset.top + inset.bottom)))
/**
* Checks whether the given point is within the boundaries of this Rectangle
*
* @return `true` IFF the given point falls within the boundaries of this Rectangle
*/
override operator fun contains(point: Point): Boolean = area > 0 && point.x in x..right && point.y in y..bottom
/** @return ```true``` IFF the given rectangle falls within the boundaries of this Polygon */
override fun contains(rectangle: Rectangle): Boolean = rectangle.position in this && Point(rectangle.right, rectangle.bottom) in this
/**
* Checks whether the given rectangle intersects this one
*
* @return `true` IFF the given rectangle intersects this Rectangle
*/
override fun intersects(rectangle: Rectangle): Boolean = !(empty ||
rectangle.empty ||
x >= rectangle.right ||
y >= rectangle.bottom ||
right <= rectangle.x ||
bottom <= rectangle.y)
override fun toString(): String = "[$x,$y,$width,$height]"
override fun equals(other: Any?): Boolean {
if (this === other ) return true
if (other !is Rectangle) return false
if (position != other.position) return false
if (size != other.size ) return false
return true
}
override fun hashCode(): Int = hashCode_
public companion object {
/** The rectangle at the [Origin] with [width] and [height] equal to `0` */
public val Empty: Rectangle = Rectangle()
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy