UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

601 lines (509 loc) 16 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() { // 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 ***************************************************************************** */ /* eslint-disable @qooxdoo/qx/no-refs-in-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() { 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() { 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(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(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(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(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(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(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(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(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() { if (this.getCursor()) { this.getApplicationRoot().resetGlobalCursor(); } if (this.__resizeFrame != null && !qx.core.ObjectRegistry.inShutDown) { this.__resizeFrame.destroy(); this.__resizeFrame = null; } this.__dragDropHandler = null; } });