1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * Any method that takes an x and y may also take a point.
10 export function Point(x, y) {
15 clone: function clone() {
16 return new Point(this.x, this.y);
19 set: function set(x, y) {
25 equals: function equals(x, y) {
26 return this.x == x && this.y == y;
29 toString: function toString() {
30 return "(" + this.x + "," + this.y + ")";
33 map: function map(f) {
34 this.x = f.call(this, this.x);
35 this.y = f.call(this, this.y);
39 add: function add(x, y) {
45 subtract: function subtract(x, y) {
51 scale: function scale(s) {
58 return this.x == 0 && this.y == 0;
63 function takePointOrArgs(f) {
64 return function (arg1, arg2) {
65 if (arg2 === undefined) {
66 return f.call(this, arg1.x, arg1.y);
68 return f.call(this, arg1, arg2);
72 for (let f of ["add", "subtract", "equals", "set"]) {
73 Point.prototype[f] = takePointOrArgs(Point.prototype[f]);
78 * Rect is a simple data structure for representation of a rectangle supporting
79 * many basic geometric operations.
81 * NOTE: Since its operations are closed, rectangles may be empty and will report
82 * non-positive widths and heights in that case.
85 export function Rect(x, y, w, h) {
92 Rect.fromRect = function fromRect(r) {
93 return new Rect(r.left, r.top, r.right - r.left, r.bottom - r.top);
104 return this.right - this.left;
107 return this.bottom - this.top;
110 let diff = this.left - v;
115 let diff = this.top - v;
120 this.right = this.left + v;
123 this.bottom = this.top + v;
126 isEmpty: function isEmpty() {
127 return this.left >= this.right || this.top >= this.bottom;
130 setRect(x, y, w, h) {
139 setBounds(l, t, r, b) {
148 equals: function equals(other) {
151 ((this.isEmpty() && other.isEmpty()) ||
152 (this.top == other.top &&
153 this.left == other.left &&
154 this.bottom == other.bottom &&
155 this.right == other.right))
159 clone: function clone() {
163 this.right - this.left,
164 this.bottom - this.top
168 center: function center() {
169 if (this.isEmpty()) {
170 throw new Error("Empty rectangles do not have centers");
173 this.left + (this.right - this.left) / 2,
174 this.top + (this.bottom - this.top) / 2
179 this.top = other.top;
180 this.left = other.left;
181 this.bottom = other.bottom;
182 this.right = other.right;
198 "[" + this.x + "," + this.y + "," + this.width + "," + this.height + "]"
202 /** return a new rect that is the union of that one and this one */
204 return this.clone().expandToContain(other);
208 if (other.isEmpty()) {
211 if (this.isEmpty()) {
216 other.left >= this.left &&
217 other.right <= this.right &&
218 other.top >= this.top &&
219 other.bottom <= this.bottom
224 return this.clone().restrictTo(other);
228 if (this.isEmpty() || other.isEmpty()) {
232 let x1 = Math.max(this.left, other.left);
233 let x2 = Math.min(this.right, other.right);
234 let y1 = Math.max(this.top, other.top);
235 let y2 = Math.min(this.bottom, other.bottom);
236 return x1 < x2 && y1 < y2;
239 /** Restrict area of this rectangle to the intersection of both rectangles. */
240 restrictTo: function restrictTo(other) {
241 if (this.isEmpty() || other.isEmpty()) {
242 return this.setRect(0, 0, 0, 0);
245 let x1 = Math.max(this.left, other.left);
246 let x2 = Math.min(this.right, other.right);
247 let y1 = Math.max(this.top, other.top);
248 let y2 = Math.min(this.bottom, other.bottom);
249 // If width or height is 0, the intersection was empty.
250 return this.setRect(x1, y1, Math.max(0, x2 - x1), Math.max(0, y2 - y1));
253 /** Expand this rectangle to the union of both rectangles. */
254 expandToContain: function expandToContain(other) {
255 if (this.isEmpty()) {
256 return this.copyFrom(other);
258 if (other.isEmpty()) {
262 let l = Math.min(this.left, other.left);
263 let r = Math.max(this.right, other.right);
264 let t = Math.min(this.top, other.top);
265 let b = Math.max(this.bottom, other.bottom);
266 return this.setRect(l, t, r - l, b - t);
270 * Expands to the smallest rectangle that contains original rectangle and is bounded
271 * by lines with integer coefficients.
273 expandToIntegers: function round() {
274 this.left = Math.floor(this.left);
275 this.top = Math.floor(this.top);
276 this.right = Math.ceil(this.right);
277 this.bottom = Math.ceil(this.bottom);
281 scale: function scale(xscl, yscl) {
289 map: function map(f) {
290 this.left = f.call(this, this.left);
291 this.top = f.call(this, this.top);
292 this.right = f.call(this, this.right);
293 this.bottom = f.call(this, this.bottom);
297 /** Ensure this rectangle is inside the other, if possible. Preserves w, h. */
298 translateInside: function translateInside(other) {
300 if (this.left <= other.left) {
301 offsetX = other.left - this.left;
302 } else if (this.right > other.right) {
303 offsetX = other.right - this.right;
307 if (this.top <= other.top) {
308 offsetY = other.top - this.top;
309 } else if (this.bottom > other.bottom) {
310 offsetY = other.bottom - this.bottom;
313 return this.translate(offsetX, offsetY);
316 /** Subtract other area from this. Returns array of rects whose union is this-other. */
317 subtract: function subtract(other) {
318 let r = new Rect(0, 0, 0, 0);
320 other = other.intersect(this);
321 if (other.isEmpty()) {
322 return [this.clone()];
326 r.setBounds(this.left, this.top, other.left, this.bottom);
328 result.push(r.clone());
331 r.setBounds(other.left, this.top, other.right, other.top);
333 result.push(r.clone());
335 r.setBounds(other.left, other.bottom, other.right, this.bottom);
337 result.push(r.clone());
340 r.setBounds(other.right, this.top, this.right, this.bottom);
342 result.push(r.clone());
349 * Blends two rectangles together.
350 * @param rect Rectangle to blend this one with
351 * @param scalar Ratio from 0 (returns a clone of this rect) to 1 (clone of rect).
352 * @return New blended rectangle.
354 blend: function blend(rect, scalar) {
356 this.left + (rect.left - this.left) * scalar,
357 this.top + (rect.top - this.top) * scalar,
358 this.width + (rect.width - this.width) * scalar,
359 this.height + (rect.height - this.height) * scalar
364 * Grows or shrinks the rectangle while keeping the center point.
365 * Accepts single multipler, or separate for both axes.
367 inflate: function inflate(xscl, yscl) {
368 let xAdj = (this.width * xscl - this.width) / 2;
369 let s = arguments.length > 1 ? yscl : xscl;
370 let yAdj = (this.height * s - this.height) / 2;
379 * Grows or shrinks the rectangle by fixed amount while keeping the center point.
380 * Accepts single fixed amount
382 inflateFixed: function inflateFixed(fixed) {
386 this.bottom += fixed;