Browse Source

[merge from 6.7] Fix #8550 in 6.7 after all

svn changeset:23610/svn branch:6.8
tags/7.0.0.alpha3
Automerge 12 years ago
parent
commit
7d2f7c99f5

+ 25
- 2
src/com/vaadin/terminal/gwt/client/ui/FocusableScrollPanel.java View 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) {

+ 167
- 101
src/com/vaadin/terminal/gwt/client/ui/TouchScrollDelegate.java View 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();
}

}

+ 22
- 6
src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java View 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());
}


Loading…
Cancel
Save