]> source.dussan.org Git - vaadin-framework.git/commitdiff
[merge from 6.7] Fix #8550 in 6.7 after all
authorAutomerge <automerge@vaadin.com>
Mon, 23 Apr 2012 09:06:42 +0000 (09:06 +0000)
committerAutomerge <automerge@vaadin.com>
Mon, 23 Apr 2012 09:06:42 +0000 (09:06 +0000)
svn changeset:23610/svn branch:6.8

src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java
src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java
src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java

index 1025752ca9236adfe29f333197c2e2b8d06e4e87..96cb4b8a3533a96f81940a2fbc72332202952a07 100644 (file)
@@ -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<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");
     }
 
     public void onScroll(ScrollEvent event) {
index 3c08741de5e76995de844852d14aca0c14e8e6d7..d4b59ed23ab955c2c607b80ff08a613ddb68e2d1 100644 (file)
@@ -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<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;
     }
@@ -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<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);
-                    }
-                }
+                layers = getElements(scrolledElement);
                 return true;
 
             }
@@ -240,10 +248,21 @@ 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) {
-            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();
+    }
+
 }
index 6bbc2a6ceb297006236a2f5842dfb20aff2ebc42..a022a2bd83a91b0c0a32309a6080b67319cb480e 100644 (file)
@@ -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<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)");
+                }
+            }
 
         }
 
@@ -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<Widget> childWidgets = new ArrayList<Widget>();
             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());
     }