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); } }