/toolkit/content/tests/fennec-tile-testapp/chrome/content/WidgetStack.js

http://github.com/zpao/v8monkey · JavaScript · 1465 lines · 913 code · 281 blank · 271 comment · 258 complexity · f4a3ba8e0be58bf4b9725f1c1f0f8b38 MD5 · raw file

  1. /* -*- Mode: js2; tab-width: 40; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- */
  2. /*
  3. * ***** BEGIN LICENSE BLOCK *****
  4. * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  5. *
  6. * The contents of this file are subject to the Mozilla Public License Version
  7. * 1.1 (the "License"); you may not use this file except in compliance with
  8. * the License. You may obtain a copy of the License at
  9. * http://www.mozilla.org/MPL/
  10. *
  11. * Software distributed under the License is distributed on an "AS IS" basis,
  12. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13. * for the specific language governing rights and limitations under the
  14. * License.
  15. *
  16. * The Original Code is Mozilla Mobile Browser.
  17. *
  18. * The Initial Developer of the Original Code is
  19. * Mozilla Corporation.
  20. * Portions created by the Initial Developer are Copyright (C) 2008
  21. * the Initial Developer. All Rights Reserved.
  22. *
  23. * Contributor(s):
  24. * Vladimir Vukicevic <vladimir@pobox.com>
  25. *
  26. * Alternatively, the contents of this file may be used under the terms of
  27. * either the GNU General Public License Version 2 or later (the "GPL"), or
  28. * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29. * in which case the provisions of the GPL or the LGPL are applicable instead
  30. * of those above. If you wish to allow use of your version of this file only
  31. * under the terms of either the GPL or the LGPL, and not to allow others to
  32. * use your version of this file under the terms of the MPL, indicate your
  33. * decision by deleting the provisions above and replace them with the notice
  34. * and other provisions required by the GPL or the LGPL. If you do not delete
  35. * the provisions above, a recipient may use your version of this file under
  36. * the terms of any one of the MPL, the GPL or the LGPL.
  37. *
  38. * ***** END LICENSE BLOCK ***** */
  39. var gWsDoLog = false;
  40. var gWsLogDiv = null;
  41. function logbase() {
  42. if (!gWsDoLog)
  43. return;
  44. if (gWsLogDiv == null && "console" in window) {
  45. console.log.apply(console, arguments);
  46. } else {
  47. var s = "";
  48. for (var i = 0; i < arguments.length; i++) {
  49. s += arguments[i] + " ";
  50. }
  51. s += "\n";
  52. if (gWsLogDiv) {
  53. gWsLogDiv.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "br"));
  54. gWsLogDiv.appendChild(document.createTextNode(s));
  55. }
  56. dump(s);
  57. }
  58. }
  59. function dumpJSStack(stopAtNamedFunction) {
  60. let caller = Components.stack.caller;
  61. dump("\tStack: " + caller.name);
  62. while ((caller = caller.caller)) {
  63. dump(" <- " + caller.name);
  64. if (stopAtNamedFunction && caller.name != "anonymous")
  65. break;
  66. }
  67. dump("\n");
  68. }
  69. function log() {
  70. return;
  71. logbase.apply(window, arguments);
  72. }
  73. function log2() {
  74. return;
  75. logbase.apply(window, arguments);
  76. }
  77. let reportError = log;
  78. /*
  79. * wsBorder class
  80. *
  81. * Simple container for top,left,bottom,right "border" values
  82. */
  83. function wsBorder(t, l, b, r) {
  84. this.setBorder(t, l, b, r);
  85. }
  86. wsBorder.prototype = {
  87. setBorder: function (t, l, b, r) {
  88. this.top = t;
  89. this.left = l;
  90. this.bottom = b;
  91. this.right = r;
  92. },
  93. toString: function () {
  94. return "[l:" + this.left + ",t:" + this.top + ",r:" + this.right + ",b:" + this.bottom + "]";
  95. }
  96. };
  97. /*
  98. * wsRect class
  99. *
  100. * Rectangle class, with both x/y/w/h and t/l/b/r accessors.
  101. */
  102. function wsRect(x, y, w, h) {
  103. this.left = x;
  104. this.top = y;
  105. this.right = x+w;
  106. this.bottom = y+h;
  107. }
  108. wsRect.prototype = {
  109. get x() { return this.left; },
  110. get y() { return this.top; },
  111. get width() { return this.right - this.left; },
  112. get height() { return this.bottom - this.top; },
  113. set x(v) {
  114. let diff = this.left - v;
  115. this.left = v;
  116. this.right -= diff;
  117. },
  118. set y(v) {
  119. let diff = this.top - v;
  120. this.top = v;
  121. this.bottom -= diff;
  122. },
  123. set width(v) { this.right = this.left + v; },
  124. set height(v) { this.bottom = this.top + v; },
  125. setRect: function(x, y, w, h) {
  126. this.left = x;
  127. this.top = y;
  128. this.right = x+w;
  129. this.bottom = y+h;
  130. return this;
  131. },
  132. setBounds: function(t, l, b, r) {
  133. this.top = t;
  134. this.left = l;
  135. this.bottom = b;
  136. this.right = r;
  137. return this;
  138. },
  139. equals: function equals(r) {
  140. return (r != null &&
  141. this.top == r.top &&
  142. this.left == r.left &&
  143. this.bottom == r.bottom &&
  144. this.right == r.right);
  145. },
  146. clone: function clone() {
  147. return new wsRect(this.left, this.top, this.right - this.left, this.bottom - this.top);
  148. },
  149. center: function center() {
  150. return [this.left + (this.right - this.left) / 2,
  151. this.top + (this.bottom - this.top) / 2];
  152. },
  153. centerRounded: function centerRounded() {
  154. return this.center().map(Math.round);
  155. },
  156. copyFrom: function(r) {
  157. this.top = r.top;
  158. this.left = r.left;
  159. this.bottom = r.bottom;
  160. this.right = r.right;
  161. return this;
  162. },
  163. copyFromTLBR: function(r) {
  164. this.left = r.left;
  165. this.top = r.top;
  166. this.right = r.right;
  167. this.bottom = r.bottom;
  168. return this;
  169. },
  170. translate: function(x, y) {
  171. this.left += x;
  172. this.right += x;
  173. this.top += y;
  174. this.bottom += y;
  175. return this;
  176. },
  177. // return a new wsRect that is the union of that one and this one
  178. union: function(rect) {
  179. let l = Math.min(this.left, rect.left);
  180. let r = Math.max(this.right, rect.right);
  181. let t = Math.min(this.top, rect.top);
  182. let b = Math.max(this.bottom, rect.bottom);
  183. return new wsRect(l, t, r-l, b-t);
  184. },
  185. toString: function() {
  186. return "[" + this.x + "," + this.y + "," + this.width + "," + this.height + "]";
  187. },
  188. expandBy: function(b) {
  189. this.left += b.left;
  190. this.right += b.right;
  191. this.top += b.top;
  192. this.bottom += b.bottom;
  193. return this;
  194. },
  195. contains: function(other) {
  196. return !!(other.left >= this.left &&
  197. other.right <= this.right &&
  198. other.top >= this.top &&
  199. other.bottom <= this.bottom);
  200. },
  201. intersect: function(r2) {
  202. let xmost1 = this.right;
  203. let xmost2 = r2.right;
  204. let x = Math.max(this.left, r2.left);
  205. let temp = Math.min(xmost1, xmost2);
  206. if (temp <= x)
  207. return null;
  208. let width = temp - x;
  209. let ymost1 = this.bottom;
  210. let ymost2 = r2.bottom;
  211. let y = Math.max(this.top, r2.top);
  212. temp = Math.min(ymost1, ymost2);
  213. if (temp <= y)
  214. return null;
  215. let height = temp - y;
  216. return new wsRect(x, y, width, height);
  217. },
  218. intersects: function(other) {
  219. let xok = (other.left > this.left && other.left < this.right) ||
  220. (other.right > this.left && other.right < this.right) ||
  221. (other.left <= this.left && other.right >= this.right);
  222. let yok = (other.top > this.top && other.top < this.bottom) ||
  223. (other.bottom > this.top && other.bottom < this.bottom) ||
  224. (other.top <= this.top && other.bottom >= this.bottom);
  225. return xok && yok;
  226. },
  227. /**
  228. * Similar to (and most code stolen from) intersect(). A restriction
  229. * is an intersection, but this modifies the receiving object instead
  230. * of returning a new rect.
  231. */
  232. restrictTo: function restrictTo(r2) {
  233. let xmost1 = this.right;
  234. let xmost2 = r2.right;
  235. let x = Math.max(this.left, r2.left);
  236. let temp = Math.min(xmost1, xmost2);
  237. if (temp <= x)
  238. throw "Intersection is empty but rects cannot be empty";
  239. let width = temp - x;
  240. let ymost1 = this.bottom;
  241. let ymost2 = r2.bottom;
  242. let y = Math.max(this.top, r2.top);
  243. temp = Math.min(ymost1, ymost2);
  244. if (temp <= y)
  245. throw "Intersection is empty but rects cannot be empty";
  246. let height = temp - y;
  247. return this.setRect(x, y, width, height);
  248. },
  249. /**
  250. * Similar to (and most code stolen from) union(). An extension is a
  251. * union (in our sense of the term, not the common set-theoretic sense),
  252. * but this modifies the receiving object instead of returning a new rect.
  253. * Effectively, this rectangle is expanded minimally to contain all of the
  254. * other rect. "Expanded minimally" means that the rect may shrink if
  255. * given a strict subset rect as the argument.
  256. */
  257. expandToContain: function extendTo(rect) {
  258. let l = Math.min(this.left, rect.left);
  259. let r = Math.max(this.right, rect.right);
  260. let t = Math.min(this.top, rect.top);
  261. let b = Math.max(this.bottom, rect.bottom);
  262. return this.setRect(l, t, r-l, b-t);
  263. },
  264. round: function round(scale) {
  265. if (!scale) scale = 1;
  266. this.left = Math.floor(this.left * scale) / scale;
  267. this.top = Math.floor(this.top * scale) / scale;
  268. this.right = Math.ceil(this.right * scale) / scale;
  269. this.bottom = Math.ceil(this.bottom * scale) / scale;
  270. return this;
  271. },
  272. scale: function scale(xscl, yscl) {
  273. this.left *= xscl;
  274. this.right *= xscl;
  275. this.top *= yscl;
  276. this.bottom *= yscl;
  277. return this;
  278. }
  279. };
  280. /*
  281. * The "Widget Stack"
  282. *
  283. * Manages a <xul:stack>'s children, allowing them to be dragged around
  284. * the stack, subject to specified constraints. Optionally supports
  285. * one widget designated as the viewport, which can be panned over a virtual
  286. * area without needing to draw that area entirely. The viewport widget
  287. * is designated by a 'viewport' attribute on the child element.
  288. *
  289. * Widgets are subject to various constraints, specified in xul via the
  290. * 'constraint' attribute. Current constraints are:
  291. * ignore-x: When panning, ignore any changes to the widget's x position
  292. * ignore-y: When panning, ignore any changes to the widget's y position
  293. * vp-relative: This widget's position should be claculated relative to
  294. * the viewport widget. It will always keep the same offset from that
  295. * widget as initially laid out, regardless of changes to the viewport
  296. * bounds.
  297. * frozen: This widget is in a fixed position and should never pan.
  298. */
  299. function WidgetStack(el, ew, eh) {
  300. this.init(el, ew, eh);
  301. }
  302. WidgetStack.prototype = {
  303. // the <stack> element
  304. _el: null,
  305. // object indexed by widget id, with state struct for each object (see _addNewWidget)
  306. _widgetState: null,
  307. // any barriers
  308. _barriers: null,
  309. // If a viewport widget is present, this will point to its state object;
  310. // otherwise null.
  311. _viewport: null,
  312. // a wsRect; the inner bounds of the viewport content
  313. _viewportBounds: null,
  314. // a wsBorder; the overflow area to the side of the bounds where our
  315. // viewport-relative widgets go
  316. _viewportOverflow: null,
  317. // a wsRect; the viewportBounds expanded by the viewportOverflow
  318. _pannableBounds: null,
  319. get pannableBounds() {
  320. if (!this._pannableBounds) {
  321. this._pannableBounds = this._viewportBounds.clone()
  322. .expandBy(this._viewportOverflow);
  323. }
  324. return this._pannableBounds.clone();
  325. },
  326. // a wsRect; the currently visible part of pannableBounds.
  327. _viewingRect: null,
  328. // the amount of current global offset applied to all widgets (whether
  329. // static or not). Set via offsetAll(). Can be used to push things
  330. // out of the way for overlaying some other UI.
  331. globalOffsetX: 0,
  332. globalOffsetY: 0,
  333. // if true (default), panning is constrained to the pannable bounds.
  334. _constrainToViewport: true,
  335. _viewportUpdateInterval: -1,
  336. _viewportUpdateTimeout: -1,
  337. _viewportUpdateHandler: null,
  338. _panHandler: null,
  339. _dragState: null,
  340. _skipViewportUpdates: 0,
  341. _forceViewportUpdate: false,
  342. //
  343. // init:
  344. // el: the <stack> element whose children are to be managed
  345. //
  346. init: function (el, ew, eh) {
  347. this._el = el;
  348. this._widgetState = {};
  349. this._barriers = [];
  350. let rect = this._el.getBoundingClientRect();
  351. let width = rect.width;
  352. let height = rect.height;
  353. if (ew != undefined && eh != undefined) {
  354. width = ew;
  355. height = eh;
  356. }
  357. this._viewportOverflow = new wsBorder(0, 0, 0, 0);
  358. this._viewingRect = new wsRect(0, 0, width, height);
  359. // listen for DOMNodeInserted/DOMNodeRemoved/DOMAttrModified
  360. let children = this._el.childNodes;
  361. for (let i = 0; i < children.length; i++) {
  362. let c = this._el.childNodes[i];
  363. if (c.tagName == "spacer")
  364. this._addNewBarrierFromSpacer(c);
  365. else
  366. this._addNewWidget(c);
  367. }
  368. // this also updates the viewportOverflow and pannableBounds
  369. this._updateWidgets();
  370. if (this._viewport) {
  371. this._viewportBounds = new wsRect(0, 0, this._viewport.rect.width, this._viewport.rect.height);
  372. } else {
  373. this._viewportBounds = new wsRect(0, 0, 0, 0);
  374. }
  375. },
  376. // moveWidgetBy: move the widget with the given id by x,y. Should
  377. // not be used on vp-relative or otherwise frozen widgets (using it
  378. // on the x coordinate for x-ignore widgets and similarily for y is
  379. // ok, as long as the other coordinate remains 0.)
  380. moveWidgetBy: function (wid, x, y) {
  381. let state = this._getState(wid);
  382. state.rect.x += x;
  383. state.rect.y += y;
  384. this._commitState(state);
  385. },
  386. // panBy: pan the entire set of widgets by the given x and y amounts.
  387. // This does the same thing as if the user dragged by the given amount.
  388. // If this is called with an outstanding drag, weirdness might happen,
  389. // but it also might work, so not disabling that.
  390. //
  391. // if ignoreBarriers is true, then barriers are ignored for the pan.
  392. panBy: function panBy(dx, dy, ignoreBarriers) {
  393. dx = Math.round(dx);
  394. dy = Math.round(dy);
  395. if (dx == 0 && dy == 0)
  396. return false;
  397. let needsDragWrap = !this._dragging;
  398. if (needsDragWrap)
  399. this.dragStart(0, 0);
  400. let panned = this._panBy(dx, dy, ignoreBarriers);
  401. if (needsDragWrap)
  402. this.dragStop();
  403. return panned;
  404. },
  405. // panTo: pan the entire set of widgets so that the given x,y
  406. // coordinates are in the upper left of the stack. If either is
  407. // null or undefined, only move the other axis
  408. panTo: function panTo(x, y) {
  409. if (x == undefined || x == null)
  410. x = this._viewingRect.x;
  411. if (y == undefined || y == null)
  412. y = this._viewingRect.y;
  413. this.panBy(x - this._viewingRect.x, y - this._viewingRect.y, true);
  414. },
  415. // freeze: set a widget as frozen. A frozen widget won't be moved
  416. // in the stack -- its x,y position will still be tracked in the
  417. // state, but the left/top attributes won't be overwritten. Call unfreeze
  418. // to move the widget back to where the ws thinks it should be.
  419. freeze: function (wid) {
  420. let state = this._getState(wid);
  421. state.frozen = true;
  422. },
  423. unfreeze: function (wid) {
  424. let state = this._getState(wid);
  425. if (!state.frozen)
  426. return;
  427. state.frozen = false;
  428. this._commitState(state);
  429. },
  430. // moveFrozenTo: move a frozen widget with id wid to x, y in the stack.
  431. // can only be used on frozen widgets
  432. moveFrozenTo: function (wid, x, y) {
  433. let state = this._getState(wid);
  434. if (!state.frozen)
  435. throw "moveFrozenTo on non-frozen widget " + wid;
  436. state.widget.setAttribute("left", x);
  437. state.widget.setAttribute("top", y);
  438. },
  439. // moveUnfrozenTo: move an unfrozen, pannable widget with id wid to x, y in
  440. // the stack. should only be used on unfrozen widgets when a dynamic change
  441. // in position needs to be made. we basically remove, adjust and re-add
  442. // the widget
  443. moveUnfrozenTo: function (wid, x, y) {
  444. delete this._widgetState[wid];
  445. let widget = document.getElementById(wid);
  446. if (x) widget.setAttribute("left", x);
  447. if (y) widget.setAttribute("top", y);
  448. this._addNewWidget(widget);
  449. this._updateWidgets();
  450. },
  451. // we're relying on viewportBounds and viewingRect having the same origin
  452. get viewportVisibleRect () {
  453. let rect = this._viewportBounds.intersect(this._viewingRect);
  454. if (!rect)
  455. rect = new wsRect(0, 0, 0, 0);
  456. return rect;
  457. },
  458. isWidgetFrozen: function isWidgetFrozen(wid) {
  459. return this._getState(wid).frozen;
  460. },
  461. // isWidgetVisible: return true if any portion of widget with id wid is
  462. // visible; otherwise return false.
  463. isWidgetVisible: function (wid) {
  464. let state = this._getState(wid);
  465. let visibleStackRect = new wsRect(0, 0, this._viewingRect.width, this._viewingRect.height);
  466. return visibleStackRect.intersects(state.rect);
  467. },
  468. // getWidgetVisibility: returns the percentage that the widget is visible
  469. getWidgetVisibility: function (wid) {
  470. let state = this._getState(wid);
  471. let visibleStackRect = new wsRect(0, 0, this._viewingRect.width, this._viewingRect.height);
  472. let visibleRect = visibleStackRect.intersect(state.rect);
  473. if (visibleRect)
  474. return [visibleRect.width / state.rect.width, visibleRect.height / state.rect.height]
  475. return [0, 0];
  476. },
  477. // offsetAll: add an offset to all widgets
  478. offsetAll: function (x, y) {
  479. this.globalOffsetX += x;
  480. this.globalOffsetY += y;
  481. for each (let state in this._widgetState) {
  482. state.rect.x += x;
  483. state.rect.y += y;
  484. this._commitState(state);
  485. }
  486. },
  487. // setViewportBounds
  488. // nb: an object containing top, left, bottom, right properties
  489. // OR
  490. // width, height: integer values; origin assumed to be 0,0
  491. // OR
  492. // top, left, bottom, right: integer values
  493. //
  494. // Set the bounds of the viewport area; that is, set the size of the
  495. // actual content that the viewport widget will be providing a view
  496. // over. For example, in the case of a 100x100 viewport showing a
  497. // view into a 100x500 webpage, the viewport bounds would be
  498. // { top: 0, left: 0, bottom: 500, right: 100 }.
  499. //
  500. // setViewportBounds will move all the viewport-relative widgets into
  501. // place based on the new viewport bounds.
  502. setViewportBounds: function setViewportBounds() {
  503. let oldBounds = this._viewportBounds.clone();
  504. if (arguments.length == 1) {
  505. this._viewportBounds.copyFromTLBR(arguments[0]);
  506. } else if (arguments.length == 2) {
  507. this._viewportBounds.setRect(0, 0, arguments[0], arguments[1]);
  508. } else if (arguments.length == 4) {
  509. this._viewportBounds.setBounds(arguments[0],
  510. arguments[1],
  511. arguments[2],
  512. arguments[3]);
  513. } else {
  514. throw "Invalid number of arguments to setViewportBounds";
  515. }
  516. let vp = this._viewport;
  517. let dleft = this._viewportBounds.left - oldBounds.left;
  518. let dright = this._viewportBounds.right - oldBounds.right;
  519. let dtop = this._viewportBounds.top - oldBounds.top;
  520. let dbottom = this._viewportBounds.bottom - oldBounds.bottom;
  521. //log2("setViewportBounds dltrb", dleft, dtop, dright, dbottom);
  522. // move all vp-relative widgets to be the right offset from the bounds again
  523. for each (let state in this._widgetState) {
  524. if (state.vpRelative) {
  525. //log2("vpRelative widget", state.id, state.rect.x, dleft, dright);
  526. if (state.vpOffsetXBefore) {
  527. state.rect.x += dleft;
  528. } else {
  529. state.rect.x += dright;
  530. }
  531. if (state.vpOffsetYBefore) {
  532. state.rect.y += dtop;
  533. } else {
  534. state.rect.y += dbottom;
  535. }
  536. //log2("vpRelative widget", state.id, state.rect.x, dleft, dright);
  537. this._commitState(state);
  538. }
  539. }
  540. for (let bid in this._barriers) {
  541. let barrier = this._barriers[bid];
  542. //log2("setViewportBounds: looking at barrier", bid, barrier.vpRelative, barrier.type);
  543. if (barrier.vpRelative) {
  544. if (barrier.type == "vertical") {
  545. let q = "v barrier moving from " + barrier.x + " to ";
  546. if (barrier.vpOffsetXBefore) {
  547. barrier.x += dleft;
  548. } else {
  549. barrier.x += dright;
  550. }
  551. //log2(q += barrier.x);
  552. } else if (barrier.type == "horizontal") {
  553. let q = "h barrier moving from " + barrier.y + " to ";
  554. if (barrier.vpOffsetYBefore) {
  555. barrier.y += dtop;
  556. } else {
  557. barrier.y += dbottom;
  558. }
  559. //log2(q += barrier.y);
  560. }
  561. }
  562. }
  563. // clear the pannable bounds cache to make sure it gets rebuilt
  564. this._pannableBounds = null;
  565. // now let's make sure that the viewing rect and inner bounds are still valid
  566. this._adjustViewingRect();
  567. this._viewportUpdate(0, 0, true);
  568. },
  569. // setViewportHandler
  570. // uh: A function object
  571. //
  572. // The given function object is called at the end of every drag and viewport
  573. // bounds change, passing in the new rect that's to be displayed in the
  574. // viewport.
  575. //
  576. setViewportHandler: function (uh) {
  577. this._viewportUpdateHandler = uh;
  578. },
  579. // setPanHandler
  580. // uh: A function object
  581. //
  582. // The given functin object is called whenever elements pan; it provides
  583. // the new area of the pannable bounds that's visible in the stack.
  584. setPanHandler: function (uh) {
  585. this._panHandler = uh;
  586. },
  587. // dragStart: start a drag, with the current coordinates being clientX,clientY
  588. dragStart: function dragStart(clientX, clientY) {
  589. log("(dragStart)", clientX, clientY);
  590. if (this._dragState) {
  591. reportError("dragStart with drag already in progress? what?");
  592. this._dragState = null;
  593. }
  594. this._dragState = { };
  595. let t = Date.now();
  596. this._dragState.barrierState = [];
  597. this._dragState.startTime = t;
  598. // outer x, that is outer from the viewport coordinates. In stack-relative coords.
  599. this._dragState.outerStartX = clientX;
  600. this._dragState.outerStartY = clientY;
  601. this._dragCoordsFromClient(clientX, clientY, t);
  602. this._dragState.outerLastUpdateDX = 0;
  603. this._dragState.outerLastUpdateDY = 0;
  604. if (this._viewport) {
  605. // create a copy of these so that we can compute
  606. // deltas correctly to update the viewport
  607. this._viewport.dragStartRect = this._viewport.rect.clone();
  608. }
  609. this._dragState.dragging = true;
  610. },
  611. _viewportDragUpdate: function viewportDragUpdate() {
  612. let vws = this._viewport;
  613. this._viewportUpdate((vws.dragStartRect.x - vws.rect.x),
  614. (vws.dragStartRect.y - vws.rect.y));
  615. },
  616. // dragStop: stop any drag in progress
  617. dragStop: function dragStop() {
  618. log("(dragStop)");
  619. if (!this._dragging)
  620. return;
  621. if (this._viewportUpdateTimeout != -1)
  622. clearTimeout(this._viewportUpdateTimeout);
  623. this._viewportDragUpdate();
  624. this._dragState = null;
  625. },
  626. // dragMove: process a mouse move to clientX,clientY for an ongoing drag
  627. dragMove: function dragMove(clientX, clientY) {
  628. if (!this._dragging)
  629. return false;
  630. this._dragCoordsFromClient(clientX, clientY);
  631. let panned = this._dragUpdate();
  632. if (this._viewportUpdateInterval != -1) {
  633. if (this._viewportUpdateTimeout != -1)
  634. clearTimeout(this._viewportUpdateTimeout);
  635. let self = this;
  636. this._viewportUpdateTimeout = setTimeout(function () { self._viewportDragUpdate(); }, this._viewportUpdateInterval);
  637. }
  638. return panned;
  639. },
  640. // dragBy: process a mouse move by dx,dy for an ongoing drag
  641. dragBy: function dragBy(dx, dy) {
  642. return this.dragMove(this._dragState.outerCurX + dx, this._dragState.outerCurY + dy);
  643. },
  644. // updateSize: tell the WidgetStack to update its size, because it
  645. // was either resized or some other event took place.
  646. updateSize: function updateSize(width, height) {
  647. if (width == undefined || height == undefined) {
  648. let rect = this._el.getBoundingClientRect();
  649. width = rect.width;
  650. height = rect.height;
  651. }
  652. // update widget rects and viewportOverflow, since the resize might have
  653. // caused them to change (widgets first, since the viewportOverflow depends
  654. // on them).
  655. // XXX these methods aren't working correctly yet, but they aren't strictly
  656. // necessary in Fennec's default config
  657. //for each (let s in this._widgetState)
  658. // this._updateWidgetRect(s);
  659. //this._updateViewportOverflow();
  660. this._viewingRect.width = width;
  661. this._viewingRect.height = height;
  662. // Wrap this call in a batch to ensure that we always call the
  663. // viewportUpdateHandler, even if _adjustViewingRect doesn't trigger a pan.
  664. // If it does, the batch also ensures that we don't call the handler twice.
  665. this.beginUpdateBatch();
  666. this._adjustViewingRect();
  667. this.endUpdateBatch();
  668. },
  669. beginUpdateBatch: function startUpdate() {
  670. if (!this._skipViewportUpdates) {
  671. this._startViewportBoundsString = this._viewportBounds.toString();
  672. this._forceViewportUpdate = false;
  673. }
  674. this._skipViewportUpdates++;
  675. },
  676. endUpdateBatch: function endUpdate(aForceRedraw) {
  677. if (!this._skipViewportUpdates)
  678. throw new Error("Unbalanced call to endUpdateBatch");
  679. this._forceViewportUpdate = this._forceViewportUpdate || aForceRedraw;
  680. this._skipViewportUpdates--;
  681. if (this._skipViewportUpdates)
  682. return;
  683. let boundsSizeChanged =
  684. this._startViewportBoundsString != this._viewportBounds.toString();
  685. this._callViewportUpdateHandler(boundsSizeChanged || this._forceViewportUpdate);
  686. },
  687. //
  688. // Internal code
  689. //
  690. _updateWidgetRect: function(state) {
  691. // don't need to support updating the viewport rect at the moment
  692. // (we'd need to duplicate the vptarget* code from _addNewWidget if we did)
  693. if (state == this._viewport)
  694. return;
  695. let w = state.widget;
  696. let x = w.getAttribute("left") || 0;
  697. let y = w.getAttribute("top") || 0;
  698. let rect = w.getBoundingClientRect();
  699. state.rect = new wsRect(parseInt(x), parseInt(y),
  700. rect.right - rect.left,
  701. rect.bottom - rect.top);
  702. if (w.hasAttribute("widgetwidth") && w.hasAttribute("widgetheight")) {
  703. state.rect.width = parseInt(w.getAttribute("widgetwidth"));
  704. state.rect.height = parseInt(w.getAttribute("widgetheight"));
  705. }
  706. },
  707. _dumpRects: function () {
  708. dump("WidgetStack:\n");
  709. dump("\tthis._viewportBounds: " + this._viewportBounds + "\n");
  710. dump("\tthis._viewingRect: " + this._viewingRect + "\n");
  711. dump("\tthis._viewport.viewportInnerBounds: " + this._viewport.viewportInnerBounds + "\n");
  712. dump("\tthis._viewport.rect: " + this._viewport.rect + "\n");
  713. dump("\tthis._viewportOverflow: " + this._viewportOverflow + "\n");
  714. dump("\tthis.pannableBounds: " + this.pannableBounds + "\n");
  715. },
  716. // Ensures that _viewingRect is within _pannableBounds (call this when either
  717. // one is resized)
  718. _adjustViewingRect: function _adjustViewingRect() {
  719. let vr = this._viewingRect;
  720. let pb = this.pannableBounds;
  721. if (pb.contains(vr))
  722. return; // nothing to do here
  723. // don't bother adjusting _viewingRect if it can't fit into
  724. // _pannableBounds
  725. if (vr.height > pb.height || vr.width > pb.width)
  726. return;
  727. let panX = 0, panY = 0;
  728. if (vr.right > pb.right)
  729. panX = pb.right - vr.right;
  730. else if (vr.left < pb.left)
  731. panX = pb.left - vr.left;
  732. if (vr.bottom > pb.bottom)
  733. panY = pb.bottom - vr.bottom;
  734. else if(vr.top < pb.top)
  735. panY = pb.top - vr.top;
  736. this.panBy(panX, panY, true);
  737. },
  738. _getState: function (wid) {
  739. let w = this._widgetState[wid];
  740. if (!w)
  741. throw "Unknown widget id '" + wid + "'; widget not in stack";
  742. return w;
  743. },
  744. get _dragging() {
  745. return this._dragState && this._dragState.dragging;
  746. },
  747. _viewportUpdate: function _viewportUpdate(dX, dY, boundsChanged) {
  748. if (!this._viewport)
  749. return;
  750. this._viewportUpdateTimeout = -1;
  751. let vws = this._viewport;
  752. let vwib = vws.viewportInnerBounds;
  753. let vpb = this._viewportBounds;
  754. // recover the amount the inner bounds moved by the amount the viewport
  755. // widget moved, but don't include offsets that we're making up from previous
  756. // drags that didn't affect viewportInnerBounds
  757. let [ignoreX, ignoreY] = this._offsets || [0, 0];
  758. let rx = dX - ignoreX;
  759. let ry = dY - ignoreY;
  760. [dX, dY] = this._rectTranslateConstrain(rx, ry, vwib, vpb);
  761. // record the offsets that correspond to the amount of the drag we're ignoring
  762. // to ensure the viewportInnerBounds remains within the viewportBounds
  763. this._offsets = [dX - rx, dY - ry];
  764. // adjust the viewportInnerBounds, and snap the viewport back
  765. vwib.translate(dX, dY);
  766. vws.rect.translate(dX, dY);
  767. this._commitState(vws);
  768. // update this so that we can call this function again during the same drag
  769. // and get the right values.
  770. vws.dragStartRect = vws.rect.clone();
  771. this._callViewportUpdateHandler(boundsChanged);
  772. },
  773. _callViewportUpdateHandler: function _callViewportUpdateHandler(boundsChanged) {
  774. if (!this._viewport || !this._viewportUpdateHandler || this._skipViewportUpdates)
  775. return;
  776. let vwb = this._viewportBounds.clone();
  777. let vwib = this._viewport.viewportInnerBounds.clone();
  778. let vis = this.viewportVisibleRect;
  779. vwib.left += this._viewport.offsetLeft;
  780. vwib.top += this._viewport.offsetTop;
  781. vwib.right += this._viewport.offsetRight;
  782. vwib.bottom += this._viewport.offsetBottom;
  783. this._viewportUpdateHandler.apply(window, [vwb, vwib, vis, boundsChanged]);
  784. },
  785. _dragCoordsFromClient: function (cx, cy, t) {
  786. this._dragState.curTime = t ? t : Date.now();
  787. this._dragState.outerCurX = cx;
  788. this._dragState.outerCurY = cy;
  789. let dx = this._dragState.outerCurX - this._dragState.outerStartX;
  790. let dy = this._dragState.outerCurY - this._dragState.outerStartY;
  791. this._dragState.outerDX = dx;
  792. this._dragState.outerDY = dy;
  793. },
  794. _panHandleBarriers: function (dx, dy) {
  795. // XXX unless the barriers are sorted by position, this will break
  796. // with multiple barriers that are near enough to eachother that a
  797. // drag could cross more than one.
  798. let vr = this._viewingRect;
  799. // XXX this just stops at the first horizontal and vertical barrier it finds
  800. // barrier_[xy] is the barrier that was used to get to the final
  801. // barrier_d[xy] value. if null, no barrier, and dx/dy shouldn't
  802. // be replaced with barrier_d[xy].
  803. let barrier_y = null, barrier_x = null;
  804. let barrier_dy = 0, barrier_dx = 0;
  805. for (let i = 0; i < this._barriers.length; i++) {
  806. let b = this._barriers[i];
  807. //log2("barrier", i, b.type, b.x, b.y);
  808. if (dx != 0 && b.type == "vertical") {
  809. if (barrier_x != null) {
  810. delete this._dragState.barrierState[i];
  811. continue;
  812. }
  813. let alreadyKnownDistance = this._dragState.barrierState[i] || 0;
  814. //log2("alreadyKnownDistance", alreadyKnownDistance);
  815. let dbx = 0;
  816. //100 <= 100 && 100-(-5) > 100
  817. if ((vr.left <= b.x && vr.left+dx > b.x) ||
  818. (vr.left >= b.x && vr.left+dx < b.x))
  819. {
  820. dbx = b.x - vr.left;
  821. } else if ((vr.right <= b.x && vr.right+dx > b.x) ||
  822. (vr.right >= b.x && vr.right+dx < b.x))
  823. {
  824. dbx = b.x - vr.right;
  825. } else {
  826. delete this._dragState.barrierState[i];
  827. continue;
  828. }
  829. let leftoverDistance = dbx - dx;
  830. //log2("initial dbx", dbx, leftoverDistance);
  831. let dist = Math.abs(leftoverDistance + alreadyKnownDistance) - b.size;
  832. if (dist >= 0) {
  833. if (dx < 0)
  834. dbx -= dist;
  835. else
  836. dbx += dist;
  837. delete this._dragState.barrierState[i];
  838. } else {
  839. dbx = 0;
  840. this._dragState.barrierState[i] = leftoverDistance + alreadyKnownDistance;
  841. }
  842. //log2("final dbx", dbx, "state", this._dragState.barrierState[i]);
  843. if (Math.abs(barrier_dx) <= Math.abs(dbx)) {
  844. barrier_x = b;
  845. barrier_dx = dbx;
  846. //log2("new barrier_dx", barrier_dx);
  847. }
  848. }
  849. if (dy != 0 && b.type == "horizontal") {
  850. if (barrier_y != null) {
  851. delete this._dragState.barrierState[i];
  852. continue;
  853. }
  854. let alreadyKnownDistance = this._dragState.barrierState[i] || 0;
  855. //log2("alreadyKnownDistance", alreadyKnownDistance);
  856. let dby = 0;
  857. //100 <= 100 && 100-(-5) > 100
  858. if ((vr.top <= b.y && vr.top+dy > b.y) ||
  859. (vr.top >= b.y && vr.top+dy < b.y))
  860. {
  861. dby = b.y - vr.top;
  862. } else if ((vr.bottom <= b.y && vr.bottom+dy > b.y) ||
  863. (vr.bottom >= b.y && vr.bottom+dy < b.y))
  864. {
  865. dby = b.y - vr.bottom;
  866. } else {
  867. delete this._dragState.barrierState[i];
  868. continue;
  869. }
  870. let leftoverDistance = dby - dy;
  871. //log2("initial dby", dby, leftoverDistance);
  872. let dist = Math.abs(leftoverDistance + alreadyKnownDistance) - b.size;
  873. if (dist >= 0) {
  874. if (dy < 0)
  875. dby -= dist;
  876. else
  877. dby += dist;
  878. delete this._dragState.barrierState[i];
  879. } else {
  880. dby = 0;
  881. this._dragState.barrierState[i] = leftoverDistance + alreadyKnownDistance;
  882. }
  883. //log2("final dby", dby, "state", this._dragState.barrierState[i]);
  884. if (Math.abs(barrier_dy) <= Math.abs(dby)) {
  885. barrier_y = b;
  886. barrier_dy = dby;
  887. //log2("new barrier_dy", barrier_dy);
  888. }
  889. }
  890. }
  891. if (barrier_x) {
  892. //log2("did barrier_x", barrier_x, "barrier_dx", barrier_dx);
  893. dx = barrier_dx;
  894. }
  895. if (barrier_y) {
  896. dy = barrier_dy;
  897. }
  898. return [dx, dy];
  899. },
  900. _panBy: function _panBy(dx, dy, ignoreBarriers) {
  901. let vr = this._viewingRect;
  902. // check if any barriers would be crossed by this pan, and take them
  903. // into account. do this first.
  904. if (!ignoreBarriers)
  905. [dx, dy] = this._panHandleBarriers(dx, dy);
  906. // constrain the full drag of the viewingRect to the pannableBounds.
  907. // note that the viewingRect needs to move in the opposite
  908. // direction of the pan, so we fiddle with the signs here (as you
  909. // pan to the upper left, more of the bottom right becomes visible,
  910. // so the viewing rect moves to the bottom right of the virtual surface).
  911. [dx, dy] = this._rectTranslateConstrain(dx, dy, vr, this.pannableBounds);
  912. // If the net result is that we don't have any room to move, then
  913. // just return.
  914. if (dx == 0 && dy == 0)
  915. return false;
  916. // the viewingRect moves opposite of the actual pan direction, see above
  917. vr.x += dx;
  918. vr.y += dy;
  919. // Go through each widget and move it by dx,dy. Frozen widgets
  920. // will be ignored in commitState.
  921. // The widget rects are in real stack space though, so we need to subtract
  922. // our (now negated) dx, dy from their coordinates.
  923. for each (let state in this._widgetState) {
  924. if (!state.ignoreX)
  925. state.rect.x -= dx;
  926. if (!state.ignoreY)
  927. state.rect.y -= dy;
  928. this._commitState(state);
  929. }
  930. /* Do not call panhandler during pans within a transaction.
  931. * Those pans always end-up covering up the checkerboard and
  932. * do not require sliding out the location bar
  933. */
  934. if (!this._skipViewportUpdates && this._panHandler)
  935. this._panHandler.apply(window, [vr.clone(), dx, dy]);
  936. return true;
  937. },
  938. _dragUpdate: function _dragUpdate() {
  939. let dx = this._dragState.outerLastUpdateDX - this._dragState.outerDX;
  940. let dy = this._dragState.outerLastUpdateDY - this._dragState.outerDY;
  941. this._dragState.outerLastUpdateDX = this._dragState.outerDX;
  942. this._dragState.outerLastUpdateDY = this._dragState.outerDY;
  943. return this.panBy(dx, dy);
  944. },
  945. //
  946. // widget addition/removal
  947. //
  948. _addNewWidget: function (w) {
  949. let wid = w.getAttribute("id");
  950. if (!wid) {
  951. reportError("WidgetStack: child widget without id!");
  952. return;
  953. }
  954. if (w.getAttribute("hidden") == "true")
  955. return;
  956. let state = {
  957. widget: w,
  958. id: wid,
  959. viewport: false,
  960. ignoreX: false,
  961. ignoreY: false,
  962. sticky: false,
  963. frozen: false,
  964. vpRelative: false,
  965. offsetLeft: 0,
  966. offsetTop: 0,
  967. offsetRight: 0,
  968. offsetBottom: 0
  969. };
  970. this._updateWidgetRect(state);
  971. if (w.hasAttribute("constraint")) {
  972. let cs = w.getAttribute("constraint").split(",");
  973. for each (let s in cs) {
  974. if (s == "ignore-x")
  975. state.ignoreX = true;
  976. else if (s == "ignore-y")
  977. state.ignoreY = true;
  978. else if (s == "sticky")
  979. state.sticky = true;
  980. else if (s == "frozen") {
  981. state.frozen = true;
  982. } else if (s == "vp-relative")
  983. state.vpRelative = true;
  984. }
  985. }
  986. if (w.hasAttribute("viewport")) {
  987. if (this._viewport)
  988. reportError("WidgetStack: more than one viewport canvas in stack!");
  989. this._viewport = state;
  990. state.viewport = true;
  991. if (w.hasAttribute("vptargetx") && w.hasAttribute("vptargety") &&
  992. w.hasAttribute("vptargetw") && w.hasAttribute("vptargeth"))
  993. {
  994. let wx = parseInt(w.getAttribute("vptargetx"));
  995. let wy = parseInt(w.getAttribute("vptargety"));
  996. let ww = parseInt(w.getAttribute("vptargetw"));
  997. let wh = parseInt(w.getAttribute("vptargeth"));
  998. state.offsetLeft = state.rect.left - wx;
  999. state.offsetTop = state.rect.top - wy;
  1000. state.offsetRight = state.rect.right - (wx + ww);
  1001. state.offsetBottom = state.rect.bottom - (wy + wh);
  1002. state.rect = new wsRect(wx, wy, ww, wh);
  1003. }
  1004. // initialize inner bounds to top-left
  1005. state.viewportInnerBounds = new wsRect(0, 0, state.rect.width, state.rect.height);
  1006. }
  1007. this._widgetState[wid] = state;
  1008. log ("(New widget: " + wid + (state.viewport ? " [viewport]" : "") + " at: " + state.rect + ")");
  1009. },
  1010. _removeWidget: function (w) {
  1011. let wid = w.getAttribute("id");
  1012. delete this._widgetState[wid];
  1013. this._updateWidgets();
  1014. },
  1015. // updateWidgets:
  1016. // Go through all the widgets and figure out their viewport-relative offsets.
  1017. // If the widget goes to the left or above the viewport widget, then
  1018. // vpOffsetXBefore or vpOffsetYBefore is set.
  1019. // See setViewportBounds for use of vpOffset* state variables, and for how
  1020. // the actual x and y coords of each widget are calculated based on their offsets
  1021. // and the viewport bounds.
  1022. _updateWidgets: function () {
  1023. let vp = this._viewport;
  1024. let ofRect = this._viewingRect.clone();
  1025. for each (let state in this._widgetState) {
  1026. if (vp && state.vpRelative) {
  1027. // compute the vpOffset from 0,0 assuming that the viewport rect is 0,0
  1028. if (state.rect.left >= vp.rect.right) {
  1029. state.vpOffsetXBefore = false;
  1030. state.vpOffsetX = state.rect.left - vp.rect.width;
  1031. } else {
  1032. state.vpOffsetXBefore = true;
  1033. state.vpOffsetX = state.rect.left - vp.rect.left;
  1034. }
  1035. if (state.rect.top >= vp.rect.bottom) {
  1036. state.vpOffsetYBefore = false;
  1037. state.vpOffsetY = state.rect.top - vp.rect.height;
  1038. } else {
  1039. state.vpOffsetYBefore = true;
  1040. state.vpOffsetY = state.rect.top - vp.rect.top;
  1041. }
  1042. log("widget", state.id, "offset", state.vpOffsetX, state.vpOffsetXBefore ? "b" : "a", state.vpOffsetY, state.vpOffsetYBefore ? "b" : "a", "rect", state.rect);
  1043. }
  1044. }
  1045. this._updateViewportOverflow();
  1046. },
  1047. // updates the viewportOverflow/pannableBounds
  1048. _updateViewportOverflow: function() {
  1049. let vp = this._viewport;
  1050. if (!vp)
  1051. return;
  1052. let ofRect = new wsRect(0, 0, this._viewingRect.width, this._viewingRect.height);
  1053. for each (let state in this._widgetState) {
  1054. if (vp && state.vpRelative) {
  1055. ofRect.left = Math.min(ofRect.left, state.rect.left);
  1056. ofRect.top = Math.min(ofRect.top, state.rect.top);
  1057. ofRect.right = Math.max(ofRect.right, state.rect.right);
  1058. ofRect.bottom = Math.max(ofRect.bottom, state.rect.bottom);
  1059. }
  1060. }
  1061. // prevent the viewportOverflow from having positive top/left or negative
  1062. // bottom/right values, which would otherwise happen if there aren't widgets
  1063. // beyond each of those edges
  1064. this._viewportOverflow = new wsBorder(
  1065. /*top*/ Math.round(Math.min(ofRect.top, 0)),
  1066. /*left*/ Math.round(Math.min(ofRect.left, 0)),
  1067. /*bottom*/ Math.round(Math.max(ofRect.bottom - vp.rect.height, 0)),
  1068. /*right*/ Math.round(Math.max(ofRect.right - vp.rect.width, 0))
  1069. );
  1070. // clear the _pannableBounds cache, since it depends on the
  1071. // viewportOverflow
  1072. this._pannableBounds = null;
  1073. },
  1074. _widgetBounds: function () {
  1075. let r = new wsRect(0,0,0,0);
  1076. for each (let state in this._widgetState)
  1077. r = r.union(state.rect);
  1078. return r;
  1079. },
  1080. _commitState: function (state) {
  1081. // if the widget is frozen, don't actually update its left/top;
  1082. // presumably the caller is managing those directly for now.
  1083. if (state.frozen)
  1084. return;
  1085. let w = state.widget;
  1086. let l = state.rect.x + state.offsetLeft;
  1087. let t = state.rect.y + state.offsetTop;
  1088. //cache left/top to avoid calling setAttribute unnessesarily
  1089. if (state._left != l) {
  1090. state._left = l;
  1091. w.setAttribute("left", l);
  1092. }
  1093. if (state._top != t) {
  1094. state._top = t;
  1095. w.setAttribute("top", t);
  1096. }
  1097. },
  1098. // constrain translate of rect by dx dy to bounds; return dx dy that can
  1099. // be used to bring rect up to the edge of bounds if we'd go over.
  1100. _rectTranslateConstrain: function (dx, dy, rect, bounds) {
  1101. let newX, newY;
  1102. // If the rect is larger than the bounds, allow it to increase its overlap
  1103. let woverflow = rect.width > bounds.width;
  1104. let hoverflow = rect.height > bounds.height;
  1105. if (woverflow || hoverflow) {
  1106. let intersection = rect.intersect(bounds);
  1107. let newIntersection = rect.clone().translate(dx, dy).intersect(bounds);
  1108. if (woverflow)
  1109. newX = (newIntersection.width > intersection.width) ? rect.x + dx : rect.x;
  1110. if (hoverflow)
  1111. newY = (newIntersection.height > intersection.height) ? rect.y + dy : rect.y;
  1112. }
  1113. // Common case, rect fits within the bounds
  1114. // clamp new X to within [bounds.left, bounds.right - rect.width],
  1115. // new Y to within [bounds.top, bounds.bottom - rect.height]
  1116. if (isNaN(newX))
  1117. newX = Math.min(Math.max(bounds.left, rect.x + dx), bounds.right - rect.width);
  1118. if (isNaN(newY))
  1119. newY = Math.min(Math.max(bounds.top, rect.y + dy), bounds.bottom - rect.height);
  1120. return [newX - rect.x, newY - rect.y];
  1121. },
  1122. // add a new barrier from a <spacer>
  1123. _addNewBarrierFromSpacer: function (el) {
  1124. let t = el.getAttribute("barriertype");
  1125. // XXX implement these at some point
  1126. // t != "lr" && t != "rl" &&
  1127. // t != "tb" && t != "bt" &&
  1128. if (t != "horizontal" &&
  1129. t != "vertical")
  1130. {
  1131. throw "Invalid barrier type: " + t;
  1132. }
  1133. let x, y;
  1134. let barrier = {};
  1135. let vp = this._viewport;
  1136. barrier.type = t;
  1137. if (el.getAttribute("left"))
  1138. barrier.x = parseInt(el.getAttribute("left"));
  1139. else if (el.getAttribute("top"))
  1140. barrier.y = parseInt(el.getAttribute("top"));
  1141. else
  1142. throw "Barrier without top or left attribute";
  1143. if (el.getAttribute("size"))
  1144. barrier.size = parseInt(el.getAttribute("size"));
  1145. else
  1146. barrier.size = 10;
  1147. if (el.hasAttribute("constraint")) {
  1148. let cs = el.getAttribute("constraint").split(",");
  1149. for each (let s in cs) {
  1150. if (s == "ignore-x")
  1151. barrier.ignoreX = true;
  1152. else if (s == "ignore-y")
  1153. barrier.ignoreY = true;
  1154. else if (s == "sticky")
  1155. barrier.sticky = true;
  1156. else if (s == "frozen") {
  1157. barrier.frozen = true;
  1158. } else if (s == "vp-relative")
  1159. barrier.vpRelative = true;
  1160. }
  1161. }
  1162. if (barrier.vpRelative) {
  1163. if (barrier.type == "vertical") {
  1164. if (barrier.x >= vp.rect.right) {
  1165. barrier.vpOffsetXBefore = false;
  1166. barrier.vpOffsetX = barrier.x - vp.rect.right;
  1167. } else {
  1168. barrier.vpOffsetXBefore = true;
  1169. barrier.vpOffsetX = barrier.x - vp.rect.left;
  1170. }
  1171. } else if (barrier.type == "horizontal") {
  1172. if (barrier.y >= vp.rect.bottom) {
  1173. barrier.vpOffsetYBefore = false;
  1174. barrier.vpOffsetY = barrier.y - vp.rect.bottom;
  1175. } else {
  1176. barrier.vpOffsetYBefore = true;
  1177. barrier.vpOffsetY = barrier.y - vp.rect.top;
  1178. }
  1179. //log2("h barrier relative", barrier.vpOffsetYBefore, barrier.vpOffsetY);
  1180. }
  1181. }
  1182. this._barriers.push(barrier);
  1183. }
  1184. };