/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package com.linkbubble.ui;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.linkbubble.Config;
import com.linkbubble.Constant;
import com.linkbubble.MainApplication;
import com.linkbubble.MainController;
import com.linkbubble.R;
import com.linkbubble.Settings;
import com.linkbubble.physics.Circle;
import com.linkbubble.util.Util;
import com.squareup.otto.Subscribe;
public class BubbleTargetView extends FrameLayout {
private ImageView mImage;
private CanvasView mCanvasView;
public enum Interpolator {
Linear,
Overshoot
}
private HorizontalAnchor mHAnchor;
private VerticalAnchor mVAnchor;
private int mDefaultX;
private int mDefaultY;
private int mMaxOffsetX;
private int mMaxOffsetY;
private int mTractorOffsetX;
private int mTractorOffsetY;
private float mSnapWidth;
private float mSnapHeight;
private Circle mSnapCircle;
private Circle mDefaultCircle;
private Constant.BubbleAction mAction;
private FrameLayout.LayoutParams mCanvasLayoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
private int mHomeX;
private int mHomeY;
public enum HorizontalAnchor {
Left,
Center,
Right
}
public enum VerticalAnchor {
Top,
Bottom
}
private LinearInterpolator mLinearInterpolator = new LinearInterpolator();
private OvershootInterpolator mOvershootInterpolator = new OvershootInterpolator(1.5f);
private boolean mIsSnapping;
private boolean mIsLongHovering;
private static boolean sEnableTractor;
private float mTimeSinceSnapping;
private final float TRANSITION_TIME = 0.15f;
public BubbleTargetView(Context context) {
this(context, null);
}
public BubbleTargetView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BubbleTargetView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
mImage = (ImageView) findViewById(R.id.image_view);
}
protected float getRadius() {
int tabSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_size);
return tabSize * 0.5f;
}
private int getXPos() {
switch (mHAnchor) {
case Left:
return mDefaultX;
case Right:
return Config.mScreenWidth - mDefaultX;
case Center:
return (int) (Config.mScreenWidth * 0.5f + mDefaultX);
}
Util.Assert(false, "Anchor not handled - " + mHAnchor);
return 0;
}
private int getYPos() {
switch (mVAnchor) {
case Top:
return mDefaultY;
case Bottom:
return Config.mScreenHeight - mDefaultY;
}
Util.Assert(false, "Anchor not handled - " + mVAnchor);
return 0;
}
public void setTargetCenter(int x, int y) {
setTargetPos((int) (x - mSnapWidth * 0.5f), (int) (y - mSnapHeight * 0.5f));
}
public void setTargetPos(int x, int y) {
mCanvasLayoutParams.leftMargin = x;
mCanvasLayoutParams.topMargin = y;
}
public void onConsumeBubblesChanged() {
Drawable d = null;
switch (mAction) {
case ConsumeLeft:
case ConsumeRight:
d = Settings.get().getConsumeBubbleIcon(mAction);
break;
default:
break;
}
if (d != null) {
mImage.setImageDrawable(d);
}
}
public void configure(CanvasView canvasView, Context context, Drawable d, Constant.BubbleAction action, int defaultX, HorizontalAnchor hAnchor,
int defaultY, VerticalAnchor vAnchor, int maxOffsetX, int maxOffsetY,
int tractorOffsetX, int tractorOffsetY) {
mCanvasView = canvasView;
mAction = action;
mHAnchor = hAnchor;
mVAnchor = vAnchor;
mDefaultX = defaultX;
mDefaultY = defaultY;
mMaxOffsetX = maxOffsetX;
mMaxOffsetY = maxOffsetY;
mTractorOffsetX = tractorOffsetX;
mTractorOffsetY = tractorOffsetY;
registerForBus();
if (d != null && mImage != null) {
mImage.setImageDrawable(d);
}
int bubbleIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_size);
mSnapWidth = bubbleIconSize;
mSnapHeight = bubbleIconSize;
Util.Assert(mSnapWidth > 0 && mSnapHeight > 0 && mSnapWidth == mSnapHeight, "mSnapWidth:" + mSnapWidth + ", mSnapHeight:" + mSnapHeight);
mSnapCircle = new Circle(getXPos(), getYPos(), mSnapWidth * 0.5f);
float r = getRadius();
Util.Assert(r > 0.0f, "r:" + r);
mDefaultCircle = new Circle(getXPos(), getYPos(), r);
switch (action) {
case ConsumeLeft:
mHomeX = (int) -mSnapWidth;
mHomeY = (int) -mSnapHeight;
break;
case ConsumeRight:
mHomeX = Config.mScreenWidth + (int) mSnapWidth;
mHomeY = (int) -mSnapHeight;
break;
case Close:
mHomeX = Config.mScreenCenterX; //mSnapWidth;
mHomeY = Config.mScreenHeight + (int) mSnapHeight;
break;
}
// Add main relative layout to canvasView
mCanvasLayoutParams.leftMargin = mHomeX;
mCanvasLayoutParams.topMargin = mHomeY;
mCanvasLayoutParams.rightMargin = -100;
mCanvasLayoutParams.bottomMargin = -100;
mCanvasView.addView(this, mCanvasLayoutParams);
setVisibility(GONE);
}
public void getOffsetDebugRegion(Rect r) {
int xMaxOffset = mMaxOffsetX;
int yMaxOffset = mMaxOffsetY;
if (sEnableTractor) {
xMaxOffset = mTractorOffsetX;
yMaxOffset = mTractorOffsetY;
}
int x0 = (int) (0.5f + getXPos() - xMaxOffset - Config.mBubbleWidth * 0.5f);
int x1 = (int) (0.5f + getXPos() + xMaxOffset + Config.mBubbleWidth * 0.5f);
int y0 = (int) (0.5f + getYPos() - yMaxOffset - Config.mBubbleHeight * 0.5f);
int y1 = (int) (0.5f + getYPos() + yMaxOffset + Config.mBubbleHeight * 0.5f);
r.left = x0;
r.right = x1;
r.top = y0;
r.bottom = y1;
}
public void getTractorDebugRegion(Rect r) {
int xMaxOffset = mTractorOffsetX;
int yMaxOffset = mTractorOffsetY;
int x0 = (int) (0.5f + getXPos() - xMaxOffset - Config.mBubbleWidth * 0.5f);
int x1 = (int) (0.5f + getXPos() + xMaxOffset + Config.mBubbleWidth * 0.5f);
int y0 = (int) (0.5f + getYPos() - yMaxOffset - Config.mBubbleHeight * 0.5f);
int y1 = (int) (0.5f + getYPos() + yMaxOffset + Config.mBubbleHeight * 0.5f);
r.left = x0;
r.right = x1;
r.top = y0;
r.bottom = y1;
}
public void destroy() {
unregisterForBus();
}
protected void registerForBus() {
MainApplication.registerForBus(getContext(), this);
}
protected void unregisterForBus() {
MainApplication.unregisterForBus(getContext(), this);
}
public boolean shouldSnap(Circle bubbleCircle, float radiusScaler) {
if (mTimeSinceSnapping > 0.5f) {
Circle snapCircle = GetSnapCircle();
if (bubbleCircle.Intersects(snapCircle, radiusScaler)) {
return true;
}
}
return false;
}
public static void enableTractor() {
sEnableTractor = true;
}
public static void disableTractor() {
sEnableTractor = false;
}
public void beginSnapping() {
mIsSnapping = true;
}
public void endSnapping() {
mIsSnapping = false;
mTimeSinceSnapping = 0.0f;
setTargetPos(mCanvasLayoutParams.leftMargin, mCanvasLayoutParams.topMargin);
}
public void beginLongHovering() {
mIsLongHovering = true;
}
public void endLongHovering() {
mIsLongHovering = false;
}
public boolean isLongHovering() {
return mIsLongHovering;
}
public Constant.BubbleAction getAction() {
return mAction;
}
@SuppressWarnings("unused")
@Subscribe
public void onBeginBubbleDrag(MainController.BeginBubbleDragEvent e) {
postDelayed(new Runnable() {
@Override
public void run() {
setVisibility(VISIBLE);
}
}, Constant.TARGET_BUBBLE_APPEAR_TIME);
mIsSnapping = false;
mTimeSinceSnapping = 1000.0f;
mSnapCircle.mX = (0.5f + getXPos());
mSnapCircle.mY = Util.clamp(0, 0.5f + getYPos() + mMaxOffsetY, Config.mScreenHeight - mDefaultCircle.mRadius);
mDefaultCircle.mX = mSnapCircle.mX;
mDefaultCircle.mY = mSnapCircle.mY;
int x = (int) (0.5f + mDefaultCircle.mX - mDefaultCircle.mRadius);
int y = (int) (0.5f + mDefaultCircle.mY - mDefaultCircle.mRadius);
setTargetPos(x, y);
MainController.get().scheduleUpdate();
}
@SuppressWarnings("unused")
@Subscribe
public void onEndBubbleDragEvent(MainController.EndBubbleDragEvent e) {
postDelayed(new Runnable() {
@Override
public void run() {
setVisibility(GONE);
}
}, Constant.TARGET_BUBBLE_APPEAR_TIME);
mIsSnapping = false;
setTargetPos(mHomeX, mHomeY);
}
@SuppressWarnings("unused")
@Subscribe
public void onDraggableBubbleMovedEvent(MainController.DraggableBubbleMovedEvent e) {
}
public void update(float dt) {
if (!mIsSnapping) {
mTimeSinceSnapping += dt;
}
}
public void OnOrientationChanged() {
mSnapCircle.mX = getXPos();
mSnapCircle.mY = getYPos();
mDefaultCircle.mX = mSnapCircle.mX;
mDefaultCircle.mY = mSnapCircle.mY;
switch (mAction) {
case ConsumeLeft:
mHomeX = (int) -mSnapWidth;
mHomeY = (int) -mSnapHeight;
break;
case ConsumeRight:
mHomeX = Config.mScreenWidth + (int) mSnapWidth;
mHomeY = (int) -mSnapHeight;
break;
case Close:
mHomeX = Config.mScreenCenterX;
mHomeY = Config.mScreenHeight + (int) mSnapHeight;
break;
}
mCanvasLayoutParams.leftMargin = mHomeX;
mCanvasLayoutParams.topMargin = mHomeY;
mCanvasView.updateViewLayout(this, mCanvasLayoutParams);
}
public Circle GetSnapCircle() {
return mSnapCircle;
}
public Circle GetDefaultCircle() {
return mDefaultCircle;
}
}