gecko/toolkit/content/Geometry.jsm

574 lines
14 KiB
JavaScript

// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
/*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Mobile Browser.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Roy Frostig <rfrostig@mozilla.com>
* Ben Combee <bcombee@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
let Ci = Components.interfaces;
// -----------------------------------------------------------
// General util/convenience tools
//
let Util = {
bind: function bind(f, thisObj) {
return function() {
return f.apply(thisObj, arguments);
};
},
bindAll: function bindAll(instance) {
let bind = Util.bind;
for (let key in instance)
if (instance[key] instanceof Function)
instance[key] = bind(instance[key], instance);
},
/** Like dump, but each arg is handled and there's an automatic newline */
dumpLn: function dumpLn() {
for (var i = 0; i < arguments.length; i++) { dump(arguments[i] + ' '); }
dump("\n");
},
/** Executes aFunc after other events have been processed. */
executeSoon: function executeSoon(aFunc) {
let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
tm.mainThread.dispatch({
run: function() {
aFunc();
}
}, Ci.nsIThread.DISPATCH_NORMAL);
}
};
// -----------------------------------------------------------
// Util.Rect is a simple data structure for representation of a rectangle supporting
// many basic geometric operations.
//
Util.Rect = function Rect(x, y, w, h) {
this.left = x;
this.top = y;
this.right = x+w;
this.bottom = y+h;
};
Util.Rect.prototype = {
get x() { return this.left; },
get y() { return this.top; },
get width() { return this.right - this.left; },
get height() { return this.bottom - this.top; },
set x(v) {
let diff = this.left - v;
this.left = v;
this.right -= diff;
},
set y(v) {
let diff = this.top - v;
this.top = v;
this.bottom -= diff;
},
set width(v) { this.right = this.left + v; },
set height(v) { this.bottom = this.top + v; },
setRect: function(x, y, w, h) {
this.left = x;
this.top = y;
this.right = x+w;
this.bottom = y+h;
return this;
},
setBounds: function(t, l, b, r) {
this.top = t;
this.left = l;
this.bottom = b;
this.right = r;
return this;
},
equals: function equals(r) {
return (r != null &&
this.top == r.top &&
this.left == r.left &&
this.bottom == r.bottom &&
this.right == r.right);
},
clone: function clone() {
return new wsRect(this.left, this.top, this.right - this.left, this.bottom - this.top);
},
center: function center() {
return [this.left + (this.right - this.left) / 2,
this.top + (this.bottom - this.top) / 2];
},
centerRounded: function centerRounded() {
return this.center().map(Math.round);
},
copyFrom: function(r) {
this.top = r.top;
this.left = r.left;
this.bottom = r.bottom;
this.right = r.right;
return this;
},
copyFromTLBR: function(r) {
this.left = r.left;
this.top = r.top;
this.right = r.right;
this.bottom = r.bottom;
return this;
},
translate: function(x, y) {
this.left += x;
this.right += x;
this.top += y;
this.bottom += y;
return this;
},
// return a new wsRect that is the union of that one and this one
union: function(rect) {
let l = Math.min(this.left, rect.left);
let r = Math.max(this.right, rect.right);
let t = Math.min(this.top, rect.top);
let b = Math.max(this.bottom, rect.bottom);
return new wsRect(l, t, r-l, b-t);
},
toString: function() {
return "[" + this.x + "," + this.y + "," + this.width + "," + this.height + "]";
},
expandBy: function(b) {
this.left += b.left;
this.right += b.right;
this.top += b.top;
this.bottom += b.bottom;
return this;
},
contains: function(other) {
return !!(other.left >= this.left &&
other.right <= this.right &&
other.top >= this.top &&
other.bottom <= this.bottom);
},
intersect: function(r2) {
let xmost1 = this.right;
let xmost2 = r2.right;
let x = Math.max(this.left, r2.left);
let temp = Math.min(xmost1, xmost2);
if (temp <= x)
return null;
let width = temp - x;
let ymost1 = this.bottom;
let ymost2 = r2.bottom;
let y = Math.max(this.top, r2.top);
temp = Math.min(ymost1, ymost2);
if (temp <= y)
return null;
let height = temp - y;
return new wsRect(x, y, width, height);
},
intersects: function(other) {
if (!other) debugger;
let left = this.left;
let right = this.right;
let otherleft = other.left;
let otherright = other.right;
let xok = (otherleft > left && otherleft < right) ||
(otherright > left && otherright < right) ||
(otherleft <= left && otherright >= right);
if (!xok) return false;
let top = this.top;
let bottom = this.bottom;
let othertop = other.top;
let otherbottom = other.bottom;
let yok = (othertop > top && othertop < bottom) ||
(otherbottom > top && otherbottom < bottom) ||
(othertop <= top && otherbottom >= bottom);
if (!yok) return false;
return true;
},
/**
* Similar to (and most code stolen from) intersect(). A restriction
* is an intersection, but this modifies the receiving object instead
* of returning a new rect.
*/
restrictTo: function restrictTo(r2) {
let xmost1 = this.right;
let xmost2 = r2.right;
let x = Math.max(this.left, r2.left);
let temp = Math.min(xmost1, xmost2);
if (temp <= x)
throw "Intersection is empty but rects cannot be empty";
let width = temp - x;
let ymost1 = this.bottom;
let ymost2 = r2.bottom;
let y = Math.max(this.top, r2.top);
temp = Math.min(ymost1, ymost2);
if (temp <= y)
throw "Intersection is empty but rects cannot be empty";
let height = temp - y;
return this.setRect(x, y, width, height);
},
/**
* Similar to (and most code stolen from) union(). An extension is a
* union (in our sense of the term, not the common set-theoretic sense),
* but this modifies the receiving object instead of returning a new rect.
* Effectively, this rectangle is expanded minimally to contain all of the
* other rect. "Expanded minimally" means that the rect may shrink if
* given a strict subset rect as the argument.
*/
expandToContain: function extendTo(rect) {
let l = Math.min(this.left, rect.left);
let r = Math.max(this.right, rect.right);
let t = Math.min(this.top, rect.top);
let b = Math.max(this.bottom, rect.bottom);
return this.setRect(l, t, r-l, b-t);
},
round: function round(scale) {
if (!scale) scale = 1;
this.left = Math.floor(this.left * scale) / scale;
this.top = Math.floor(this.top * scale) / scale;
this.right = Math.ceil(this.right * scale) / scale;
this.bottom = Math.ceil(this.bottom * scale) / scale;
return this;
},
scale: function scale(xscl, yscl) {
this.left *= xscl;
this.right *= xscl;
this.top *= yscl;
this.bottom *= yscl;
return this;
}
};
// -----------------------------------------------------------
// Deprecated, global version of wsRect
//
function wsRect(x, y, w, h) {
this.left = x;
this.top = y;
this.right = x+w;
this.bottom = y+h;
}
wsRect.prototype = {
get x() { return this.left; },
get y() { return this.top; },
get width() { return this.right - this.left; },
get height() { return this.bottom - this.top; },
set x(v) {
let diff = this.left - v;
this.left = v;
this.right -= diff;
},
set y(v) {
let diff = this.top - v;
this.top = v;
this.bottom -= diff;
},
set width(v) { this.right = this.left + v; },
set height(v) { this.bottom = this.top + v; },
setRect: function(x, y, w, h) {
this.left = x;
this.top = y;
this.right = x+w;
this.bottom = y+h;
return this;
},
setBounds: function(t, l, b, r) {
this.top = t;
this.left = l;
this.bottom = b;
this.right = r;
return this;
},
equals: function equals(r) {
return (r != null &&
this.top == r.top &&
this.left == r.left &&
this.bottom == r.bottom &&
this.right == r.right);
},
clone: function clone() {
return new wsRect(this.left, this.top, this.right - this.left, this.bottom - this.top);
},
center: function center() {
return [this.left + (this.right - this.left) / 2,
this.top + (this.bottom - this.top) / 2];
},
centerRounded: function centerRounded() {
return this.center().map(Math.round);
},
copyFrom: function(r) {
this.top = r.top;
this.left = r.left;
this.bottom = r.bottom;
this.right = r.right;
return this;
},
copyFromTLBR: function(r) {
this.left = r.left;
this.top = r.top;
this.right = r.right;
this.bottom = r.bottom;
return this;
},
translate: function(x, y) {
this.left += x;
this.right += x;
this.top += y;
this.bottom += y;
return this;
},
// return a new wsRect that is the union of that one and this one
union: function(rect) {
let l = Math.min(this.left, rect.left);
let r = Math.max(this.right, rect.right);
let t = Math.min(this.top, rect.top);
let b = Math.max(this.bottom, rect.bottom);
return new wsRect(l, t, r-l, b-t);
},
toString: function() {
return "[" + this.x + "," + this.y + "," + this.width + "," + this.height + "]";
},
expandBy: function(b) {
this.left += b.left;
this.right += b.right;
this.top += b.top;
this.bottom += b.bottom;
return this;
},
contains: function(other) {
return !!(other.left >= this.left &&
other.right <= this.right &&
other.top >= this.top &&
other.bottom <= this.bottom);
},
intersect: function(r2) {
let xmost1 = this.right;
let xmost2 = r2.right;
let x = Math.max(this.left, r2.left);
let temp = Math.min(xmost1, xmost2);
if (temp <= x)
return null;
let width = temp - x;
let ymost1 = this.bottom;
let ymost2 = r2.bottom;
let y = Math.max(this.top, r2.top);
temp = Math.min(ymost1, ymost2);
if (temp <= y)
return null;
let height = temp - y;
return new wsRect(x, y, width, height);
},
intersects: function(other) {
if (!other) debugger;
let left = this.left;
let right = this.right;
let otherleft = other.left;
let otherright = other.right;
let xok = (otherleft > left && otherleft < right) ||
(otherright > left && otherright < right) ||
(otherleft <= left && otherright >= right);
if (!xok) return false;
let top = this.top;
let bottom = this.bottom;
let othertop = other.top;
let otherbottom = other.bottom;
let yok = (othertop > top && othertop < bottom) ||
(otherbottom > top && otherbottom < bottom) ||
(othertop <= top && otherbottom >= bottom);
if (!yok) return false;
return true;
},
/**
* Similar to (and most code stolen from) intersect(). A restriction
* is an intersection, but this modifies the receiving object instead
* of returning a new rect.
*/
restrictTo: function restrictTo(r2) {
let xmost1 = this.right;
let xmost2 = r2.right;
let x = Math.max(this.left, r2.left);
let temp = Math.min(xmost1, xmost2);
if (temp <= x)
throw "Intersection is empty but rects cannot be empty";
let width = temp - x;
let ymost1 = this.bottom;
let ymost2 = r2.bottom;
let y = Math.max(this.top, r2.top);
temp = Math.min(ymost1, ymost2);
if (temp <= y)
throw "Intersection is empty but rects cannot be empty";
let height = temp - y;
return this.setRect(x, y, width, height);
},
/**
* Similar to (and most code stolen from) union(). An extension is a
* union (in our sense of the term, not the common set-theoretic sense),
* but this modifies the receiving object instead of returning a new rect.
* Effectively, this rectangle is expanded minimally to contain all of the
* other rect. "Expanded minimally" means that the rect may shrink if
* given a strict subset rect as the argument.
*/
expandToContain: function extendTo(rect) {
let l = Math.min(this.left, rect.left);
let r = Math.max(this.right, rect.right);
let t = Math.min(this.top, rect.top);
let b = Math.max(this.bottom, rect.bottom);
return this.setRect(l, t, r-l, b-t);
},
round: function round(scale) {
if (!scale) scale = 1;
this.left = Math.floor(this.left * scale) / scale;
this.top = Math.floor(this.top * scale) / scale;
this.right = Math.ceil(this.right * scale) / scale;
this.bottom = Math.ceil(this.bottom * scale) / scale;
return this;
},
scale: function scale(xscl, yscl) {
this.left *= xscl;
this.right *= xscl;
this.top *= yscl;
this.bottom *= yscl;
return this;
}
};