/*
* This file is part of TREB.
*
* TREB is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* TREB is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with TREB. If not, see .
*
* Copyright 2022-2026 trebco, llc.
* info@treb.app
*
*/
/** structure represents rectangle coordinates */
export interface IRectangle {
top: number;
left: number;
width: number;
height: number;
}
export class Rectangle implements IRectangle {
public get right(): number { return this.left + this.width; }
public get bottom(): number { return this.top + this.height; }
/**
* create a rectangle from an object that looks
* like a rectangle, probably a serialized object
*/
public static Create(obj: Partial): Rectangle {
return new Rectangle(
obj.left || 0,
obj.top || 0,
obj.width || 0,
obj.height || 0);
}
public static IsRectangle(obj: unknown): obj is IRectangle {
return (!!obj && typeof obj === 'object') &&
(typeof (obj as Record).left === 'number') &&
(typeof (obj as Record).top === 'number') &&
(typeof (obj as Record).width === 'number') &&
(typeof (obj as Record).height === 'number');
}
constructor( public left = 0,
public top = 0,
public width = 0,
public height = 0 ){}
/** returns a new rect shifted from this one by (x,y) */
public Shift(x = 0, y = 0): Rectangle {
return new Rectangle(this.left + x, this.top + y, this.width, this.height );
}
public Scale(scale_x = 1, scale_y = scale_x): Rectangle {
return new Rectangle(this.left * scale_x, this.top * scale_y, this.width * scale_x, this.height * scale_y);
}
/** returns a new rect expanded from this one by (x,y) */
public Expand(x = 0, y = 0): Rectangle {
return new Rectangle(this.left, this.top, this.width + x, this.height + y );
}
/** returns a new rectangle that combines this rectangle with the argument */
public Combine(rect: Rectangle): Rectangle {
return new Rectangle(
Math.min(this.left, rect.left),
Math.min(this.top, rect.top),
Math.max(this.right, rect.right) - Math.min(this.left, rect.left),
Math.max(this.bottom, rect.bottom) - Math.min(this.top, rect.top),
);
}
/*
* removing this method as part of cleaning up bitwise flags.
* if you need this method, rewrite it to use explicit flags.
*
public CheckEdges(x: number, y: number, border = 16): number{
let edge = 0;
// tslint:disable-next-line:no-bitwise
if (x - this.left < border) edge |= 1;
// tslint:disable-next-line:no-bitwise
if (this.right - x < border) edge |= 2;
// tslint:disable-next-line:no-bitwise
if (y - this.top < border) edge |= 4;
// tslint:disable-next-line:no-bitwise
if (this.bottom - y < border) edge |= 8;
return edge;
}
*/
/**
* check if rectangle contains the given coordinates, optionally with
* some added padding
*/
public Contains(x: number, y: number, padding = 0): boolean {
return (x >= this.left - padding)
&& (x <= (this.left + this.width + padding))
&& (y >= this.top - padding)
&& (y <= (this.top + this.height + padding));
}
/** convenience method for canvas */
public ContextFill(context: CanvasRenderingContext2D){
context.fillRect(this.left, this.top, this.width, this.height);
}
/** convenience method for canvas */
public ContextStroke(context: CanvasRenderingContext2D){
context.strokeRect(this.left, this.top, this.width, this.height);
}
/** clamp coordinate to rectangle */
public Clamp(x: number, y: number){
x = Math.min(Math.max(x, this.left), this.right);
y = Math.min(Math.max(y, this.top), this.bottom);
return { x, y };
}
/** convenience method for html element style */
public ApplyStyle(element: HTMLElement){
element.style.top = this.top + 'px';
element.style.left = this.left + 'px';
element.style.width = this.width + 'px';
element.style.height = this.height + 'px';
}
public toJSON(){
return {
top: this.top, left: this.left, width: this.width, height: this.height,
};
}
}