michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // ********** michael@0: // Title: drag.js michael@0: michael@0: // ---------- michael@0: // Variable: drag michael@0: // The Drag that's currently in process. michael@0: var drag = { michael@0: info: null, michael@0: zIndex: 100, michael@0: lastMoveTime: 0 michael@0: }; michael@0: michael@0: //---------- michael@0: //Variable: resize michael@0: //The resize (actually a Drag) that is currently in process michael@0: var resize = { michael@0: info: null, michael@0: lastMoveTime: 0 michael@0: }; michael@0: michael@0: // ########## michael@0: // Class: Drag (formerly DragInfo) michael@0: // Helper class for dragging s michael@0: // michael@0: // ---------- michael@0: // Constructor: Drag michael@0: // Called to create a Drag in response to an draggable "start" event. michael@0: // Note that it is also used partially during 's resizable method as well. michael@0: // michael@0: // Parameters: michael@0: // item - The being dragged michael@0: // event - The DOM event that kicks off the drag michael@0: function Drag(item, event) { michael@0: Utils.assert(item && (item.isAnItem || item.isAFauxItem), michael@0: 'must be an item, or at least a faux item'); michael@0: michael@0: this.item = item; michael@0: this.el = item.container; michael@0: this.$el = iQ(this.el); michael@0: this.parent = this.item.parent; michael@0: this.startPosition = new Point(event.clientX, event.clientY); michael@0: this.startTime = Date.now(); michael@0: michael@0: this.item.isDragging = true; michael@0: this.item.setZ(999999); michael@0: michael@0: this.safeWindowBounds = Items.getSafeWindowBounds(); michael@0: michael@0: Trenches.activateOthersTrenches(this.el); michael@0: }; michael@0: michael@0: Drag.prototype = { michael@0: // ---------- michael@0: // Function: toString michael@0: // Prints [Drag (item)] for debug use michael@0: toString: function Drag_toString() { michael@0: return "[Drag (" + this.item + ")]"; michael@0: }, michael@0: michael@0: // ---------- michael@0: // Function: snapBounds michael@0: // Adjusts the given bounds according to the currently active trenches. Used by michael@0: // michael@0: // Parameters: michael@0: // bounds - () bounds michael@0: // stationaryCorner - which corner is stationary? by default, the top left in LTR mode, michael@0: // and top right in RTL mode. michael@0: // "topleft", "bottomleft", "topright", "bottomright" michael@0: // assumeConstantSize - (boolean) whether the bounds' dimensions are sacred or not. michael@0: // keepProportional - (boolean) if assumeConstantSize is false, whether we should resize michael@0: // proportionally or not michael@0: // checkItemStatus - (boolean) make sure this is a valid item which should be snapped michael@0: snapBounds: function Drag_snapBounds(bounds, stationaryCorner, assumeConstantSize, keepProportional, checkItemStatus) { michael@0: if (!stationaryCorner) michael@0: stationaryCorner = UI.rtl ? 'topright' : 'topleft'; michael@0: var update = false; // need to update michael@0: var updateX = false; michael@0: var updateY = false; michael@0: var newRect; michael@0: var snappedTrenches = {}; michael@0: michael@0: // OH SNAP! michael@0: michael@0: // if we aren't holding down the meta key or have trenches disabled... michael@0: if (!Keys.meta && !Trenches.disabled) { michael@0: // snappable = true if we aren't a tab on top of something else, and michael@0: // there's no active drop site... michael@0: let snappable = !(this.item.isATabItem && michael@0: this.item.overlapsWithOtherItems()) && michael@0: !iQ(".acceptsDrop").length; michael@0: if (!checkItemStatus || snappable) { michael@0: newRect = Trenches.snap(bounds, stationaryCorner, assumeConstantSize, michael@0: keepProportional); michael@0: if (newRect) { // might be false if no changes were made michael@0: update = true; michael@0: snappedTrenches = newRect.snappedTrenches || {}; michael@0: bounds = newRect; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // make sure the bounds are in the window. michael@0: newRect = this.snapToEdge(bounds, stationaryCorner, assumeConstantSize, michael@0: keepProportional); michael@0: if (newRect) { michael@0: update = true; michael@0: bounds = newRect; michael@0: Utils.extend(snappedTrenches, newRect.snappedTrenches); michael@0: } michael@0: michael@0: Trenches.hideGuides(); michael@0: for (var edge in snappedTrenches) { michael@0: var trench = snappedTrenches[edge]; michael@0: if (typeof trench == 'object') { michael@0: trench.showGuide = true; michael@0: trench.show(); michael@0: } michael@0: } michael@0: michael@0: return update ? bounds : false; michael@0: }, michael@0: michael@0: // ---------- michael@0: // Function: snap michael@0: // Called when a drag or mousemove occurs. Set the bounds based on the mouse move first, then michael@0: // call snap and it will adjust the item's bounds if appropriate. Also triggers the display of michael@0: // trenches that it snapped to. michael@0: // michael@0: // Parameters: michael@0: // stationaryCorner - which corner is stationary? by default, the top left in LTR mode, michael@0: // and top right in RTL mode. michael@0: // "topleft", "bottomleft", "topright", "bottomright" michael@0: // assumeConstantSize - (boolean) whether the bounds' dimensions are sacred or not. michael@0: // keepProportional - (boolean) if assumeConstantSize is false, whether we should resize michael@0: // proportionally or not michael@0: snap: function Drag_snap(stationaryCorner, assumeConstantSize, keepProportional) { michael@0: var bounds = this.item.getBounds(); michael@0: bounds = this.snapBounds(bounds, stationaryCorner, assumeConstantSize, keepProportional, true); michael@0: if (bounds) { michael@0: this.item.setBounds(bounds, true); michael@0: return true; michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: // -------- michael@0: // Function: snapToEdge michael@0: // Returns a version of the bounds snapped to the edge if it is close enough. If not, michael@0: // returns false. If is true, this function will simply enforce the michael@0: // window edges. michael@0: // michael@0: // Parameters: michael@0: // rect - () current bounds of the object michael@0: // stationaryCorner - which corner is stationary? by default, the top left in LTR mode, michael@0: // and top right in RTL mode. michael@0: // "topleft", "bottomleft", "topright", "bottomright" michael@0: // assumeConstantSize - (boolean) whether the rect's dimensions are sacred or not michael@0: // keepProportional - (boolean) if we are allowed to change the rect's size, whether the michael@0: // dimensions should scaled proportionally or not. michael@0: snapToEdge: function Drag_snapToEdge(rect, stationaryCorner, assumeConstantSize, keepProportional) { michael@0: michael@0: var swb = this.safeWindowBounds; michael@0: var update = false; michael@0: var updateX = false; michael@0: var updateY = false; michael@0: var snappedTrenches = {}; michael@0: michael@0: var snapRadius = (Keys.meta ? 0 : Trenches.defaultRadius); michael@0: if (rect.left < swb.left + snapRadius ) { michael@0: if (stationaryCorner.indexOf('right') > -1 && !assumeConstantSize) michael@0: rect.width = rect.right - swb.left; michael@0: rect.left = swb.left; michael@0: update = true; michael@0: updateX = true; michael@0: snappedTrenches.left = 'edge'; michael@0: } michael@0: michael@0: if (rect.right > swb.right - snapRadius) { michael@0: if (updateX || !assumeConstantSize) { michael@0: var newWidth = swb.right - rect.left; michael@0: if (keepProportional) michael@0: rect.height = rect.height * newWidth / rect.width; michael@0: rect.width = newWidth; michael@0: update = true; michael@0: } else if (!updateX || !Trenches.preferLeft) { michael@0: rect.left = swb.right - rect.width; michael@0: update = true; michael@0: } michael@0: snappedTrenches.right = 'edge'; michael@0: delete snappedTrenches.left; michael@0: } michael@0: if (rect.top < swb.top + snapRadius) { michael@0: if (stationaryCorner.indexOf('bottom') > -1 && !assumeConstantSize) michael@0: rect.height = rect.bottom - swb.top; michael@0: rect.top = swb.top; michael@0: update = true; michael@0: updateY = true; michael@0: snappedTrenches.top = 'edge'; michael@0: } michael@0: if (rect.bottom > swb.bottom - snapRadius) { michael@0: if (updateY || !assumeConstantSize) { michael@0: var newHeight = swb.bottom - rect.top; michael@0: if (keepProportional) michael@0: rect.width = rect.width * newHeight / rect.height; michael@0: rect.height = newHeight; michael@0: update = true; michael@0: } else if (!updateY || !Trenches.preferTop) { michael@0: rect.top = swb.bottom - rect.height; michael@0: update = true; michael@0: } michael@0: snappedTrenches.top = 'edge'; michael@0: delete snappedTrenches.bottom; michael@0: } michael@0: michael@0: if (update) { michael@0: rect.snappedTrenches = snappedTrenches; michael@0: return rect; michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: // ---------- michael@0: // Function: drag michael@0: // Called in response to an draggable "drag" event. michael@0: drag: function Drag_drag(event) { michael@0: this.snap(UI.rtl ? 'topright' : 'topleft', true); michael@0: michael@0: if (this.parent && this.parent.expanded) { michael@0: var distance = this.startPosition.distance(new Point(event.clientX, event.clientY)); michael@0: if (distance > 100) { michael@0: this.parent.remove(this.item); michael@0: this.parent.collapse(); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: // ---------- michael@0: // Function: stop michael@0: // Called in response to an draggable "stop" event. michael@0: // michael@0: // Parameters: michael@0: // immediately - bool for doing the pushAway immediately, without animation michael@0: stop: function Drag_stop(immediately) { michael@0: Trenches.hideGuides(); michael@0: this.item.isDragging = false; michael@0: michael@0: if (this.parent && this.parent != this.item.parent) michael@0: this.parent.closeIfEmpty(); michael@0: michael@0: if (this.parent && this.parent.expanded) michael@0: this.parent.arrange(); michael@0: michael@0: if (this.item.parent) michael@0: this.item.parent.arrange(); michael@0: michael@0: if (this.item.isAGroupItem) { michael@0: this.item.setZ(drag.zIndex); michael@0: drag.zIndex++; michael@0: michael@0: this.item.pushAway(immediately); michael@0: } michael@0: michael@0: Trenches.disactivate(); michael@0: } michael@0: };