summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenrik Paul <henrik@vaadin.com>2013-11-21 16:52:32 +0200
committerHenrik Paul <henrik@vaadin.com>2013-11-21 16:54:53 +0200
commitc2d38fa6c2d67457065fd3dce7e0d939ae0a1278 (patch)
tree88bca7fb1008a3ce5c36d7f8ba9e0f1da894e48f
parentc2b988781045d1e6723b8159ca34d6c0afcdcde8 (diff)
downloadvaadin-framework-c2d38fa6c2d67457065fd3dce7e0d939ae0a1278.tar.gz
vaadin-framework-c2d38fa6c2d67457065fd3dce7e0d939ae0a1278.zip
Support OSX's hiding scrollbars (#12645)
Change-Id: If5df6a7651482a33558088398330fd73a4d43645
-rw-r--r--WebContent/VAADIN/themes/base/escalator/escalator.scss16
-rw-r--r--client/src/com/vaadin/client/ui/grid/Escalator.java177
-rw-r--r--client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java351
3 files changed, 455 insertions, 89 deletions
diff --git a/WebContent/VAADIN/themes/base/escalator/escalator.scss b/WebContent/VAADIN/themes/base/escalator/escalator.scss
index 436f849456..8136f23df6 100644
--- a/WebContent/VAADIN/themes/base/escalator/escalator.scss
+++ b/WebContent/VAADIN/themes/base/escalator/escalator.scss
@@ -11,9 +11,23 @@ $border-color: #aaa;
.#{$primaryStyleName}-scroller {
position: absolute;
overflow: auto;
- height: inherit;
+ z-index: 20;
+}
+
+.#{$primaryStyleName}-scroller-horizontal {
left: 0; /* Left position adjusted to align with frozen columns */
right: 0;
+ bottom: 0;
+ overflow-y: hidden;
+ -ms-overflow-y: hidden;
+}
+
+.#{$primaryStyleName}-scroller-vertical {
+ right: 0;
+ top: 0; /* this will be overridden by code, but it's a good default behavior */
+ bottom: 0; /* this will be overridden by code, but it's a good default behavior */
+ overflow-x: hidden;
+ -ms-overflow-x: hidden;
}
.#{$primaryStyleName}-tablewrapper {
diff --git a/client/src/com/vaadin/client/ui/grid/Escalator.java b/client/src/com/vaadin/client/ui/grid/Escalator.java
index bd9d4ba245..769a109569 100644
--- a/client/src/com/vaadin/client/ui/grid/Escalator.java
+++ b/client/src/com/vaadin/client/ui/grid/Escalator.java
@@ -46,6 +46,8 @@ import com.vaadin.client.ui.grid.PositionFunction.AbsolutePosition;
import com.vaadin.client.ui.grid.PositionFunction.Translate3DPosition;
import com.vaadin.client.ui.grid.PositionFunction.TranslatePosition;
import com.vaadin.client.ui.grid.PositionFunction.WebkitTranslate3DPosition;
+import com.vaadin.client.ui.grid.ScrollbarBundle.HorizontalScrollbarBundle;
+import com.vaadin.client.ui.grid.ScrollbarBundle.VerticalScrollbarBundle;
/*-
@@ -227,10 +229,6 @@ public class Escalator extends Widget {
* re-measure)
*/
/*
- * [[escalator]]: This code will require alterations that are relevant for
- * the escalator functionality.
- */
- /*
* [[rowwidth]] [[colwidth]]: This code will require alterations that are
* relevant for being able to support variable row heights or column widths.
* NOTE: these bits can most often also be identified by searching for code
@@ -391,7 +389,7 @@ public class Escalator extends Widget {
}
}
- moveScrollFromEvent(escalator.scrollerElem, -deltaX, -deltaY,
+ moveScrollFromEvent(escalator, -deltaX, -deltaY,
event.getNativeEvent());
}
@@ -406,21 +404,16 @@ public class Escalator extends Widget {
}
}
- public static void moveScrollFromEvent(final Element scrollerElem,
+ public static void moveScrollFromEvent(final Escalator escalator,
final double deltaX, final double deltaY,
final NativeEvent event) {
- /*
- * TODO [[optimize]]: instead of calling getScollLeft/Top that
- * potentially causes a reflow, update a new scroll absolute
- * position.
- */
- if (!Double.isNaN(deltaX) && deltaX != 0) {
- scrollerElem
- .setScrollLeft((int) (scrollerElem.getScrollLeft() + deltaX));
+
+ if (!Double.isNaN(deltaX)) {
+ escalator.horizontalScrollbar.setScrollPosByDelta((int) deltaX);
}
- if (!Double.isNaN(deltaY) && deltaY != 0) {
- scrollerElem
- .setScrollTop((int) (scrollerElem.getScrollTop() + deltaY));
+
+ if (!Double.isNaN(deltaY)) {
+ escalator.verticalScrollbar.setScrollPosByDelta((int) deltaY);
}
/*
@@ -514,8 +507,8 @@ public class Escalator extends Widget {
private void cancelBecauseOfEdgeOrCornerMaybe(final int lastLeft,
final int lastTop) {
- if (lastLeft == scrollerElem.getScrollLeft()
- && lastTop == scrollerElem.getScrollTop()) {
+ if (lastLeft == horizontalScrollbar.getScrollPos()
+ && lastTop == verticalScrollbar.getScrollPos()) {
cancel();
}
}
@@ -564,8 +557,7 @@ public class Escalator extends Widget {
deltaY = -0.5*e.wheelDelta;
}
- var scroller = esc.@com.vaadin.client.ui.grid.Escalator::scrollerElem;
- @com.vaadin.client.ui.grid.Escalator.JsniUtil::moveScrollFromEvent(*)(scroller, deltaX, deltaY, e);
+ @com.vaadin.client.ui.grid.Escalator.JsniUtil::moveScrollFromEvent(*)(esc, deltaX, deltaY, e);
});
}-*/;
@@ -574,41 +566,35 @@ public class Escalator extends Widget {
* that the sizes of the scroll handles appear correct in the browser
*/
public void recalculateScrollbarsForVirtualViewport() {
- double scrollerHeight = height - header.height;
-
- /*
- * It looks better this way: if we only have a vertical scrollbar,
- * keep it between the header and footer. But if we have a
- * horizontal scrollbar, envelop the footer also.
- */
- if (!needsHorizontalScrollbars()) {
- scrollerHeight -= footer.height;
- }
-
- scrollerElem.getStyle().setHeight(scrollerHeight, Unit.PX);
-
- final double scrollerTop = header.height;
- scrollerElem.getStyle().setTop(scrollerTop, Unit.PX);
-
- /*
- * TODO [[freezecol]]: cut scrollerElem from the left to take freeze
- * columns into account. innerScroller probably needs some
- * adjustments too.
- */
-
// TODO [[rowheight]]: adjust for variable row heights.
- double innerScrollerHeight = ROW_HEIGHT_PX * body.getRowCount();
+ int innerScrollerHeight = ROW_HEIGHT_PX * body.getRowCount();
+
+ double vScrollBottom = footer.height;
if (needsHorizontalScrollbars()) {
- innerScrollerHeight += footer.height;
+ vScrollBottom += horizontalScrollbar.getScrollbarThickness();
}
- innerScrollerElem.getStyle()
- .setHeight(innerScrollerHeight, Unit.PX);
+
+ verticalScrollbar.getElement().getStyle()
+ .setTop(header.height, Unit.PX);
+ verticalScrollbar.getElement().getStyle()
+ .setBottom(vScrollBottom, Unit.PX);
+ verticalScrollbar.setScrollSize(innerScrollerHeight);
// TODO [[colwidth]]: adjust for variable column widths.
int columnsToScroll = columnConfiguration.getColumnCount()
- columnConfiguration.getFrozenColumnCount();
- innerScrollerElem.getStyle().setWidth(
- COLUMN_WIDTH_PX * columnsToScroll, Unit.PX);
+ horizontalScrollbar
+ .setScrollSize(COLUMN_WIDTH_PX * columnsToScroll);
+
+ final Style hScrollbarStyle = horizontalScrollbar.getElement()
+ .getStyle();
+ if (needsVerticalScrollbars()) {
+ final int hScrollbarRight = verticalScrollbar
+ .getScrollbarThickness();
+ hScrollbarStyle.setRight(hScrollbarRight, Unit.PX);
+ } else {
+ hScrollbarStyle.clearRight();
+ }
// we might've got new or got rid of old scrollbars.
recalculateTableWrapperSize();
@@ -620,15 +606,15 @@ public class Escalator extends Widget {
*/
public void recalculateTableWrapperSize() {
double wrapperHeight = height;
- if (scrollerElem.getOffsetWidth() < innerScrollerElem
- .getOffsetWidth()) {
- wrapperHeight -= Util.getNativeScrollbarSize();
+ if (horizontalScrollbar.getOffsetSize() < horizontalScrollbar
+ .getScrollSize()) {
+ wrapperHeight -= horizontalScrollbar.getScrollbarThickness();
}
double wrapperWidth = width;
- if (scrollerElem.getOffsetHeight() - footer.height < innerScrollerElem
- .getOffsetHeight()) {
- wrapperWidth -= Util.getNativeScrollbarSize();
+ if (verticalScrollbar.getOffsetSize() - footer.height < verticalScrollbar
+ .getScrollSize()) {
+ wrapperWidth -= verticalScrollbar.getScrollbarThickness();
}
tableWrapper.getStyle().setHeight(wrapperHeight, Unit.PX);
@@ -651,8 +637,8 @@ public class Escalator extends Widget {
return;
}
- final int scrollLeft = scrollerElem.getScrollLeft();
- final int scrollTop = scrollerElem.getScrollTop();
+ final int scrollLeft = horizontalScrollbar.getScrollPos();
+ final int scrollTop = verticalScrollbar.getScrollPos();
if (lastScrollLeft != scrollLeft) {
for (int i = 0; i < columnConfiguration.frozenColumns; i++) {
@@ -895,7 +881,7 @@ public class Escalator extends Widget {
}
}
- private static final String CLASS_NAME = "v-escalator";
+ static final String CLASS_NAME = "v-escalator";
private abstract class AbstractRowContainer implements RowContainer {
private EscalatorUpdater updater = EscalatorUpdater.NULL;
@@ -1165,11 +1151,11 @@ public class Escalator extends Widget {
}
/*
- * TODO [[escalator]][[rowheight]]: even if no rows are evaluated in
- * the current viewport, the heights of some unrendered rows might
- * change in a refresh. This would cause the scrollbar to be
- * adjusted (in scrollHeight and/or scrollTop). Do we want to take
- * this into account?
+ * TODO [[rowheight]]: even if no rows are evaluated in the current
+ * viewport, the heights of some unrendered rows might change in a
+ * refresh. This would cause the scrollbar to be adjusted (in
+ * scrollHeight and/or scrollTop). Do we want to take this into
+ * account?
*/
if (hasColumnAndRowData()) {
/*
@@ -1251,7 +1237,7 @@ public class Escalator extends Widget {
final int leftByDiff = (int) (scroller.lastScrollLeft - removedColumnsPxAmount);
final int newScrollLeft = Math.max(firstRemovedColumnLeft,
leftByDiff);
- scrollerElem.setScrollLeft(newScrollLeft);
+ horizontalScrollbar.setScrollPos(newScrollLeft);
}
// this needs to be after the scroll position adjustment above.
@@ -1304,8 +1290,8 @@ public class Escalator extends Widget {
* COLUMN_WIDTH_PX;
if (columnsWereAddedToTheLeftOfViewport) {
- scrollerElem
- .setScrollLeft((int) (scroller.lastScrollLeft + numberOfColumns
+ horizontalScrollbar
+ .setScrollPos((int) (scroller.lastScrollLeft + numberOfColumns
* COLUMN_WIDTH_PX));
}
}
@@ -1562,10 +1548,9 @@ public class Escalator extends Widget {
*/
scroller.recalculateScrollbarsForVirtualViewport();
- final boolean addedRowsAboveCurrentViewport = index * ROW_HEIGHT_PX < scrollerElem
- .getScrollTop();
- final boolean addedRowsBelowCurrentViewport = index * ROW_HEIGHT_PX > scrollerElem
- .getScrollTop() + calculateHeight();
+ final boolean addedRowsAboveCurrentViewport = index * ROW_HEIGHT_PX < getScrollTop();
+ final boolean addedRowsBelowCurrentViewport = index * ROW_HEIGHT_PX > getScrollTop()
+ + calculateHeight();
if (addedRowsAboveCurrentViewport) {
/*
@@ -1751,7 +1736,7 @@ public class Escalator extends Widget {
}
internalScrollEventCalls++;
- scrollerElem.setScrollTop(scrollerElem.getScrollTop() + yDelta);
+ verticalScrollbar.setScrollPosByDelta(yDelta);
final int snappedYDelta = yDelta - yDelta % ROW_HEIGHT_PX;
for (final Element tr : visualRowOrder) {
@@ -1876,8 +1861,8 @@ public class Escalator extends Widget {
// TODO [[rowheight]]
final int yDelta = removedAbove.length() * ROW_HEIGHT_PX;
final int firstLogicalRowHeight = ROW_HEIGHT_PX;
- final boolean removalScrollsToShowFirstLogicalRow = scrollerElem
- .getScrollTop() - yDelta < firstLogicalRowHeight;
+ final boolean removalScrollsToShowFirstLogicalRow = verticalScrollbar
+ .getScrollPos() - yDelta < firstLogicalRowHeight;
if (removedVisualInside.isEmpty()
&& (!removalScrollsToShowFirstLogicalRow || !firstVisualRowIsRemoved)) {
@@ -1895,7 +1880,8 @@ public class Escalator extends Widget {
* current negative scrolltop, presto!), so that it isn't
* aligned funnily
*/
- adjustScrollPosIgnoreEvents(-scrollerElem.getScrollTop());
+ adjustScrollPosIgnoreEvents(-verticalScrollbar
+ .getScrollPos());
}
}
@@ -2584,9 +2570,17 @@ public class Escalator extends Widget {
}
}
- scrollerElem.getStyle().setLeft(frozenColumns * COLUMN_WIDTH_PX,
- Unit.PX);
+ /*
+ * If decreasing the amount of frozen columns, and scrolled to the
+ * right, the scroll position will reset. So we need to remember the
+ * scroll position, and re-apply it once the scrollbar size has been
+ * adjusted.
+ */
+ int scrollPos = horizontalScrollbar.getScrollPos();
+ horizontalScrollbar.getElement().getStyle()
+ .setLeft(frozenColumns * COLUMN_WIDTH_PX, Unit.PX);
scroller.recalculateScrollbarsForVirtualViewport();
+ horizontalScrollbar.setScrollPos(scrollPos);
}
@Override
@@ -2632,8 +2626,8 @@ public class Escalator extends Widget {
private int tBodyScrollTop = 0;
private int tBodyScrollLeft = 0;
- private final Element scrollerElem = DOM.createDiv();
- private final Element innerScrollerElem = DOM.createDiv();
+ private final VerticalScrollbarBundle verticalScrollbar = new VerticalScrollbarBundle();
+ private final HorizontalScrollbarBundle horizontalScrollbar = new HorizontalScrollbarBundle();
private final HeaderRowContainer header = new HeaderRowContainer(headElem);
private final BodyRowContainer body = new BodyRowContainer(bodyElem);
@@ -2690,10 +2684,11 @@ public class Escalator extends Widget {
setElement(root);
setStyleName(CLASS_NAME);
- scrollerElem.setClassName(CLASS_NAME + "-scroller");
- root.appendChild(scrollerElem);
-
- scrollerElem.appendChild(innerScrollerElem);
+ root.appendChild(verticalScrollbar.getElement());
+ root.appendChild(horizontalScrollbar.getElement());
+ verticalScrollbar.setScrollbarThickness(Util.getNativeScrollbarSize());
+ horizontalScrollbar
+ .setScrollbarThickness(Util.getNativeScrollbarSize());
tableWrapper = DOM.createDiv();
tableWrapper.setClassName(CLASS_NAME + "-tablewrapper");
@@ -2732,11 +2727,17 @@ public class Escalator extends Widget {
recalculateElementSizes();
body.paintInsertRows(0, body.getRowCount());
- scroller.attachScrollListener(scrollerElem);
+ scroller.attachScrollListener(verticalScrollbar
+ .getElement());
+ scroller.attachScrollListener(horizontalScrollbar
+ .getElement());
scroller.attachMousewheelListener(getElement());
scroller.attachTouchListeners(getElement());
} else {
- scroller.detachScrollListener(scrollerElem);
+ scroller.detachScrollListener(verticalScrollbar
+ .getElement());
+ scroller.detachScrollListener(horizontalScrollbar
+ .getElement());
scroller.detachMousewheelListener(getElement());
scroller.detachTouchListeners(getElement());
}
@@ -2870,7 +2871,7 @@ public class Escalator extends Widget {
* @return the logical vertical scroll offset
*/
public double getScrollTop() {
- return scrollerElem.getScrollTop();
+ return verticalScrollbar.getScrollPos();
}
/**
@@ -2881,7 +2882,7 @@ public class Escalator extends Widget {
* the number of pixels to scroll vertically
*/
public void setScrollTop(final double scrollTop) {
- scrollerElem.setScrollTop((int) scrollTop);
+ verticalScrollbar.setScrollPos((int) scrollTop);
}
/**
@@ -2891,7 +2892,7 @@ public class Escalator extends Widget {
* @return the logical horizontal scroll offset
*/
public int getScrollLeft() {
- return scrollerElem.getScrollLeft();
+ return horizontalScrollbar.getScrollPos();
}
/**
@@ -2902,7 +2903,7 @@ public class Escalator extends Widget {
* the number of pixels to scroll horizontally
*/
public void setScrollLeft(final int scrollLeft) {
- scrollerElem.setScrollLeft(scrollLeft);
+ horizontalScrollbar.setScrollPos(scrollLeft);
}
/**
diff --git a/client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java b/client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java
new file mode 100644
index 0000000000..82c2c56117
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/grid/ScrollbarBundle.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui.grid;
+
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+
+/**
+ * An element-like bundle representing a configurable and visual scrollbar in
+ * one axis.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ * @see VerticalScrollbarBundle
+ * @see HorizontalScrollbarBundle
+ */
+abstract class ScrollbarBundle {
+ private static final String CLASS_NAME = Escalator.CLASS_NAME + "-scroller";
+
+ /**
+ * The pixel size for OSX's invisible scrollbars.
+ * <p>
+ * Touch devices don't show a scrollbar at all, so the scrollbar size is
+ * irrelevant in their case. There doesn't seem to be any other popular
+ * platforms that has scrollbars similar to OSX. Thus, this behavior is
+ * tailored for OSX only, until additional platforms start behaving this
+ * way.
+ */
+ private static final int OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX = 13;
+
+ /**
+ * A representation of a single vertical scrollbar.
+ *
+ * @see VerticalScrollbarBundle#getElement()
+ */
+ final static class VerticalScrollbarBundle extends ScrollbarBundle {
+ public VerticalScrollbarBundle() {
+ root.addClassName(CLASS_NAME + "-vertical");
+ }
+
+ @Override
+ public void setScrollPos(int px) {
+ root.setScrollTop(px);
+ }
+
+ @Override
+ public int getScrollPos() {
+ return root.getScrollTop();
+ }
+
+ @Override
+ protected void internalSetScrollSize(int px) {
+ scrollSizeElement.getStyle().setHeight(px, Unit.PX);
+ }
+
+ @Override
+ public int getScrollSize() {
+ return scrollSizeElement.getOffsetHeight();
+ }
+
+ @Override
+ protected void internalSetOffsetSize(int px) {
+ root.getStyle().setHeight(px, Unit.PX);
+ }
+
+ @Override
+ public int getOffsetSize() {
+ return root.getOffsetHeight();
+ }
+
+ @Override
+ protected void internalSetScrollbarThickness(int px) {
+ root.getStyle().setWidth(px, Unit.PX);
+ scrollSizeElement.getStyle().setWidth(px, Unit.PX);
+ }
+
+ @Override
+ protected int internalGetScrollbarThickness() {
+ return root.getOffsetWidth();
+ }
+
+ @Override
+ protected void forceScrollbar(boolean enable) {
+ if (enable) {
+ root.getStyle().setOverflowY(Overflow.SCROLL);
+ } else {
+ root.getStyle().clearOverflowY();
+ }
+ }
+ }
+
+ /**
+ * A representation of a single horizontal scrollbar.
+ *
+ * @see HorizontalScrollbarBundle#getElement()
+ */
+ final static class HorizontalScrollbarBundle extends ScrollbarBundle {
+ protected HorizontalScrollbarBundle() {
+ root.addClassName(CLASS_NAME + "-horizontal");
+ }
+
+ @Override
+ public void setScrollPos(int px) {
+ root.setScrollLeft(px);
+ }
+
+ @Override
+ public int getScrollPos() {
+ return root.getScrollLeft();
+ }
+
+ @Override
+ protected void internalSetScrollSize(int px) {
+ scrollSizeElement.getStyle().setWidth(px, Unit.PX);
+ }
+
+ @Override
+ public int getScrollSize() {
+ return scrollSizeElement.getOffsetWidth();
+ }
+
+ @Override
+ protected void internalSetOffsetSize(int px) {
+ root.getStyle().setWidth(px, Unit.PX);
+ }
+
+ @Override
+ public int getOffsetSize() {
+ return root.getOffsetWidth();
+ }
+
+ @Override
+ protected void internalSetScrollbarThickness(int px) {
+ root.getStyle().setHeight(px, Unit.PX);
+ scrollSizeElement.getStyle().setHeight(px, Unit.PX);
+ }
+
+ @Override
+ protected int internalGetScrollbarThickness() {
+ return root.getOffsetHeight();
+ }
+
+ @Override
+ protected void forceScrollbar(boolean enable) {
+ if (enable) {
+ root.getStyle().setOverflowX(Overflow.SCROLL);
+ } else {
+ root.getStyle().clearOverflowX();
+ }
+ }
+ }
+
+ protected final Element root = DOM.createDiv();
+ protected final Element scrollSizeElement = DOM.createDiv();
+ protected boolean isInvisibleScrollbar = false;
+
+ private ScrollbarBundle() {
+ root.appendChild(scrollSizeElement);
+ root.setClassName(CLASS_NAME);
+ }
+
+ /**
+ * Gets the root element of this scrollbar-composition.
+ *
+ * @return the root element
+ */
+ public Element getElement() {
+ return root;
+ }
+
+ /**
+ * Modifies the scroll position of this scrollbar by a number of pixels
+ *
+ * @param delta
+ * the delta in pixels to change the scroll position by
+ */
+ public void setScrollPosByDelta(int delta) {
+ if (delta != 0) {
+ /*
+ * TODO [[optimize]]: rewrite this so that we can evoid a potential
+ * reflow from getScrollPos()
+ */
+ setScrollPos(getScrollPos() + delta);
+ }
+ }
+
+ /**
+ * Modifies {@link #root root's} dimensions in the axis the scrollbar is
+ * representing.
+ *
+ * @param px
+ * the new size of {@link #root} in the dimension this scrollbar
+ * is representing
+ */
+ protected abstract void internalSetOffsetSize(int px);
+
+ /**
+ * Sets the length of the scrollbar.
+ *
+ * @param px
+ * the length of the scrollbar in pixels
+ */
+ public void setOffsetSize(int px) {
+ internalSetOffsetSize(px);
+ forceScrollbar(needsScrollbars());
+ }
+
+ /**
+ * Force the scrollbar to be visible with CSS. In practice, this means to
+ * set either <code>overflow-x</code> or <code>overflow-y</code> to "
+ * <code>scroll</code>" in the scrollbar's direction.
+ * <p>
+ * This is an IE8 workaround, since it doesn't always show scrollbars with
+ * <code>overflow: auto</code> enabled.
+ */
+ protected abstract void forceScrollbar(boolean enable);
+
+ /**
+ * Gets the length of the scrollbar
+ *
+ * @return the length of the scrollbar in pixels
+ */
+ public abstract int getOffsetSize();
+
+ /**
+ * Sets the scroll position of the scrollbar in the axis the scrollbar is
+ * representing.
+ *
+ * @param px
+ * the new scroll position in pixels
+ */
+ public abstract void setScrollPos(int px);
+
+ /**
+ * Gets the scroll position of the scrollbar in the axis the scrollbar is
+ * representing.
+ *
+ * @return the new scroll position in pixels
+ */
+ public abstract int getScrollPos();
+
+ /**
+ * Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in
+ * such a way that the scrollbar is able to scroll a certain number of
+ * pixels in the axis it is representing.
+ *
+ * @param px
+ * the new size of {@link #scrollSizeElement} in the dimension
+ * this scrollbar is representing
+ */
+ protected abstract void internalSetScrollSize(int px);
+
+ /**
+ * Sets the amount of pixels the scrollbar needs to be able to scroll
+ * through.
+ *
+ * @param px
+ * the number of pixels the scrollbar should be able to scroll
+ * through
+ */
+ public void setScrollSize(int px) {
+ internalSetScrollSize(px);
+ forceScrollbar(needsScrollbars());
+ }
+
+ /**
+ * Gets the amount of pixels the scrollbar needs to be able to scroll
+ * through.
+ *
+ * @return the number of pixels the scrollbar should be able to scroll
+ * through
+ */
+ public abstract int getScrollSize();
+
+ /**
+ * Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in the
+ * opposite axis to what the scrollbar is representing.
+ *
+ * @param px
+ * the dimension that {@link #scrollSizeElement} should take in
+ * the opposite axis to what the scrollbar is representing
+ */
+ protected abstract void internalSetScrollbarThickness(int px);
+
+ /**
+ * Sets the scrollbar's thickness.
+ * <p>
+ * If the thickness is set to 0, the scrollbar will be treated as an
+ * "invisible" scrollbar. This means, the DOM structure will be given a
+ * non-zero size, but {@link #getScrollbarThickness()} will still return the
+ * value 0.
+ *
+ * @param px
+ * the scrollbar's thickness in pixels
+ */
+ public void setScrollbarThickness(int px) {
+ isInvisibleScrollbar = (px == 0);
+ internalSetScrollbarThickness(px != 0 ? px
+ : OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX);
+ }
+
+ /**
+ * Gets the scrollbar's thickness as defined in the DOM.
+ *
+ * @return the scrollbar's thickness as defined in the DOM, in pixels
+ */
+ protected abstract int internalGetScrollbarThickness();
+
+ /**
+ * Gets the scrollbar's thickness.
+ * <p>
+ * This value will differ from the value in the DOM, if the thickness was
+ * set to 0 with {@link #setScrollbarThickness(int)}, as the scrollbar is
+ * then treated as "invisible."
+ *
+ * @return the scrollbar's thickness in pixels
+ */
+ public int getScrollbarThickness() {
+ if (!isInvisibleScrollbar) {
+ return internalGetScrollbarThickness();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Checks whether the scrollbar should be active and needed, i.e. if the
+ * "content" is larger than the "frame".
+ *
+ * @return <code>true</code> iff the scrollbar should be active and needed
+ */
+ protected boolean needsScrollbars() {
+ return getOffsetSize() < getScrollSize();
+ }
+}