package hk.hku.cs.srli.widget.util;
import android.content.Context;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import hk.hku.cs.srli.widget.R;
import hk.hku.cs.srli.widget.Tooltip;
public class HoverHandler {
public static final int HOVER_TIMEOUT = 300;
public static final int LONGHOVER_TIMEOUT = 800;
private View view;
private OnLongHoverListener onLongHoverListener;
private OnHoverMoveListener onHoverMoveListener;
/**
* Internal hover state.
*/
private boolean hovering = false;
private boolean viewEntered = false;
private boolean tooltipEnabled = false;
private boolean tooltipEntered = false;
private float hoverX = 0;
private float hoverY = 0;
private boolean hasPerformedLongHover = false;
private CheckForLongHover pendingCheckForLongHover = new CheckForLongHover();
private boolean enabled;
private int timeout;
public HoverHandler(View view) {
this.view = view;
enabled = isHoverEnabled(view.getContext());
timeout = HOVER_TIMEOUT;
}
public HoverHandler(View view, int timeout) {
this(view);
this.timeout = timeout;
}
public static boolean isHoverEnabled(Context context) {
TypedValue a = new TypedValue();
context.getTheme().resolveAttribute(R.attr.hoverEnabled, a, true);
if (a.type == TypedValue.TYPE_INT_BOOLEAN) {
return a.data != 0;
} else {
return false;
}
}
public boolean onHoverEvent(MotionEvent event) {
// do nothing if disabled
if (!enabled) return false;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_HOVER_ENTER:
viewEntered = true;
refreshInternalHoverState();
return true;
case MotionEvent.ACTION_HOVER_MOVE:
hoverX = event.getRawX();
hoverY = event.getRawY();
// trigger onHover events only if it's already hovered.
if (viewEntered && view.isHovered()) {
if (onHoverMoveListener != null) {
final int[] screenPos = new int[2];
getLocalCoordinate(screenPos);
// fire hover move event
onHoverMoveListener.onHoverMove(view, screenPos[0], screenPos[1]);
}
}
return true;
case MotionEvent.ACTION_HOVER_EXIT:
viewEntered = false;
refreshInternalHoverState();
break;
}
return false;
}
public void attachTooltip(Tooltip tooltip) {
tooltip.setHoverHandler(this);
tooltipEnabled = true;
}
public void dettachTooltip() {
tooltipEnabled = false;
}
public boolean onTooltipHoverEvent(MotionEvent event) {
if (!enabled) return false;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_HOVER_ENTER:
tooltipEntered = true;
refreshInternalHoverState();
break;
case MotionEvent.ACTION_HOVER_EXIT:
tooltipEntered = false;
refreshInternalHoverState();
break;
}
return false;
}
public void setHoverTimeout(int timeout) {
this.timeout = timeout;
}
public void setOnLongHoverListener(OnLongHoverListener onLongHoverListener) {
this.onLongHoverListener = onLongHoverListener;
}
public void setOnHoverMoveListener(OnHoverMoveListener onHoverMoveListener) {
this.onHoverMoveListener = onHoverMoveListener;
}
public interface OnLongHoverListener {
public boolean onLongHover(View v, int x, int y);
}
public interface OnHoverMoveListener {
public void onHoverMove(View v, int x, int y);
}
private void setHoveredExternal(boolean hovered) {
view.setHovered(hovered);
if (hovered) {
checkForLongHover();
}
}
private void refreshInternalHoverState() {
boolean toHover = viewEntered || (tooltipEnabled && tooltipEntered);
// check if the state is really different
if (toHover != hovering) {
// change internal hover state
hovering = toHover;
// try to change external hover state
checkForExternalHoverChange(hovering);
}
}
private void checkForExternalHoverChange(boolean hovering) {
if (hovering != view.isHovered()) {
// delay external hover change
view.postDelayed(new CheckForHoverChange(hovering), timeout);
}
}
private class CheckForHoverChange implements Runnable {
private final boolean oldHoverState;
public CheckForHoverChange(boolean hovering) {
oldHoverState = hovering;
}
public void run() {
// if still in the same internal hover state.
if (hovering == oldHoverState) {
setHoveredExternal(hovering);
}
}
}
private void checkForLongHover() {
if (onLongHoverListener != null) {
hasPerformedLongHover = false;
view.postDelayed(pendingCheckForLongHover, LONGHOVER_TIMEOUT);
}
}
private class CheckForLongHover implements Runnable {
public void run() {
if (view.isHovered()
&& !hasPerformedLongHover
&& onLongHoverListener != null) {
final int[] screenPos = new int[2];
if (getLocalCoordinate(screenPos)) {
// fire long hover event
hasPerformedLongHover =
onLongHoverListener.onLongHover(view, screenPos[0], screenPos[1]);
}
}
}
}
private boolean getLocalCoordinate(int[] position) {
if (hoverX > 0 && hoverY > 0) {
view.getLocationOnScreen(position);
// bound by view rect
position[0] = Math.max(0, Math.min(view.getWidth(), (int) hoverX - position[0]));
position[1] = Math.max(0, Math.min(view.getHeight(), (int) hoverY - position[1]));
return true;
} else return false;
}
}