package org.geogebra.web.html5.gui;
import java.util.ArrayList;
import java.util.List;
import org.geogebra.common.util.debug.Log;
import org.geogebra.web.html5.gui.util.CancelEventTimer;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.CustomButton;
/**
*
* GWT Implementation influenced by Google's FastPressElement: <a
* href=https://developers
* .google.com/mobile/articles/fast_buttons>FastButtons</a>
*
* Using Code examples and comments from: <a
* href=http://stackoverflow.com/questions
* /9596807/converting-gwt-click-events-to-touch-events>Converting GWT
* ClickEvents to TouchEvents</a>
*
* The FastButton is used to avoid the 300ms delay on mobile devices (Only do
* this if you want to ignore the possibility of a double tap - The browser
* waits to see if we actually want to double top)
*
* The "press" event will occur significantly fast (around 300ms faster).
* However the biggest improvement is from enabling fast consecutive touches.
*
* If you try to rapidly touch one or more FastButtons, you will notice a MUCH
* great improvement.
*
* NOTE: Different browsers will handle quick swipe or long hold/drag touches
* differently. This is an edge case if the user is long pressing or pressing
* while dragging the finger slightly (but staying on the element) - The browser
* may or may not fire the event. However, the browser will always fire the
* regular tap/press very quickly.
*
*
* @author ashton with changes from Matthias Meisinger
*
*/
public abstract class FastButton extends CustomButton {
// in case the same touch reaches different Buttons (f.e. TouchStart +
// TouchEnd open the StyleBar and MouseUp reaches the first Button on the
// StyleBar
private boolean touchMoved = false;
private int touchId;
private boolean isActive;
private List<FastClickHandler> handlers;
/**
* New fast button
*/
public FastButton() {
setStyleName("button");
// Sink Click and Touch Events
// I am not going to sink Mouse events since
// I don't think we will gain anything
sinkEvents(Event.ONCLICK | Event.TOUCHEVENTS); // Event.TOUCHEVENTS adds
// all (Start, End,
// Cancel, Change)
this.handlers = new ArrayList<FastClickHandler>();
}
/**
* @param active
* whether it's enabled
*/
public void setActive(boolean active) {
if (active) {
onEnablePressStyle();
} else {
onDisablePressStyle();
}
this.isActive = active;
}
/**
* @return whether it's enabled
*/
public boolean isActive() {
return this.isActive;
}
/**
* Use this method in the same way you would use addClickHandler or
* addDomHandler
*
* @param handler
* handler
*
*/
public void addFastClickHandler(FastClickHandler handler) {
this.handlers.add(handler);
}
/**
* Implement the handler for pressing but NOT releasing the button. Normally
* you just want to show some CSS style change to alert the user the element
* is active but not yet pressed
*
* ONLY FOR STYLE CHANGE - Will briefly be called onClick
*
* TIP: Don't make a dramatic style change. Take note that if a user is just
* trying to scroll, and start on the element and then scrolls off, we may
* not want to distract them too much. If a user does scroll off the
* element,
*
*/
public abstract void onHoldPressDownStyle();
/**
* Implement the handler for release of press. This should just be some CSS
* or Style change.
*
* ONLY FOR STYLE CHANGE - Will briefly be called onClick
*
* TIP: This should just go back to the normal style.
*/
public abstract void onHoldPressOffStyle();
/**
* Change styling to disabled
*/
public abstract void onDisablePressStyle();
/**
* Change styling to enabled
*
* TIP:
*/
public abstract void onEnablePressStyle();
@Override
public void onBrowserEvent(Event event) {
if (!this.isEnabled()) {
event.stopPropagation();
return;
}
switch (DOM.eventGetType(event)) {
case Event.ONTOUCHSTART: {
onTouchStart(event);
event.stopPropagation();
break;
}
case Event.ONTOUCHEND: {
onTouchEnd(event);
event.stopPropagation();
break;
}
case Event.ONTOUCHMOVE: {
onTouchMove(event);
event.stopPropagation();
break;
}
case Event.ONMOUSEUP: {
Log.debug("touch up");
// because Event.ONCLICK always came twice on desktop browsers oO
onClick(event);
event.stopPropagation();
break;
}
case Event.ONMOUSEDOWN: {
event.stopPropagation();
break;
}
default: {
// Let parent handle event if not one of the above (?)
try {
super.onBrowserEvent(event);
} catch (Throwable t) {
Log.debug(DOM.eventGetType(event) + "event failed");
}
}
}
}
private void onClick(Event event) {
event.stopPropagation();
event.preventDefault();
if (!CancelEventTimer.cancelMouseEvent()) {
// Press not handled yet
fireFastClickEvent();
}
super.onBrowserEvent(event);
}
private void onTouchStart(Event event) {
onHoldPressDownStyle(); // Show style change
// Stop the event from bubbling up
event.stopPropagation();
// Only handle if we have exactly one touch
if (event.getTargetTouches().length() == 1) {
Touch start = event.getTargetTouches().get(0);
this.touchId = start.getIdentifier();
this.touchMoved = false;
}
}
/**
* Check to see if the touch has moved off of the element.
*
* NOTE that in iOS the elasticScroll may make the touch/move cancel more
* difficult.
*
* @param event
*/
private void onTouchMove(Event event) {
if (!this.touchMoved) {
Touch move = null;
for (int i = 0; i < event.getChangedTouches().length(); i++) {
if (event.getChangedTouches().get(i).getIdentifier() == this.touchId) {
move = event.getChangedTouches().get(i);
}
}
// Check to see if we moved off of the original element
// Use Page coordinates since we compare with widget's absolute
// coordinates
if (move != null) {
int yCord = move.getPageY();
int xCord = move.getPageX();
// is y above element
boolean yTop = this.getAbsoluteTop() > yCord;
boolean yBottom = (this.getAbsoluteTop() + this
.getOffsetHeight()) < yCord; // y below
// is x to the left of element
boolean xLeft = this.getAbsoluteLeft() > xCord;
boolean xRight = (this.getAbsoluteLeft() + this
.getOffsetWidth()) < xCord; // x to the right
if (yTop || yBottom || xLeft || xRight) {
this.touchMoved = true;
onHoldPressOffStyle();// Go back to normal style
}
}
}
}
private void onTouchEnd(Event event) {
CancelEventTimer.touchEventOccured();
if (!this.touchMoved) {
fireFastClickEvent();
event.preventDefault();
onHoldPressOffStyle();// Change back the style
}
}
/**
* Notify all handlers
*/
protected void fireFastClickEvent() {
for (FastClickHandler h : this.handlers) {
h.onClick(this);
}
}
}