All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.user.client.ui.CustomScrollPanel Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * Copyright 2011 Google Inc.
 * 
 * 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.google.gwt.user.client.ui;

import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
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.Element;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.layout.client.Layout;
import com.google.gwt.layout.client.Layout.Layer;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CommonResources;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;

/**
 * A custom version of the {@link ScrollPanel} that allows user provided
 * scrollbars.
 * 
 * 

* The postion of scrollbars in a {@link CustomScrollPanel} differs from that of * a native scrollable element. In a native element, scrollbars appear adjacent * to the content, shrinking the content client height and width when they * appear. {@link CustomScrollPanel} instead overlays scrollbars on top of the * content, so the content does not change size when scrollbars appear. If the * scrollbars obscures the content, you can set the padding-top and * padding-bottom of the content to shift the content out from * under the scrollbars. *

* *

* NOTE: Unlike {@link ScrollPanel}, which implements {@link RequiresResize} but * doesn't really require it, {@link CustomScrollPanel} actually does require * resize and should only be added to a panel that implements * {@link ProvidesResize}, such as most layout panels and * {@link ResizeLayoutPanel}. *

*/ public class CustomScrollPanel extends ScrollPanel { /** * A ClientBundle of resources used by this widget. */ public interface Resources extends ClientBundle { /** * The styles used in this widget. */ @Source(Style.DEFAULT_CSS) Style customScrollPanelStyle(); } /** * Styles used by this widget. */ @ImportedWithPrefix("gwt-CustomScrollPanel") public interface Style extends CssResource { /** * The path to the default CSS styles used by this resource. */ String DEFAULT_CSS = "com/google/gwt/user/client/ui/CustomScrollPanel.css"; /** * Applied to the widget. */ String customScrollPanel(); /** * Applied to the square that appears in the bottom corner where the * vertical and horizontal scrollbars meet, when both are visible. */ String customScrollPanelCorner(); } private static Resources DEFAULT_RESOURCES; /** * The timeout to ignore scroll events after updating the scroll position. * Some browsers queue up scroll events and fire them after a delay. So, if * the user quickly scrolls from position 0 to 100 to 200, the scroll event * will fire for position 100 after the scroller has already moved to position * 200. If we do not ignore the scroll events, we can end up with a loop where * the scrollbars update the scroll position, and vice versa. */ private static int IGNORE_SCROLL_TIMEOUT = 500; /** * Get the default {@link Resources} for this widget. */ private static Resources getDefaultResources() { if (DEFAULT_RESOURCES == null) { DEFAULT_RESOURCES = GWT.create(Resources.class); } return DEFAULT_RESOURCES; } private boolean alwaysShowScrollbars; private final ResizeLayoutPanel.Impl containerResizeImpl = GWT .create(ResizeLayoutPanel.Impl.class); private final Element cornerElem; private final Layer cornerLayer; private double ignoreContentUntil = 0; private double ignoreScrollbarsUntil = 0; private final Layout layout; private final Layer scrollableLayer; // Information about the horizontal scrollbar. private HorizontalScrollbar hScrollbar; private int hScrollbarHeight; private HandlerRegistration hScrollbarHandler; private Layer hScrollbarLayer; // Information about the vertical scrollbar. private VerticalScrollbar vScrollbar; private int vScrollbarWidth; private HandlerRegistration vScrollbarHandler; private Layer vScrollbarLayer; /** * Creates an empty {@link CustomScrollPanel}. */ public CustomScrollPanel() { this(getDefaultResources()); } public CustomScrollPanel(Resources resources) { super(DOM.createDiv(), DOM.createDiv(), DOM.createDiv()); // Inject the styles used by this widget. Style style = resources.customScrollPanelStyle(); style.ensureInjected(); setStyleName(style.customScrollPanel()); // Initialize the layout implementation. layout = new Layout(getElement()); /* * Apply the inline block style to the container element so it resizes with * the content. */ Element containerElem = getContainerElement(); containerElem.setClassName(CommonResources.getInlineBlockStyle()); /* * Attach the scrollable element with the container. The scrollable element * always shows its scrollbars, but they are hidden beneath the root * element. */ Element scrollable = getScrollableElement(); scrollable.getStyle().setOverflow(Overflow.SCROLL); scrollable.appendChild(containerElem); scrollableLayer = layout.attachChild(scrollable); /* * Hide the native scrollbars beneath the root element. The scrollable * element's dimensions are large enough that the scrollbars are outside of * the root element, and the root element is set to hide overflow, making * the scrollbars invisible. The scrollable elements dimensions are set * after the widget is attached to the document in hideNativeScrollbars(). */ getElement().getStyle().setOverflow(Overflow.HIDDEN); /* * Create a corner element that appears at the gap where the vertical and * horizontal scrollbars meet (when both are visible). This prevents the * content from peeking out from this gap. */ cornerElem = Document.get().createDivElement(); cornerElem.addClassName(style.customScrollPanelCorner()); cornerLayer = layout.attachChild(cornerElem); // Initialize the default scrollbars using the transparent styles. NativeHorizontalScrollbar.Resources hResources = GWT.create(NativeHorizontalScrollbar.ResourcesTransparant.class); setHorizontalScrollbar(new NativeHorizontalScrollbar(hResources), AbstractNativeScrollbar .getNativeScrollbarHeight()); NativeVerticalScrollbar.Resources vResources = GWT.create(NativeVerticalScrollbar.ResourcesTransparant.class); setVerticalScrollbar(new NativeVerticalScrollbar(vResources), AbstractNativeScrollbar .getNativeScrollbarWidth()); /* * Add a handler to catch changes in the content size and update the * scrollbars accordingly. */ ResizeLayoutPanel.Impl.Delegate containerResizeDelegate = new ResizeLayoutPanel.Impl.Delegate() { @Override public void onResize() { maybeUpdateScrollbars(); } }; containerResizeImpl.init(getContainerElement(), containerResizeDelegate); /* * Listen for scroll events from the root element and the scrollable element * so we can align the scrollbars with the content. Scroll events usually * come from the scrollable element, but they can also come from the root * element if the user clicks and drags the content, which reveals the * hidden scrollbars. */ Event.sinkEvents(getElement(), Event.ONSCROLL); Event.sinkEvents(getScrollableElement(), Event.ONSCROLL); } /** * Creates a {@link CustomScrollPanel} with the specified child widget. * * @param child the widget to be wrapped by the scroll panel */ public CustomScrollPanel(Widget child) { this(getDefaultResources()); setWidget(child); } /** * Get the scrollbar used for horizontal scrolling. * * @return the horizontal scrollbar, or null if none specified */ public HorizontalScrollbar getHorizontalScrollbar() { return hScrollbar; } /** * Get the scrollbar used for vertical scrolling. * * @return the vertical scrollbar, or null if none specified */ public VerticalScrollbar getVerticalScrollbar() { return vScrollbar; } @Override public void onBrowserEvent(Event event) { // Align the scrollbars with the content. if (Event.ONSCROLL == event.getTypeInt()) { double curTime = Duration.currentTimeMillis(); if (curTime > ignoreContentUntil) { ignoreScrollbarsUntil = curTime + IGNORE_SCROLL_TIMEOUT; maybeUpdateScrollbarPositions(); } } super.onBrowserEvent(event); } @Override public void onResize() { maybeUpdateScrollbars(); super.onResize(); } @Override public boolean remove(Widget w) { // Validate. if (w.getParent() != this) { return false; } if (w == getWidget()) { // Remove the content widget. boolean toRet = super.remove(w); maybeUpdateScrollbars(); return toRet; } // Remove a scrollbar. try { // Orphan. orphan(w); } finally { // Physical detach. w.getElement().removeFromParent(); // Logical detach. Widget hScrollbarWidget = (hScrollbar == null) ? null : hScrollbar.asWidget(); Widget vScrollbarWidget = (vScrollbar == null) ? null : vScrollbar.asWidget(); if (w == hScrollbarWidget) { hScrollbar = null; hScrollbarHandler.removeHandler(); hScrollbarHandler = null; layout.removeChild(hScrollbarLayer); hScrollbarLayer = null; } else if (w == vScrollbarWidget) { vScrollbar = null; vScrollbarHandler.removeHandler(); vScrollbarHandler = null; layout.removeChild(vScrollbarLayer); vScrollbarLayer = null; } } maybeUpdateScrollbars(); return true; } /** * Remove the {@link HorizontalScrollbar}, if one exists. */ public void removeHorizontalScrollbar() { if (hScrollbar != null) { remove(hScrollbar); } } /** * Remove the {@link VerticalScrollbar}, if one exists. */ public void removeVerticalScrollbar() { if (vScrollbar != null) { remove(vScrollbar); } } @Override public void setAlwaysShowScrollBars(boolean alwaysShow) { if (this.alwaysShowScrollbars != alwaysShow) { this.alwaysShowScrollbars = alwaysShow; maybeUpdateScrollbars(); } } /** * Set the scrollbar used for horizontal scrolling. * * @param scrollbar the scrollbar, or null to clear it * @param height the height of the scrollbar in pixels */ public void setHorizontalScrollbar(final HorizontalScrollbar scrollbar, int height) { // Physical attach. hScrollbarLayer = add(scrollbar, hScrollbar, hScrollbarLayer); // Logical attach. hScrollbar = scrollbar; hScrollbarHeight = height; // Initialize the new scrollbar. if (scrollbar != null) { hScrollbarHandler = scrollbar.addScrollHandler(new ScrollHandler() { @Override public void onScroll(ScrollEvent event) { double curTime = Duration.currentTimeMillis(); if (curTime > ignoreScrollbarsUntil) { ignoreContentUntil = curTime + IGNORE_SCROLL_TIMEOUT; int hPos = scrollbar.getHorizontalScrollPosition(); if (getHorizontalScrollPosition() != hPos) { setHorizontalScrollPosition(hPos); } } } }); } maybeUpdateScrollbars(); } /** * Set the scrollbar used for vertical scrolling. * * @param scrollbar the scrollbar, or null to clear it * @param width the width of the scrollbar in pixels */ public void setVerticalScrollbar(final VerticalScrollbar scrollbar, int width) { // Physical attach. vScrollbarLayer = add(scrollbar, vScrollbar, vScrollbarLayer); // Logical attach. vScrollbar = scrollbar; vScrollbarWidth = width; // Initialize the new scrollbar. if (scrollbar != null) { vScrollbarHandler = scrollbar.addScrollHandler(new ScrollHandler() { @Override public void onScroll(ScrollEvent event) { double curTime = Duration.currentTimeMillis(); if (curTime > ignoreScrollbarsUntil) { ignoreContentUntil = curTime + IGNORE_SCROLL_TIMEOUT; int vPos = scrollbar.getVerticalScrollPosition(); int v = getVerticalScrollPosition(); if (getVerticalScrollPosition() != vPos) { setVerticalScrollPosition(vPos); } } } }); } maybeUpdateScrollbars(); } @Override public void setWidget(Widget w) { // Early exit if the widget is unchanged. Avoids updating the scrollbars. if (w == getWidget()) { return; } super.setWidget(w); maybeUpdateScrollbars(); } @Override protected void doAttachChildren() { AttachDetachException.tryCommand(AttachDetachException.attachCommand, getWidget(), hScrollbar, vScrollbar); } @Override protected void doDetachChildren() { AttachDetachException.tryCommand(AttachDetachException.detachCommand, getWidget(), hScrollbar, vScrollbar); } @Override protected void onAttach() { super.onAttach(); containerResizeImpl.onAttach(); layout.onAttach(); } @Override protected void onDetach() { super.onDetach(); containerResizeImpl.onDetach(); layout.onDetach(); } @Override protected void onLoad() { hideNativeScrollbars(); Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { maybeUpdateScrollbars(); } }); } /** * Add a widget to the panel in the specified layer. Note that this method * does not do the logical attach. * * @param w the widget to add, or null to clear the widget * @param toReplace the widget to replace * @param layer the layer in which the existing widget is placed * @return the layer in which the new widget is placed, or null if no widget */ private Layer add(IsWidget w, IsWidget toReplace, Layer layer) { // Validate. if (w == toReplace) { return layer; } // Detach new child. if (w != null) { w.asWidget().removeFromParent(); } // Remove old child. if (toReplace != null) { remove(toReplace); } Layer toRet = null; if (w != null) { // Physical attach. toRet = layout.attachChild(w.asWidget().getElement()); adopt(w.asWidget()); } return toRet; } /** * Hide the native scrollbars. We call this after attaching to ensure that we * inherit the direction (rtl or ltr). */ private void hideNativeScrollbars() { int barWidth = AbstractNativeScrollbar.getNativeScrollbarWidth(); int barHeight = AbstractNativeScrollbar.getNativeScrollbarHeight(); scrollableLayer.setTopBottom(0.0, Unit.PX, -barHeight, Unit.PX); if (AbstractNativeScrollbar.isScrollbarLeftAlignedInRtl() && ScrollImpl.get().isRtl(getScrollableElement())) { scrollableLayer.setLeftRight(-barWidth, Unit.PX, 0.0, Unit.PX); } else { scrollableLayer.setLeftRight(0.0, Unit.PX, -barWidth, Unit.PX); } layout.layout(); } /** * Synchronize the scroll positions of the scrollbars with the actual scroll * position of the content. */ private void maybeUpdateScrollbarPositions() { if (!isAttached()) { return; } if (hScrollbar != null) { int hPos = getHorizontalScrollPosition(); if (hScrollbar.getHorizontalScrollPosition() != hPos) { hScrollbar.setHorizontalScrollPosition(hPos); } } if (vScrollbar != null) { int vPos = getVerticalScrollPosition(); if (vScrollbar.getVerticalScrollPosition() != vPos) { vScrollbar.setVerticalScrollPosition(vPos); } } /* * Ensure that the viewport is anchored to the corner. If the user clicks * and drags the content, its possible to shift the viewport and reveal the * hidden scrollbars. */ if (getElement().getScrollLeft() != 0) { getElement().setScrollLeft(0); } if (getElement().getScrollTop() != 0) { getElement().setScrollTop(0); } } /** * Update the position of the scrollbars. * *

* If only the vertical scrollbar is present, it takes up the entire height of * the right side. If only the horizontal scrollbar is present, it takes up * the entire width of the bottom. If both scrollbars are present, the * vertical scrollbar extends from the top to just above the horizontal * scrollbar, and the horizontal scrollbar extends from the left to just right * of the vertical scrollbar, leaving a small square in the bottom right * corner. * *

* In RTL, the vertical scrollbar appears on the left. */ private void maybeUpdateScrollbars() { if (!isAttached()) { return; } /* * Measure the height and width of the content directly. Note that measuring * the height and width of the container element (which should be the same) * doesn't work correctly in IE. */ Widget w = getWidget(); int contentHeight = (w == null) ? 0 : w.getOffsetHeight(); int contentWidth = (w == null) ? 0 : w.getOffsetWidth(); // Determine which scrollbars to show. int realScrollbarHeight = 0; int realScrollbarWidth = 0; if (hScrollbar != null && (alwaysShowScrollbars || getElement().getClientWidth() < contentWidth)) { // Horizontal scrollbar is defined and required. realScrollbarHeight = hScrollbarHeight; } if (vScrollbar != null && (alwaysShowScrollbars || getElement().getClientHeight() < contentHeight)) { // Vertical scrollbar is defined and required. realScrollbarWidth = vScrollbarWidth; } /* * Add some padding to the so bottom we can scroll to the bottom without the * content being hidden beneath the horizontal scrollbar. */ if (w != null) { if (realScrollbarHeight > 0) { w.getElement().getStyle().setMarginBottom(realScrollbarHeight, Unit.PX); contentHeight += realScrollbarHeight; } else { w.getElement().getStyle().clearMarginBottom(); } } // Adjust the scrollbar layers to display the visible scrollbars. boolean isRtl = ScrollImpl.get().isRtl(getScrollableElement()); if (realScrollbarHeight > 0) { hScrollbarLayer.setVisible(true); if (isRtl) { hScrollbarLayer.setLeftRight(realScrollbarWidth, Unit.PX, 0.0, Unit.PX); } else { hScrollbarLayer.setLeftRight(0.0, Unit.PX, realScrollbarWidth, Unit.PX); } hScrollbarLayer.setBottomHeight(0.0, Unit.PX, realScrollbarHeight, Unit.PX); hScrollbar.setScrollWidth(Math.max(0, contentWidth - realScrollbarWidth)); } else if (hScrollbarLayer != null) { hScrollbarLayer.setVisible(false); } if (realScrollbarWidth > 0) { vScrollbarLayer.setVisible(true); vScrollbarLayer.setTopBottom(0.0, Unit.PX, realScrollbarHeight, Unit.PX); if (isRtl) { vScrollbarLayer.setLeftWidth(0.0, Unit.PX, realScrollbarWidth, Unit.PX); } else { vScrollbarLayer.setRightWidth(0.0, Unit.PX, realScrollbarWidth, Unit.PX); } vScrollbar.setScrollHeight(Math.max(0, contentHeight - realScrollbarHeight)); } else if (vScrollbarLayer != null) { vScrollbarLayer.setVisible(false); } /* * Show the corner in the gap between the vertical and horizontal * scrollbars. */ cornerLayer.setBottomHeight(0.0, Unit.PX, realScrollbarHeight, Unit.PX); if (isRtl) { cornerLayer.setLeftWidth(0.0, Unit.PX, realScrollbarWidth, Unit.PX); } else { cornerLayer.setRightWidth(0.0, Unit.PX, realScrollbarWidth, Unit.PX); } cornerLayer.setVisible(hScrollbarHeight > 0 && vScrollbarWidth > 0); // Apply the layout. layout.layout(); maybeUpdateScrollbarPositions(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy