commit 15846fe4cbf48884b6642355d13f47162191fdaa
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu Jun 25 22:35:15 2020 +0100

    cube_back and cube_solve buttons.

diff --git a/src/main/java/org/distorted/main/RubikSurfaceView.java b/src/main/java/org/distorted/main/RubikSurfaceView.java
index 1fb5d237..b7b70151 100644
--- a/src/main/java/org/distorted/main/RubikSurfaceView.java
+++ b/src/main/java/org/distorted/main/RubikSurfaceView.java
@@ -34,6 +34,7 @@ import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectMovement;
 import org.distorted.solvers.SolverMain;
 import org.distorted.states.RubikState;
+import org.distorted.states.RubikStatePlay;
 import org.distorted.states.RubikStateSolver;
 import org.distorted.states.RubikStateSolving;
 
@@ -393,10 +394,18 @@ public class RubikSurfaceView extends GLSurfaceView
       int angle = mPreRender.getObject().computeNearestAngle(mCurrentAngle, mCurrRotSpeed);
       mPreRender.finishRotation(angle);
 
-      if( RubikState.getCurrentState()==RubikState.SOLV && angle!=0 )
+      if( angle!=0 )
         {
-        RubikStateSolving solving = (RubikStateSolving)RubikState.SOLV.getStateClass();
-        solving.addMove(mCurrentAxis, mCurrentRow, angle);
+        if( RubikState.getCurrentState()==RubikState.SOLV )
+          {
+          RubikStateSolving solving = (RubikStateSolving)RubikState.SOLV.getStateClass();
+          solving.addMove(mCurrentAxis, mCurrentRow, angle);
+          }
+        if( RubikState.getCurrentState()==RubikState.PLAY )
+          {
+          RubikStatePlay play = (RubikStatePlay)RubikState.PLAY.getStateClass();
+          play.addMove(mCurrentAxis, mCurrentRow, angle);
+          }
         }
 
       mContinuingRotation = false;
diff --git a/src/main/java/org/distorted/states/RubikStatePlay.java b/src/main/java/org/distorted/states/RubikStatePlay.java
index c4f471ea..3757a248 100644
--- a/src/main/java/org/distorted/states/RubikStatePlay.java
+++ b/src/main/java/org/distorted/states/RubikStatePlay.java
@@ -45,12 +45,18 @@ import org.distorted.dialogs.RubikDialogAbout;
 import org.distorted.dialogs.RubikDialogScores;
 import org.distorted.main.R;
 import org.distorted.main.RubikActivity;
+import org.distorted.main.RubikPreRender;
+import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
 
+import java.util.ArrayList;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class RubikStatePlay extends RubikStateAbstract implements AdapterView.OnItemSelectedListener
+public class RubikStatePlay extends RubikStateAbstract implements AdapterView.OnItemSelectedListener,
+                                                                  RubikPreRender.ActionFinishedListener
   {
+  private static final int DURATION_MILLIS = 750;
   private static final int DEF_LEVEL =  1;
   public  static final int DEF_OBJECT= RubikObjectList.CUBE.ordinal();
   public  static final int DEF_SIZE  =  3;
@@ -58,8 +64,8 @@ public class RubikStatePlay extends RubikStateAbstract implements AdapterView.On
   private static int[] BUTTON_LABELS = { R.string.scores, R.string.patterns, R.string.solver, R.string.about };
   private static final int NUM_BUTTONS = BUTTON_LABELS.length;
 
-  private ImageButton mObjButton, mMenuButton;
-  private Button mBackButton, mSolveButton, mPlayButton;
+  private ImageButton mObjButton, mMenuButton, mPrevButton, mSolveButton;
+  private Button mPlayButton;
   private PopupWindow mObjectPopup, mMenuPopup;
   private int mObject = DEF_OBJECT;
   private int mSize   = DEF_SIZE;
@@ -70,6 +76,21 @@ public class RubikStatePlay extends RubikStateAbstract implements AdapterView.On
   private int mLevelValue;
   private float mButtonSize, mTitleSize;
 
+  private ArrayList<Move> mMoves;
+  private boolean mCanPrevMove;
+
+  private static class Move
+    {
+    private int mAxis, mRow, mAngle;
+
+    Move(int axis, int row, int angle)
+      {
+      mAxis = axis;
+      mRow  = row;
+      mAngle= angle;
+      }
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   void leaveState(RubikActivity act)
@@ -88,6 +109,11 @@ public class RubikStatePlay extends RubikStateAbstract implements AdapterView.On
     mButtonSize = width*RubikActivity.BUTTON_TEXT_SIZE;
     mTitleSize  = width*RubikActivity.TITLE_TEXT_SIZE;
 
+    mCanPrevMove = true;
+
+    if( mMoves==null ) mMoves = new ArrayList<>();
+    else               mMoves.clear();
+
     // TOP ////////////////////////////
     LinearLayout layoutTop = act.findViewById(R.id.upperBar);
     layoutTop.removeAllViews();
@@ -106,16 +132,16 @@ public class RubikStatePlay extends RubikStateAbstract implements AdapterView.On
     LinearLayout layoutLeft = act.findViewById(R.id.mainBarLeft);
     layoutLeft.removeAllViews();
 
-    setupMenuButton(act,scale,width);
-    layoutLeft.addView(mMenuButton);
-    setupSolveButton(act,scale);
+    setupPrevButton(act,scale,width);
+    layoutLeft.addView(mPrevButton);
+    setupSolveButton(act,scale,width);
     layoutLeft.addView(mSolveButton);
 
     LinearLayout layoutRight = act.findViewById(R.id.mainBarRight);
     layoutRight.removeAllViews();
 
-    setupBackButton(act,scale);
-    layoutRight.addView(mBackButton);
+    setupMenuButton(act,scale);
+    layoutRight.addView(mMenuButton);
 
     setupMenuWindow(act, scale);
     }
@@ -161,17 +187,17 @@ public class RubikStatePlay extends RubikStateAbstract implements AdapterView.On
 
   private void setupLevelSpinner(final RubikActivity act, final float scale)
     {
-    int spinnerPadding = (int)(scale* 10 + 0.5f);
-    int spinnerMargin  = (int)(scale*  3 + 0.5f);
+    int padding = (int)(scale* 10 + 0.5f);
+    int margin  = (int)(scale*  3 + 0.5f);
     LinearLayout.LayoutParams spinnerLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);
-    spinnerLayoutParams.topMargin    =   spinnerMargin;
-    spinnerLayoutParams.bottomMargin =   spinnerMargin;
-    spinnerLayoutParams.leftMargin   =   spinnerMargin;
-    spinnerLayoutParams.rightMargin  = 2*spinnerMargin;
+    spinnerLayoutParams.topMargin    = margin;
+    spinnerLayoutParams.bottomMargin = margin;
+    spinnerLayoutParams.leftMargin   = margin;
+    spinnerLayoutParams.rightMargin  = margin;
 
     mLevelSpinner = new AppCompatSpinner(act);
     mLevelSpinner.setLayoutParams(spinnerLayoutParams);
-    mLevelSpinner.setPadding(spinnerPadding,0,spinnerPadding,0);
+    mLevelSpinner.setPadding(padding,0,padding,0);
     mLevelSpinner.setBackgroundResource(R.drawable.spinner);
     mLevelSpinner.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
 
@@ -228,11 +254,10 @@ public class RubikStatePlay extends RubikStateAbstract implements AdapterView.On
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void setupMenuButton(final RubikActivity act, final float scale, final float width)
+  private void setupMenuButton(final RubikActivity act, final float scale)
     {
     int padding = (int)(3*scale + 0.5f);
-    int widthBut = (int)(width/6);
-    LinearLayout.LayoutParams objectParams = new LinearLayout.LayoutParams(widthBut,LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams objectParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
     mMenuButton = new ImageButton(act);
     mMenuButton.setLayoutParams(objectParams);
     mMenuButton.setPadding(padding,0,padding,0);
@@ -258,16 +283,15 @@ public class RubikStatePlay extends RubikStateAbstract implements AdapterView.On
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void setupSolveButton(final RubikActivity act, final float scale)
+  private void setupSolveButton(final RubikActivity act, final float scale, final float width)
     {
     int padding = (int)(3*scale + 0.5f);
-    LinearLayout.LayoutParams backParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
-    mSolveButton = new Button(act);
+    int widthBut = (int)(width/6);
+    LinearLayout.LayoutParams backParams = new LinearLayout.LayoutParams(widthBut, LinearLayout.LayoutParams.MATCH_PARENT);
+    mSolveButton = new ImageButton(act);
     mSolveButton.setLayoutParams(backParams);
     mSolveButton.setPadding(padding,0,padding,0);
-    mSolveButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, mButtonSize);
-
-    mSolveButton.setText(R.string.solve);
+    mSolveButton.setImageResource(R.drawable.cube_solve);
 
     mSolveButton.setOnClickListener( new View.OnClickListener()
       {
@@ -281,22 +305,23 @@ public class RubikStatePlay extends RubikStateAbstract implements AdapterView.On
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void setupBackButton(final RubikActivity act, final float scale)
+  private void setupPrevButton(final RubikActivity act, final float scale, final float width)
     {
     int padding = (int)(3*scale + 0.5f);
-    LinearLayout.LayoutParams backParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
-    mBackButton = new Button(act);
-    mBackButton.setLayoutParams(backParams);
-    mBackButton.setPadding(padding,0,padding,0);
-    mBackButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, mButtonSize);
-    mBackButton.setText(R.string.exit);
-
-    mBackButton.setOnClickListener( new View.OnClickListener()
+    int widthBut = (int)(width/6);
+    LinearLayout.LayoutParams backParams = new LinearLayout.LayoutParams(widthBut, LinearLayout.LayoutParams.MATCH_PARENT);
+    mPrevButton = new ImageButton(act);
+    mPrevButton.setLayoutParams(backParams);
+    mPrevButton.setPadding(padding,0,padding,0);
+    mPrevButton.setImageResource(R.drawable.cube_back);
+
+    mPrevButton.setOnClickListener( new View.OnClickListener()
       {
       @Override
       public void onClick(View v)
         {
-        if( act.getPreRender().canPlay() ) RubikState.goBack(act);
+        RubikPreRender pre = act.getPreRender();
+        backMove(pre);
         }
       });
     }
@@ -398,6 +423,40 @@ public class RubikStatePlay extends RubikStateAbstract implements AdapterView.On
     mMenuLayoutHeight= (int)(margin + NUM_BUTTONS*(mButtonSize+margin));
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void backMove(RubikPreRender pre)
+    {
+    if( mCanPrevMove )
+      {
+      int numMoves = mMoves.size();
+
+      if( numMoves>0 )
+        {
+        Move move = mMoves.remove(numMoves-1);
+        RubikObject object = pre.getObject();
+
+        int axis  = move.mAxis;
+        int row   = (1<<move.mRow);
+        int angle = move.mAngle;
+        int numRot= Math.abs(angle*object.getBasicAngle()/360);
+
+        if( angle!=0 )
+          {
+          mCanPrevMove = false;
+          pre.addRotation(this, axis, row, -angle, numRot*DURATION_MILLIS);
+          }
+        else
+          {
+          android.util.Log.e("solution", "error: trying to back move of angle 0");
+          }
+        }
+      else
+        {
+        android.util.Log.e("solv", "error: no moves to back!");
+        }
+      }
+    }
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   private void Action(RubikActivity act, int button)
@@ -522,6 +581,13 @@ public class RubikStatePlay extends RubikStateAbstract implements AdapterView.On
     return mLevelValue;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void addMove(int axis, int row, int angle)
+    {
+    mMoves.add(new Move(axis,row,angle));
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public int getObject()
@@ -546,4 +612,11 @@ public class RubikStatePlay extends RubikStateAbstract implements AdapterView.On
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void onNothingSelected(AdapterView<?> parent) { }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onActionFinished(final long effectID)
+    {
+    mCanPrevMove = true;
+    }
   }
diff --git a/src/main/java/org/distorted/states/RubikStateReady.java b/src/main/java/org/distorted/states/RubikStateReady.java
index 579d13e6..2b10a821 100644
--- a/src/main/java/org/distorted/states/RubikStateReady.java
+++ b/src/main/java/org/distorted/states/RubikStateReady.java
@@ -25,6 +25,7 @@ import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.Button;
+import android.widget.ImageButton;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -35,6 +36,10 @@ import org.distorted.main.RubikActivity;
 
 public class RubikStateReady extends RubikStateAbstract
   {
+  private ImageButton mPrevButton;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
   void leaveState(RubikActivity act)
     {
 
@@ -64,6 +69,9 @@ public class RubikStateReady extends RubikStateAbstract
     LinearLayout layoutLeft = act.findViewById(R.id.mainBarLeft);
     layoutLeft.removeAllViews();
 
+    setupPrevMoveButtom(act,scale,width);
+    layoutLeft.addView(mPrevButton);
+
     LinearLayout layoutRight = act.findViewById(R.id.mainBarRight);
     layoutRight.removeAllViews();
 
@@ -88,6 +96,29 @@ public class RubikStateReady extends RubikStateAbstract
     layoutRight.addView(back);
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupPrevMoveButtom(final RubikActivity act, float scale, float width)
+    {
+    int padding = (int)( 3*scale + 0.5f);
+    int widthBut= (int)(width/6);
+
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(widthBut,LinearLayout.LayoutParams.MATCH_PARENT);
+    mPrevButton = new ImageButton(act);
+    mPrevButton.setLayoutParams(params);
+    mPrevButton.setPadding(padding,0,padding,0);
+    mPrevButton.setImageResource(R.drawable.cube_back);
+
+    mPrevButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        // empty
+        }
+      });
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public void savePreferences(SharedPreferences.Editor editor)
diff --git a/src/main/java/org/distorted/states/RubikStateSolving.java b/src/main/java/org/distorted/states/RubikStateSolving.java
index 8406b391..1a7a0890 100644
--- a/src/main/java/org/distorted/states/RubikStateSolving.java
+++ b/src/main/java/org/distorted/states/RubikStateSolving.java
@@ -152,8 +152,7 @@ public class RubikStateSolving extends RubikStateAbstract implements RubikPreRen
     mPrevButton = new ImageButton(act);
     mPrevButton.setLayoutParams(params);
     mPrevButton.setPadding(padding,0,padding,0);
-    mPrevButton.setImageResource(R.drawable.left);
-    mPrevButton.setVisibility(View.INVISIBLE);
+    mPrevButton.setImageResource(R.drawable.cube_back);
 
     mPrevButton.setOnClickListener( new View.OnClickListener()
       {
@@ -188,11 +187,6 @@ public class RubikStateSolving extends RubikStateAbstract implements RubikPreRen
           {
           mCanPrevMove = false;
           pre.addRotation(this, axis, row, -angle, numRot*DURATION_MILLIS);
-
-          if( numMoves==1 )
-            {
-            mPrevButton.setVisibility(View.INVISIBLE);
-            }
           }
         else
           {
@@ -211,11 +205,6 @@ public class RubikStateSolving extends RubikStateAbstract implements RubikPreRen
   public void addMove(int axis, int row, int angle)
     {
     mMoves.add(new Move(axis,row,angle));
-
-    if( mMoves.size()==1 )
-      {
-      mPrevButton.setVisibility(View.VISIBLE);
-      }
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/res/drawable/cube_back.png b/src/main/res/drawable/cube_back.png
new file mode 100644
index 00000000..f7c7d3bd
Binary files /dev/null and b/src/main/res/drawable/cube_back.png differ
diff --git a/src/main/res/drawable/cube_solve.png b/src/main/res/drawable/cube_solve.png
new file mode 100644
index 00000000..176eb4f2
Binary files /dev/null and b/src/main/res/drawable/cube_solve.png differ
