diff options
3 files changed, 109 insertions, 214 deletions
diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java index 96cb4b8a35..1025752ca9 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java @@ -3,8 +3,6 @@ */ package com.vaadin.terminal.gwt.client.ui; -import java.util.ArrayList; - import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.DivElement; @@ -24,7 +22,6 @@ import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.impl.FocusImpl; import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.VConsole; /** * A scrollhandlers similar to {@link ScrollPanel}. @@ -139,11 +136,7 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements * @return the vertical scroll position, in pixels */ public int getScrollPosition() { - if (getElement().getPropertyJSO("_vScrollTop") != null) { - return getElement().getPropertyInt("_vScrollTop"); - } else { - return getElement().getScrollTop(); - } + return getElement().getScrollTop(); } /** @@ -163,23 +156,7 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements * the new vertical scroll position, in pixels */ public void setScrollPosition(int position) { - if (isAndroidWithBrokenScrollTop()) { - ArrayList<com.google.gwt.dom.client.Element> elements = TouchScrollDelegate - .getElements(getElement()); - for (com.google.gwt.dom.client.Element el : elements) { - final Style style = el.getStyle(); - style.setProperty("webkitTransform", "translate3d(0px," - + -position + "px,0px)"); - } - getElement().setPropertyInt("_vScrollTop", position); - } else { - getElement().setScrollTop(position); - } - } - - private boolean isAndroidWithBrokenScrollTop() { - return BrowserInfo.getBrowserString().contains("Android 3") - || BrowserInfo.getBrowserString().contains("Android 4"); + getElement().setScrollTop(position); } public void onScroll(ScrollEvent event) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java b/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java index d4b59ed23a..3c08741de5 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java +++ b/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java @@ -4,22 +4,19 @@ package com.vaadin.terminal.gwt.client.ui; import java.util.ArrayList; +import java.util.Date; -import com.google.gwt.animation.client.Animation; -import com.google.gwt.core.client.Duration; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Touch; -import com.google.gwt.event.dom.client.ScrollHandler; import com.google.gwt.event.dom.client.TouchStartEvent; 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.terminal.gwt.client.BrowserInfo; import com.vaadin.terminal.gwt.client.VConsole; /** @@ -67,34 +64,27 @@ public class TouchScrollDelegate implements NativePreviewHandler { private static final double FRICTION = 0.002; private static final double DECELERATION = 0.002; private static final int MAX_DURATION = 1500; + private int origX; private int origY; private Element[] scrollableElements; private Element scrolledElement; private int origScrollTop; private HandlerRegistration handlerRegistration; - private double lastAnimatedTranslateY; private int lastClientY; + private double pixxelsPerMs; + private boolean transitionPending = false; private int deltaScrollPos; private boolean transitionOn = false; private int finalScrollTop; private ArrayList<Element> layers; private boolean moved; - private ScrollHandler scrollHandler; private static TouchScrollDelegate activeScrollDelegate; - private static final boolean androidWithBrokenScrollTop = BrowserInfo - .getBrowserString().contains("Android 3") - || BrowserInfo.getBrowserString().contains("Android 4"); - public TouchScrollDelegate(Element... elements) { scrollableElements = elements; } - public void setScrollHandler(ScrollHandler scrollHandler) { - this.scrollHandler = scrollHandler; - } - public static TouchScrollDelegate getActiveScrollDelegate() { return activeScrollDelegate; } @@ -126,8 +116,37 @@ public class TouchScrollDelegate implements NativePreviewHandler { public void onTouchStart(TouchStartEvent event) { if (activeScrollDelegate == null && event.getTouches().length() == 1) { - NativeEvent nativeEvent = event.getNativeEvent(); - doTouchStart(nativeEvent); + + Touch touch = event.getTouches().get(0); + if (detectScrolledElement(touch)) { + VConsole.log("TouchDelegate takes over"); + event.stopPropagation(); + handlerRegistration = Event.addNativePreviewHandler(this); + activeScrollDelegate = this; + hookTransitionEndListener(scrolledElement + .getFirstChildElement()); + origX = touch.getClientX(); + origY = touch.getClientY(); + yPositions[0] = origY; + eventTimeStamps[0] = new Date(); + nextEvent = 1; + + if (transitionOn) { + // TODO calculate current position of ongoing transition, + // fix to that and start scroll from there. Another option + // is to investigate if we can get even close the same + // framerate with scheduler based impl instead of using + // transitions (GWT examples has impl of this, with jsni + // though). This is very smooth on native ipad, now we + // ignore touch starts during animation. + origScrollTop = scrolledElement.getScrollTop(); + } else { + origScrollTop = scrolledElement.getScrollTop(); + } + moved = false; + // event.preventDefault(); + // event.stopPropagation(); + } } else { /* * Touch scroll is currenly on (possibly bouncing). Ignore. @@ -135,39 +154,16 @@ public class TouchScrollDelegate implements NativePreviewHandler { } } - private void doTouchStart(NativeEvent nativeEvent) { - if (transitionOn) { - momentum.cancel(); - } - Touch touch = nativeEvent.getTouches().get(0); - if (detectScrolledElement(touch)) { - VConsole.log("TouchDelegate takes over"); - nativeEvent.stopPropagation(); - handlerRegistration = Event.addNativePreviewHandler(this); - activeScrollDelegate = this; - origY = touch.getClientY(); - yPositions[0] = origY; - eventTimeStamps[0] = getTimeStamp(); - nextEvent = 1; - - origScrollTop = getScrollTop(); - VConsole.log("ST" + origScrollTop); - - moved = false; - // event.preventDefault(); - // event.stopPropagation(); - } - } - - private int getScrollTop() { - if (androidWithBrokenScrollTop) { - if (scrolledElement.getPropertyJSO("_vScrollTop") != null) { - return scrolledElement.getPropertyInt("_vScrollTop"); - } - return 0; + private native void hookTransitionEndListener(Element element) + /*-{ + if(!element.hasTransitionEndListener) { + var that = this; + element.addEventListener("webkitTransitionEnd",function(event){ + that.@com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate::onTransitionEnd()(); + },false); + element.hasTransitionEndListener = true; } - return scrolledElement.getScrollTop(); - } + }-*/; private void onTransitionEnd() { if (finalScrollTop < 0) { @@ -179,6 +175,7 @@ public class TouchScrollDelegate implements NativePreviewHandler { } else { moveTransformationToScrolloffset(); } + transitionOn = false; } private void animateToScrollPosition(int to, int from) { @@ -187,14 +184,7 @@ public class TouchScrollDelegate implements NativePreviewHandler { if (time <= 0) { time = 1; // get animation and transition end event } - VConsole.log("Animate " + time + " " + from + " " + to); - int translateTo = -to + origScrollTop; - int fromY = -from + origScrollTop; - if (androidWithBrokenScrollTop) { - fromY -= origScrollTop; - translateTo -= origScrollTop; - } - translateTo(time, fromY, translateTo); + translateTo(time, -to + origScrollTop); } private int getAnimationTimeForDistance(int dist) { @@ -210,21 +200,16 @@ public class TouchScrollDelegate implements NativePreviewHandler { * scrolltop, causing onscroll event. */ private void moveTransformationToScrolloffset() { - if (androidWithBrokenScrollTop) { - scrolledElement.setPropertyInt("_vScrollTop", finalScrollTop); - if (scrollHandler != null) { - scrollHandler.onScroll(null); - } - } else { - for (Element el : layers) { - Style style = el.getStyle(); - style.setProperty("webkitTransform", "translate3d(0,0,0)"); - } - scrolledElement.setScrollTop(finalScrollTop); + for (Element el : layers) { + Style style = el.getStyle(); + style.setProperty("webkitTransitionProperty", "none"); + style.setProperty("webkitTransform", "translate3d(0,0,0)"); } + scrolledElement.setScrollTop(finalScrollTop); activeScrollDelegate = null; handlerRegistration.removeHandler(); handlerRegistration = null; + } /** @@ -240,7 +225,14 @@ public class TouchScrollDelegate implements NativePreviewHandler { if (el.isOrHasChild(target) && el.getScrollHeight() > el.getClientHeight()) { scrolledElement = el; - layers = getElements(scrolledElement); + NodeList<Node> childNodes = scrolledElement.getChildNodes(); + layers = new ArrayList<Element>(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.getItem(i); + if (item.getNodeType() == Node.ELEMENT_NODE) { + layers.add((Element) item); + } + } return true; } @@ -248,21 +240,10 @@ public class TouchScrollDelegate implements NativePreviewHandler { return false; } - public static ArrayList<Element> getElements(Element scrolledElement2) { - NodeList<Node> childNodes = scrolledElement2.getChildNodes(); - ArrayList<Element> l = new ArrayList<Element>(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node item = childNodes.getItem(i); - if (item.getNodeType() == Node.ELEMENT_NODE) { - l.add((Element) item); - } - } - return l; - } - private void onTouchMove(NativeEvent event) { if (!moved) { - double l = (getTimeStamp() - eventTimeStamps[0]); + Date date = new Date(); + long l = (date.getTime() - eventTimeStamps[0].getTime()); VConsole.log(l + " ms from start to move"); } boolean handleMove = readPositionAndSpeed(event); @@ -274,15 +255,15 @@ public class TouchScrollDelegate implements NativePreviewHandler { int overscroll = (deltaScrollTop + origScrollTop) - getMaxFinalY(); overscroll = overscroll / 2; - if (overscroll > getMaxOverScroll()) { - overscroll = getMaxOverScroll(); + if (overscroll > scrolledElement.getClientHeight() / 2) { + overscroll = scrolledElement.getClientHeight() / 2; } deltaScrollTop = getMaxFinalY() + overscroll - origScrollTop; } else if (finalPos < 0) { // spring effect at the beginning int overscroll = finalPos / 2; - if (-overscroll > getMaxOverScroll()) { - overscroll = -getMaxOverScroll(); + if (-overscroll > scrolledElement.getClientHeight() / 2) { + overscroll = -scrolledElement.getClientHeight() / 2; } deltaScrollTop = overscroll - origScrollTop; } @@ -295,20 +276,16 @@ public class TouchScrollDelegate implements NativePreviewHandler { private void quickSetScrollPosition(int deltaX, int deltaY) { deltaScrollPos = deltaY; - if (androidWithBrokenScrollTop) { - deltaY += origScrollTop; - translateTo(-deltaY); - } else { - translateTo(-deltaScrollPos); - } + translateTo(0, -deltaScrollPos); } private static final int EVENTS_FOR_SPEED_CALC = 3; public static final int SIGNIFICANT_MOVE_THRESHOLD = 3; private int[] yPositions = new int[EVENTS_FOR_SPEED_CALC]; - private double[] eventTimeStamps = new double[EVENTS_FOR_SPEED_CALC]; + private Date[] eventTimeStamps = new Date[EVENTS_FOR_SPEED_CALC]; private int nextEvent = 0; - private Animation momentum; + private Date transitionStart; + private Date transitionDuration; /** * @@ -316,11 +293,12 @@ public class TouchScrollDelegate implements NativePreviewHandler { * @return */ private boolean readPositionAndSpeed(NativeEvent event) { + Date now = new Date(); Touch touch = event.getChangedTouches().get(0); lastClientY = touch.getClientY(); int eventIndx = nextEvent++; eventIndx = eventIndx % EVENTS_FOR_SPEED_CALC; - eventTimeStamps[eventIndx] = getTimeStamp(); + eventTimeStamps[eventIndx] = now; yPositions[eventIndx] = lastClientY; return isMovedSignificantly(); } @@ -370,11 +348,15 @@ public class TouchScrollDelegate implements NativePreviewHandler { // VConsole.log("To max overscroll"); finalY = getMaxFinalY() + getMaxOverScroll(); int fixedPixelsToMove = finalY - currentY; + pixelsPerMs = pixelsPerMs * pixelsToMove / fixedPixelsToMove + / FRICTION; pixelsToMove = fixedPixelsToMove; } else if (finalY < 0 - getMaxOverScroll()) { // VConsole.log("to min overscroll"); finalY = -getMaxOverScroll(); int fixedPixelsToMove = finalY - currentY; + pixelsPerMs = pixelsPerMs * pixelsToMove / fixedPixelsToMove + / FRICTION; pixelsToMove = fixedPixelsToMove; } else { duration = (int) (Math.abs(pixelsPerMs / DECELERATION)); @@ -398,13 +380,8 @@ public class TouchScrollDelegate implements NativePreviewHandler { return; } - int translateTo = -finalY + origScrollTop; - int fromY = -currentY + origScrollTop; - if (androidWithBrokenScrollTop) { - fromY -= origScrollTop; - translateTo -= origScrollTop; - } - translateTo(duration, fromY, translateTo); + int translateY = -finalY + origScrollTop; + translateTo(duration, translateY); } private double calculateSpeed() { @@ -415,75 +392,47 @@ public class TouchScrollDelegate implements NativePreviewHandler { } int idx = nextEvent % EVENTS_FOR_SPEED_CALC; final int firstPos = yPositions[idx]; - final double firstTs = eventTimeStamps[idx]; + final Date firstTs = eventTimeStamps[idx]; idx += EVENTS_FOR_SPEED_CALC; idx--; idx = idx % EVENTS_FOR_SPEED_CALC; final int lastPos = yPositions[idx]; - final double lastTs = eventTimeStamps[idx]; + final Date lastTs = eventTimeStamps[idx]; // speed as in change of scrolltop == -speedOfTouchPos - return (firstPos - lastPos) / (lastTs - firstTs); + return (firstPos - lastPos) + / (double) (lastTs.getTime() - firstTs.getTime()); } /** * Note positive scrolltop moves layer up, positive translate moves layer * down. + * + * @param duration + * @param translateY */ - private void translateTo(double translateY) { + private void translateTo(int duration, int translateY) { for (Element el : layers) { - Style style = el.getStyle(); + final Style style = el.getStyle(); + if (duration > 0) { + style.setProperty("webkitTransitionDuration", duration + "ms"); + style.setProperty("webkitTransitionTimingFunction", + "cubic-bezier(0,0,0.25,1)"); + style.setProperty("webkitTransitionProperty", + "-webkit-transform"); + transitionOn = true; + transitionStart = new Date(); + transitionDuration = new Date(); + } else { + style.setProperty("webkitTransitionProperty", "none"); + } style.setProperty("webkitTransform", "translate3d(0px," + translateY + "px,0px)"); } } - /** - * Note positive scrolltop moves layer up, positive translate moves layer - * down. - * - * @param duration - */ - private void translateTo(int duration, final int fromY, final int finalY) { - if (duration > 0) { - transitionOn = true; - - momentum = new Animation() { - - @Override - protected void onUpdate(double progress) { - lastAnimatedTranslateY = (fromY + (finalY - fromY) - * progress); - translateTo(lastAnimatedTranslateY); - } - - @Override - protected double interpolate(double progress) { - return 1 + Math.pow(progress - 1, 3); - } - - @Override - protected void onComplete() { - super.onComplete(); - transitionOn = false; - onTransitionEnd(); - } - - @Override - protected void onCancel() { - int delta = (int) (finalY - lastAnimatedTranslateY); - finalScrollTop -= delta; - moveTransformationToScrolloffset(); - transitionOn = false; - } - }; - momentum.run(duration); - } - } - private int getMaxOverScroll() { - return androidWithBrokenScrollTop ? 0 : scrolledElement - .getClientHeight() / 3; + return scrolledElement.getClientHeight() / 4; } private int getMaxFinalY() { @@ -492,18 +441,14 @@ public class TouchScrollDelegate implements NativePreviewHandler { } public void onPreviewNativeEvent(NativePreviewEvent event) { - int typeInt = event.getTypeInt(); if (transitionOn) { /* * TODO allow starting new events. See issue in onTouchStart */ event.cancel(); - - if (typeInt == Event.ONTOUCHSTART) { - doTouchStart(event.getNativeEvent()); - } return; } + int typeInt = event.getTypeInt(); switch (typeInt) { case Event.ONTOUCHMOVE: if (!event.isCanceled()) { @@ -539,15 +484,4 @@ public class TouchScrollDelegate implements NativePreviewHandler { public void setElements(com.google.gwt.user.client.Element[] elements) { scrollableElements = elements; } - - /** - * long calcucation are not very efficient in GWT, so this helper method - * returns timestamp in double. - * - * @return - */ - public static double getTimeStamp() { - return Duration.currentTimeMillis(); - } - } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java index a022a2bd83..6bbc2a6ceb 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java @@ -18,7 +18,6 @@ import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Display; @@ -513,7 +512,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, if (touchScrollDelegate == null) { touchScrollDelegate = new TouchScrollDelegate( scrollBodyPanel.getElement()); - touchScrollDelegate.setScrollHandler(this); } return touchScrollDelegate; @@ -4064,14 +4062,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, DOM.appendChild(container, preSpacer); DOM.appendChild(container, table); DOM.appendChild(container, postSpacer); - if (BrowserInfo.get().isTouchDevice()) { - NodeList<Node> childNodes = container.getChildNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - Element item = (Element) childNodes.getItem(i); - item.getStyle().setProperty("webkitTransform", - "translate3d(0,0,0)"); - } - } } @@ -4617,7 +4607,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, public class VScrollTableRow extends Panel implements ActionOwner, Container { - private static final int TOUCHSCROLL_TIMEOUT = 100; + private static final int TOUCHSCROLL_TIMEOUT = 70; private static final int DRAGMODE_MULTIROW = 2; protected ArrayList<Widget> childWidgets = new ArrayList<Widget>(); private boolean selected = false; @@ -5291,11 +5281,9 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } }; } - if (contextTouchTimeout != null) { - contextTouchTimeout.cancel(); - contextTouchTimeout - .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); - } + contextTouchTimeout.cancel(); + contextTouchTimeout + .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); } break; case Event.ONMOUSEDOWN: @@ -5452,7 +5440,8 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, int top = Util.getTouchOrMouseClientY(event); top += Window.getScrollTop(); left += Window.getScrollLeft(); - contextMenu = new ContextMenuDetails(getKey(), left, top); + contextMenu = new ContextMenuDetails(this.getKey(), left, + top); client.getContextMenu().showAt(this, left, top); } } @@ -6501,11 +6490,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, * The row to ensure is visible */ private void ensureRowIsVisible(VScrollTableRow row) { - if (BrowserInfo.get().isTouchDevice()) { - // Skip due to android devices that have broken scrolltop will may - // get odd scrolling here. - return; - } Util.scrollIntoViewVertically(row.getElement()); } |