diff options
author | Pekka Hyvönen <pekka@vaadin.com> | 2015-02-24 10:56:00 +0200 |
---|---|---|
committer | Pekka Hyvönen <pekka@vaadin.com> | 2015-02-24 17:23:05 +0200 |
commit | 6db686cd52e2c72387f555523eedf4f81708d0ab (patch) | |
tree | 6c7e56c527c3c507930c39f3c1f2ae0e8c9a617d | |
parent | bff2a3f558ea0416ae48b5595b59057a3475d6b6 (diff) | |
download | vaadin-framework-6db686cd52e2c72387f555523eedf4f81708d0ab.tar.gz vaadin-framework-6db686cd52e2c72387f555523eedf4f81708d0ab.zip |
Auto scroll when dnd reorder columns and cursor close to Grid edge. (#16643)
Change-Id: I38c18d44afb6666677c1593541021d36c8cb2afc
3 files changed, 720 insertions, 27 deletions
diff --git a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java index c8b651bd4e..eec52d5def 100644 --- a/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java +++ b/client/src/com/vaadin/client/ui/dd/DragAndDropHandler.java @@ -47,8 +47,11 @@ public class DragAndDropHandler { public interface DragAndDropCallback { /** * Called when the drag has started. + * + * @param startEvent + * the original event that started the drag */ - void showDragElement(); + void onDragStart(NativeEvent startEvent); /** * Called on drag. @@ -56,12 +59,12 @@ public class DragAndDropHandler { * @param event * the event related to the drag */ - void updateDragElement(NativePreviewEvent event); + void onDragUpdate(NativePreviewEvent event); /** - * Called when the has ended on a drop or cancel. + * Called after the has ended on a drop or cancel. */ - void removeDragElement(); + void onDragEnd(); /** * Called when the drag has ended on a drop. @@ -69,7 +72,7 @@ public class DragAndDropHandler { void onDrop(); /** - * Called when the drag has been canceled before drop. + * Called when the drag has been canceled. */ void onDragCancel(); } @@ -97,7 +100,7 @@ public class DragAndDropHandler { break; case Event.ONMOUSEMOVE: case Event.ONTOUCHMOVE: - callback.updateDragElement(event); + callback.onDragUpdate(event); // prevent text selection on IE event.getNativeEvent().preventDefault(); break; @@ -109,7 +112,7 @@ public class DragAndDropHandler { event.getNativeEvent().preventDefault(); //$FALL-THROUGH$ case Event.ONMOUSEUP: - callback.updateDragElement(event); + callback.onDragUpdate(event); callback.onDrop(); stopDrag(); event.cancel(); @@ -122,14 +125,6 @@ public class DragAndDropHandler { } } - private void cancelDrag(NativePreviewEvent event) { - callback.onDragCancel(); - callback.removeDragElement(); - stopDrag(); - event.cancel(); - event.getNativeEvent().preventDefault(); - } - }; private static Logger getLogger() { @@ -194,7 +189,7 @@ public class DragAndDropHandler { if (Math.abs(startX - currentX) > 3 || Math.abs(startY - currentY) > 3) { removeNativePreviewHandlerRegistration(); - startDrag(event, callback); + startDrag(dragStartingEvent, event, callback); } break; default: @@ -207,15 +202,15 @@ public class DragAndDropHandler { }); } - private void startDrag(NativePreviewEvent event, - DragAndDropCallback callback) { + private void startDrag(NativeEvent startEvent, + NativePreviewEvent triggerEvent, DragAndDropCallback callback) { dragging = true; // just capture something to prevent text selection in IE Event.setCapture(RootPanel.getBodyElement()); this.callback = callback; dragHandlerRegistration = Event.addNativePreviewHandler(dragHandler); - callback.showDragElement(); - callback.updateDragElement(event); + callback.onDragStart(startEvent); + callback.onDragUpdate(triggerEvent); } private void stopDrag() { @@ -226,11 +221,19 @@ public class DragAndDropHandler { } Event.releaseCapture(RootPanel.getBodyElement()); if (callback != null) { - callback.removeDragElement(); + callback.onDragEnd(); callback = null; } } + private void cancelDrag(NativePreviewEvent event) { + callback.onDragCancel(); + callback.onDragEnd(); + stopDrag(); + event.cancel(); + event.getNativeEvent().preventDefault(); + } + private void removeNativePreviewHandlerRegistration() { if (dragStartNativePreviewHandlerRegistration != null) { dragStartNativePreviewHandlerRegistration.removeHandler(); diff --git a/client/src/com/vaadin/client/widget/grid/AutoScroller.java b/client/src/com/vaadin/client/widget/grid/AutoScroller.java new file mode 100644 index 0000000000..8f4e2036fc --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/AutoScroller.java @@ -0,0 +1,649 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.client.widget.grid; + +import com.google.gwt.animation.client.AnimationScheduler; +import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback; +import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle; +import com.google.gwt.dom.client.BrowserEvents; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.TableElement; +import com.google.gwt.dom.client.TableSectionElement; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.widgets.Grid; + +/** + * A class for handling automatic scrolling vertically / horizontally in the + * Grid when the cursor is close enough the edge of the body of the grid, + * depending on the scroll direction chosen. + * + * @since + * @author Vaadin Ltd + */ +public class AutoScroller { + + /** + * Callback that notifies when the cursor is on top of a new row or column + * because of the automatic scrolling. + * + * @since + */ + public interface AutoScrollerCallback { + + /** + * Triggered when doing automatic horizontal scrolling. + * + * @param scrollDiff + * the amount of pixels that have been auto scrolled since + * last call + */ + void onHorizontalAutoScroll(int scrollDiff); + + /** + * Triggered when doing automatic vertical scrolling. + * + * @param scrollDiff + * the amount of pixels that have been auto scrolled since + * last call + */ + void onVerticalAutoScroll(int scrollDiff); + } + + public enum ScrollAxis { + VERTICAL, HORIZONTAL + }; + + /** The maximum number of pixels per second to autoscroll. */ + private static final int SCROLL_TOP_SPEED_PX_SEC = 500; + + /** + * The minimum area where the grid doesn't scroll while the pointer is + * pressed. + */ + private static final int MIN_NO_AUTOSCROLL_AREA_PX = 50; + + /** The size of the autoscroll area, both top/left and bottom/right. */ + private int scrollAreaPX = 100; + + /** + * This class's main objective is to listen when to stop autoscrolling, and + * make sure everything stops accordingly. + */ + private class TouchEventHandler implements NativePreviewHandler { + @Override + public void onPreviewNativeEvent(final NativePreviewEvent event) { + /* + * Remember: targetElement is always where touchstart started, not + * where the finger is pointing currently. + */ + switch (event.getTypeInt()) { + case Event.ONTOUCHSTART: { + if (event.getNativeEvent().getTouches().length() == 1) { + /* + * Something has dropped a touchend/touchcancel and the + * scroller is most probably running amok. Let's cancel it + * and pretend that everything's going as expected + * + * Because this is a preview, this code is run before start + * event can be passed to the start(...) method. + */ + stop(); + + /* + * Related TODO: investigate why iOS seems to ignore a + * touchend/touchcancel when frames are dropped, and/or if + * something can be done about that. + */ + } + break; + } + + case Event.ONTOUCHMOVE: + event.cancel(); + break; + + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + // TODO investigate if this works as desired + stop(); + break; + } + } + + } + + /** + * This class's responsibility is to scroll the table while a pointer is + * kept in a scrolling zone. + * <p> + * <em>Techical note:</em> This class is an AnimationCallback because we + * need a timer: when the finger is kept in place while the grid scrolls, we + * still need to be able to make new selections. So, instead of relying on + * events (which won't be fired, since the pointer isn't necessarily + * moving), we do this check on each frame while the pointer is "active" + * (mouse is pressed, finger is on screen). + */ + private class AutoScrollingFrame implements AnimationCallback { + + /** + * If the acceleration gradient area is smaller than this, autoscrolling + * will be disabled (it becomes too quick to accelerate to be usable). + */ + private static final int GRADIENT_MIN_THRESHOLD_PX = 10; + + /** + * The speed at which the gradient area recovers, once scrolling in that + * direction has started. + */ + private static final int SCROLL_AREA_REBOUND_PX_PER_SEC = 1; + private static final double SCROLL_AREA_REBOUND_PX_PER_MS = SCROLL_AREA_REBOUND_PX_PER_SEC / 1000.0d; + + /** + * The lowest y/x-coordinate on the {@link Event#getClientY() client-y} + * or {@link Event#getClientX() client-x} from where we need to start + * scrolling towards the top/left. + */ + private int startBound = -1; + + /** + * The highest y/x-coordinate on the {@link Event#getClientY() client-y} + * or {@link Event#getClientX() client-x} from where we need to + * scrolling towards the bottom. + */ + private int endBound = -1; + + /** + * The area where the selection acceleration takes place. If < + * {@link #GRADIENT_MIN_THRESHOLD_PX}, autoscrolling is disabled + */ + private final int gradientArea; + + /** + * The number of pixels per seconds we currently are scrolling (negative + * is towards the top/left, positive is towards the bottom/right). + */ + private double scrollSpeed = 0; + + private double prevTimestamp = 0; + + /** + * This field stores fractions of pixels to scroll, to make sure that + * we're able to scroll less than one px per frame. + */ + private double pixelsToScroll = 0.0d; + + /** Should this animator be running. */ + private boolean running = false; + + /** The handle in which this instance is running. */ + private AnimationHandle handle; + + /** + * The pointer's pageY (VERTICAL) / pageX (HORIZONTAL) coordinate + * depending on scrolling axis. + */ + private int scrollingAxisPageCoordinate; + + /** @see #doScrollAreaChecks(int) */ + private int finalStartBound; + + /** @see #doScrollAreaChecks(int) */ + private int finalEndBound; + + private boolean scrollAreaShouldRebound = false; + + public AutoScrollingFrame(final int startBound, final int endBound, + final int gradientArea) { + finalStartBound = startBound; + finalEndBound = endBound; + this.gradientArea = gradientArea; + } + + @Override + public void execute(final double timestamp) { + final double timeDiff = timestamp - prevTimestamp; + prevTimestamp = timestamp; + + reboundScrollArea(timeDiff); + + pixelsToScroll += scrollSpeed * (timeDiff / 1000.0d); + final int intPixelsToScroll = (int) pixelsToScroll; + pixelsToScroll -= intPixelsToScroll; + + if (intPixelsToScroll != 0) { + if (scrollDirection == ScrollAxis.VERTICAL) { + grid.setScrollTop(grid.getScrollTop() + intPixelsToScroll); + callback.onVerticalAutoScroll(intPixelsToScroll); + } else { + grid.setScrollLeft(grid.getScrollLeft() + intPixelsToScroll); + callback.onHorizontalAutoScroll(intPixelsToScroll); + } + } + + reschedule(); + } + + /** + * If the scroll are has been offset by the pointer starting out there, + * move it back a bit + */ + private void reboundScrollArea(double timeDiff) { + if (!scrollAreaShouldRebound) { + return; + } + + int reboundPx = (int) Math.ceil(SCROLL_AREA_REBOUND_PX_PER_MS + * timeDiff); + if (startBound < finalStartBound) { + startBound += reboundPx; + startBound = Math.min(startBound, finalStartBound); + updateScrollSpeed(scrollingAxisPageCoordinate); + } else if (endBound > finalEndBound) { + endBound -= reboundPx; + endBound = Math.max(endBound, finalEndBound); + updateScrollSpeed(scrollingAxisPageCoordinate); + } + } + + private void updateScrollSpeed(final int pointerPageCordinate) { + + final double ratio; + if (pointerPageCordinate < startBound) { + final double distance = pointerPageCordinate - startBound; + ratio = Math.max(-1, distance / gradientArea); + } + + else if (pointerPageCordinate > endBound) { + final double distance = pointerPageCordinate - endBound; + ratio = Math.min(1, distance / gradientArea); + } + + else { + ratio = 0; + } + + scrollSpeed = ratio * SCROLL_TOP_SPEED_PX_SEC; + } + + public void start() { + running = true; + reschedule(); + } + + public void stop() { + running = false; + + if (handle != null) { + handle.cancel(); + handle = null; + } + } + + private void reschedule() { + if (running && gradientArea >= GRADIENT_MIN_THRESHOLD_PX) { + handle = AnimationScheduler.get().requestAnimationFrame(this, + grid.getElement()); + } + } + + public void updatePointerCoords(int pageX, int pageY) { + final int pageCordinate; + if (scrollDirection == ScrollAxis.VERTICAL) { + pageCordinate = pageY; + } else { + pageCordinate = pageX; + } + doScrollAreaChecks(pageCordinate); + updateScrollSpeed(pageCordinate); + scrollingAxisPageCoordinate = pageCordinate; + } + + /** + * This method checks whether the first pointer event started in an area + * that would start scrolling immediately, and does some actions + * accordingly. + * <p> + * If it is, that scroll area will be offset "beyond" the pointer (above + * if pointer is towards the top/left, otherwise below/right). + */ + private void doScrollAreaChecks(int pageCordinate) { + /* + * The first run makes sure that neither scroll position is + * underneath the finger, but offset to either direction from + * underneath the pointer. + */ + if (startBound == -1) { + startBound = Math.min(finalStartBound, pageCordinate); + endBound = Math.max(finalEndBound, pageCordinate); + } + + /* + * Subsequent runs make sure that the scroll area grows (but doesn't + * shrink) with the finger, but no further than the final bound. + */ + else { + int oldTopBound = startBound; + if (startBound < finalStartBound) { + startBound = Math.max(startBound, + Math.min(finalStartBound, pageCordinate)); + } + + int oldBottomBound = endBound; + if (endBound > finalEndBound) { + endBound = Math.min(endBound, + Math.max(finalEndBound, pageCordinate)); + } + + final boolean startDidNotMove = oldTopBound == startBound; + final boolean endDidNotMove = oldBottomBound == endBound; + final boolean wasMovement = pageCordinate != scrollingAxisPageCoordinate; + scrollAreaShouldRebound = (startDidNotMove && endDidNotMove && wasMovement); + } + } + } + + /** + * This handler makes sure that pointer movements are handled. + * <p> + * Essentially, a native preview handler is registered (so that selection + * gestures can happen outside of the selection column). The handler itself + * makes sure that it's detached when the pointer is "lifted". + */ + private final NativePreviewHandler scrollPreviewHandler = new NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(final NativePreviewEvent event) { + if (autoScroller == null) { + stop(); + return; + } + + final NativeEvent nativeEvent = event.getNativeEvent(); + int pageY = 0; + int pageX = 0; + switch (event.getTypeInt()) { + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + pageY = WidgetUtil.getTouchOrMouseClientY(nativeEvent); + pageX = WidgetUtil.getTouchOrMouseClientX(nativeEvent); + autoScroller.updatePointerCoords(pageX, pageY); + break; + case Event.ONMOUSEUP: + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + stop(); + break; + } + } + }; + /** The registration info for {@link #scrollPreviewHandler} */ + private HandlerRegistration handlerRegistration; + + /** + * The top/left bound, as calculated from the {@link Event#getClientY() + * client-y} or {@link Event#getClientX() client-x} coordinates. + */ + private double startingBound = -1; + + /** + * The bottom/right bound, as calculated from the {@link Event#getClientY() + * client-y} or or {@link Event#getClientX() client-x} coordinates. + */ + private int endingBound = -1; + + /** The size of the autoscroll acceleration area. */ + private int gradientArea; + + private Grid<?> grid; + + private HandlerRegistration nativePreviewHandlerRegistration; + + private ScrollAxis scrollDirection; + + private AutoScrollingFrame autoScroller; + + private AutoScrollerCallback callback; + + /** + * Creates a new instance for scrolling the given grid. + * + * @param grid + * the grid to auto scroll + */ + public AutoScroller(Grid<?> grid) { + this.grid = grid; + } + + /** + * Starts the automatic scrolling detection. The given event must be a touch + * start or a left mouse triggered mouse down event. + * + * @param startEvent + * the event that starts the automatic scroll + * @param scrollAxis + * the axis along which the scrolling should happen + * @param callback + * the callback for getting info about the automatic scrolling + */ + public void start(final NativeEvent startEvent, ScrollAxis scrollAxis, + AutoScrollerCallback callback) { + if (BrowserEvents.TOUCHSTART.equals(startEvent.getType()) + || (BrowserEvents.MOUSEDOWN.equals(startEvent.getType()) && startEvent + .getButton() == NativeEvent.BUTTON_LEFT)) { + scrollDirection = scrollAxis; + this.callback = callback; + injectNativeHandler(); + start(startEvent); + startEvent.preventDefault(); + startEvent.stopPropagation(); + } else { + throw new IllegalStateException("received unexpected event: " + + startEvent.getType()); + } + } + + /** + * Stops the automatic scrolling. + */ + public void stop() { + if (handlerRegistration != null) { + handlerRegistration.removeHandler(); + handlerRegistration = null; + } + + if (autoScroller != null) { + autoScroller.stop(); + autoScroller = null; + } + + removeNativeHandler(); + } + + /** + * Set the auto scroll area height or width depending on the scrolling axis. + * This is the amount of pixels from the edge of the grid that the scroll is + * triggered. + * <p> + * Defaults to 100px. + * + * @param px + * the height/width for the auto scroll area depending on + * direction + */ + public void setScrollAreaPX(int px) { + scrollAreaPX = px; + } + + /** + * Returns the size of the auto scroll area in pixels. + * <p> + * Defaults to 100px. + * + * @return size in pixels + */ + public int getScrollAreaPX() { + return scrollAreaPX; + } + + private void start(final NativeEvent event) { + /* + * bounds are updated whenever the autoscroll cycle starts, to make sure + * that the widget hasn't changed in size, moved around, or whatnot. + */ + updateScrollBounds(); + + assert handlerRegistration == null : "handlerRegistration was not null"; + assert autoScroller == null : "autoScroller was not null"; + handlerRegistration = Event + .addNativePreviewHandler(scrollPreviewHandler); + autoScroller = new AutoScrollingFrame((int) Math.ceil(startingBound), + endingBound, gradientArea); + autoScroller.start(); + } + + private void updateScrollBounds() { + double startBorder = getBodyClientStart(); + final int endBorder = getBodyClientEnd(); + + for (int i = 0; i < grid.getFrozenColumnCount(); i++) { + startBorder += grid.getColumn(i).getWidthActual(); + } + + final int scrollCompensation = getScrollCompensation(); + startingBound = scrollCompensation + startBorder + scrollAreaPX; + endingBound = scrollCompensation + endBorder - scrollAreaPX; + gradientArea = scrollAreaPX; + + // modify bounds if they're too tightly packed + if (endingBound - startingBound < MIN_NO_AUTOSCROLL_AREA_PX) { + double adjustment = MIN_NO_AUTOSCROLL_AREA_PX + - (endingBound - startingBound); + startingBound -= adjustment / 2; + endingBound += adjustment / 2; + gradientArea -= adjustment / 2; + } + } + + private int getScrollCompensation() { + Element cursor = grid.getElement(); + int scroll = 0; + while (cursor != null) { + scroll -= scrollDirection == ScrollAxis.VERTICAL ? cursor + .getScrollTop() : cursor.getScrollLeft(); + cursor = cursor.getParentElement(); + } + + return scroll; + } + + private void injectNativeHandler() { + removeNativeHandler(); + nativePreviewHandlerRegistration = Event + .addNativePreviewHandler(new TouchEventHandler()); + } + + private void removeNativeHandler() { + if (nativePreviewHandlerRegistration != null) { + nativePreviewHandlerRegistration.removeHandler(); + nativePreviewHandlerRegistration = null; + } + } + + private TableElement getTableElement() { + final Element root = grid.getElement(); + final Element tablewrapper = Element.as(root.getChild(2)); + if (tablewrapper != null) { + return TableElement.as(tablewrapper.getFirstChildElement()); + } else { + return null; + } + } + + private TableSectionElement getTbodyElement() { + TableElement table = getTableElement(); + if (table != null) { + return table.getTBodies().getItem(0); + } else { + return null; + } + } + + private TableSectionElement getTheadElement() { + TableElement table = getTableElement(); + if (table != null) { + return table.getTHead(); + } else { + return null; + } + } + + private TableSectionElement getTfootElement() { + TableElement table = getTableElement(); + if (table != null) { + return table.getTFoot(); + } else { + return null; + } + } + + /** Get the "top" of an element in relation to "client" coordinates. */ + @SuppressWarnings("static-method") + private int getClientTop(final Element e) { + Element cursor = e; + int top = 0; + while (cursor != null) { + top += cursor.getOffsetTop(); + cursor = cursor.getOffsetParent(); + } + return top; + } + + /** Get the "left" of an element in relation to "client" coordinates. */ + @SuppressWarnings("static-method") + private int getClientLeft(final Element e) { + Element cursor = e; + int left = 0; + while (cursor != null) { + left += cursor.getOffsetLeft(); + cursor = cursor.getOffsetParent(); + } + return left; + } + + private int getBodyClientEnd() { + if (scrollDirection == ScrollAxis.VERTICAL) { + return getClientTop(getTfootElement()) - 1; + } else { + TableSectionElement tbodyElement = getTbodyElement(); + return getClientLeft(tbodyElement) + tbodyElement.getOffsetWidth() + - 1; + } + + } + + private int getBodyClientStart() { + if (scrollDirection == ScrollAxis.VERTICAL) { + return getClientTop(grid.getElement()) + + getTheadElement().getOffsetHeight(); + } else { + return getClientLeft(getTbodyElement()); + } + } +} diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index d54c023e3d..514cb6185c 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -83,6 +83,9 @@ import com.vaadin.client.widget.escalator.RowContainer; import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent; import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler; import com.vaadin.client.widget.escalator.ScrollbarBundle.Direction; +import com.vaadin.client.widget.grid.AutoScroller; +import com.vaadin.client.widget.grid.AutoScroller.AutoScrollerCallback; +import com.vaadin.client.widget.grid.AutoScroller.ScrollAxis; import com.vaadin.client.widget.grid.CellReference; import com.vaadin.client.widget.grid.CellStyleGenerator; import com.vaadin.client.widget.grid.DataAvailableEvent; @@ -2865,8 +2868,22 @@ public class Grid<T> extends ResizeComposite implements private DragAndDropHandler dndHandler = new DragAndDropHandler(); + private AutoScroller autoScroller = new AutoScroller(this); + private DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() { + private final AutoScrollerCallback autoScrollerCallback = new AutoScrollerCallback() { + + @Override + public void onVerticalAutoScroll(int scrollDiff) { + // NOP + } + + @Override + public void onHorizontalAutoScroll(int scrollDiff) { + onDragUpdate(null); + } + }; /** * Elements for displaying the dragged column(s) and drop marker * properly @@ -2884,6 +2901,8 @@ public class Grid<T> extends ResizeComposite implements */ private HandlerRegistration columnSortPreventRegistration; + private int clientX; + private void initHeaderDragElementDOM() { if (table == null) { tableHeader = DOM.createTHead(); @@ -2902,9 +2921,11 @@ public class Grid<T> extends ResizeComposite implements } @Override - public void updateDragElement(NativePreviewEvent event) { - int clientX = WidgetUtil.getTouchOrMouseClientX(event - .getNativeEvent()); + public void onDragUpdate(NativePreviewEvent event) { + if (event != null) { + clientX = WidgetUtil.getTouchOrMouseClientX(event + .getNativeEvent()); + } resolveDragElementHorizontalPosition(clientX); updateDragDropMarker(clientX); } @@ -2940,7 +2961,7 @@ public class Grid<T> extends ResizeComposite implements } @Override - public void showDragElement() { + public void onDragStart(NativeEvent startingEvent) { initHeaderDragElementDOM(); // needs to clone focus and sorting indicators too (UX) dragElement = DOM.clone(eventCell.getElement(), true); @@ -2952,10 +2973,14 @@ public class Grid<T> extends ResizeComposite implements eventCell.getElement().addClassName("dragged"); // mark the floating cell, for styling & testing dragElement.addClassName("dragged-column-header"); + // start the auto scroll handler + autoScroller.setScrollAreaPX(60); + autoScroller.start(startingEvent, ScrollAxis.HORIZONTAL, + autoScrollerCallback); } @Override - public void removeDragElement() { + public void onDragEnd() { table.removeFromParent(); dragElement.removeFromParent(); eventCell.getElement().removeClassName("dragged"); @@ -2977,8 +3002,8 @@ public class Grid<T> extends ResizeComposite implements @SuppressWarnings("unchecked") Column<?, T>[] array = reordered.toArray(new Column[reordered .size()]); - transferCellFocusOnDrop(); setColumnOrder(array); + transferCellFocusOnDrop(); } // else no reordering } @@ -3007,6 +3032,9 @@ public class Grid<T> extends ResizeComposite implements && draggedColumnIndex < focusedCellColumnIndex) { cellFocusHandler.setCellFocus(focusedRowIndex, focusedCellColumnIndex - 1, rowContainer); + } else { + cellFocusHandler.setCellFocus(focusedRowIndex, + focusedCellColumnIndex, rowContainer); } } } @@ -3032,7 +3060,9 @@ public class Grid<T> extends ResizeComposite implements } }); } + autoScroller.stop(); } + }; /** @@ -4973,6 +5003,17 @@ public class Grid<T> extends ResizeComposite implements } /** + * Sets the horizontal scroll offset + * + * @since + * @param px + * the number of pixels this grid should be scrolled right + */ + public void setScrollLeft(double px) { + escalator.setScrollLeft(px); + } + + /** * Gets the horizontal scroll offset * * @return the number of pixels this grid is scrolled to the right |