@eclipse-scout/core
Version:
Eclipse Scout runtime
236 lines (214 loc) • 6.66 kB
text/typescript
/*
* Copyright (c) 2010, 2024 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
import {Dimension, Insets, Point} from '../index';
export class Rectangle {
x: number;
y: number;
width: number;
height: number;
/**
* If no parameters are passed, all members are initialized with 0.
*/
constructor(xOrRectangle?: number | Rectangle, y?: number, width?: number, height?: number) {
if (xOrRectangle instanceof Rectangle) {
this.x = xOrRectangle.x;
this.y = xOrRectangle.y;
this.width = xOrRectangle.width;
this.height = xOrRectangle.height;
} else {
this.x = xOrRectangle || 0;
this.y = y || 0;
this.width = width || 0;
this.height = height || 0;
}
}
toString(): string {
return 'Rectangle[x=' + this.x + ' y=' + this.y + ' width=' + this.width + ' height=' + this.height + ']';
}
equals(o: Rectangle): boolean {
if (!o) {
return false;
}
return (this.x === o.x && this.y === o.y && this.width === o.width && this.height === o.height);
}
clone(): Rectangle {
return new Rectangle(this.x, this.y, this.width, this.height);
}
center(): Point {
return new Point(this.x + this.width / 2, this.y + this.height / 2);
}
right(): number {
return this.x + this.width;
}
bottom(): number {
return this.y + this.height;
}
contains(xOrPoint: number | Point, y?: number): boolean {
let point = new Point(xOrPoint, y);
return point.y >= this.y && point.y < this.bottom() && point.x >= this.x && point.x < this.right();
}
/**
* Tests whether the specified rectangle intersects this rectangle.
* This means the two rectangles share at least one internal point.
*
* @param r the rectangle to test against
* @returns true if the specified rectangle intersects this one
*/
intersects(r: Rectangle): boolean {
if (!r) {
return false;
}
return r.width > 0 && r.height > 0 && this.width > 0 && this.height > 0 &&
r.x < this.right() && r.right() > this.x &&
r.y < this.bottom() && r.bottom() > this.y;
}
/**
* Determines the intersection with the given rectangle.
*
* @returns the rectangle that represents the intersection, or an empty rectangle, if the rectangles don't intersect.
*/
intersect(rect: Rectangle): Rectangle {
if (!this.intersects(rect)) {
return new Rectangle();
}
let newRect = new Rectangle(Math.max(this.x, rect.x), Math.max(this.y, rect.y));
newRect.width = Math.min(this.right(), rect.right()) - newRect.x;
newRect.height = Math.min(this.bottom(), rect.bottom()) - newRect.y;
return newRect;
}
/**
* Subtracts the given insets from the rectangle.
*/
subtract(insets: Insets): Rectangle {
return new Rectangle(
this.x + insets.left,
this.y + insets.top,
this.width - insets.horizontal(),
this.height - insets.vertical());
}
/**
* Subtracts the insets only from the dimension properties (width and height)
*/
subtractFromDimension(insets: Insets): Rectangle {
return new Rectangle(
this.x,
this.y,
this.width - insets.horizontal(),
this.height - insets.vertical());
}
/**
* Adds the given insets to the rectangle.
*/
add(insets: Insets): Rectangle {
return new Rectangle(
this.x - insets.left,
this.y - insets.top,
this.width + insets.horizontal(),
this.height + insets.vertical());
}
/**
* Moves the rectangle the given distance.
*
* @param dxOrPoint the distance to move the rectangle along the x-axis, or a point object containing the diff on the x and y-axis.
* @param dy the distance to move the rectangle along the y-axis.
*/
translate(dxOrPoint: number | Point, dy?: number): Rectangle {
let point = this.point().add(new Point(dxOrPoint, dy));
return this.moveTo(point);
}
/**
* Moves the rectangle to the new point.
*
* @param xOrPoint the new position on the x-axis or a point object containing the new position.
* @param y the new position on the y-axis.
*/
moveTo(xOrPoint: number | Point, y?: number): Rectangle {
let point = new Point(xOrPoint, y);
return new Rectangle(
point.x,
point.y,
this.width,
this.height);
}
/**
* @returns property x and y of this instance as new {@link Point} instance.
*/
point(): Point {
return new Point(this.x, this.y);
}
/**
* @returns property width and height of this instance as new {@link Dimension} instance.
*/
dimension(): Dimension {
return new Dimension(this.width, this.height);
}
union(r: Rectangle): Rectangle {
let tx2 = this.width;
let ty2 = this.height;
if (tx2 < 0 || ty2 < 0) {
// This rectangle has negative dimensions...
// If r has non-negative dimensions then it is the answer.
// If r is non-existent (has a negative dimension), then both
// are non-existent, and we can return any non-existent rectangle
// as an answer. Thus, returning r meets that criterion.
// Either way, r is our answer.
return new Rectangle(r.x, r.y, r.width, r.height);
}
let rx2 = r.width;
let ry2 = r.height;
if (rx2 < 0 || ry2 < 0) {
return new Rectangle(this.x, this.y, this.width, this.height);
}
let tx1 = this.x;
let ty1 = this.y;
tx2 += tx1;
ty2 += ty1;
let rx1 = r.x;
let ry1 = r.y;
rx2 += rx1;
ry2 += ry1;
if (tx1 > rx1) {
tx1 = rx1;
}
if (ty1 > ry1) {
ty1 = ry1;
}
if (tx2 < rx2) {
tx2 = rx2;
}
if (ty2 < ry2) {
ty2 = ry2;
}
tx2 -= tx1;
ty2 -= ty1;
// tx2,ty2 will never underflow since both original rectangles
// were already proven to be non-empty
// they might overflow, though...
if (tx2 > Number.MAX_VALUE) {
tx2 = Number.MAX_VALUE;
}
if (ty2 > Number.MAX_VALUE) {
ty2 = Number.MAX_VALUE;
}
return new Rectangle(tx1, ty1, tx2, ty2);
}
/**
* Creates a copy and calls Math.floor() on each property.
*/
floor(): Rectangle {
return new Rectangle(Math.floor(this.x), Math.floor(this.y), Math.floor(this.width), Math.floor(this.height));
}
/**
* Creates a copy and calls Math.ceil() on each property.
*/
ceil(): Rectangle {
return new Rectangle(Math.ceil(this.x), Math.ceil(this.y), Math.ceil(this.width), Math.ceil(this.height));
}
}