/** * Copyright 2011 ArcBees Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.code.gwt.crop.client; import gwtupload.client.PreloadedImage; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JsArray; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Cursor; import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.Touch; import com.google.gwt.event.dom.client.LoadEvent; import com.google.gwt.event.dom.client.LoadHandler; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseMoveHandler; import com.google.gwt.event.dom.client.MouseOutEvent; import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.event.dom.client.TouchEndEvent; import com.google.gwt.event.dom.client.TouchEndHandler; import com.google.gwt.event.dom.client.TouchMoveEvent; import com.google.gwt.event.dom.client.TouchMoveHandler; import com.google.gwt.event.dom.client.TouchStartEvent; import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Image; /** * GWT Cropper - widget allowing to select any area on the top of a picture in * order to crop it. * * @author ilja * */ public class GWTCropper extends HTMLPanel implements MouseMoveHandler, MouseUpHandler, MouseOutHandler, TouchMoveHandler, TouchEndHandler { private final ICropperStyleSource bundleResources = GWT .create(ICropperStyleSource.class); // canvas sizes private int nOuterWidth; private int nOuterHeight; // selection coordinates private int nInnerX; private int nInnerY; private int nInnerWidth; private int nInnerHeight; private boolean isDown = false; private byte action = Constants.DRAG_NONE; // initials to provide crop actions private int initW = -1; private int initH = -1; // X and Y coordinates of cursor before dragging private int initX = -1; private int initY = -1; private int offsetX = -1; private int offsetY = -1; // instances to canvas and selection area, available for the cropper private final AbsolutePanel _container; private AbsolutePanel handlesContainer; private AbsolutePanel selectionContainer = new AbsolutePanel(); private HTML draggableBackground; // settings private float aspectRatio = 0; // minimum size of height or width. Just to prevent selection area to be // shrunk to a dot private final int MIN_SIZE = 30; /** * Constructor. Requires URL to the full image to be cropped * * @param strImageURL */ public GWTCropper(PreloadedImage image1) { super(""); final PreloadedImage image = image1; bundleResources.css().ensureInjected(); this._container = new AbsolutePanel(); this._container.add(image, 0, 0); this.add(this._container); addSelection(image); addDomHandler(this, MouseMoveEvent.getType()); addDomHandler(this, MouseUpEvent.getType()); addDomHandler(this, MouseOutEvent.getType()); addDomHandler(this, TouchMoveEvent.getType()); addDomHandler(this, TouchEndEvent.getType()); } public GWTCropper(String strImageURL) { super(""); bundleResources.css().ensureInjected(); this._container = new AbsolutePanel(); this.addCanvas(strImageURL); addDomHandler(this, MouseMoveEvent.getType()); addDomHandler(this, MouseUpEvent.getType()); addDomHandler(this, MouseOutEvent.getType()); addDomHandler(this, TouchMoveEvent.getType()); addDomHandler(this, TouchEndEvent.getType()); } // ---------- Public API ------------------ /** * <p> * Sets the aspect ratio (proportion of width to height) for the selection. * </p> * * <p> * Examples: * <ul> * <li><b>Default</b> is 0, it means, that the selection can have any shape. * </li> * <li>Ratio is 1/1=1, then the selection will be square.</li> * <li>Ratio 2/1=2, then the selection will be rectangular where width is * twice longer than height</li> * <li>Ratio 1/2=0.5, then the selection will be rectangular where height is * twice higher than width</li> * </ul> * </p> * * <p> * <i>Usage example:</i> You can declare a side proportion in this way: <br/> * * <pre> * cropper.setAspectRatio((float) 1 / 2); * </pre> * * </p> * * @param acpectRatio * - float value, proportion width/height */ public void setAspectRatio(float acpectRatio) { this.aspectRatio = acpectRatio; } public float getAspectRatio() { return this.aspectRatio; } /** * Get the X coordinate of the selection top left corner (CSS parameter: * left) * * @return X coordinate */ public int getSelectionXCoordinate() { return this.nInnerX; } /** * Get the Y coordinate of the selection top left corner (CSS parameter: * top) * * @return Y coordinate */ public int getSelectionYCoordinate() { return this.nInnerY; } /** * Get the width of the selection area * * @return width in pixels */ public int getSelectionWidth() { return this.nInnerWidth; } /** * Get the height of the selection area * * @return height in pixels */ public int getSelectionHeight() { return this.nInnerHeight; } /** * Get the canvas height (original image you wish to crop) * * @return height in PX */ public int getCanvasHeight() { return this.nOuterHeight; } /** * Get the canvas width (original image you wish to crop) * * @return width in PX */ public int getCanvasWidth() { return this.nOuterWidth; } // --------- private methods ------------ /** * Adds a canvas with background image. * * @param src * - image URL */ private void addCanvas(final String src) { super.setStyleName(bundleResources.css().base()); final Image image = new Image(src); image.setStyleName(bundleResources.css().imageCanvas()); image.addLoadHandler(new LoadHandler() { @Override public void onLoad(LoadEvent event) { nOuterWidth = image.getWidth(); nOuterHeight = image.getHeight(); _container.setWidth(nOuterWidth + "px"); _container.setHeight(nOuterHeight + "px"); addSelection(src); } }); this._container.add(image, 0, 0); this.add(this._container); } /** * Adds initial selection * */ private void addSelection(final String src) { selectionContainer.addStyleName(this.bundleResources.css().selection()); // set initial coordinates this.nInnerX = (int) (nOuterWidth * 0.2); this.nInnerY = (int) (nOuterHeight * 0.2); this.nInnerWidth = (int) (nOuterWidth * 0.2); this.nInnerHeight = (int) ((this.aspectRatio == 0) ? (nOuterHeight * 0.2) : (nInnerWidth / aspectRatio)); selectionContainer.setWidth(this.nInnerWidth + "px"); selectionContainer.setHeight(this.nInnerHeight + "px"); // add background image for selection selectionContainer.add(new Image(src), -this.nInnerX, -this.nInnerY); this._container.add(selectionContainer, this.nInnerX, this.nInnerY); this.buildSelectionArea(selectionContainer); this._container.add(this.handlesContainer, this.nInnerX, this.nInnerY); } private void addSelection(PreloadedImage image) { selectionContainer.addStyleName(this.bundleResources.css().selection()); // set initial coordinates this.nInnerX = (int) (nOuterWidth * 0.2); this.nInnerY = (int) (nOuterHeight * 0.2); this.nInnerWidth = (int) (nOuterWidth * 0.2); this.nInnerHeight = (int) ((this.aspectRatio == 0) ? (nOuterHeight * 0.2) : (nInnerWidth / aspectRatio)); selectionContainer.setWidth(this.nInnerWidth + "px"); selectionContainer.setHeight(this.nInnerHeight + "px"); // add background image for selection selectionContainer.add(image, -this.nInnerX, -this.nInnerY); this._container.add(selectionContainer, this.nInnerX, this.nInnerY); this.buildSelectionArea(selectionContainer); this._container.add(this.handlesContainer, this.nInnerX, this.nInnerY); } /** * Add special handles. User can drag these handle and change form of the * selected area * * @return container with handles and needed attached event listeners */ private AbsolutePanel buildSelectionArea( final AbsolutePanel selectionContainer) { // add selection handles this.handlesContainer = new AbsolutePanel(); this.handlesContainer.setWidth(this.nInnerWidth + "px"); this.handlesContainer.setHeight(this.nInnerHeight + "px"); this.handlesContainer.setStyleName(this.bundleResources.css() .handlesContainer()); this.handlesContainer.getElement().getStyle() .setOverflow(Overflow.VISIBLE); // append background this.draggableBackground = this.appendDraggableBackground(); // find the center of handle to make an offset final int h = this.bundleResources.css().handleSize() / 2; // append top left corner handler this.appendHandle(Cursor.NW_RESIZE, Constants.DRAG_TOP_LEFT_CORNER, -h, 0, 0, -h); // append top right corner handler this.appendHandle(Cursor.NE_RESIZE, Constants.DRAG_TOP_RIGHT_CORNER, -h, -h, 0, 0); // append bottom left corner handler this.appendHandle(Cursor.SW_RESIZE, Constants.DRAG_BOTTOM_LEFT_CORNER, 0, 0, -h, -h); // append bottom right corner handler this.appendHandle(Cursor.SE_RESIZE, Constants.DRAG_BOTTOM_RIGHT_CORNER, 0, -h, -h, 0); return handlesContainer; } /** * Creates small draggable selection handle and appends it to the corner of * selection area. User can drag this handle and change shape of the * selection area * * @param cursor * - cursor type for the CSS * @param actionType * - action type for the event processor * @param top * - top value in PX * @param right * - right value in PX * @param bottom * - bottom value in PX * @param left * - left value in PX */ private void appendHandle(Cursor cursor, final byte actionType, int top, int right, int bottom, int left) { HTML handle = new HTML(); handle.setStyleName(this.bundleResources.css().handle()); handle.getElement().getStyle().setCursor(cursor); handle.addMouseDownHandler(new MouseDownHandler() { @Override public void onMouseDown(MouseDownEvent event) { isDown = true; action = actionType; } }); handle.addTouchStartHandler(new TouchStartHandler() { @Override public void onTouchStart(TouchStartEvent event) { isDown = true; action = actionType; } }); if (top != 0) handle.getElement().getStyle().setTop(top, Unit.PX); if (right != 0) handle.getElement().getStyle().setRight(right, Unit.PX); if (bottom != 0) handle.getElement().getStyle().setBottom(bottom, Unit.PX); if (left != 0) handle.getElement().getStyle().setLeft(left, Unit.PX); this.handlesContainer.add(handle); } /** * Append draggable selection background * * @param sc * - container of selection * @param hc * - container of handles */ private HTML appendDraggableBackground() { final HTML backgroundHandle = new HTML(); backgroundHandle.setWidth(this.nInnerWidth + "px"); backgroundHandle.setHeight(this.nInnerHeight + "px"); backgroundHandle.getElement().getStyle().setCursor(Cursor.MOVE); backgroundHandle.addMouseDownHandler(new MouseDownHandler() { @Override public void onMouseDown(MouseDownEvent event) { isDown = true; action = Constants.DRAG_BACKGROUND; } }); backgroundHandle.addTouchStartHandler(new TouchStartHandler() { @Override public void onTouchStart(TouchStartEvent event) { isDown = true; action = Constants.DRAG_BACKGROUND; } }); this.handlesContainer.add(backgroundHandle, 0, 0); return backgroundHandle; } /** * provides dragging action * * @param cursorX * - cursor X-position relatively the canvas * @param cursorY * - cursor Y-position relatively the canvas */ private void provideDragging(int cursorX, int cursorY) { Element el = null; Element el2 = null; Element elImg = null; int futureWidth = 0; int futureHeight = 0; switch (this.action) { case Constants.DRAG_BACKGROUND: if (offsetX == -1) { offsetX = cursorX - _container.getWidgetLeft(this.handlesContainer); } if (offsetY == -1) { offsetY = cursorY - _container.getWidgetTop(this.handlesContainer); } el = this.handlesContainer.getElement(); this.nInnerX = cursorX - offsetX; this.nInnerY = cursorY - offsetY; // don't drag selection out of the canvas borders if (this.nInnerX < 0) this.nInnerX = 0; if (this.nInnerY < 0) this.nInnerY = 0; if (this.nInnerX + this.nInnerWidth > this.nOuterWidth) this.nInnerX = this.nOuterWidth - this.nInnerWidth; if (this.nInnerY + this.nInnerHeight > this.nOuterHeight) this.nInnerY = this.nOuterHeight - this.nInnerHeight; el.getStyle().setLeft(this.nInnerX, Unit.PX); el.getStyle().setTop(this.nInnerY, Unit.PX); el2 = this.selectionContainer.getElement(); el2.getStyle().setLeft(this.nInnerX, Unit.PX); el2.getStyle().setTop(this.nInnerY, Unit.PX); elImg = ((Image) this.selectionContainer.getWidget(0)).getElement(); elImg.getStyle().setLeft(-this.nInnerX, Unit.PX); elImg.getStyle().setTop(-this.nInnerY, Unit.PX); break; case Constants.DRAG_TOP_LEFT_CORNER: if (initX == -1) { initX = _container.getWidgetLeft(this.handlesContainer); initW = nInnerWidth; } if (initY == -1) { initY = _container.getWidgetTop(this.handlesContainer); initH = nInnerHeight; } futureWidth = initW + (initX - cursorX); futureHeight = initH + (initY - cursorY); if (futureWidth < this.MIN_SIZE || futureHeight < this.MIN_SIZE) { return; } this.nInnerWidth = futureWidth; this.nInnerHeight = futureHeight; this.nInnerX = cursorX; this.nInnerY = cursorY; // compensation for specified aspect ratio if (this.aspectRatio != 0) { if (abs(this.initX - this.nInnerX) > abs(this.initY - this.nInnerY)) { int newHeight = (int) (this.nInnerWidth / this.aspectRatio); this.nInnerY -= newHeight - this.nInnerHeight; // to prevent resizing out of the canvas on the Y axes if (this.nInnerY <= 0) { this.nInnerY = 0; newHeight = this.initY + this.initH; this.nInnerWidth = (int) (newHeight * this.aspectRatio); this.nInnerX = this.initX - (int) (this.initY * this.aspectRatio); } this.nInnerHeight = newHeight; } else { int newWidth = (int) (this.nInnerHeight * this.aspectRatio); this.nInnerX -= newWidth - this.nInnerWidth; // to prevent resizing out of the canvas on the X axis if (this.nInnerX < 0) { this.nInnerX = 0; newWidth = this.initX + this.initW; this.nInnerHeight = (int) (newWidth / this.aspectRatio); this.nInnerY = this.initY - (int) (this.initX / this.aspectRatio); } this.nInnerWidth = newWidth; } } el = this.handlesContainer.getElement(); el.getStyle().setLeft(this.nInnerX, Unit.PX); el.getStyle().setTop(this.nInnerY, Unit.PX); el.getStyle().setWidth(nInnerWidth, Unit.PX); el.getStyle().setHeight(nInnerHeight, Unit.PX); el2 = this.selectionContainer.getElement(); el2.getStyle().setLeft(this.nInnerX, Unit.PX); el2.getStyle().setTop(this.nInnerY, Unit.PX); el2.getStyle().setWidth(nInnerWidth, Unit.PX); el2.getStyle().setHeight(nInnerHeight, Unit.PX); elImg = ((Image) this.selectionContainer.getWidget(0)).getElement(); elImg.getStyle().setLeft(-this.nInnerX, Unit.PX); elImg.getStyle().setTop(-this.nInnerY, Unit.PX); Element el3 = this.draggableBackground.getElement(); el3.getStyle().setWidth(nInnerWidth, Unit.PX); el3.getStyle().setHeight(nInnerHeight, Unit.PX); break; case Constants.DRAG_TOP_RIGHT_CORNER: if (initX == -1) { initX = _container.getWidgetLeft(this.handlesContainer) + nInnerWidth; initW = nInnerWidth; } if (initY == -1) { initY = _container.getWidgetTop(this.handlesContainer); initH = nInnerHeight; } futureWidth = initW + (cursorX - initX); futureHeight = initH + (initY - cursorY); if (futureWidth < this.MIN_SIZE || futureHeight < this.MIN_SIZE) { return; } nInnerWidth = futureWidth; nInnerHeight = futureHeight; // compensation for specified aspect ratio if (this.aspectRatio != 0) { if (abs(initX - cursorX) > abs(initY - cursorY)) { // move cursor right, top side has been adjusted // automatically int newHeight = (int) (nInnerWidth / this.aspectRatio); cursorY -= newHeight - nInnerHeight; // to prevent resizing out of the canvas on the Y axes if (cursorY <= 0) { cursorY = 0; newHeight = this.initY + this.initH; this.nInnerWidth = (int) (newHeight * this.aspectRatio); } nInnerHeight = newHeight; } else { // move cursor up, right side has been adjusted // automatically nInnerWidth = (int) (nInnerHeight * this.aspectRatio); // to prevent resizing out of the canvas on the X axis if ((this.nInnerWidth + this.nInnerX) >= this.nOuterWidth) { this.nInnerWidth = this.nOuterWidth - this.nInnerX; this.nInnerHeight = (int) (this.nInnerWidth / this.aspectRatio); this.nInnerY = this.initY - (int) ((this.nOuterWidth - this.nInnerX - this.initW) / this.aspectRatio); cursorY = this.nInnerY; } } } this.nInnerY = cursorY; el = this.handlesContainer.getElement(); el.getStyle().setTop(cursorY, Unit.PX); el.getStyle().setWidth(nInnerWidth, Unit.PX); el.getStyle().setHeight(nInnerHeight, Unit.PX); el2 = this.selectionContainer.getElement(); el2.getStyle().setTop(cursorY, Unit.PX); el2.getStyle().setWidth(nInnerWidth, Unit.PX); el2.getStyle().setHeight(nInnerHeight, Unit.PX); elImg = ((Image) this.selectionContainer.getWidget(0)).getElement(); elImg.getStyle().setTop(-cursorY, Unit.PX); el3 = this.draggableBackground.getElement(); el3.getStyle().setWidth(nInnerWidth, Unit.PX); el3.getStyle().setHeight(nInnerHeight, Unit.PX); break; case Constants.DRAG_BOTTOM_LEFT_CORNER: if (initX == -1) { initX = _container.getWidgetLeft(this.handlesContainer); initW = nInnerWidth; } if (initY == -1) { initY = _container.getWidgetTop(this.handlesContainer) + nInnerHeight; initH = nInnerHeight; } futureWidth = initW + (initX - cursorX); futureHeight = initH + (cursorY - initY); if (futureWidth < this.MIN_SIZE || futureHeight < this.MIN_SIZE) { return; } nInnerWidth = futureWidth; nInnerHeight = futureHeight; // compensation for specified aspect ratio if (this.aspectRatio != 0) { if (abs(initX - cursorX) > abs(initY - cursorY)) { // cursor goes left, bottom side goes down... nInnerHeight = (int) (nInnerWidth / this.aspectRatio); // to prevent resizing out of the canvas on the Y axis if ((this.nInnerHeight + this.nInnerY) >= this.nOuterHeight) { this.nInnerHeight = this.nOuterHeight - this.nInnerY; this.nInnerWidth = (int) (this.nInnerHeight * this.aspectRatio); this.nInnerY = this.nOuterHeight - this.nInnerHeight; cursorX = this.initX - (int) ((this.nOuterHeight - this.initY) * this.aspectRatio); } } else { // cursor goes down, left side goes to left int newWidth = (int) (nInnerHeight * this.aspectRatio); cursorX -= newWidth - nInnerWidth; // to prevent resizing out of the canvas on the X axis if (cursorX <= 0) { newWidth = this.nInnerWidth + this.initX; this.nInnerHeight = (int) (newWidth / this.aspectRatio); cursorX = 0; } nInnerWidth = newWidth; } } this.nInnerX = cursorX; el = this.handlesContainer.getElement(); el.getStyle().setLeft(cursorX, Unit.PX); el.getStyle().setWidth(nInnerWidth, Unit.PX); el.getStyle().setHeight(nInnerHeight, Unit.PX); el2 = this.selectionContainer.getElement(); el2.getStyle().setLeft(cursorX, Unit.PX); el2.getStyle().setWidth(nInnerWidth, Unit.PX); el2.getStyle().setHeight(nInnerHeight, Unit.PX); elImg = ((Image) this.selectionContainer.getWidget(0)).getElement(); elImg.getStyle().setLeft(-cursorX, Unit.PX); el3 = this.draggableBackground.getElement(); el3.getStyle().setWidth(nInnerWidth, Unit.PX); el3.getStyle().setHeight(nInnerHeight, Unit.PX); break; case Constants.DRAG_BOTTOM_RIGHT_CORNER: if (initX == -1) { initX = _container.getWidgetLeft(this.handlesContainer) + nInnerWidth; initW = nInnerWidth; } if (initY == -1) { initY = _container.getWidgetTop(this.handlesContainer) + nInnerHeight; initH = nInnerHeight; } futureWidth = initW + (cursorX - initX); futureHeight = initH + (cursorY - initY); if (futureWidth < this.MIN_SIZE || futureHeight < this.MIN_SIZE) { return; } nInnerWidth = futureWidth; nInnerHeight = futureHeight; // compensation for specified aspect ratio if (this.aspectRatio != 0) { if (abs(initX - cursorX) > abs(initY - cursorY)) { // cursor goes right, bottom side goes down... nInnerHeight = (int) (nInnerWidth / this.aspectRatio); // to prevent resizing out of the canvas on the Y axis if ((this.nInnerHeight + this.nInnerY) >= this.nOuterHeight) { this.nInnerHeight = this.nOuterHeight - this.nInnerY; this.nInnerWidth = (int) (this.nInnerHeight * this.aspectRatio); this.nInnerY = this.nOuterHeight - this.nInnerHeight; cursorX = this.nOuterWidth; } } else { // cursor goes down, right side goes to right nInnerWidth = (int) (nInnerHeight * this.aspectRatio); // to prevent resizing out of the canvas on the X axis if (this.nInnerWidth + this.nInnerX >= this.nOuterWidth) { this.nInnerWidth = this.nOuterWidth - this.nInnerX; this.nInnerHeight = (int) (this.nInnerWidth / this.aspectRatio); cursorX = this.nOuterHeight; } } } el = this.handlesContainer.getElement(); el.getStyle().setWidth(nInnerWidth, Unit.PX); el.getStyle().setHeight(nInnerHeight, Unit.PX); el2 = this.selectionContainer.getElement(); el2.getStyle().setWidth(nInnerWidth, Unit.PX); el2.getStyle().setHeight(nInnerHeight, Unit.PX); el3 = this.draggableBackground.getElement(); el3.getStyle().setWidth(nInnerWidth, Unit.PX); el3.getStyle().setHeight(nInnerHeight, Unit.PX); break; default: break; } } /** * Resets all initial values. * */ private void reset() { this.initX = -1; this.initY = -1; this.initW = -1; this.initH = -1; this.offsetX = -1; this.offsetY = -1; this.action = Constants.DRAG_NONE; } /** * Returns absolute value * * @param value * @return absolute value */ private int abs(int value) { return value >= 0 ? value : -value; } // DOM HANDLERS /** * {@inheritDoc} */ @Override public void onMouseMove(MouseMoveEvent event) { if (this.isDown) { this.provideDragging( event.getRelativeX(this._container.getElement()), event.getRelativeY(this._container.getElement())); } } /** * {@inheritDoc} */ @Override public void onTouchMove(TouchMoveEvent event) { if (this.isDown) { JsArray<Touch> touches = event.getTouches(); if (touches.length() > 0) { int x = touches.get(0).getRelativeX( this._container.getElement()); int y = touches.get(0).getRelativeY( this._container.getElement()); if (x < 0 || y < 0 || x > nOuterWidth || y > nOuterHeight) { /* * There is no such method as "onMouseOut" for touching, so * we can't rely on event handler. We should process * manually these cases, when finger (i.e. cursor) is out of * the cropper area. * * If user moves his finger out of the canvas, then "stick" * the selection to the canvas edges */ if (x < 0) x = 0; if (x > nOuterWidth) x = nOuterWidth; if (y < 0) y = 0; if (y > nOuterHeight) y = nOuterHeight; } this.provideDragging(x, y); } } } /** * Resets the dragging state. After this method, the cropper presumes that * the dragging action is finished. */ private void resetDraggingState() { if (this.isDown) { this.isDown = false; this.reset(); } } /** * {@inheritDoc} */ @Override public void onMouseUp(MouseUpEvent event) { this.resetDraggingState(); } /** * {@inheritDoc} */ @Override public void onMouseOut(MouseOutEvent event) { /* * if cursor is out of canvas and the mouse is clicked, then we want to * "unclick" the mouse button programmatically. Otherwise the selection * would become "sticky". */ this.resetDraggingState(); } /** * {@inheritDoc} */ @Override public void onTouchEnd(TouchEndEvent event) { this.resetDraggingState(); } }