UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

374 lines (329 loc) 11.3 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2013 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Richard Sternagel (rsternagel) ************************************************************************ */ /** * Provides scrolling ability during drag session to the widget. */ qx.Mixin.define("qx.ui.core.MDragDropScrolling", { /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ construct : function() { var widget = this; if (this instanceof qx.ui.core.DragDropScrolling) { widget = this._getWidget(); } widget.addListener("drag", this.__onDrag, this); widget.addListener("dragend", this.__onDragend, this); this.__xDirs = ["left", "right"]; this.__yDirs = ["top", "bottom"]; }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties : { /** The threshold for the x-axis (in pixel) to activate scrolling at the edges. */ dragScrollThresholdX : { check : "Integer", init : 30 }, /** The threshold for the y-axis (in pixel) to activate scrolling at the edges. */ dragScrollThresholdY : { check : "Integer", init : 30 }, /** The factor for slowing down the scrolling. */ dragScrollSlowDownFactor : { check : "Float", init : 0.1 } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { __dragScrollTimer : null, __xDirs : null, __yDirs : null, /** * Finds the first scrollable parent (in the parent chain). * * @param widget {qx.ui.core.LayoutItem} The widget to start from. * @return {qx.ui.core.Widget} A scrollable widget. */ _findScrollableParent : function(widget) { var cur = widget; if (cur === null) { return null; } while (cur.getLayoutParent()) { cur = cur.getLayoutParent(); if (this._isScrollable(cur)) { return cur; } } return null; }, /** * Whether the widget is scrollable. * * @param widget {qx.ui.core.Widget} The widget to check. * @return {Boolean} Whether the widget is scrollable. */ _isScrollable : function(widget) { return qx.Class.hasMixin(widget.constructor, qx.ui.core.scroll.MScrollBarFactory); }, /** * Gets the bounds of the given scrollable. * * @param scrollable {qx.ui.core.Widget} Scrollable which has scrollbar child controls. * @return {Map} A map with all four bounds (e.g. {"left":0, "top":20, "right":0, "bottom":80}). */ _getBounds : function(scrollable) { var bounds = scrollable.getContentLocation(); // the scrollable may dictate a nested widget for more precise bounds if (scrollable.getScrollAreaContainer) { bounds = scrollable.getScrollAreaContainer().getContentLocation(); } return bounds; }, /** * Gets the edge type or null if the pointer isn't within one of the thresholds. * * @param diff {Map} Difference map with all for edgeTypes. * @param thresholdX {Number} x-axis threshold. * @param thresholdY {Number} y-axis threshold. * @return {String} One of the four edgeTypes ('left', 'right', 'top', 'bottom'). */ _getEdgeType : function(diff, thresholdX, thresholdY) { if ((diff.left * -1) <= thresholdX && diff.left < 0) { return "left"; } else if ((diff.top * -1) <= thresholdY && diff.top < 0) { return "top"; } else if (diff.right <= thresholdX && diff.right > 0) { return "right"; } else if (diff.bottom <= thresholdY && diff.bottom > 0) { return "bottom"; } else { return null; } }, /** * Gets the axis ('x' or 'y') by the edge type. * * @param edgeType {String} One of the four edgeTypes ('left', 'right', 'top', 'bottom'). * @throws {Error} If edgeType is not one of the distinct four ones. * @return {String} Returns 'y' or 'x'. */ _getAxis : function(edgeType) { if (this.__xDirs.indexOf(edgeType) !== -1) { return "x"; } else if (this.__yDirs.indexOf(edgeType) !== -1) { return "y"; } else { throw new Error("Invalid edge type given ("+edgeType+"). Must be: 'left', 'right', 'top' or 'bottom'"); } }, /** * Gets the threshold amount by edge type. * * @param edgeType {String} One of the four edgeTypes ('left', 'right', 'top', 'bottom'). * @return {Number} The threshold of the x or y axis. */ _getThresholdByEdgeType : function(edgeType) { if (this.__xDirs.indexOf(edgeType) !== -1) { return this.getDragScrollThresholdX(); } else if(this.__yDirs.indexOf(edgeType) !== -1) { return this.getDragScrollThresholdY(); } }, /** * Whether the scrollbar is visible. * * @param scrollable {qx.ui.core.Widget} Scrollable which has scrollbar child controls. * @param axis {String} Can be 'y' or 'x'. * @return {Boolean} Whether the scrollbar is visible. */ _isScrollbarVisible : function(scrollable, axis) { if (scrollable && scrollable._isChildControlVisible) { return scrollable._isChildControlVisible("scrollbar-"+axis); } else { return false; } }, /** * Whether the scrollbar is exceeding it's maximum position. * * @param scrollbar {qx.ui.core.scroll.IScrollBar} Scrollbar to check. * @param axis {String} Can be 'y' or 'x'. * @param amount {Number} Amount to scroll which may be negative. * @return {Boolean} Whether the amount will exceed the scrollbar max position. */ _isScrollbarExceedingMaxPos : function(scrollbar, axis, amount) { var newPos = 0; if (!scrollbar) { return true; } newPos = scrollbar.getPosition() + amount; return (newPos > scrollbar.getMaximum() || newPos < 0); }, /** * Calculates the threshold exceedance (which may be negative). * * @param diff {Number} Difference value of one edgeType. * @param threshold {Number} x-axis or y-axis threshold. * @return {Number} Threshold exceedance amount (positive or negative). */ _calculateThresholdExceedance : function(diff, threshold) { var amount = threshold - Math.abs(diff); return diff < 0 ? (amount * -1) : amount; }, /** * Calculates the scroll amount (which may be negative). * The amount is influenced by the scrollbar size (bigger = faster) * the exceedanceAmount (bigger = faster) and the slowDownFactor. * * @param scrollbarSize {Number} Size of the scrollbar. * @param exceedanceAmount {Number} Threshold exceedance amount (positive or negative). * @return {Number} Scroll amount (positive or negative). */ _calculateScrollAmount : function(scrollbarSize, exceedanceAmount) { return Math.floor(((scrollbarSize / 100) * exceedanceAmount) * this.getDragScrollSlowDownFactor()); }, /** * Scrolls the given scrollable on the given axis for the given amount. * * @param scrollable {qx.ui.core.Widget} Scrollable which has scrollbar child controls. * @param axis {String} Can be 'y' or 'x'. * @param exceedanceAmount {Number} Threshold exceedance amount (positive or negative). */ _scrollBy : function(scrollable, axis, exceedanceAmount) { var scrollbar = scrollable.getChildControl("scrollbar-"+axis, true); if (!scrollbar) { return; } var bounds = scrollbar.getBounds(), scrollbarSize = axis === "x" ? bounds.width : bounds.height, amount = this._calculateScrollAmount(scrollbarSize, exceedanceAmount); if (this._isScrollbarExceedingMaxPos(scrollbar, axis, amount)) { this.__dragScrollTimer.stop(); } scrollbar.scrollBy(amount); }, /* --------------------------------------------------------------------------- EVENT HANDLERS --------------------------------------------------------------------------- */ /** * Event handler for the drag event. * * @param e {qx.event.type.Drag} The drag event instance. */ __onDrag : function(e) { if (this.__dragScrollTimer) { // stop last scroll action this.__dragScrollTimer.stop(); } var target; if (e.getOriginalTarget() instanceof qx.ui.core.Widget) { target = e.getOriginalTarget(); } else { target = qx.ui.core.Widget.getWidgetByElement(e.getOriginalTarget()); } if (!target) { return; } var scrollable; if (this._isScrollable(target)) { scrollable = target; } else { scrollable = this._findScrollableParent(target); } while (scrollable) { var bounds = this._getBounds(scrollable), xPos = e.getDocumentLeft(), yPos = e.getDocumentTop(), diff = { "left": bounds.left - xPos, "right": bounds.right - xPos, "top": bounds.top - yPos, "bottom": bounds.bottom - yPos }, edgeType = null, axis = "", exceedanceAmount = 0; edgeType = this._getEdgeType(diff, this.getDragScrollThresholdX(), this.getDragScrollThresholdY()); if (!edgeType) { scrollable = this._findScrollableParent(scrollable); continue; } axis = this._getAxis(edgeType); if (this._isScrollbarVisible(scrollable, axis)) { exceedanceAmount = this._calculateThresholdExceedance(diff[edgeType], this._getThresholdByEdgeType(edgeType)); if (this.__dragScrollTimer) { this.__dragScrollTimer.dispose(); } this.__dragScrollTimer = new qx.event.Timer(50); this.__dragScrollTimer.addListener("interval", function(scrollable, axis, amount) { this._scrollBy(scrollable, axis, amount); }.bind(this, scrollable, axis, exceedanceAmount)); this.__dragScrollTimer.start(); e.stopPropagation(); return; } else { scrollable = this._findScrollableParent(scrollable); } } }, /** * Event handler for the dragend event. * * @param e {qx.event.type.Drag} The drag event instance. */ __onDragend : function(e) { if (this.__dragScrollTimer) { this.__dragScrollTimer.stop(); } } }, destruct : function() { if (this.__dragScrollTimer) { this.__dragScrollTimer.dispose(); } } });