package org.vaadin.touchkit.gwt.client.ui;
import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.TouchCancelEvent;
import com.google.gwt.event.dom.client.TouchCancelHandler;
import com.google.gwt.event.dom.client.TouchEndEvent;
import com.google.gwt.event.dom.client.TouchEndHandler;
import com.google.gwt.event.dom.client.TouchEvent;
import com.google.gwt.event.dom.client.TouchMoveEvent;
import com.google.gwt.event.dom.client.TouchMoveHandler;
import com.google.gwt.event.dom.client.TouchStartEvent;
import com.google.gwt.event.dom.client.TouchStartHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.HasValue;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.ui.Field;
/**
* VSwitch is the client-side implementation of import
* com.vaadin.client.ui.Field; the Switch component.
*
* Derived from implementations by Teemu Pöntelin Vaadin Ltd.
*
*/
public class VSwitch extends FocusWidget implements Field, HasValue<Boolean>,
KeyUpHandler, MouseDownHandler, MouseUpHandler, MouseMoveHandler,
TouchStartHandler, TouchMoveHandler, TouchEndHandler,
TouchCancelHandler, FocusHandler, BlurHandler, NativePreviewHandler {
/** Set the CSS class name to allow styling. */
public static final String CLASSNAME = "v-touchkit-switch";
private final int DRAG_THRESHOLD_PIXELS = 10;
private final int ANIMATION_DURATION_MS = 300;
/** Reference to the server connection object. */
ApplicationConnection client;
private Element mainElement;
private Element slider;
private boolean value;
private com.google.gwt.user.client.Element errorIndicatorElement;
private boolean mouseDown;
private int unvisiblePartWidth;
private int tabIndex;
private int dragStartX;
private int dragStartY;
private int sliderOffsetLeft;
private boolean dragging;
private boolean scrolling;
private HandlerRegistration previewHandler;
/**
* The constructor should first call super() to initialize the component and
* then handle any initialization relevant to Vaadin.
*/
public VSwitch() {
// Change to proper element or remove if extending another widget
setElement(Document.get().createDivElement());
DivElement el = Document.get().createDivElement();
el.addClassName(CLASSNAME + "-wrapper");
mainElement = Document.get().createDivElement();
// This method call of the Paintable interface sets the component
// style name in DOM tree
mainElement.setClassName(CLASSNAME);
el.appendChild(mainElement);
getElement().appendChild(el);
// build the DOM
slider = Document.get().createDivElement();
slider.setClassName(CLASSNAME + "-slider");
mainElement.appendChild(slider);
updateVisibleState(true); // Set the initial position without animation.
addHandlers();
}
private void addHandlers() {
addDomHandler(this, KeyUpEvent.getType());
if (TouchEvent.isSupported()) {
addDomHandler(this, TouchStartEvent.getType());
addDomHandler(this, TouchMoveEvent.getType());
addDomHandler(this, TouchEndEvent.getType());
addDomHandler(this, TouchCancelEvent.getType());
} else {
addDomHandler(this, MouseDownEvent.getType());
addDomHandler(this, MouseUpEvent.getType());
addDomHandler(this, MouseMoveEvent.getType());
}
addDomHandler(this, FocusEvent.getType());
addDomHandler(this, BlurEvent.getType());
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
setStyleName(mainElement.getParentElement(), "v-disabled", !enabled);
if (!enabled) {
super.setTabIndex(-1);
} else {
super.setTabIndex(tabIndex);
}
}
@Override
public void setTabIndex(int index) {
super.setTabIndex(index);
tabIndex = index;
}
private void updateVisibleState() {
updateVisibleState(false);
}
private int getUnvisiblePartWidth() {
if (unvisiblePartWidth == 0) {
// Calculate the width of the part that is not currently visible
// and init the state on the first rendering.
int width = mainElement.getParentElement().getClientWidth();
int sliderWidth = mainElement.getClientWidth();
unvisiblePartWidth = sliderWidth - width;
if (unvisiblePartWidth < 3) {
// CSS not loaded yet
unvisiblePartWidth = 0;
}
}
return unvisiblePartWidth;
}
private void updateVisibleState(final boolean skipAnimation) {
ScheduledCommand command = new ScheduledCommand() {
public void execute() {
final int targetLeft = (value ? 0 : -getUnvisiblePartWidth());
final Element parentElement = mainElement.getParentElement();
if (skipAnimation) {
parentElement.getStyle().setProperty(
Css3Propertynames.INSTANCE._transition(), "");
setStyleName(parentElement, CLASSNAME + "-off", !value);
mainElement.getStyle().setProperty("left",
targetLeft + "px");
} else {
Animation a = new Animation() {
@Override
protected void onUpdate(double progress) {
int currentLeft = getCurrentPosition();
int newLeft = (int) (currentLeft + (progress * (targetLeft - currentLeft)));
mainElement.getStyle().setProperty("left",
newLeft + "px");
}
@Override
protected void onComplete() {
mainElement.getStyle().setProperty("left",
targetLeft + "px");
};
};
a.run(ANIMATION_DURATION_MS);
float d = ANIMATION_DURATION_MS / 1000;
parentElement.getStyle().setProperty(
Css3Propertynames.INSTANCE._transition(),
"background " + d + "s");
setStyleName(parentElement, CLASSNAME + "-off", !value);
}
}
};
if (getUnvisiblePartWidth() == 0) {
// CSS not properly injected yet
Scheduler.get().scheduleDeferred(command);
} else {
command.execute();
}
}
private int getCurrentPosition() {
String currentLeftString = mainElement.getStyle().getProperty("left");
if (currentLeftString == null || currentLeftString.length() == 0) {
currentLeftString = "0px";
}
int currentLeft = Integer.parseInt(currentLeftString.substring(0,
currentLeftString.length() - 2));
return currentLeft;
}
public void onKeyUp(KeyUpEvent event) {
if (isEnabled() && event.getNativeKeyCode() == 32) {
// 32 = space bar
setValue(!value, true);
}
}
public void onMouseDown(MouseDownEvent event) {
if (isEnabled()) {
handleMouseDown(event.getScreenX(), event.getScreenY());
event.preventDefault();
}
}
private void handleMouseDown(int clientX, int clientY) {
mouseDown = true;
dragStartX = clientX;
dragStartY = clientY;
sliderOffsetLeft = getCurrentPosition();
previewHandler = Event.addNativePreviewHandler(this);
}
@Override
public void onPreviewNativeEvent(NativePreviewEvent event) {
String type = event.getNativeEvent().getType();
if (!getElement().isOrHasChild(
(Node) event.getNativeEvent().getEventTarget().cast())) {
if (type.contains("up") || type.contains("end")
|| type.contains("cancel")) {
if (isEnabled()) {
handleMouseUp();
}
}
}
}
public void onMouseUp(MouseUpEvent event) {
if (isEnabled()) {
handleMouseUp();
}
}
private void handleMouseUp(boolean cancel) {
if (!dragging) {
if (mouseDown && !scrolling && !cancel) {
setValue(!value, true);
}
} else {
if (getCurrentPosition() < (-getUnvisiblePartWidth() / 2)) {
setValue(false, true);
} else {
setValue(true, true);
}
updateVisibleState();
DOM.releaseCapture((com.google.gwt.user.client.Element) mainElement);
}
mouseDown = false;
dragging = false; // not dragging anymore
scrolling = false;
if (previewHandler != null) {
previewHandler.removeHandler();
previewHandler = null;
}
}
public void handleMouseUp() {
handleMouseUp(false);
}
public void onMouseMove(MouseMoveEvent event) {
if (isEnabled()) {
handleMouseMove(event.getScreenX(), event.getScreenY());
}
}
private void handleMouseMove(int clientX, int clientY) {
if (mouseDown) {
int dragXDistance = clientX - dragStartX;
if (!scrolling && Math.abs(dragXDistance) > DRAG_THRESHOLD_PIXELS) {
dragging = true;
// Use capture to catch mouse events even if user
// drags the mouse cursor out of the widget area.
DOM.setCapture((com.google.gwt.user.client.Element) mainElement);
}
int dragYDistance = clientY - dragStartY;
if (!dragging && Math.abs(dragYDistance) > DRAG_THRESHOLD_PIXELS) {
scrolling = true;
}
if (dragging) {
// calculate new left position and
// check for boundaries
int left = sliderOffsetLeft + dragXDistance;
if (left < -getUnvisiblePartWidth()) {
left = -getUnvisiblePartWidth();
} else if (left > 0) {
left = 0;
}
// set the CSS left
mainElement.getStyle().setProperty("left", left + "px");
}
}
}
public void onFocus(FocusEvent event) {
addStyleDependentName("focus");
}
public void onBlur(BlurEvent event) {
removeStyleDependentName("focus");
}
public void onTouchEnd(TouchEndEvent event) {
if (isEnabled()) {
handleMouseUp();
event.preventDefault();
}
}
public void onTouchMove(TouchMoveEvent event) {
if (isEnabled()) {
Touch touch = event.getTouches().get(0).cast();
handleMouseMove(touch.getPageX(), touch.getPageY());
if (dragging) {
event.preventDefault();
}
}
}
public void onTouchStart(TouchStartEvent event) {
if (isEnabled()) {
Touch touch = event.getTouches().get(0).cast();
handleMouseDown(touch.getPageX(), touch.getPageY());
event.stopPropagation();
}
}
public void onTouchCancel(TouchCancelEvent event) {
if (isEnabled()) {
handleMouseUp(true);
}
}
@Override
public HandlerRegistration addValueChangeHandler(
ValueChangeHandler<Boolean> handler) {
return addHandler(handler, ValueChangeEvent.getType());
}
@Override
public Boolean getValue() {
return value;
}
@Override
public void setValue(Boolean value) {
setValue(value, false);
}
@Override
public void setValue(Boolean value, boolean fireEvents) {
if (value == null) {
value = Boolean.FALSE;
}
if (this.value == value) {
return;
}
this.value = value;
// update the UI
updateVisibleState();
if (fireEvents) {
ValueChangeEvent.fire(this, value);
}
}
public com.google.gwt.user.client.Element getErrorIndicator() {
return errorIndicatorElement;
}
public void setErrorIndicator(
com.google.gwt.user.client.Element errorIndicator) {
errorIndicatorElement = errorIndicator;
}
}