package stu.tnt.gdx.widget;
import stu.tnt.gdx.utils.Debug;
import stu.tnt.gdx.utils.E;
import stu.tnt.gdx.utils.Properties;
import stu.tnt.gdx.utils.Refreshable;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Cell;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane.ScrollPaneStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.Value;
import com.badlogic.gdx.scenes.scene2d.utils.Align;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.ObjectMap;
public abstract class SwipeView extends Table implements Refreshable, Debug, Disposable {
private int orientation;
private final FlickPane mFlickScrollPane;
private final FlickTable mTable;
private int mCurrentRow;
private int mCurrentCol;
private final Interpolation mAutoScrollInterpolation = Interpolation.elasticOut;
private SwipeEffect mSwipeEffect;
/**
* Auto scroll to focus on one child at center
*/
private boolean isAutoFocusScroll = false;
/**
* Make a child map
*/
private final ObjectMap<Integer, Array<Actor>> RCChildMap = new ObjectMap<Integer, Array<Actor>>();
// ------------------------------------------------------------
// Auto focus data
private float currentScroll;
private float targetScroll;
private boolean justPanning = false;
private boolean startAutoScroll = false;
private float timer = 0;
private float previousVelX = 0;
private boolean checkAutoFocusX = false;
private boolean checkAutoFocusY = false;
private float previousVelY = 0;
// -------------------------------------------------------------
// Swipe effect data to get current and last focus
float cellSize;
float spacing;
float containerSize;
private int mCurrentFocusID = 0;
private int mLastFocusID = -1;
private Array<Actor> tmp;
// -------------------------------------------------------------
// // Data for auto gen table property
IntArray mUnFocusID = new IntArray(2);
int tmpInt;
public SwipeView () {
super();
mTable = new FlickTable();
mFlickScrollPane = new FlickPane(mTable);
mFlickScrollPane.setFlickScroll(true);
}
public SwipeView (Drawable backgroundRegion) {
super();
setBackground(backgroundRegion);
mTable = new FlickTable();
mFlickScrollPane = new FlickPane(mTable);
mFlickScrollPane.setFlickScroll(true);
}
public SwipeView (Skin skin) {
super();
mTable = new FlickTable();
mFlickScrollPane = new FlickPane(mTable, skin);
mFlickScrollPane.setFlickScroll(true);
}
public SwipeView (ScrollPaneStyle style) {
super();
mTable = new FlickTable();
mFlickScrollPane = new FlickPane(mTable, style);
mFlickScrollPane.setFlickScroll(true);
}
public abstract void initialize (final FlickTable flickTable);
/****************************************************************
* Container method
****************************************************************/
@Override
public void invalidate () {
super.invalidate();
refresh();
}
public void show (Stage stage) {
initialize(mTable);
add(mFlickScrollPane).align(Align.center).expand();
stage.addActor(this);
}
/****************************************************************
* Calculate method
****************************************************************/
private void generateContainerData () {
if (orientation == 0) return;
switch (orientation) {
case E.orientation.HORIZONTAL:
final float containerPadLeft = getPadLeft();
final float containerPadRight = getPadRight();
containerSize = (int)(getPrefWidth() - containerPadLeft - containerPadRight);
// D.out("container " + containerPadLeft + " " + containerPadRight +
// " " + containerSize);
break;
case E.orientation.VERTICAL:
final float containerPadTop = getPadTop();
final float containerPadBottom = getPadBottom();
containerSize = (int)(getPrefHeight() - containerPadBottom - containerPadTop);
break;
}
}
private void generateFlickData () {
if (mTable.getCells().size == 0 || orientation == 0) return;
switch (orientation) {
case E.orientation.HORIZONTAL:
final Cell sampleCell = mTable.getCells().get(0);
cellSize = sampleCell.getPrefWidth();
spacing = sampleCell.getSpaceRight();
// D.out("flick " +cellSize + " " + spacing + " " );
break;
case E.orientation.VERTICAL:
final Cell sampleCell1 = mTable.getCells().get(0);
cellSize = sampleCell1.getPrefHeight();
spacing = sampleCell1.getSpaceBottom();
break;
}
}
@Override
public void refresh () {
generateContainerData();
generateFlickData();
}
/****************************************************************
* Swipe method
****************************************************************/
public SwipeView putDefaultProperty (Properties property) {
property.pad(0);
property.apply(mTable.defaults());
return this;
}
public void addSwipeEffect (SwipeEffect effect) {
mSwipeEffect = effect;
if (mCurrentFocusID == 0 && mTable.getCells().size > 1) switch (orientation) {
case E.orientation.HORIZONTAL:
for (int i = 0; i <= mCurrentRow; i++)
effect.CurrentFocusChild(getChild(i, 0));
break;
case E.orientation.VERTICAL:
final int max = mCurrentRow;
for (int i = 0; i < max; i++)
effect.CurrentFocusChild(getChild(0, i));
break;
}
}
public void setAutoFocusScroll (boolean isAuto) {
if (orientation != E.orientation.HORIZONTAL && orientation != E.orientation.VERTICAL)
isAutoFocusScroll = false;
else
isAutoFocusScroll = isAuto;
}
@Override
public void draw (Batch batch, float parentAlpha) {
super.draw(batch, parentAlpha);
if (isAutoFocusScroll) {
if (previousVelX != mFlickScrollPane.getVelocityX()) {
checkAutoFocusX = true;
previousVelX = mFlickScrollPane.getVelocityX();
}
if (previousVelY != mFlickScrollPane.getVelocityY()) {
checkAutoFocusY = true;
previousVelY = mFlickScrollPane.getVelocityY();
}
if (mFlickScrollPane.isPanning()) {
justPanning = true;
}
if (!mFlickScrollPane.isFlinging() && !mFlickScrollPane.isPanning()) {
switch (orientation) {
case E.orientation.HORIZONTAL:
if (checkAutoFocusX || justPanning) autoScrollX();
break;
case E.orientation.VERTICAL:
if (checkAutoFocusY || justPanning) autoScrollY();
break;
}
} else {
startAutoScroll = false;
timer = 0;
}
}
}
@Override
public void act (float delta) {
super.act(delta);
if (orientation == 0) return;
if (mFlickScrollPane.isFlinging() || mFlickScrollPane.isPanning()) {
switch (orientation) {
case E.orientation.HORIZONTAL:
// Caculate current focus ID
mCurrentFocusID = (int)((mFlickScrollPane.getScrollX() + spacing / 2 + (containerSize / 2)) / (cellSize + spacing));
// D.out((mCurrentFocusID + " " +
// mFlickScrollPane.getScrollX()+ " result " +
// (mFlickScrollPane.getScrollX() + spacing/2 +
// (containerSize/2))));
tmpInt = getChildSize();
if (mCurrentFocusID >= tmpInt) mCurrentFocusID = tmpInt - 1;
// check unfocus array
if (mUnFocusID.contains(mCurrentFocusID)) {
if (mCurrentFocusID < table().getCells().size / 2)
mCurrentFocusID++;
else
mCurrentFocusID--;
}
// If nothign return;
if (mCurrentFocusID == mLastFocusID || mSwipeEffect == null) return;
// run swipe effect
for (int i = 0; i <= mCurrentRow; i++) {
mSwipeEffect.CurrentFocusChild(getChild(i, mCurrentFocusID));
if (mLastFocusID > 0)
mSwipeEffect.PreviousFocusChild(getChild(i, mLastFocusID));
else
mSwipeEffect.PreviousFocusChild(getChild(i, 0));
}
mLastFocusID = mCurrentFocusID;
break;
case E.orientation.VERTICAL:
// Caculate current focus ID
mCurrentFocusID = (int)((mFlickScrollPane.getScrollY() + (spacing / 2) + (containerSize / 2)) / (cellSize + spacing));
tmpInt = getChildSize();
if (mCurrentFocusID >= tmpInt) mCurrentFocusID = tmpInt - 1;
// check unfocus array
if (mUnFocusID.contains(mCurrentFocusID)) {
if (mCurrentFocusID < table().getCells().size / 2)
mCurrentFocusID++;
else
mCurrentFocusID--;
}
// If nothign return;
if (mCurrentFocusID == mLastFocusID || mSwipeEffect == null) return;
// run swipe effect
tmp = getChildList(mCurrentFocusID);
for (int i = 0; i < tmp.size; i++) {
mSwipeEffect.CurrentFocusChild(tmp.get(i));
}
if (mLastFocusID > 0)
tmp = getChildList(mLastFocusID);
else
tmp = getChildList(0);
for (int i = 0; i < tmp.size; i++) {
mSwipeEffect.PreviousFocusChild(tmp.get(i));
}
mLastFocusID = mCurrentFocusID;
break;
}
}
if (startAutoScroll) {
timer += delta;
float mdelta = targetScroll - currentScroll;
float alpha = mAutoScrollInterpolation.apply(Math.min(1f, timer / 1.5f));
switch (orientation) {
case E.orientation.HORIZONTAL:
mFlickScrollPane.setScrollX(currentScroll + mdelta * alpha);
break;
case E.orientation.VERTICAL:
mFlickScrollPane.setScrollY(currentScroll + mdelta * alpha);
break;
}
if (timer >= 1.5f) {
startAutoScroll = false;
timer = 0;
}
}
}
private void autoScrollX () {
currentScroll = mFlickScrollPane.getScrollX();
targetScroll = (mCurrentFocusID * (cellSize + spacing)) - ((containerSize / 2) - (cellSize / 2));
startAutoScroll = true;
checkAutoFocusX = false;
justPanning = false;
}
private void autoScrollY () {
currentScroll = mFlickScrollPane.getScrollY();
targetScroll = (mCurrentFocusID * (cellSize + spacing)) - ((containerSize / 2) - (cellSize / 2));
startAutoScroll = true;
justPanning = false;
checkAutoFocusY = false;
}
/****************************************************************
* Child method
****************************************************************/
public void addUnfocusID (int... id) {
for (int i : id)
mUnFocusID.add(i);
}
public void removeUnfocusID (int... id) {
for (int i : id)
mUnFocusID.removeValue(i);
}
public int getCurrentFocusID () {
return mCurrentFocusID;
}
public int getChildSize () {
return mTable.getCells().size;
}
public ObjectMap<Integer, Array<Actor>> getRCChildMap () {
return RCChildMap;
}
public Array<Actor> getChildList (int givenRow) {
return RCChildMap.get(givenRow);
}
public Actor getChild (int givenRow, int givenCol) {
return getChildList(givenRow).get(givenCol);
}
/****************************************************************
* Simple get and set method
****************************************************************/
public int getCurrentRow () {
return mCurrentRow;
}
public int getCurrentCol () {
return mCurrentCol;
}
public boolean isAutoFocus () {
return isAutoFocusScroll;
}
public int getOrientation () {
return orientation;
}
public float getCellSize () {
return cellSize;
}
public float getCellSpacing () {
return spacing;
}
public float getContainerSize () {
return containerSize;
}
public String info () {
return "Orientation: " + orientation + " auto: " + isAutoFocusScroll + " row: " + mCurrentRow + " col: " + mCurrentCol;
}
/****************************************************************
* Table method
****************************************************************/
public Table table () {
return mTable;
}
public Cell tableCell () {
return mTable.getCells().get(0);
}
public Table spacing (int space) {
Array<Cell> cellList = mTable.getCells();
for (int i = 0; i < cellList.size; i++)
cellList.get(i).space(space);
generateFlickData();
return mTable;
}
/****************************************************************
* Flick scroll pane method
****************************************************************/
/** Prevents scrolling out of the widget's bounds. Default is true. */
public SwipeView setClamp (boolean isClamp) {
mFlickScrollPane.setClamp(isClamp);
return this;
}
public SwipeView setSwipeOrientation (int orientation) {
if (orientation == E.orientation.HORIZONTAL || orientation == E.orientation.LANDSCAPE) {
this.orientation = E.orientation.HORIZONTAL;
mFlickScrollPane.setScrollingDisabled(false, true);
} else if (orientation == E.orientation.VERTICAL || orientation == E.orientation.PORTRAIT) {
this.orientation = E.orientation.VERTICAL;
mFlickScrollPane.setScrollingDisabled(true, false);
} else {
mFlickScrollPane.setScrollingDisabled(true, true);
}
return this;
}
/**
* If true, the widget can be scrolled slightly past its bounds and will animate back to its bounds when scrolling is stopped.
* Default is true.
*/
public SwipeView setOverscroll (boolean overscroll) {
mFlickScrollPane.setOverscroll(overscroll, overscroll);
return this;
}
/**
* Sets the overscroll distance in pixels and the speed it returns to the widgets bounds in seconds. Default is 50, 30, 200.
*/
public SwipeView setupOverscroll (float distance, float speedMin, float speedMax) {
mFlickScrollPane.setupOverscroll(distance, speedMin, speedMax);
return this;
}
/**
* Forces the enabling of overscrolling in a direction, even if the contents do not exceed the bounds in that direction.
*/
public SwipeView setForceOverscroll (boolean x, boolean y) {
// TODO: add this methods
// mFlickScrollPane.setForceOverscroll(x, y);
return this;
}
/**
* Sets the amount of time in seconds that a fling will continue to scroll. Default is 1.
*/
public SwipeView setFlingTime (float flingTime) {
mFlickScrollPane.setFlingTime(flingTime);
return this;
}
public SwipeView setVelocityX (float velocityX) {
mFlickScrollPane.setVelocityX(velocityX);
return this;
}
public SwipeView setVelocityY (float velocityY) {
mFlickScrollPane.setVelocityY(velocityY);
return this;
}
public SwipeView setFadeScrollBars (boolean fadeScrollBars) {
mFlickScrollPane.setFadeScrollBars(fadeScrollBars);
return this;
}
public SwipeView setupFadeScrollBars (float fadeAlphaSeconds, float fadeDelaySeconds) {
mFlickScrollPane.setupFadeScrollBars(fadeAlphaSeconds, fadeDelaySeconds);
return this;
}
/**
* From 0.0f to 1.0f ( slow to fast)
*/
public SwipeView setFlingSensitive (float sensitive) {
// mFlickScrollPane.setFlingSensitive(sensitive);
return this;
}
public float getScrollX () {
return mFlickScrollPane.getScrollX();
}
public float getScrollY () {
return mFlickScrollPane.getScrollY();
}
public float getScrollPercentX () {
return mFlickScrollPane.getScrollPercentX();
}
public float getScrollPercentY () {
return mFlickScrollPane.getScrollPercentY();
}
/**************************************************************
*
**************************************************************/
@Override
public void dispose () {
Array<Actor> a = mTable.getChildren();
final int size = a.size;
for (int i = 0; i < size; i++) {
if (a instanceof Disposable) ((Disposable)a).dispose();
}
mTable.clear();
mFlickScrollPane.clear();
clear();
}
/**************************************************************
*
**************************************************************/
private void addChild (int row, Actor actor) {
Array<Actor> tmp = RCChildMap.get(row);
if (tmp == null) {
tmp = new Array<Actor>();
RCChildMap.put(row, tmp);
}
tmp.add(actor);
}
/**
*
* @author trung
*/
public class FlickTable extends Table {
private int row = 0;
@Override
public Cell add (String text) {
Cell cell = super.add(text).pad(0);
addChild(row, (Actor)cell.getTable());
return cell;
}
@Override
public Cell add (String text, String labelStyleName) {
Cell cell = super.add(text, labelStyleName).pad(0);
addChild(row, (Actor)cell.getTable());
return cell;
}
@Override
public Cell add () {
Cell cell = super.add().pad(0);
addChild(row, (Actor)cell.getTable());
return cell;
}
@Override
public Cell add (Actor actor) {
addChild(row, actor);
return super.add(actor).pad(0);
}
@Override
public Cell row () {
row++;
return super.row();
}
@Override
public Table pad (Value pad) {
return super.pad(0);
}
@Override
public Table pad (Value top, Value left, Value bottom, Value right) {
return super.pad(0, 0, 0, 0);
}
@Override
public Table padTop (Value padTop) {
return super.padTop(0);
}
@Override
public Table padLeft (Value padLeft) {
return super.padLeft(0);
}
@Override
public Table padBottom (Value padBottom) {
return super.padBottom(0);
}
@Override
public Table padRight (Value padRight) {
return super.padRight(0);
}
@Override
public Table pad (float pad) {
return super.pad(0);
}
@Override
public Table pad (float top, float left, float bottom, float right) {
return super.pad(0, 0, 0, 0);
}
@Override
public Table padTop (float padTop) {
return super.padTop(0);
}
@Override
public Table padLeft (float padLeft) {
return super.padLeft(0);
}
@Override
public Table padBottom (float padBottom) {
return super.padBottom(0);
}
@Override
public Table padRight (float padRight) {
return super.padRight(0);
}
}
/**
*
* @author trung
*/
private static class FlickPane extends ScrollPane {
public FlickPane (Actor widget, ScrollPaneStyle style) {
super(widget, style);
}
public FlickPane (Actor widget) {
super(widget);
}
public FlickPane (Actor widget, Skin skin) {
super(widget, skin);
}
@Override
public void setFlickScroll (boolean flickScroll) {
super.setFlickScroll(true);
}
}
public static interface SwipeEffect {
public void PreviousFocusChild (Actor actor);
public void CurrentFocusChild (Actor actor);
}
}