UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

634 lines (517 loc) 16.1 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2007 David Pérez Carmona 2004-2008 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: * David Perez Carmona (david-perez) * Sebastian Werner (wpbasti) * Fabian Jakobs (fjakobs) * Martin Wittemann (martinwittemann) ************************************************************************ */ /** * Provides resizing behavior to any widget. */ qx.Mixin.define("qx.ui.core.MResizable", { /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ construct : function() { // Register listeners to the content var content = this.getContentElement(); content.addListener("pointerdown", this.__onResizePointerDown, this, true); content.addListener("pointerup", this.__onResizePointerUp, this); content.addListener("pointermove", this.__onResizePointerMove, this); content.addListener("pointerout", this.__onResizePointerOut, this); content.addListener("losecapture", this.__onResizeLoseCapture, this); // Get a reference of the drag and drop handler var domElement = content.getDomElement(); if (domElement == null) { domElement = window; } this.__dragDropHandler = qx.event.Registration.getManager(domElement).getHandler(qx.event.handler.DragDrop); }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties : { /** Whether the top edge is resizable */ resizableTop : { check : "Boolean", init : true }, /** Whether the right edge is resizable */ resizableRight : { check : "Boolean", init : true }, /** Whether the bottom edge is resizable */ resizableBottom : { check : "Boolean", init : true }, /** Whether the left edge is resizable */ resizableLeft : { check : "Boolean", init : true }, /** * Property group to configure the resize behaviour for all edges at once */ resizable : { group : [ "resizableTop", "resizableRight", "resizableBottom", "resizableLeft" ], mode : "shorthand" }, /** The tolerance to activate resizing */ resizeSensitivity : { check : "Integer", init : 5 }, /** Whether a frame replacement should be used during the resize sequence */ useResizeFrame : { check : "Boolean", init : true } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { __dragDropHandler : null, __resizeFrame : null, __resizeActive : null, __resizeLeft : null, __resizeTop : null, __resizeStart : null, __resizeRange : null, RESIZE_TOP : 1, RESIZE_BOTTOM : 2, RESIZE_LEFT : 4, RESIZE_RIGHT : 8, /* --------------------------------------------------------------------------- CORE FEATURES --------------------------------------------------------------------------- */ /** * Get the widget, which draws the resize/move frame. The resize frame is * shared by all widgets and is added to the root widget. * * @return {qx.ui.core.Widget} The resize frame */ _getResizeFrame : function() { var frame = this.__resizeFrame; if (!frame) { frame = this.__resizeFrame = new qx.ui.core.Widget(); frame.setAppearance("resize-frame"); frame.exclude(); qx.core.Init.getApplication().getRoot().add(frame); } return frame; }, /** * Creates, shows and syncs the frame with the widget. */ __showResizeFrame : function() { var location = this.getContentLocation(); var frame = this._getResizeFrame(); frame.setUserBounds( location.left, location.top, location.right - location.left, location.bottom - location.top ); frame.show(); frame.setZIndex(this.getZIndex()+1); }, /* --------------------------------------------------------------------------- RESIZE SUPPORT --------------------------------------------------------------------------- */ /** * Computes the new boundaries at each interval * of the resize sequence. * * @param e {qx.event.type.Pointer} Last pointer event * @return {Map} A map with the computed boundaries */ __computeResizeResult : function(e) { // Detect mode var resizeActive = this.__resizeActive; // Read size hint var hint = this.getSizeHint(); var range = this.__resizeRange; // Read original values var start = this.__resizeStart; var width = start.width; var height = start.height; var left = start.left; var top = start.top; var diff; if ( (resizeActive & this.RESIZE_TOP) || (resizeActive & this.RESIZE_BOTTOM) ) { diff = Math.max(range.top, Math.min(range.bottom, e.getDocumentTop())) - this.__resizeTop; if (resizeActive & this.RESIZE_TOP) { height -= diff; } else { height += diff; } if (height < hint.minHeight) { height = hint.minHeight; } else if (height > hint.maxHeight) { height = hint.maxHeight; } if (resizeActive & this.RESIZE_TOP) { top += start.height - height; } } if ( (resizeActive & this.RESIZE_LEFT) || (resizeActive & this.RESIZE_RIGHT) ) { diff = Math.max(range.left, Math.min(range.right, e.getDocumentLeft())) - this.__resizeLeft; if (resizeActive & this.RESIZE_LEFT) { width -= diff; } else { width += diff; } if (width < hint.minWidth) { width = hint.minWidth; } else if (width > hint.maxWidth) { width = hint.maxWidth; } if (resizeActive & this.RESIZE_LEFT) { left += start.width - width; } } return { // left and top of the visible widget viewportLeft : left, viewportTop : top, parentLeft : start.bounds.left + left - start.left, parentTop : start.bounds.top + top - start.top, // dimensions of the visible widget width : width, height : height }; }, /** * @type {Map} Maps internal states to cursor symbols to use * * @lint ignoreReferenceField(__resizeCursors) */ __resizeCursors : { 1 : "n-resize", 2 : "s-resize", 4 : "w-resize", 8 : "e-resize", 5 : "nw-resize", 6 : "sw-resize", 9 : "ne-resize", 10 : "se-resize" }, /** * Updates the internally stored resize mode * * @param e {qx.event.type.Pointer} Last pointer event */ __computeResizeMode : function(e) { var location = this.getContentLocation(); var pointerTolerance = this.getResizeSensitivity(); var pointerLeft = e.getDocumentLeft(); var pointerTop = e.getDocumentTop(); var resizeActive = this.__computeResizeActive( location, pointerLeft, pointerTop, pointerTolerance ); // check again in case we have a corner [BUG #1200] if (resizeActive > 0) { // this is really a | (or)! resizeActive = resizeActive | this.__computeResizeActive( location, pointerLeft, pointerTop, pointerTolerance * 2 ); } this.__resizeActive = resizeActive; }, /** * Internal helper for computing the proper resize action based on the * given parameters. * * @param location {Map} The current location of the widget. * @param pointerLeft {Integer} The left position of the pointer. * @param pointerTop {Integer} The top position of the pointer. * @param pointerTolerance {Integer} The desired distance to the edge. * @return {Integer} The resize active number. */ __computeResizeActive : function(location, pointerLeft, pointerTop, pointerTolerance) { var resizeActive = 0; // TOP if ( this.getResizableTop() && Math.abs(location.top - pointerTop) < pointerTolerance && pointerLeft > location.left - pointerTolerance && pointerLeft < location.right + pointerTolerance ) { resizeActive += this.RESIZE_TOP; // BOTTOM } else if ( this.getResizableBottom() && Math.abs(location.bottom - pointerTop) < pointerTolerance && pointerLeft > location.left - pointerTolerance && pointerLeft < location.right + pointerTolerance ) { resizeActive += this.RESIZE_BOTTOM; } // LEFT if ( this.getResizableLeft() && Math.abs(location.left - pointerLeft) < pointerTolerance && pointerTop > location.top - pointerTolerance && pointerTop < location.bottom + pointerTolerance ) { resizeActive += this.RESIZE_LEFT; // RIGHT } else if ( this.getResizableRight() && Math.abs(location.right - pointerLeft) < pointerTolerance && pointerTop > location.top - pointerTolerance && pointerTop < location.bottom + pointerTolerance ) { resizeActive += this.RESIZE_RIGHT; } return resizeActive; }, /* --------------------------------------------------------------------------- RESIZE EVENT HANDLERS --------------------------------------------------------------------------- */ /** * Event handler for the pointer down event * * @param e {qx.event.type.Pointer} The pointer event instance */ __onResizePointerDown : function(e) { // Check for active resize if (!this.__resizeActive || !this.getEnabled() || e.getPointerType() == "touch") { return; } // Add resize state this.addState("resize"); // Store pointer coordinates this.__resizeLeft = e.getDocumentLeft(); this.__resizeTop = e.getDocumentTop(); // Cache bounds var location = this.getContentLocation(); var bounds = this.getBounds(); this.__resizeStart = { top : location.top, left : location.left, width : location.right - location.left, height : location.bottom - location.top, bounds : qx.lang.Object.clone(bounds) }; // Compute range var parent = this.getLayoutParent(); var parentLocation = parent.getContentLocation(); var parentBounds = parent.getBounds(); this.__resizeRange = { left : parentLocation.left, top : parentLocation.top, right : parentLocation.left + parentBounds.width, bottom : parentLocation.top + parentBounds.height }; // Show frame if configured this way if (this.getUseResizeFrame()) { this.__showResizeFrame(); } // Enable capturing this.capture(); // Stop event e.stop(); }, /** * Event handler for the pointer up event * * @param e {qx.event.type.Pointer} The pointer event instance */ __onResizePointerUp : function(e) { // Check for active resize if (!this.hasState("resize") || !this.getEnabled() || e.getPointerType() == "touch") { return; } // Hide frame afterwards if (this.getUseResizeFrame()) { this._getResizeFrame().exclude(); } // Compute bounds var bounds = this.__computeResizeResult(e); // Sync with widget this.setWidth(bounds.width); this.setHeight(bounds.height); // Update coordinate in canvas if (this.getResizableLeft() || this.getResizableTop()) { this.setLayoutProperties({ left : bounds.parentLeft, top : bounds.parentTop }); } // Clear mode this.__resizeActive = 0; // Remove resize state this.removeState("resize"); // Reset cursor this.resetCursor(); this.getApplicationRoot().resetGlobalCursor(); // Disable capturing this.releaseCapture(); e.stopPropagation(); }, /** * Event listener for <code>losecapture</code> event. * * @param e {qx.event.type.Event} Lose capture event */ __onResizeLoseCapture : function(e) { // Check for active resize if (!this.__resizeActive) { return; } // Reset cursor this.resetCursor(); this.getApplicationRoot().resetGlobalCursor(); // Remove drag state this.removeState("move"); // Hide frame afterwards if (this.getUseResizeFrame()) { this._getResizeFrame().exclude(); } }, /** * Event handler for the pointer move event * * @param e {qx.event.type.Pointer} The pointer event instance */ __onResizePointerMove : function(e) { if (!this.getEnabled() || e.getPointerType() == "touch") { return; } if (this.hasState("resize")) { var bounds = this.__computeResizeResult(e); // Update widget if (this.getUseResizeFrame()) { // Sync new bounds to frame var frame = this._getResizeFrame(); frame.setUserBounds(bounds.viewportLeft, bounds.viewportTop, bounds.width, bounds.height); } else { // Update size this.setWidth(bounds.width); this.setHeight(bounds.height); // Update coordinate in canvas if (this.getResizableLeft() || this.getResizableTop()) { this.setLayoutProperties({ left : bounds.parentLeft, top : bounds.parentTop }); } } // Full stop for event e.stopPropagation(); } else if (!this.hasState("maximized") && !this.__dragDropHandler.isSessionActive()) { this.__computeResizeMode(e); var resizeActive = this.__resizeActive; var root = this.getApplicationRoot(); if (resizeActive) { var cursor = this.__resizeCursors[resizeActive]; this.setCursor(cursor); root.setGlobalCursor(cursor); } else if (this.getCursor()) { this.resetCursor(); root.resetGlobalCursor(); } } }, /** * Event handler for the pointer out event * * @param e {qx.event.type.Pointer} The pointer event instance */ __onResizePointerOut : function(e) { if (e.getPointerType() == "touch") { return; } // When the pointer left the window and resizing is not yet // active we must be sure to (especially) reset the global // cursor. if (this.getCursor() && !this.hasState("resize")) { this.resetCursor(); this.getApplicationRoot().resetGlobalCursor(); } } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct : function() { if(this.getCursor()) { this.getApplicationRoot().resetGlobalCursor(); } if (this.__resizeFrame != null && !qx.core.ObjectRegistry.inShutDown) { this.__resizeFrame.destroy(); this.__resizeFrame = null; } this.__dragDropHandler = null; } });