From: Automerge Date: Mon, 23 Apr 2012 09:06:42 +0000 (+0000) Subject: [merge from 6.7] Fix #8550 in 6.7 after all X-Git-Tag: 7.0.0.alpha3~260^2~3 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=7d2f7c99f5e5ee07af0b06bb7f6c894959372d9f;p=vaadin-framework.git [merge from 6.7] Fix #8550 in 6.7 after all svn changeset:23610/svn branch:6.8 --- diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java index 1025752ca9..96cb4b8a35 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java @@ -3,6 +3,8 @@ */ 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; @@ -22,6 +24,7 @@ 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}. @@ -136,7 +139,11 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements * @return the vertical scroll position, in pixels */ public int getScrollPosition() { - return getElement().getScrollTop(); + if (getElement().getPropertyJSO("_vScrollTop") != null) { + return getElement().getPropertyInt("_vScrollTop"); + } else { + return getElement().getScrollTop(); + } } /** @@ -156,7 +163,23 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements * the new vertical scroll position, in pixels */ public void setScrollPosition(int position) { - getElement().setScrollTop(position); + if (isAndroidWithBrokenScrollTop()) { + ArrayList 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"); } 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 3c08741de5..d4b59ed23a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java +++ b/src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java @@ -4,19 +4,22 @@ 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; /** @@ -64,27 +67,34 @@ 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 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; } @@ -116,37 +126,8 @@ public class TouchScrollDelegate implements NativePreviewHandler { public void onTouchStart(TouchStartEvent event) { if (activeScrollDelegate == null && event.getTouches().length() == 1) { - - 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(); - } + NativeEvent nativeEvent = event.getNativeEvent(); + doTouchStart(nativeEvent); } else { /* * Touch scroll is currenly on (possibly bouncing). Ignore. @@ -154,16 +135,39 @@ public class TouchScrollDelegate implements NativePreviewHandler { } } - 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; + 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; } - }-*/; + return scrolledElement.getScrollTop(); + } private void onTransitionEnd() { if (finalScrollTop < 0) { @@ -175,7 +179,6 @@ public class TouchScrollDelegate implements NativePreviewHandler { } else { moveTransformationToScrolloffset(); } - transitionOn = false; } private void animateToScrollPosition(int to, int from) { @@ -184,7 +187,14 @@ public class TouchScrollDelegate implements NativePreviewHandler { if (time <= 0) { time = 1; // get animation and transition end event } - translateTo(time, -to + origScrollTop); + VConsole.log("Animate " + time + " " + from + " " + to); + int translateTo = -to + origScrollTop; + int fromY = -from + origScrollTop; + if (androidWithBrokenScrollTop) { + fromY -= origScrollTop; + translateTo -= origScrollTop; + } + translateTo(time, fromY, translateTo); } private int getAnimationTimeForDistance(int dist) { @@ -200,16 +210,21 @@ public class TouchScrollDelegate implements NativePreviewHandler { * scrolltop, causing onscroll event. */ private void moveTransformationToScrolloffset() { - for (Element el : layers) { - Style style = el.getStyle(); - style.setProperty("webkitTransitionProperty", "none"); - style.setProperty("webkitTransform", "translate3d(0,0,0)"); + 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); } - scrolledElement.setScrollTop(finalScrollTop); activeScrollDelegate = null; handlerRegistration.removeHandler(); handlerRegistration = null; - } /** @@ -225,14 +240,7 @@ public class TouchScrollDelegate implements NativePreviewHandler { if (el.isOrHasChild(target) && el.getScrollHeight() > el.getClientHeight()) { scrolledElement = el; - NodeList childNodes = scrolledElement.getChildNodes(); - layers = new ArrayList(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node item = childNodes.getItem(i); - if (item.getNodeType() == Node.ELEMENT_NODE) { - layers.add((Element) item); - } - } + layers = getElements(scrolledElement); return true; } @@ -240,10 +248,21 @@ public class TouchScrollDelegate implements NativePreviewHandler { return false; } + public static ArrayList getElements(Element scrolledElement2) { + NodeList childNodes = scrolledElement2.getChildNodes(); + ArrayList l = new ArrayList(); + 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) { - Date date = new Date(); - long l = (date.getTime() - eventTimeStamps[0].getTime()); + double l = (getTimeStamp() - eventTimeStamps[0]); VConsole.log(l + " ms from start to move"); } boolean handleMove = readPositionAndSpeed(event); @@ -255,15 +274,15 @@ public class TouchScrollDelegate implements NativePreviewHandler { int overscroll = (deltaScrollTop + origScrollTop) - getMaxFinalY(); overscroll = overscroll / 2; - if (overscroll > scrolledElement.getClientHeight() / 2) { - overscroll = scrolledElement.getClientHeight() / 2; + if (overscroll > getMaxOverScroll()) { + overscroll = getMaxOverScroll(); } deltaScrollTop = getMaxFinalY() + overscroll - origScrollTop; } else if (finalPos < 0) { // spring effect at the beginning int overscroll = finalPos / 2; - if (-overscroll > scrolledElement.getClientHeight() / 2) { - overscroll = -scrolledElement.getClientHeight() / 2; + if (-overscroll > getMaxOverScroll()) { + overscroll = -getMaxOverScroll(); } deltaScrollTop = overscroll - origScrollTop; } @@ -276,16 +295,20 @@ public class TouchScrollDelegate implements NativePreviewHandler { private void quickSetScrollPosition(int deltaX, int deltaY) { deltaScrollPos = deltaY; - translateTo(0, -deltaScrollPos); + if (androidWithBrokenScrollTop) { + deltaY += origScrollTop; + translateTo(-deltaY); + } else { + translateTo(-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 Date[] eventTimeStamps = new Date[EVENTS_FOR_SPEED_CALC]; + private double[] eventTimeStamps = new double[EVENTS_FOR_SPEED_CALC]; private int nextEvent = 0; - private Date transitionStart; - private Date transitionDuration; + private Animation momentum; /** * @@ -293,12 +316,11 @@ 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] = now; + eventTimeStamps[eventIndx] = getTimeStamp(); yPositions[eventIndx] = lastClientY; return isMovedSignificantly(); } @@ -348,15 +370,11 @@ 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)); @@ -380,8 +398,13 @@ public class TouchScrollDelegate implements NativePreviewHandler { return; } - int translateY = -finalY + origScrollTop; - translateTo(duration, translateY); + int translateTo = -finalY + origScrollTop; + int fromY = -currentY + origScrollTop; + if (androidWithBrokenScrollTop) { + fromY -= origScrollTop; + translateTo -= origScrollTop; + } + translateTo(duration, fromY, translateTo); } private double calculateSpeed() { @@ -392,47 +415,75 @@ public class TouchScrollDelegate implements NativePreviewHandler { } int idx = nextEvent % EVENTS_FOR_SPEED_CALC; final int firstPos = yPositions[idx]; - final Date firstTs = eventTimeStamps[idx]; + final double firstTs = eventTimeStamps[idx]; idx += EVENTS_FOR_SPEED_CALC; idx--; idx = idx % EVENTS_FOR_SPEED_CALC; final int lastPos = yPositions[idx]; - final Date lastTs = eventTimeStamps[idx]; + final double lastTs = eventTimeStamps[idx]; // speed as in change of scrolltop == -speedOfTouchPos - return (firstPos - lastPos) - / (double) (lastTs.getTime() - firstTs.getTime()); + return (firstPos - lastPos) / (lastTs - firstTs); } /** * Note positive scrolltop moves layer up, positive translate moves layer * down. - * - * @param duration - * @param translateY */ - private void translateTo(int duration, int translateY) { + private void translateTo(double translateY) { for (Element el : layers) { - 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 style = el.getStyle(); 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 scrolledElement.getClientHeight() / 4; + return androidWithBrokenScrollTop ? 0 : scrolledElement + .getClientHeight() / 3; } private int getMaxFinalY() { @@ -441,14 +492,18 @@ 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()) { @@ -484,4 +539,15 @@ 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 6bbc2a6ceb..a022a2bd83 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java @@ -18,6 +18,7 @@ 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; @@ -512,6 +513,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, if (touchScrollDelegate == null) { touchScrollDelegate = new TouchScrollDelegate( scrollBodyPanel.getElement()); + touchScrollDelegate.setScrollHandler(this); } return touchScrollDelegate; @@ -4062,6 +4064,14 @@ 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 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)"); + } + } } @@ -4607,7 +4617,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, public class VScrollTableRow extends Panel implements ActionOwner, Container { - private static final int TOUCHSCROLL_TIMEOUT = 70; + private static final int TOUCHSCROLL_TIMEOUT = 100; private static final int DRAGMODE_MULTIROW = 2; protected ArrayList childWidgets = new ArrayList(); private boolean selected = false; @@ -5281,9 +5291,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, } }; } - contextTouchTimeout.cancel(); - contextTouchTimeout - .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + if (contextTouchTimeout != null) { + contextTouchTimeout.cancel(); + contextTouchTimeout + .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + } } break; case Event.ONMOUSEDOWN: @@ -5440,8 +5452,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, int top = Util.getTouchOrMouseClientY(event); top += Window.getScrollTop(); left += Window.getScrollLeft(); - contextMenu = new ContextMenuDetails(this.getKey(), left, - top); + contextMenu = new ContextMenuDetails(getKey(), left, top); client.getContextMenu().showAt(this, left, top); } } @@ -6490,6 +6501,11 @@ 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()); }