aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/src/main/java/com/vaadin/client/widgets/Escalator.java261
1 files changed, 140 insertions, 121 deletions
diff --git a/client/src/main/java/com/vaadin/client/widgets/Escalator.java b/client/src/main/java/com/vaadin/client/widgets/Escalator.java
index 25e83592d7..4c78311ea7 100644
--- a/client/src/main/java/com/vaadin/client/widgets/Escalator.java
+++ b/client/src/main/java/com/vaadin/client/widgets/Escalator.java
@@ -30,9 +30,6 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gwt.animation.client.Animation;
-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.core.client.Duration;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
@@ -48,6 +45,7 @@ import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.dom.client.Touch;
@@ -55,6 +53,7 @@ import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.logging.client.LogConfiguration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.RootPanel;
@@ -319,16 +318,10 @@ public class Escalator extends Widget implements RequiresResize,
* {@link com.google.gwt.dom.client.NativeEvent NativeEvent} isn't
* properly populated with the correct values.
*/
- private final static class CustomTouchEvent extends
- JavaScriptObject {
+ private final static class CustomTouchEvent extends NativeEvent {
protected CustomTouchEvent() {
}
- public native NativeEvent getNativeEvent()
- /*-{
- return this;
- }-*/;
-
public native int getPageX()
/*-{
return this.targetTouches[0].pageX;
@@ -338,6 +331,11 @@ public class Escalator extends Widget implements RequiresResize,
/*-{
return this.targetTouches[0].pageY;
}-*/;
+
+ public native boolean isCancelable()
+ /*-{
+ return this.cancelable;
+ }-*/;
}
private final Escalator escalator;
@@ -389,6 +387,7 @@ public class Escalator extends Widget implements RequiresResize,
final List<Double> speeds = new ArrayList<Double>();
final ScrollbarBundle scroll;
double position, offset, velocity, prevPos, prevTime, delta;
+ double scrollMax;
boolean run, vertical;
public Movement(boolean vertical) {
@@ -401,11 +400,15 @@ public class Escalator extends Widget implements RequiresResize,
speeds.clear();
prevPos = pagePosition(event);
prevTime = Duration.currentTimeMillis();
+ scrollMax = scroll.getScrollSize() - scroll.getOffsetSize();
+ delta = 0;
}
public void moveTouch(CustomTouchEvent event) {
double pagePosition = pagePosition(event);
- if (pagePosition > -1) {
+ run = false;
+ // skip grids without scroll
+ if (scrollMax > 1) {
delta = prevPos - pagePosition;
double now = Duration.currentTimeMillis();
double ellapsed = now - prevTime;
@@ -414,14 +417,23 @@ public class Escalator extends Widget implements RequiresResize,
// storing again
if (speeds.size() > 0 && !validSpeed(speeds.get(0))) {
speeds.clear();
- run = true;
}
speeds.add(0, velocity);
prevTime = now;
prevPos = pagePosition;
+ position = scroll.getScrollPos();
}
}
+ public void validate(Movement other) {
+ // We don't move the scroll if no delta, scroll position
+ // has reached the edge, or movement in one direction is
+ // insignificant.
+ run = delta != 0 && inScrollRange(position + delta)
+ && Math.abs(other.delta / delta) < F_AXIS;
+ if (!run) delta = 0;
+ }
+
public void endTouch(CustomTouchEvent event) {
// Compute average speed
velocity = 0;
@@ -438,24 +450,24 @@ public class Escalator extends Widget implements RequiresResize,
// Enable or disable inertia movement in this axis
run = validSpeed(velocity);
if (run) {
- event.getNativeEvent().preventDefault();
+ event.preventDefault();
}
}
- void validate(Movement other) {
- if (!run || other.velocity > 0
- && Math.abs(velocity / other.velocity) < F_AXIS) {
- delta = offset = 0;
- run = false;
+ void stepAnimation(double progress) {
+ if (run) {
+ double p = position + offset * progress;
+ scroll.setScrollPos(p);
+ run = inScrollRange(p);
}
}
- void stepAnimation(double progress) {
- scroll.setScrollPos(position + offset * progress);
+ boolean inScrollRange(double p) {
+ return p > 0 && p < scrollMax;
}
int pagePosition(CustomTouchEvent event) {
- JsArray<Touch> a = event.getNativeEvent().getTouches();
+ JsArray<Touch> a = event.getTouches();
return vertical ? a.get(0).getPageY() : a.get(0).getPageX();
}
@@ -470,6 +482,11 @@ public class Escalator extends Widget implements RequiresResize,
public void onUpdate(double progress) {
xMov.stepAnimation(progress);
yMov.stepAnimation(progress);
+ if (!xMov.run && !yMov.run) {
+ // Stop animation as soon as we reach the border,
+ // so as we do not wait to move the external scroll.
+ cancel();
+ }
}
@Override
@@ -494,14 +511,19 @@ public class Escalator extends Widget implements RequiresResize,
};
public void touchStart(final CustomTouchEvent event) {
- if (event.getNativeEvent().getTouches().length() == 1) {
+ // Consider only one-finger gestures over the body.
+ if (eventOnBody(escalator, event)
+ && event.getTouches().length() == 1) {
if (yMov == null) {
yMov = new Movement(true);
xMov = new Movement(false);
+ // Mark this as a touch device. Useful for
+ // fix hover styles in iOS.
+ escalator.bodyElem.addClassName("touch");
}
if (animation.isRunning()) {
acceleration += F_ACC;
- event.getNativeEvent().preventDefault();
+ event.preventDefault();
animation.cancel();
} else {
acceleration = 1;
@@ -510,6 +532,7 @@ public class Escalator extends Widget implements RequiresResize,
yMov.startTouch(event);
touching = true;
} else {
+ // Cancel to allow multi-finger gestures like zoom.
touching = false;
animation.cancel();
acceleration = 1;
@@ -517,14 +540,22 @@ public class Escalator extends Widget implements RequiresResize,
}
public void touchMove(final CustomTouchEvent event) {
- if (touching) {
+ if (touching && event.isCancelable()) {
xMov.moveTouch(event);
yMov.moveTouch(event);
xMov.validate(yMov);
yMov.validate(xMov);
- event.getNativeEvent().preventDefault();
- moveScrollFromEvent(escalator, xMov.delta, yMov.delta,
- event.getNativeEvent());
+ if (xMov.run) {
+ xMov.scroll.setScrollPosByDelta(xMov.delta);
+ }
+ if (yMov.run) {
+ yMov.scroll.setScrollPosByDelta(yMov.delta);
+ }
+ if (xMov.run || yMov.run) {
+ // If we move the scroll prevent default, otherwise
+ // pass the control to the device.
+ event.preventDefault();
+ }
}
}
@@ -534,7 +565,7 @@ public class Escalator extends Widget implements RequiresResize,
yMov.endTouch(event);
xMov.validate(yMov);
yMov.validate(xMov);
- // Adjust duration so as longer movements take more duration
+ // Adjust duration so as longer movements take bigger duration
boolean vert = !xMov.run || yMov.run
&& Math.abs(yMov.offset) > Math.abs(xMov.offset);
double delta = Math.abs((vert ? yMov : xMov).offset);
@@ -556,28 +587,47 @@ public class Escalator extends Widget implements RequiresResize,
}
}
+ public static boolean eventOnBody(Escalator escalator, NativeEvent event) {
+ Element e = event.getEventTarget().<Element>cast();
+ // Consider the event if it comes from an element in the body,
+ // of from the main table (when setting position by code)
+ return TableElement.is(e)
+ || escalator.bodyElem.isOrHasChild(e);
+ }
+
public static void moveScrollFromEvent(final Escalator escalator,
final double deltaX, final double deltaY,
final NativeEvent event) {
- if (!Double.isNaN(deltaX)) {
- escalator.horizontalScrollbar.setScrollPosByDelta(deltaX);
+ // Prevent scrolling on Headers/Footers
+ if (!eventOnBody(escalator, event)) {
+ return;
}
- if (!Double.isNaN(deltaY)) {
- escalator.verticalScrollbar.setScrollPosByDelta(deltaY);
- }
+ boolean movex = !Double.isNaN(deltaX);
+ boolean movey = !Double.isNaN(deltaY);
+ if (movex || movey) {
+ escalator.bodyElem.addClassName("scrolling");
+ if (movex) {
+ escalator.horizontalScrollbar.setScrollPosByDelta(deltaX);
+ }
+ if (movey) {
+ escalator.verticalScrollbar.setScrollPosByDelta(deltaY);
+ }
+ escalator.body.domSorter.reschedule();
- /*
- * TODO: only prevent if not scrolled to end/bottom. Or no? UX team
- * needs to decide.
- */
- final boolean warrantedYScroll = deltaY != 0
- && escalator.verticalScrollbar.showsScrollHandle();
- final boolean warrantedXScroll = deltaX != 0
- && escalator.horizontalScrollbar.showsScrollHandle();
- if (warrantedYScroll || warrantedXScroll) {
- event.preventDefault();
+ /*
+ * TODO: only prevent if not scrolled to end/bottom. Or no? UX
+ * team needs to decide. In touch devices movement is not
+ * prevented when the edge is reached.
+ */
+ final boolean warrantedYScroll = deltaY != 0
+ && escalator.verticalScrollbar.showsScrollHandle();
+ final boolean warrantedXScroll = deltaX != 0
+ && escalator.horizontalScrollbar.showsScrollHandle();
+ if (warrantedYScroll || warrantedXScroll) {
+ event.preventDefault();
+ }
}
}
}
@@ -1083,6 +1133,10 @@ public class Escalator extends Widget implements RequiresResize,
private double defaultRowHeight = INITIAL_DEFAULT_ROW_HEIGHT;
+ private boolean autodetectRowHeightLaterQueued = false;
+
+ private Element detectionTr, cellElem;
+
public AbstractRowContainer(
final TableSectionElement rowContainerElement) {
root = rowContainerElement;
@@ -1884,41 +1938,45 @@ public class Escalator extends Widget implements RequiresResize,
}
public void autodetectRowHeightLater() {
- Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
- @Override
- public void execute() {
- if (defaultRowHeightShouldBeAutodetected && isAttached()) {
- autodetectRowHeightNow();
- defaultRowHeightShouldBeAutodetected = false;
+ if (!autodetectRowHeightLaterQueued) {
+ autodetectRowHeightLaterQueued = true;
+ Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
+ @Override
+ public void execute() {
+ if (isAttached()) {
+ autodetectRowHeightLaterQueued = false;
+ autodetectRowHeightNow();
+ }
}
- }
- });
+ });
+ }
}
public void autodetectRowHeightNow() {
- if (!isAttached()) {
- // Run again when attached
- defaultRowHeightShouldBeAutodetected = true;
+ if (!defaultRowHeightShouldBeAutodetected && !isAttached()) {
return;
}
+ if (detectionTr == null) {
+ detectionTr = DOM.createTR();
+ detectionTr.setClassName(getStylePrimaryName() + "-row");
+ cellElem = DOM.createElement(getCellElementTagName());
+ cellElem.setClassName(getStylePrimaryName() + "-cell");
+ cellElem.setInnerText("Ij");
+ detectionTr.appendChild(cellElem);
+ }
- final Element detectionTr = DOM.createTR();
- detectionTr.setClassName(getStylePrimaryName() + "-row");
-
- final Element cellElem = DOM.createElement(getCellElementTagName());
- cellElem.setClassName(getStylePrimaryName() + "-cell");
- cellElem.setInnerText("Ij");
-
- detectionTr.appendChild(cellElem);
root.appendChild(detectionTr);
- double boundingHeight = WidgetUtil
- .getRequiredHeightBoundingClientRectDouble(cellElem);
- defaultRowHeight = Math.max(1.0d, boundingHeight);
+ double boundingHeight = WidgetUtil.getRequiredHeightBoundingClientRectDouble(cellElem);
root.removeChild(detectionTr);
- if (root.hasChildNodes()) {
- reapplyDefaultRowHeights();
- applyHeightByRows();
+ // Height lesser than 1px causes serious performance problems, skip this hit.
+ if (boundingHeight >= 1 && defaultRowHeight != boundingHeight) {
+ defaultRowHeight = boundingHeight;
+ defaultRowHeightShouldBeAutodetected = false;
+ if (root.hasChildNodes()) {
+ reapplyDefaultRowHeights();
+ applyHeightByRows();
+ }
}
}
@@ -2342,62 +2400,23 @@ public class Escalator extends Widget implements RequiresResize,
setTopRowLogicalIndex(topRowLogicalIndex + diff);
}
- private class DeferredDomSorter {
- private static final int SORT_DELAY_MILLIS = 50;
-
- // as it happens, 3 frames = 50ms @ 60fps.
- private static final int REQUIRED_FRAMES_PASSED = 3;
-
- private final AnimationCallback frameCounter = new AnimationCallback() {
- @Override
- public void execute(double timestamp) {
- framesPassed++;
- boolean domWasSorted = sortIfConditionsMet();
- if (!domWasSorted) {
- animationHandle = AnimationScheduler.get()
- .requestAnimationFrame(this);
- } else {
- waiting = false;
- }
- }
- };
-
- private int framesPassed;
- private double startTime;
- private AnimationHandle animationHandle;
-
- /** <code>true</code> if a sort is scheduled */
- public boolean waiting = false;
+ private class DeferredDomSorter extends Timer implements ScheduledCommand {
+ // 1000/50 (@50fps)
+ private static final int SORT_DELAY_MILLIS = 20;
public void reschedule() {
- waiting = true;
- resetConditions();
- animationHandle = AnimationScheduler.get()
- .requestAnimationFrame(frameCounter);
- }
-
- private boolean sortIfConditionsMet() {
- boolean enoughFramesHavePassed = framesPassed >= REQUIRED_FRAMES_PASSED;
- boolean enoughTimeHasPassed = (Duration.currentTimeMillis() - startTime) >= SORT_DELAY_MILLIS;
- boolean notTouchActivity = !scroller.touchHandlerBundle.touching;
- boolean conditionsMet = enoughFramesHavePassed
- && enoughTimeHasPassed && notTouchActivity;
-
- if (conditionsMet) {
- resetConditions();
- sortDomElements();
- }
+ schedule(SORT_DELAY_MILLIS);
+ }
- return conditionsMet;
+ @Override
+ public void run() {
+ Scheduler.get().scheduleFinally(this);
}
- private void resetConditions() {
- if (animationHandle != null) {
- animationHandle.cancel();
- animationHandle = null;
- }
- startTime = Duration.currentTimeMillis();
- framesPassed = 0;
+ @Override
+ public void execute() {
+ sortDomElements();
+ bodyElem.removeClassName("scrolling");
}
}
@@ -6072,7 +6091,7 @@ public class Escalator extends Widget implements RequiresResize,
public void scrollToRowAndSpacer(final int rowIndex,
final ScrollDestination destination, final int padding)
throws IllegalArgumentException {
- Scheduler.get().scheduleFinally(new ScheduledCommand() {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
validateScrollDestination(destination, padding);
@@ -6496,7 +6515,7 @@ public class Escalator extends Widget implements RequiresResize,
@Override
public boolean isWorkPending() {
- return body.domSorter.waiting || verticalScrollbar.isWorkPending()
+ return body.domSorter.isRunning() || verticalScrollbar.isWorkPending()
|| horizontalScrollbar.isWorkPending() || layoutIsScheduled;
}