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

de.swm.commons.mobile.client.widgets.scroll.ScrollPanel Maven / Gradle / Ivy

/*
 * Copyright 2011 SWM Services GmbH.
 *
 * 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 de.swm.commons.mobile.client.widgets.scroll;

import com.google.gwt.dom.client.Element;
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.Unit;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
import de.swm.commons.mobile.client.SWMMobile;
import de.swm.commons.mobile.client.base.PanelBase;
import de.swm.commons.mobile.client.event.*;
import de.swm.commons.mobile.client.utils.Utils;

/**
 * Scroll panel - has the ability to keep the {@link de.swm.commons.mobile.client.widgets.itf.IHeaderPanel} implementations always on the top.
 */
public class ScrollPanel extends PanelBase implements HasWidgets, DragEventsHandler, SwipeEventsHandler, IScrollPanel {

	private static final int TIME_FACTOR = 3000;
	private static final double DISTANCE_FACTOR = 0.25;
	private static final int DEFAULT_TRANSITION_DURATION = 500;
	private static final int OFFSET = 2;
	private boolean myHasTextBox = false;
	private IScrollMonitor scrollMonitor;

	private ScrollPanelEventsHandler scrollPanelEventsHandler;
	private int offsetHeight = -1;
	/**
	 * Defines how many pixels a button overscrolling should be possible (e.g 50px).
	 */
	private int overscrollButtonTolerance = 0;


	/**
	 * Default constructor.
	 */
	public ScrollPanel() {
		setStyleName(SWMMobile.getTheme().getMGWTCssBundle().getScrollPanelCss().scrollPanel());
	}

	/**
	 * A scroll monitor provides callback when the scrolling is done.
	 *
	 * @param scrollMonitor
	 */
	@Override
	public void setScrollMonitor(IScrollMonitor scrollMonitor) {
		this.scrollMonitor = scrollMonitor;
	}


	public void setHasTextBox(boolean hasTextBox) {
		myHasTextBox = hasTextBox && SWMMobile.getOsDetection().isAndroid();
	}


	public boolean getHasTextBox() {
		return myHasTextBox;
	}


	@Override
	public void onLoad() {
		DragController.get().addDragEventsHandler(this);
		DragController.get().addSwipeEventsHandler(this);
	}


	@Override
	public void onUnload() {
		DragController.get().removeDragEventsHandler(this);
		DragController.get().removeSwipeEventHandler(this);
	}


	@Override
	public Widget getWidget() {
		return myFlowPanel.getWidget(0);
	}


	public ScrollPanelEventsHandler getScrollPanelEventsHandler() {
		return scrollPanelEventsHandler;
	}

	/**
	 * Neuen Handler fuer Scroll Panel Events registrieren.
	 *
	 * @param scrollPanelEventsHandler eventhandler
	 */
	public void addScrollPanelEventsHandler(ScrollPanelEventsHandler scrollPanelEventsHandler) {
		this.scrollPanelEventsHandler = scrollPanelEventsHandler;
	}

	/**
	 * Sets the button overscroll tolerance in px
	 * @param overscrollButtonTolerance
	 */
	public void setOverscrollButtonTolerance(int overscrollButtonTolerance) {
		this.overscrollButtonTolerance = overscrollButtonTolerance;
	}

	/**
	 * Scrolls to the default position.
	 */
	public void reset() {
		Utils.setTransitionDuration(getWidget().getElement(), 0);
		Utils.setTranslateY(getWidget().getElement(), 0);
	}


	/**
	 * Scrolls to the top.
	 */
	@Override
	public void setPostionToTop() {
		Utils.setTransitionDuration(getWidget().getElement(), 0);
		Utils.setTranslateY(getWidget().getElement(), 0);
	}


	/**
	 * Scrolls to the button.
	 */
	@Override
	public void setPositionToBottom() {
		Utils.setTransitionDuration(getWidget().getElement(), 0);
		Utils.setTranslateY(getWidget().getElement(), this.getElement().getClientHeight()
				- this.getElement().getScrollHeight());
	}


	/**
	 * Sets the croll position
	 *
	 * @param pos the x axis pos
	 */
	@Override
	public void setScrollPosition(int pos) {
		if (myHasTextBox) {
			setStyleTop(pos);
		} else {
			Element element = getWidget().getElement();
			Utils.setTranslateY(element, pos);
		}
	}


	/**
	 * Returns the current scroll position.
	 *
	 * @return the position
	 */
	@Override
	public int getScrollPosition() {
		if (myHasTextBox) {
			return getStyleTop();
		} else {
			Element element = getWidget().getElement();
			return Utils.getTranslateY(element);
		}
	}


	/**
	 * Returns the next scroll position
	 *
	 * @return then next scroll position
	 */
	@Override
	public int getScrollToPosition() {
		if (myHasTextBox) {
			return getStyleTop();
		} else {
			Element element = getWidget().getElement();
			return Utils.getMatrixY(element);
		}
	}


	@Override
	public void onDragStart(DragEvent e) {
		if (scrollMonitor != null) {
			scrollMonitor.onScrollStart();
		}
		int matrix = getScrollToPosition();
		int current = getScrollPosition();
		Utils.setTransitionDuration(getWidget().getElement(), 0);
		if (current != matrix) { // scroll on going
			int diff = current - matrix;
			int offset = diff > OFFSET ? OFFSET : diff > -OFFSET ? diff : -OFFSET;
			setScrollPosition(matrix + offset);
			DragController.get().suppressNextClick();
		}
	}


	@Override
	public void onDragMove(DragEvent e) {
		Element widgetEle = getWidget().getElement();
		int panelHeight = Utils.getHeight(this.getElement());
		int widgetHeight = calcOffsetHeight(widgetEle);
		int current = getScrollPosition();
		if (current > 0) {
			// exceed top boundary
			if (e.getOffsetY() > 0) { // resist scroll down.
				current += (int) (e.getOffsetY() / OFFSET); // need the cast for production mode.
			} else {
				current += e.getOffsetY() * OFFSET;
			}
		} else if (-current + panelHeight > widgetHeight) {
			//correct only scroll position if overscroll is prohibited
			if (overscrollButtonTolerance == 0) {
				// exceed bottom boundary
				if (e.getOffsetY() < 0) { // resist scroll up.
					current += (int) (e.getOffsetY() / OFFSET);
				} else {
					current += e.getOffsetY() * OFFSET;
				}
			} else {
				//if button overscrolling is allowed
				current += e.getOffsetY();
			}
		} else {
			current += e.getOffsetY();
		}
		setScrollPosition(current);
	}

	private int calcOffsetHeight(Element widgetEle) {
		return ((this.offsetHeight > 0) ? this.offsetHeight : widgetEle.getOffsetHeight());
	}

	/**
	 * A value greater zero will set the offset height of the panel manually.
	 * Otherwise it will be calculated automatically.
	 *
	 * @param offsetHeight the offset height.
	 */
	@Override
	public void setOffsetHeight(int offsetHeight) {
		this.offsetHeight = offsetHeight;
	}


	@Override
	public void onDragEnd(DragEvent e) {
		Element widgetEle = getWidget().getElement();
		if (scrollMonitor != null) {
			scrollMonitor.onScrollEnd();
		}
		int current = getScrollPosition();
		if (current == 0) {
			return;
		}
		int panelHeight = Utils.getHeight(this.getElement());
		int widgetHeight = getHeightOfElementOrChildElements(widgetEle);

		if (current > 0 // exceed top boundary
				|| panelHeight > widgetHeight) {

			// fire eventshandler for top boundary
			if (getScrollPanelEventsHandler() != null) {
				getScrollPanelEventsHandler().onTop(e);
			}

			Utils.setTransitionDuration(widgetEle, DEFAULT_TRANSITION_DURATION);
			setScrollPosition(0);
		} else if (-current + panelHeight > widgetHeight) { // exceed bottom boundary

			// fire eventshandler for bottom boundary
			if (getScrollPanelEventsHandler() != null) {
				getScrollPanelEventsHandler().onBottom(e);
			}

			Utils.setTransitionDuration(widgetEle, DEFAULT_TRANSITION_DURATION);
			setScrollPosition((panelHeight - overscrollButtonTolerance) - widgetHeight);
		}
	}


	@Override
	public void onSwipeVertical(SwipeEvent e) {
		Element widgetEle = getWidget().getElement();
		int panelHeight = Utils.getHeight(this.getElement());
		int widgetHeight = widgetEle.getOffsetHeight();
		long current = getScrollPosition();
		if ((current >= 0) // exceed top boundary
				|| (-current + panelHeight >= widgetHeight)) { // exceed bottom boundary
			return;
		}

		double speed = e.getSpeed();
		double timeFactor = TIME_FACTOR;
		long time = (long) Math.abs(speed * timeFactor);
		double dicstanceFactor = DISTANCE_FACTOR;
		long distance = (long) (speed * time * dicstanceFactor);
		// Utils.Console("speed " + speed + " time " + time + " distance " + distance + " current " + current);
		current += distance;
		// exceed top boundary?
		if (current > 0) {
			double timeAdj = 1 - (double) current / distance;
			time = (long) (time * timeAdj);
			current = 0;
		} else if (-current + panelHeight > widgetHeight) { // exceed bottom boundary
			long bottom = panelHeight - widgetHeight;
			double timeAdj = 1 - (double) (current - bottom) / distance;
			time = (long) (time * timeAdj);
			current = bottom;
		}
		Utils.setTransitionDuration(widgetEle, time);
		setScrollPosition((int) current);
	}


	@Override
	public void onSwipeHorizontal(SwipeEvent e) {
	}


	@Override
	public void add(Widget w) {
		super.add(w);
		if (SWMMobile.getOsDetection().isIOs()) {
			Utils.setTranslateY(w.getElement(), 0); // anti-flickering on iOS.
		}
	}


	/**
	 * Returns the height of the given element. If the element has a height of 0 it searches
	 * recursively for the height of the next visible child (e.g. the next visible slide of a
	 * SliderPanel).
	 *
	 * @param element parent element
	 * @return height of the element of a visible child
	 */
	private int getHeightOfElementOrChildElements(Element element) {
		if (this.offsetHeight > 0) {
			return this.offsetHeight;
		}

		// current element has height? Return this height.
		if (element.getOffsetHeight() != 0) {
			return element.getOffsetHeight();
		}

		// current element is hidden? no height for this element.
		if (element.getStyle().getVisibility().equals("hidden")
				|| element.getStyle().getDisplay().equals("none")) {
			return 0;
		}

		// search children for height
		NodeList children = element.getChildNodes();
		if (children.getLength() == 0) {
			return 0;
		}

		int height = 0;
		for (int i = 0; i < children.getLength(); i++) {
			Node currentNode = children.getItem(i);
			if (currentNode instanceof Element) {
				height = getHeightOfElementOrChildElements((Element) currentNode);
			}

			if (height != 0) {
				return height;
			}
		}

		return 0;
	}


	/**
	 * Returns the top style
	 *
	 * @return the top style in px
	 */
	private int getStyleTop() {
		Style style = getWidget().getElement().getStyle();
		String top = style.getTop();
		if (top.isEmpty()) {
			return 0;
		} else {
			return Integer.parseInt(top.replace("px", ""));
		}
	}


	/**
	 * Sets the top stype in pixel
	 *
	 * @param top the top syle
	 */

	private void setStyleTop(int top) {
		Style style = getWidget().getElement().getStyle();
		style.setTop(top, Unit.PX);
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy