diff options
-rw-r--r-- | client/src/com/vaadin/client/ui/grid/Escalator.java | 98 | ||||
-rw-r--r-- | client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java | 162 |
2 files changed, 181 insertions, 79 deletions
diff --git a/client/src/com/vaadin/client/ui/grid/Escalator.java b/client/src/com/vaadin/client/ui/grid/Escalator.java index 787631fb24..5d310d4d1a 100644 --- a/client/src/com/vaadin/client/ui/grid/Escalator.java +++ b/client/src/com/vaadin/client/ui/grid/Escalator.java @@ -436,11 +436,11 @@ public class Escalator extends Widget { final NativeEvent event) { if (!Double.isNaN(deltaX)) { - escalator.horizontalScrollbar.setScrollPosByDelta((int) deltaX); + escalator.horizontalScrollbar.setScrollPosByDelta(deltaX); } if (!Double.isNaN(deltaY)) { - escalator.verticalScrollbar.setScrollPosByDelta((int) deltaY); + escalator.verticalScrollbar.setScrollPosByDelta(deltaY); } /* @@ -473,8 +473,8 @@ public class Escalator extends Widget { private double yFric; private boolean cancelled = false; - private int lastLeft; - private int lastTop; + private double lastLeft; + private double lastTop; /** * Creates a new animation callback to handle touch-scrolling flick with @@ -531,12 +531,12 @@ public class Escalator extends Widget { return; } - int currentLeft = horizontalScrollbar.getScrollPos(); - int currentTop = verticalScrollbar.getScrollPos(); + double currentLeft = horizontalScrollbar.getScrollPos(); + double currentTop = verticalScrollbar.getScrollPos(); final double timeDiff = timestamp - prevTime; double left = currentLeft - velX * timeDiff; - setScrollLeft((int) left); + setScrollLeft(left); velX -= xFric * timeDiff; double top = currentTop - velY * timeDiff; @@ -736,8 +736,8 @@ public class Escalator extends Widget { tableWrapper.getStyle().setHeight(tableWrapperHeight, Unit.PX); tableWrapper.getStyle().setWidth(tableWrapperWidth, Unit.PX); - verticalScrollbar.setOffsetSize((int) (tableWrapperHeight - - footer.heightOfSection - header.heightOfSection)); + verticalScrollbar.setOffsetSize(tableWrapperHeight + - footer.heightOfSection - header.heightOfSection); verticalScrollbar.setScrollSize(scrollContentHeight); /* @@ -746,7 +746,7 @@ public class Escalator extends Widget { * the scroll position, and re-apply it once the scrollbar size has * been adjusted. */ - int prevScrollPos = horizontalScrollbar.getScrollPos(); + double prevScrollPos = horizontalScrollbar.getScrollPos(); int unfrozenPixels = columnConfiguration .getCalculatedColumnsWidth(Range.between( @@ -754,7 +754,7 @@ public class Escalator extends Widget { columnConfiguration.getColumnCount())); int frozenPixels = scrollContentWidth - unfrozenPixels; double hScrollOffsetWidth = tableWrapperWidth - frozenPixels; - horizontalScrollbar.setOffsetSize((int) hScrollOffsetWidth); + horizontalScrollbar.setOffsetSize(hScrollOffsetWidth); horizontalScrollbar.setScrollSize(unfrozenPixels); horizontalScrollbar.getElement().getStyle() .setLeft(frozenPixels, Unit.PX); @@ -770,9 +770,8 @@ public class Escalator extends Widget { return; } - final int scrollLeft = horizontalScrollbar.getScrollPos(); - final int scrollTop = verticalScrollbar.getScrollPos(); - + final double scrollTop = verticalScrollbar.getScrollPos(); + final double scrollLeft = horizontalScrollbar.getScrollPos(); if (lastScrollLeft != scrollLeft) { for (int i = 0; i < columnConfiguration.frozenColumns; i++) { header.updateFreezePosition(i, scrollLeft); @@ -976,9 +975,9 @@ public class Escalator extends Widget { final int targetEndPx = targetStartPx + columnConfiguration.getColumnWidthActual(columnIndex); - final int viewportStartPx = getScrollLeft(); - int viewportEndPx = viewportStartPx + getElement().getOffsetWidth() - - frozenPixels; + final double viewportStartPx = getScrollLeft(); + double viewportEndPx = viewportStartPx + + getElement().getOffsetWidth() - frozenPixels; if (verticalScrollbar.showsScrollHandle()) { viewportEndPx -= Util.getNativeScrollbarSize(); } @@ -991,7 +990,7 @@ public class Escalator extends Widget { * content, since the browser will adjust for that, and everything * fall into line accordingly. */ - setScrollLeft((int) scrollLeft); + setScrollLeft(scrollLeft); } public void scrollToRow(final int rowIndex, @@ -1460,8 +1459,8 @@ public class Escalator extends Widget { int insertedColumnsWidth = columnConfiguration .getCalculatedColumnsWidth(Range.withLength(offset, numberOfColumns)); - horizontalScrollbar - .setScrollPos((int) (scroller.lastScrollLeft + insertedColumnsWidth)); + horizontalScrollbar.setScrollPos(scroller.lastScrollLeft + + insertedColumnsWidth); } /* @@ -1936,10 +1935,10 @@ public class Escalator extends Widget { boolean rowsWereMoved = false; - final int topRowPos = getRowTop(visualRowOrder.getFirst()); + final double topRowPos = getRowTop(visualRowOrder.getFirst()); // TODO [[mpixscroll]] - final int scrollTop = tBodyScrollTop; - final int viewportOffset = topRowPos - scrollTop; + final double scrollTop = tBodyScrollTop; + final double viewportOffset = topRowPos - scrollTop; /* * TODO [[optimize]] this if-else can most probably be refactored @@ -1954,7 +1953,7 @@ public class Escalator extends Widget { * heights - will not work with variable row heights */ int originalRowsToMove = (int) Math.ceil(viewportOffset - / (double) getDefaultRowHeight()); + / getDefaultRowHeight()); int rowsToMove = Math.min(originalRowsToMove, root.getChildCount()); @@ -1964,7 +1963,7 @@ public class Escalator extends Widget { * FIXME [[rowheight]]: coded to work only with default row * heights - will not work with variable row heights */ - final int logicalRowIndex = scrollTop / getDefaultRowHeight(); + final int logicalRowIndex = (int) (scrollTop / getDefaultRowHeight()); moveAndUpdateEscalatorRows(Range.between(start, end), 0, logicalRowIndex); @@ -1984,11 +1983,7 @@ public class Escalator extends Widget { * row. */ - /* - * Using the fact that integer division has implicit - * floor-function to our advantage here. - */ - int originalRowsToMove = Math.abs(viewportOffset + int originalRowsToMove = (int) Math.abs(viewportOffset / getDefaultRowHeight()); int rowsToMove = Math.min(originalRowsToMove, root.getChildCount()); @@ -2011,7 +2006,7 @@ public class Escalator extends Widget { * calculate the first logical row index from the scroll * position. */ - logicalRowIndex = scrollTop / getDefaultRowHeight(); + logicalRowIndex = (int) (scrollTop / getDefaultRowHeight()); } /* @@ -2300,15 +2295,15 @@ public class Escalator extends Widget { * side-effects. * <p> * <em>Note:</em> {@link Scroller#onScroll()} <em>will</em> be - * triggered, but it will not do anything, with the help of {@link - * Escalator#internalScrollEventCalls}. - * + * triggered, but it will not do anything, with the help of + * {@link Escalator#internalScrollEventCalls}. + * * @param yDelta * the delta of pixels to scrolls. A positive value moves the * viewport downwards, while a negative value moves the * viewport upwards */ - public void adjustScrollPosIgnoreEvents(final int yDelta) { + public void adjustScrollPosIgnoreEvents(final double yDelta) { if (yDelta == 0) { return; } @@ -2320,7 +2315,8 @@ public class Escalator extends Widget { * FIXME [[rowheight]]: coded to work only with default row heights * - will not work with variable row heights */ - final int rowTopPos = yDelta - yDelta % getDefaultRowHeight(); + final int rowTopPos = (int) yDelta + - ((int) yDelta % getDefaultRowHeight()); for (final Element tr : visualRowOrder) { setRowPosition(tr, 0, getRowTop(tr) + rowTopPos); } @@ -2632,7 +2628,7 @@ public class Escalator extends Widget { * |4| |4| |*| * 5 5 5 */ - int newTop = getRowTop(visualRowOrder + double newTop = getRowTop(visualRowOrder .get(removedVisualInside.getStart())); for (int i = 0; i < removedVisualInside.length(); i++) { final Element tr = visualRowOrder @@ -2642,7 +2638,7 @@ public class Escalator extends Widget { for (int i = removedVisualInside.getStart(); i < escalatorRowCount; i++) { final Element tr = visualRowOrder.get(i); - setRowPosition(tr, 0, newTop); + setRowPosition(tr, 0, (int) newTop); /* * FIXME [[rowheight]]: coded to work only with @@ -2907,8 +2903,8 @@ public class Escalator extends Widget { } } - private void setBodyScrollPosition(final int scrollLeft, - final int scrollTop) { + private void setBodyScrollPosition(final double scrollLeft, + final double scrollTop) { tBodyScrollLeft = scrollLeft; tBodyScrollTop = scrollTop; position.set(bodyElem, -tBodyScrollLeft, -tBodyScrollTop); @@ -3121,8 +3117,8 @@ public class Escalator extends Widget { * scroll position) in order to align the top row with the new * scroll position. */ - double scrollRatio = (double) verticalScrollbar.getScrollPos() - / (double) verticalScrollbar.getScrollSize(); + double scrollRatio = verticalScrollbar.getScrollPos() + / verticalScrollbar.getScrollSize(); scroller.recalculateScrollbarsForVirtualViewport(); internalScrollEventCalls++; verticalScrollbar.setScrollPos((int) (getDefaultRowHeight() @@ -3463,7 +3459,7 @@ public class Escalator extends Widget { * @deprecated maybe... */ @Deprecated - private int tBodyScrollTop = 0; + private double tBodyScrollTop = 0; /** * TODO: investigate whether this field is now unnecessary, as @@ -3472,7 +3468,7 @@ public class Escalator extends Widget { * @deprecated maybe... */ @Deprecated - private int tBodyScrollLeft = 0; + private double tBodyScrollLeft = 0; private final VerticalScrollbarBundle verticalScrollbar = new VerticalScrollbarBundle(); private final HorizontalScrollbarBundle horizontalScrollbar = new HorizontalScrollbarBundle(); @@ -3772,7 +3768,7 @@ public class Escalator extends Widget { * the number of pixels to scroll vertically */ public void setScrollTop(final double scrollTop) { - verticalScrollbar.setScrollPos((int) scrollTop); + verticalScrollbar.setScrollPos(scrollTop); } /** @@ -3781,7 +3777,7 @@ public class Escalator extends Widget { * * @return the logical horizontal scroll offset */ - public int getScrollLeft() { + public double getScrollLeft() { return horizontalScrollbar.getScrollPos(); } @@ -3792,7 +3788,7 @@ public class Escalator extends Widget { * @param scrollLeft * the number of pixels to scroll horizontally */ - public void setScrollLeft(final int scrollLeft) { + public void setScrollLeft(final double scrollLeft) { horizontalScrollbar.setScrollPos(scrollLeft); } @@ -3915,8 +3911,8 @@ public class Escalator extends Widget { * classes, so instead we call the outer class' method, which calls the * inner class' respective method. * <p> - * Ideally, this method would not exist, and - * {@link Scroller#onScroll()} would be called directly. + * Ideally, this method would not exist, and {@link Scroller#onScroll()} + * would be called directly. */ private void onScroll() { scroller.onScroll(); @@ -4108,8 +4104,8 @@ public class Escalator extends Widget { } /** - * Gets the amount of rows in Escalator's body that are shown, while {@link - * #getHeightMode()} is {@link HeightMode#ROW}. + * Gets the amount of rows in Escalator's body that are shown, while + * {@link #getHeightMode()} is {@link HeightMode#ROW}. * <p> * By default, it is {@value GridState#DEFAULT_HEIGHT_BY_ROWS}. * diff --git a/client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java b/client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java index 21935df4e6..1f637a9ccc 100644 --- a/client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java +++ b/client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java @@ -99,6 +99,15 @@ abstract class ScrollbarBundle { private static final int OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX = 13; /** + * The allowed value inaccuracy when comparing two double-typed pixel + * values. + * <p> + * Since we're comparing pixels on a screen, epsilon must be less than 1. + * 0.49 was deemed a perfectly fine and beautifully round number. + */ + private static final double PIXEL_EPSILON = 0.49d; + + /** * A representation of a single vertical scrollbar. * * @see VerticalScrollbarBundle#getElement() @@ -127,17 +136,17 @@ abstract class ScrollbarBundle { } @Override - public int getScrollSize() { + protected int internalGetScrollSize() { return scrollSizeElement.getOffsetHeight(); } @Override - protected void internalSetOffsetSize(int px) { + protected void internalSetOffsetSize(double px) { root.getStyle().setHeight(px, Unit.PX); } @Override - public int getOffsetSize() { + public double getOffsetSize() { return root.getOffsetHeight(); } @@ -191,17 +200,17 @@ abstract class ScrollbarBundle { } @Override - public int getScrollSize() { + protected int internalGetScrollSize() { return scrollSizeElement.getOffsetWidth(); } @Override - protected void internalSetOffsetSize(int px) { + protected void internalSetOffsetSize(double px) { root.getStyle().setWidth(px, Unit.PX); } @Override - public int getOffsetSize() { + public double getOffsetSize() { return root.getOffsetWidth(); } @@ -230,8 +239,8 @@ abstract class ScrollbarBundle { protected final Element scrollSizeElement = DOM.createDiv(); protected boolean isInvisibleScrollbar = false; - private int scrollPos = 0; - private int maxScrollPos = 0; + private double scrollPos = 0; + private double maxScrollPos = 0; private boolean scrollHandleIsVisible = false; @@ -243,6 +252,8 @@ abstract class ScrollbarBundle { root.appendChild(scrollSizeElement); } + protected abstract int internalGetScrollSize(); + /** * Sets the primary style name * @@ -263,12 +274,17 @@ abstract class ScrollbarBundle { } /** - * Modifies the scroll position of this scrollbar by a number of pixels + * Modifies the scroll position of this scrollbar by a number of pixels. + * <p> + * <em>Note:</em> Even though {@code double} values are used, they are + * currently only used as integers as large {@code int} (or small but fast + * {@code long}). This means, all values are truncated to zero decimal + * places. * * @param delta * the delta in pixels to change the scroll position by */ - public final void setScrollPosByDelta(int delta) { + public final void setScrollPosByDelta(double delta) { if (delta != 0) { setScrollPos(getScrollPos() + delta); } @@ -282,16 +298,21 @@ abstract class ScrollbarBundle { * the new size of {@link #root} in the dimension this scrollbar * is representing */ - protected abstract void internalSetOffsetSize(int px); + protected abstract void internalSetOffsetSize(double px); /** * Sets the length of the scrollbar. + * <p> + * <em>Note:</em> Even though {@code double} values are used, they are + * currently only used as integers as large {@code int} (or small but fast + * {@code long}). This means, all values are truncated to zero decimal + * places. * * @param px * the length of the scrollbar in pixels */ - public final void setOffsetSize(int px) { - internalSetOffsetSize(px); + public final void setOffsetSize(double px) { + internalSetOffsetSize(truncate(px)); forceScrollbar(showsScrollHandle()); recalculateMaxScrollPos(); fireVisibilityChangeIfNeeded(); @@ -312,24 +333,65 @@ abstract class ScrollbarBundle { * * @return the length of the scrollbar in pixels */ - public abstract int getOffsetSize(); + public abstract double getOffsetSize(); /** * Sets the scroll position of the scrollbar in the axis the scrollbar is * representing. + * <p> + * <em>Note:</em> Even though {@code double} values are used, they are + * currently only used as integers as large {@code int} (or small but fast + * {@code long}). This means, all values are truncated to zero decimal + * places. * * @param px * the new scroll position in pixels */ - public final void setScrollPos(int px) { - int oldScrollPos = scrollPos; - scrollPos = Math.max(0, Math.min(maxScrollPos, px)); + public final void setScrollPos(double px) { + double oldScrollPos = scrollPos; + scrollPos = Math.max(0, Math.min(maxScrollPos, truncate(px))); + + if (!pixelValuesEqual(oldScrollPos, scrollPos)) { + /* + * This is where the value needs to be converted into an integer no + * matter how we flip it, since GWT expects an integer value. + * There's no point making a JSNI method that accepts doubles as the + * scroll position, since the browsers themselves don't support such + * large numbers (as of today, 25.3.2014). This double-ranged is + * only facilitating future virtual scrollbars. + */ + internalSetScrollPos(toInt32(px)); + } + } - if (oldScrollPos != scrollPos) { - internalSetScrollPos(px); + /** + * Truncates a double such that no decimal places are retained. + * <p> + * E.g. {@code trunc(2.3d) == 2.0d} and {@code trunc(-2.3d) == -2.0d}. + * + * @param num + * the double value to be truncated + * @return the {@code num} value without any decimal digits + */ + private static double truncate(double num) { + if (num > 0) { + return Math.floor(num); + } else { + return Math.ceil(num); } } + /** + * Modifies the element's scroll position (scrollTop or scrollLeft). + * <p> + * <em>Note:</em> The parameter here is a type of integer (instead of a + * double) by design. The browsers internally convert all double values into + * an integer value. To make this fact explicit, this API has chosen to + * force integers already at this level. + * + * @param px + * integer pixel value to scroll to + */ protected abstract void internalSetScrollPos(int px); /** @@ -338,14 +400,24 @@ abstract class ScrollbarBundle { * * @return the new scroll position in pixels */ - public final int getScrollPos() { - assert internalGetScrollPos() == scrollPos : "calculated scroll position (" - + scrollPos + public final double getScrollPos() { + assert internalGetScrollPos() == toInt32(scrollPos) : "calculated scroll position (" + + toInt32(scrollPos) + ") did not match the DOM element scroll position (" + internalGetScrollPos() + ")"; return scrollPos; } + /** + * Retrieves the element's scroll position (scrollTop or scrollLeft). + * <p> + * <em>Note:</em> The parameter here is a type of integer (instead of a + * double) by design. The browsers internally convert all double values into + * an integer value. To make this fact explicit, this API has chosen to + * force integers already at this level. + * + * @return integer pixel value of the scroll position + */ protected abstract int internalGetScrollPos(); /** @@ -362,13 +434,18 @@ abstract class ScrollbarBundle { /** * Sets the amount of pixels the scrollbar needs to be able to scroll * through. + * <p> + * <em>Note:</em> Even though {@code double} values are used, they are + * currently only used as integers as large {@code int} (or small but fast + * {@code long}). This means, all values are truncated to zero decimal + * places. * * @param px * the number of pixels the scrollbar should be able to scroll * through */ - public final void setScrollSize(int px) { - internalSetScrollSize(px); + public final void setScrollSize(double px) { + internalSetScrollSize(toInt32(truncate(px))); forceScrollbar(showsScrollHandle()); recalculateMaxScrollPos(); fireVisibilityChangeIfNeeded(); @@ -381,7 +458,9 @@ abstract class ScrollbarBundle { * @return the number of pixels the scrollbar should be able to scroll * through */ - public abstract int getScrollSize(); + public double getScrollSize() { + return internalGetScrollSize(); + } /** * Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in the @@ -447,8 +526,8 @@ abstract class ScrollbarBundle { } public void recalculateMaxScrollPos() { - int scrollSize = getScrollSize(); - int offsetSize = getOffsetSize(); + double scrollSize = getScrollSize(); + double offsetSize = getOffsetSize(); maxScrollPos = Math.max(0, scrollSize - offsetSize); // make sure that the correct max scroll position is maintained. @@ -459,7 +538,6 @@ abstract class ScrollbarBundle { * This is a method that JSNI can call to synchronize the object state from * the DOM. */ - @SuppressWarnings("unused") private final void updateScrollPosFromDom() { scrollPos = internalGetScrollPos(); } @@ -493,4 +571,32 @@ abstract class ScrollbarBundle { getHandlerManager().fireEvent(event); } } + + + /** + * Converts a double into an integer by JavaScript's terms. + * <p> + * Implementation copied from {@link Element#toInt32(double)}. + * + * @param val + * the double value to convert into an integer + * @return the double value converted to an integer + */ + private static native int toInt32(double val) + /*-{ + return val | 0; + }-*/; + + /** + * Compares two double values with the error margin of + * {@link #PIXEL_EPSILON} (i.e. {@value #PIXEL_EPSILON}) + * + * @param num1 + * the first value for which to compare equality + * @param num2 + * the second value for which to compare equality + */ + private static boolean pixelValuesEqual(final double num1, final double num2) { + return Math.abs(num1 - num2) <= PIXEL_EPSILON; + } } |