commit 58fd2ec06dc450f943fb359ba29caae69758940e
Author: leszek <leszek@koltunski.pl>
Date:   Fri Feb 16 01:15:25 2024 +0100

    Initial support for configuring the stickers.

diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 2416ce07..ca5bdf1e 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -37,6 +37,7 @@
         <activity android:name="org.distorted.solverui.SolverActivity" android:exported="false" android:screenOrientation="portrait"/>
         <activity android:name="org.distorted.patternui.PatternActivity" android:exported="false" android:screenOrientation="portrait"/>
         <activity android:name="org.distorted.playui.PlayActivity" android:exported="false" android:screenOrientation="portrait"/>
+        <activity android:name="org.distorted.config.ConfigActivity" android:exported="false" android:screenOrientation="portrait"/>
 
         <service
             android:name="org.distorted.messaging.RubikMessagingService"
diff --git a/src/main/java/org/distorted/config/ConfigActivity.java b/src/main/java/org/distorted/config/ConfigActivity.java
new file mode 100644
index 00000000..793cead8
--- /dev/null
+++ b/src/main/java/org/distorted/config/ConfigActivity.java
@@ -0,0 +1,228 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.config;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import org.distorted.dialogs.RubikDialogError;
+import org.distorted.library.effect.MatrixEffectQuaternion;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.main.MainActivity;
+import org.distorted.main.R;
+import org.distorted.objectlib.main.InitAssets;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+
+import java.io.InputStream;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ConfigActivity extends AppCompatActivity
+{
+    private static final int ACTIVITY_NUMBER = 7;
+    private static final float RATIO_BAR  = MainActivity.RATIO_BAR;
+    public static final int FLAGS = MainActivity.FLAGS;
+
+    private static int mScreenWidth, mScreenHeight;
+    private int mCurrentApiVersion;
+    private ConfigScreen mScreen;
+    private int mObjectOrdinal;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected void onCreate(Bundle savedState)
+      {
+      super.onCreate(savedState);
+      DistortedLibrary.onCreate(ACTIVITY_NUMBER);
+      setTheme(R.style.MaterialThemeNoActionBar);
+      setContentView(R.layout.config);
+
+      Bundle b = getIntent().getExtras();
+
+      if(b != null) mObjectOrdinal = b.getInt("obj");
+
+      DisplayMetrics displaymetrics = new DisplayMetrics();
+      getWindowManager().getDefaultDisplay().getRealMetrics(displaymetrics);
+      mScreenWidth =displaymetrics.widthPixels;
+      mScreenHeight=displaymetrics.heightPixels;
+
+      hideNavigationBar();
+      cutoutHack();
+      computeBarHeights();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this does not include possible insets
+
+    private void computeBarHeights()
+      {
+      int barHeight = (int)(mScreenHeight*RATIO_BAR);
+      LinearLayout layout = findViewById(R.id.lowerBar);
+      ViewGroup.LayoutParams params = layout.getLayoutParams();
+      params.height = barHeight;
+      layout.setLayoutParams(params);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void hideNavigationBar()
+      {
+      mCurrentApiVersion = Build.VERSION.SDK_INT;
+
+      if(mCurrentApiVersion >= Build.VERSION_CODES.KITKAT)
+        {
+        final View decorView = getWindow().getDecorView();
+
+        decorView.setSystemUiVisibility(FLAGS);
+
+        decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener()
+          {
+          @Override
+          public void onSystemUiVisibilityChange(int visibility)
+            {
+            if((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0)
+              {
+              decorView.setSystemUiVisibility(FLAGS);
+              }
+            }
+          });
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// do not avoid cutouts
+
+    private void cutoutHack()
+      {
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
+        {
+        getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus)
+      {
+      super.onWindowFocusChanged(hasFocus);
+
+      if(mCurrentApiVersion >= Build.VERSION_CODES.KITKAT && hasFocus)
+        {
+        getWindow().getDecorView().setSystemUiVisibility(FLAGS);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onPause() 
+      {
+      super.onPause();
+      ConfigSurfaceView view = findViewById(R.id.configSurfaceView);
+      view.onPause();
+      DistortedLibrary.onPause(ACTIVITY_NUMBER);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onResume() 
+      {
+      super.onResume();
+      DistortedLibrary.onResume(ACTIVITY_NUMBER);
+      ConfigSurfaceView view = findViewById(R.id.configSurfaceView);
+      view.onResume();
+
+      if( mScreen==null ) mScreen = new ConfigScreen();
+      mScreen.onAttachedToWindow(this,mObjectOrdinal);
+
+      if( mObjectOrdinal>=0 && mObjectOrdinal< RubikObjectList.getNumObjects() )
+        {
+        RubikObject object = RubikObjectList.getObject(mObjectOrdinal);
+        changeIfDifferent(object,mObjectOrdinal,view.getObjectControl());
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onDestroy() 
+      {
+      super.onDestroy();
+      DistortedLibrary.onDestroy(ACTIVITY_NUMBER);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void OpenGLError()
+      {
+      RubikDialogError errDiag = new RubikDialogError();
+      errDiag.show(getSupportFragmentManager(), null);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void changeIfDifferent(RubikObject object,int ordinal,ObjectControl control)
+      {
+      if( object!=null )
+        {
+        int iconMode           = TwistyObject.MODE_NORM;
+        InputStream jsonStream = object.getObjectStream(this);
+        InputStream meshStream = object.getMeshStream(this);
+        String name            = object.getUpperName();
+        InitAssets asset       = new InitAssets(jsonStream,meshStream,null);
+        control.changeIfDifferent(ordinal,name,iconMode,asset);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public ConfigRenderer getRenderer()
+      {
+      ConfigSurfaceView view = findViewById(R.id.configSurfaceView);
+      return view.getRenderer();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenWidthInPixels()
+      {
+      return mScreenWidth;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenHeightInPixels()
+      {
+      return mScreenHeight;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public ObjectControl getControl()
+      {
+      ConfigSurfaceView view = findViewById(R.id.configSurfaceView);
+      return view.getObjectControl();
+      }
+}
diff --git a/src/main/java/org/distorted/config/ConfigObjectLibInterface.java b/src/main/java/org/distorted/config/ConfigObjectLibInterface.java
new file mode 100644
index 00000000..ab277951
--- /dev/null
+++ b/src/main/java/org/distorted/config/ConfigObjectLibInterface.java
@@ -0,0 +1,148 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2023 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.config;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import org.distorted.library.message.EffectMessageSender;
+import org.distorted.main.BuildConfig;
+import org.distorted.objectlib.helpers.BlockController;
+import org.distorted.objectlib.helpers.ObjectLibInterface;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.solvers.SolverMain;
+import org.distorted.solverui.ScreenList;
+import org.distorted.solverui.ScreenSolver;
+import org.distorted.solverui.SolverActivity;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ConfigObjectLibInterface implements ObjectLibInterface
+{
+  public void onWinEffectFinished(long startTime, long endTime, String debug, int scrambleNum) { }
+  public void onScrambleEffectFinished() { }
+  public void onBeginRotation() { }
+  public void onSolved() { }
+  public void onObjectCreated(long time) { }
+  public void onRemoveRotation(int axis, int row, int angle) { }
+  public void failedToDrag() { }
+  public void reportJSONError(String error, int ordinal) { }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void reportProblem(String problem, boolean reportException)
+    {
+    if( BuildConfig.DEBUG )
+      {
+      android.util.Log.e("interface", problem);
+      }
+    else
+      {
+      if( reportException )
+        {
+        Exception ex = new Exception(problem);
+        FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+        crashlytics.setCustomKey("problem" , problem);
+        crashlytics.recordException(ex);
+        }
+      else
+        {
+        FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+        crashlytics.log(problem);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportScramblingProblem(int place, long pause, long resume, long time)
+    {
+    String error = "SCRAMBLING BLOCK "+place+" blocked for "+time;
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("D", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , pause );
+      crashlytics.setCustomKey("resume", resume );
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportRotationProblem(int place, long pause, long resume, long time)
+    {
+    String error = "ROTATION BLOCK "+place+" blocked for "+time;
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("D", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , pause );
+      crashlytics.setCustomKey("resume", resume);
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void reportThreadProblem(int place, long pause, long resume, long time)
+    {
+    String error = EffectMessageSender.reportState();
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("D", error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("pause" , pause  );
+      crashlytics.setCustomKey("resume", resume );
+      crashlytics.recordException(ex);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void reportBlockProblem(int type, int place, long pause, long resume, long time)
+    {
+    switch(type)
+      {
+      case BlockController.TYPE_SCRAMBLING: reportScramblingProblem(place,pause,resume,time); break;
+      case BlockController.TYPE_ROTATION  : reportRotationProblem(place,pause,resume,time); break;
+      case BlockController.TYPE_THREAD    : reportThreadProblem(place,pause,resume,time); break;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+  public void onReplaceModeDown(int cubit, int face)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onReplaceModeUp()
+    {
+
+    }
+}
diff --git a/src/main/java/org/distorted/config/ConfigRenderer.java b/src/main/java/org/distorted/config/ConfigRenderer.java
new file mode 100644
index 00000000..633dd342
--- /dev/null
+++ b/src/main/java/org/distorted/config/ConfigRenderer.java
@@ -0,0 +1,180 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.config;
+
+import static org.distorted.library.helpers.QuatHelper.rotateVectorByInvertedQuat;
+
+import android.content.res.Resources;
+import android.opengl.GLSurfaceView;
+
+import org.distorted.library.effect.EffectType;
+import org.distorted.library.effect.MatrixEffectRotate;
+import org.distorted.library.effect.VertexEffectQuaternion;
+import org.distorted.library.effect.VertexEffectRotate;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.objectlib.effects.BaseEffect;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObject;
+
+import java.io.InputStream;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ConfigRenderer implements GLSurfaceView.Renderer, DistortedLibrary.LibraryUser
+{
+   private static final int RESET_DURATION = 1000;
+
+   private final ConfigSurfaceView mView;
+   private final Resources mResources;
+   private final DistortedScreen mScreen;
+   private final ObjectControl mControl;
+   private boolean mResettingObject, mInitialPhase, mEffectApplied;
+   private long mStartTime;
+   private final Static1D mAngle;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   ConfigRenderer(ConfigSurfaceView v)
+     {
+     final float BRIGHTNESS = 0.333f;
+
+     mResettingObject = false;
+     mEffectApplied   = false;
+     mAngle = new Static1D(0);
+
+     mView = v;
+     mControl = mView.getObjectControl();
+
+     mResources = v.getResources();
+     mScreen = new DistortedScreen();
+     mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onDrawFrame(GL10 glUnused)
+     {
+     long time = System.currentTimeMillis();
+     mControl.preRender();
+     mScreen.render(time);
+
+     if( mResettingObject )
+       {
+       if( !mEffectApplied )
+         {
+         mEffectApplied = true;
+         ObjectControl control = mView.getObjectControl();
+         TwistyObject object = control.getObject();
+
+         Static4D quaternion = object.getRotationQuat();                       // always rotate around
+         Static4D tmpAxis    = new Static4D(0,-1,0,0);                         // vert axis no matter
+         Static4D rotated    = rotateVectorByInvertedQuat(tmpAxis,quaternion); // how cube is rotated
+         Static3D axis       = new Static3D(rotated.get0(), rotated.get1(), rotated.get2());
+         Static3D center     = new Static3D(0,0,0);
+
+         MatrixEffectRotate effect = new MatrixEffectRotate(mAngle, axis, center );
+         object.applyEffect(effect,0);
+         }
+
+       boolean done = continueResetting(time);
+       if( done ) mResettingObject = false;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void setupReset()
+     {
+     if( !mResettingObject )
+       {
+       mResettingObject = true;
+       mInitialPhase    = true;
+       mStartTime       = System.currentTimeMillis();
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private boolean continueResetting(long time)
+     {
+     long diff = time-mStartTime;
+     float quotient = ((float)diff)/RESET_DURATION;
+
+     if( mInitialPhase && quotient>0.5f )
+       {
+       mInitialPhase=false;
+       mView.resetObject();
+       }
+
+     float angle = 720*quotient*quotient*(3-2*quotient);
+     mAngle.set( angle );
+
+     return quotient>1.0f;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onSurfaceChanged(GL10 glUnused, int width, int height)
+      {
+      mScreen.resize(width,height);
+      mView.setScreenSize(width,height);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   DistortedScreen getScreen()
+     {
+     return mScreen;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
+      {
+      DistortedLibrary.setMax(EffectType.VERTEX, ObjectControl.MAX_QUATS+1);
+      VertexEffectRotate.enable();
+      VertexEffectQuaternion.enable();
+      BaseEffect.Type.enableEffects();
+
+      DistortedLibrary.onSurfaceCreated(this,1);
+      DistortedLibrary.setCull(true);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void distortedException(Exception ex)
+     {
+     android.util.Log.e("Config", "unexpected exception: "+ex.getMessage() );
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public InputStream localFile(int fileID)
+      {
+      return mResources.openRawResource(fileID);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void logMessage(String message)
+      {
+      android.util.Log.e("Config", message );
+      }
+}
diff --git a/src/main/java/org/distorted/config/ConfigScreen.java b/src/main/java/org/distorted/config/ConfigScreen.java
new file mode 100644
index 00000000..9efd598d
--- /dev/null
+++ b/src/main/java/org/distorted/config/ConfigScreen.java
@@ -0,0 +1,90 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.config;
+
+import android.view.View;
+import android.widget.LinearLayout;
+
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ConfigScreen
+{
+  private TransparentImageButton mBackButton, mResetButton;
+  private ConfigScreenPane mPane;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final ConfigActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+    mBackButton = new TransparentImageButton(act,R.drawable.ui_smallback,params);
+
+    mBackButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        act.finish();
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupResetButton(final ConfigActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+    mResetButton = new TransparentImageButton(act,R.drawable.ui_reset,params);
+
+    mResetButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ConfigRenderer renderer = act.getRenderer();
+        renderer.setupReset();
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void onAttachedToWindow(final ConfigActivity act, final int objectOrdinal)
+    {
+    int width = act.getScreenWidthInPixels();
+
+    LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams(width/2, LinearLayout.LayoutParams.MATCH_PARENT);
+    LinearLayout.LayoutParams paramsR = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
+
+    LinearLayout layoutLeft = new LinearLayout(act);
+    layoutLeft.setLayoutParams(paramsL);
+    LinearLayout layoutMid  = new LinearLayout(act);
+    layoutMid.setLayoutParams(paramsM);
+    LinearLayout layoutRight= new LinearLayout(act);
+    layoutRight.setLayoutParams(paramsR);
+
+    setupBackButton(act);
+    setupResetButton(act);
+    layoutLeft.addView(mResetButton);
+    layoutRight.addView(mBackButton);
+
+    LinearLayout layout = act.findViewById(R.id.lowerBar);
+    layout.removeAllViews();
+    layout.addView(layoutLeft);
+    layout.addView(layoutMid);
+    layout.addView(layoutRight);
+
+    mPane = new ConfigScreenPane(act,objectOrdinal);
+    }
+}
diff --git a/src/main/java/org/distorted/config/ConfigScreenPane.java b/src/main/java/org/distorted/config/ConfigScreenPane.java
new file mode 100644
index 00000000..913073e0
--- /dev/null
+++ b/src/main/java/org/distorted/config/ConfigScreenPane.java
@@ -0,0 +1,66 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.config;
+
+import static org.distorted.objects.RubikObjectCategories.DIFF_IDS;
+import static org.distorted.objects.RubikObjectCategories.DIFF_IMAGES;
+
+import android.graphics.PorterDuff;
+import android.util.TypedValue;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.distorted.main.R;
+import org.distorted.objectlib.json.JsonReader;
+import org.distorted.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+
+import java.io.InputStream;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO
+
+public class ConfigScreenPane
+{
+  private static final int NUM_IDS         = DIFF_IDS.length;
+  private static final float PADDING_RATIO = 0.016f;
+  private static final float TEXT_RATIO    = 0.025f;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void updatePane(ConfigActivity act, int objectOrdinal)
+    {
+
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  ConfigScreenPane(final ConfigActivity act, int objectOrdinal)
+    {
+    int height = act.getScreenHeightInPixels();
+    float textSize = height*TEXT_RATIO;
+    int padding = (int)(height*PADDING_RATIO);
+
+    LinearLayout configLayout = act.findViewById(R.id.configLayout);
+    configLayout.setPadding(padding,padding,padding,padding);
+
+    LinearLayout.LayoutParams paramsLayout = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.00f);
+    paramsLayout.bottomMargin = padding;
+    paramsLayout.topMargin    = 0;
+    paramsLayout.leftMargin   = padding;
+    paramsLayout.rightMargin  = padding;
+
+    configLayout.setLayoutParams(paramsLayout);
+
+    TextView textStickers = configLayout.findViewById(R.id.configTextStickers);
+    textStickers.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+    }
+}
diff --git a/src/main/java/org/distorted/config/ConfigSurfaceView.java b/src/main/java/org/distorted/config/ConfigSurfaceView.java
new file mode 100644
index 00000000..7783fbed
--- /dev/null
+++ b/src/main/java/org/distorted/config/ConfigSurfaceView.java
@@ -0,0 +1,150 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.config;
+
+import static org.distorted.objectlib.main.ObjectControl.MODE_REPLACE;
+
+import android.annotation.SuppressLint;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ConfigurationInfo;
+import android.opengl.GLES30;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObjectNode;
+import org.distorted.os.OSInterface;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class ConfigSurfaceView extends GLSurfaceView
+{
+    private ObjectControl mObjectController;
+    private ConfigRenderer mRenderer;
+    private OSInterface mInterface;
+    private boolean mCreated;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void setScreenSize(int width, int height)
+      {
+      mObjectController.setScreenSizeAndScaling(width,height, Math.min(width,height));
+      mObjectController.setObjectScale(1.00f);
+
+      if( !mCreated )
+        {
+        mCreated = true;
+        mObjectController.createNode(width,height);
+        TwistyObjectNode objectNode = mObjectController.getNode();
+        mRenderer.getScreen().attach(objectNode);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    ObjectControl getObjectControl()
+      {
+      return mObjectController;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    ConfigRenderer getRenderer()
+      {
+      return mRenderer;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void resetObject()
+      {
+      // TODO
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public ConfigSurfaceView(Context context, AttributeSet attrs)
+      {
+      super(context,attrs);
+
+      mCreated = false;
+
+      if(!isInEditMode())
+        {
+        ConfigActivity act = (ConfigActivity)context;
+        ConfigObjectLibInterface ref = new ConfigObjectLibInterface();
+        mInterface = new OSInterface(act,ref);
+        mObjectController = new ObjectControl(mInterface);
+        mObjectController.setRotateOnCreation(true);
+        mRenderer = new ConfigRenderer(this);
+
+        final ActivityManager activityManager= (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+
+        try
+          {
+          final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
+          int esVersion = configurationInfo.reqGlEsVersion>>16;
+          setEGLContextClientVersion(esVersion);
+          setRenderer(mRenderer);
+          }
+        catch(Exception ex)
+          {
+          act.OpenGLError();
+
+          String shading = GLES30.glGetString(GLES30.GL_SHADING_LANGUAGE_VERSION);
+          String version = GLES30.glGetString(GLES30.GL_VERSION);
+          String vendor  = GLES30.glGetString(GLES30.GL_VENDOR);
+          String renderer= GLES30.glGetString(GLES30.GL_RENDERER);
+
+          FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+          crashlytics.setCustomKey("GLSL Version"  , shading );
+          crashlytics.setCustomKey("GL version"    , version );
+          crashlytics.setCustomKey("GL Vendor "    , vendor  );
+          crashlytics.setCustomKey("GLSL renderer" , renderer);
+          crashlytics.recordException(ex);
+          }
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onPause()
+      {
+      super.onPause();
+      mObjectController.onPause();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onResume()
+      {
+      super.onResume();
+      mObjectController.onResume();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @SuppressLint("ClickableViewAccessibility")
+    @Override
+    public boolean onTouchEvent(MotionEvent event)
+      {
+      mInterface.setMotionEvent(event);
+      return mObjectController.onTouchEvent(MODE_REPLACE);
+      }
+}
+
diff --git a/src/main/java/org/distorted/info/InfoRenderer.java b/src/main/java/org/distorted/info/InfoRenderer.java
index 03d59d29..706504c8 100644
--- a/src/main/java/org/distorted/info/InfoRenderer.java
+++ b/src/main/java/org/distorted/info/InfoRenderer.java
@@ -89,7 +89,7 @@ public class InfoRenderer implements GLSurfaceView.Renderer, DistortedLibrary.Li
 
    public void distortedException(Exception ex)
      {
-     android.util.Log.e("Config", "unexpected exception: "+ex.getMessage() );
+     android.util.Log.e("Info", "unexpected exception: "+ex.getMessage() );
      }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -103,6 +103,6 @@ public class InfoRenderer implements GLSurfaceView.Renderer, DistortedLibrary.Li
 
    public void logMessage(String message)
       {
-      android.util.Log.e("Config", message );
+      android.util.Log.e("Info", message );
       }
 }
diff --git a/src/main/java/org/distorted/info/InfoScreen.java b/src/main/java/org/distorted/info/InfoScreen.java
index db3d85c8..785d5db9 100644
--- a/src/main/java/org/distorted/info/InfoScreen.java
+++ b/src/main/java/org/distorted/info/InfoScreen.java
@@ -9,87 +9,18 @@
 
 package org.distorted.info;
 
-import android.os.Build;
-import android.util.TypedValue;
-import android.view.Gravity;
 import android.view.View;
-import android.widget.GridLayout;
-import android.widget.ImageButton;
 import android.widget.LinearLayout;
-import android.widget.PopupWindow;
-import android.widget.ScrollView;
-import android.widget.TextView;
-
-import org.distorted.helpers.ObjectGridCreator;
 
 import org.distorted.helpers.TransparentImageButton;
-import org.distorted.main.MainActivity;
 import org.distorted.main.R;
-import org.distorted.objects.RubikObject;
-import org.distorted.objects.RubikObjectList;
-
-import static android.view.View.inflate;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 public class InfoScreen
 {
-  private static final float TEXT_SIZE = 0.0310f;
-  private static final float PADDING   = 0.0060f;
-  private static final float MARGIN    = 0.0024f;
-  private static final int[] mLocation = new int[2];
-  private static final float POPUP_W  = 0.8f;
-
-  private TransparentImageButton mBackButton, mObjectButton, mPrevButton, mNextButton;
-  private TextView mMovesText;
-  private PopupWindow mObjectPopup;
+  private TransparentImageButton mBackButton;
   private InfoScreenPane mPane;
-  private int mObjectOrdinal;
-  private float mButtonSize;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupObjectWindow(final InfoActivity act, final float width, final float height)
-    {
-    int numObjects= RubikObjectList.getNumObjects();
-    ScrollView view = (ScrollView)inflate( act, R.layout.popup_object_simple, null);
-    GridLayout objectGrid = view.findViewById(R.id.objectGrid);
-
-    int[] objects = new int[numObjects];
-    for(int i=0; i<numObjects; i++) objects[i] = i;
-
-    ObjectGridCreator creator = new ObjectGridCreator(width);
-    creator.createObjectGridClassic(objectGrid,act,objects);
-
-    for(int child=0; child<numObjects; child++)
-      {
-      final RubikObject obj = RubikObjectList.getObject(child);
-      View v = objectGrid.getChildAt(child);
-      ImageButton button = creator.getButton(obj,v);
-      final int ordinal = child;
-
-      button.setOnClickListener( new View.OnClickListener()
-        {
-        @Override
-        public void onClick(View v)
-          {
-          if( mObjectOrdinal!=ordinal )
-            {
-            mObjectOrdinal = ordinal;
-            act.changeObject(mObjectOrdinal);
-            mMovesText.setText(act.getString(R.string.mo_placeholder,mObjectOrdinal+1,numObjects));
-            mPane.updatePane(act,mObjectOrdinal);
-            }
-
-          mObjectPopup.dismiss();
-          }
-        });
-      }
-
-    mObjectPopup = new PopupWindow(act);
-    mObjectPopup.setFocusable(true);
-    mObjectPopup.setContentView(view);
-    }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -108,163 +39,11 @@ public class InfoScreen
       });
     }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupObjectButton(final InfoActivity act, final int width, final int height)
-    {
-    final int margin= (int)(height*MARGIN);
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
-    mObjectButton = new TransparentImageButton(act,R.drawable.ui_cube_menu,params);
-
-    mObjectButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View view)
-        {
-        if( mObjectPopup==null ) setupObjectWindow(act,width,height);
-        View popupView = mObjectPopup.getContentView();
-        popupView.setSystemUiVisibility(MainActivity.FLAGS);
-        displayPopup(act,view,mObjectPopup,width,height,margin,margin);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// work around lame bugs in Android's version <= 10 pop-up and split-screen modes
-
-  private void displayPopup(InfoActivity act, View view, PopupWindow window, int w, int h, int xoff, int yoff)
-    {
-    View topLayout = act.findViewById(R.id.mainLayout);
-    boolean isFullScreen;
-
-    if( topLayout!=null )
-      {
-      topLayout.getLocationOnScreen(mLocation);
-      isFullScreen = (mLocation[1]==0);
-      }
-    else
-      {
-      isFullScreen = true;
-      }
-
-    try
-      {
-      // if on Android 11 or we are fullscreen
-      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R || isFullScreen )
-        {
-        window.showAsDropDown(view, xoff, yoff, Gravity.CENTER);
-        window.update(view, w, h);
-        }
-      else  // Android 10 or below in pop-up mode or split-screen mode
-        {
-        view.getLocationOnScreen(mLocation);
-        int width  = view.getWidth();
-        int height = view.getHeight();
-        int x = mLocation[0]+(width-w)/2;
-        int y = mLocation[1]+height+yoff;
-
-        window.showAsDropDown(view);
-        window.update(x,y,w,h);
-        }
-      }
-    catch( IllegalArgumentException iae )
-      {
-      // ignore, this means window is 'not attached to window manager' -
-      // which most probably is because we are already exiting the app.
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void prevObject(InfoActivity act, int numObjects)
-    {
-    mObjectOrdinal--;
-    if( mObjectOrdinal<0 ) mObjectOrdinal=numObjects-1;
-
-    act.changeObject(mObjectOrdinal);
-    mPane.updatePane(act,mObjectOrdinal);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void nextObject(InfoActivity act, int numObjects)
-    {
-    mObjectOrdinal++;
-    if( mObjectOrdinal>=numObjects ) mObjectOrdinal=0;
-
-    act.changeObject(mObjectOrdinal);
-    mPane.updatePane(act,mObjectOrdinal);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupPrevButton(final InfoActivity act)
-    {
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
-    mPrevButton = new TransparentImageButton(act,R.drawable.ui_left,params);
-
-    mPrevButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        int numObjects = RubikObjectList.getNumObjects();
-        prevObject(act,numObjects);
-        mMovesText.setText(act.getString(R.string.mo_placeholder,mObjectOrdinal+1,numObjects));
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupNextButton(final InfoActivity act)
-    {
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
-    mNextButton = new TransparentImageButton(act,R.drawable.ui_right,params);
-
-    mNextButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        int numObjects = RubikObjectList.getNumObjects();
-        nextObject(act,numObjects);
-        mMovesText.setText(act.getString(R.string.mo_placeholder,mObjectOrdinal+1,numObjects));
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupTextView(final InfoActivity act, final float height, int numObjects)
-    {
-    int padding = (int)(height*PADDING);
-    int margin  = (int)(height*MARGIN);
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.MATCH_PARENT,2.0f);
-    params.topMargin    = margin;
-    params.bottomMargin = margin;
-    params.leftMargin   = margin;
-    params.rightMargin  = margin;
-
-    mMovesText = new TextView(act);
-    mMovesText.setTextSize(20);
-    mMovesText.setLayoutParams(params);
-    mMovesText.setPadding(padding,0,padding,0);
-    mMovesText.setGravity(Gravity.CENTER);
-    mMovesText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mButtonSize);
-    mMovesText.setText(act.getString(R.string.mo_placeholder,mObjectOrdinal+1,numObjects));
-    }
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   void onAttachedToWindow(final InfoActivity act, final int objectOrdinal)
     {
-    int numObjects = RubikObjectList.getNumObjects();
     int width = act.getScreenWidthInPixels();
-    int height= act.getScreenHeightInPixels();
-    int barHeight = act.getHeightBar();
-    mButtonSize = height*TEXT_SIZE;
-    mObjectOrdinal = objectOrdinal;
 
     LinearLayout.LayoutParams paramsL = new LinearLayout.LayoutParams(width/4, LinearLayout.LayoutParams.MATCH_PARENT);
     LinearLayout.LayoutParams paramsM = new LinearLayout.LayoutParams(width/2, LinearLayout.LayoutParams.MATCH_PARENT);
@@ -277,19 +56,7 @@ public class InfoScreen
     LinearLayout layoutRight= new LinearLayout(act);
     layoutRight.setLayoutParams(paramsR);
 
-    int popupW = (int)(POPUP_W*width);
-    int popupH = (int)(height-barHeight);
-
-    setupObjectButton(act,popupW,popupH);
-    setupPrevButton(act);
-    setupNextButton(act);
-    setupTextView(act,height,numObjects);
     setupBackButton(act);
-
-    layoutLeft.addView(mObjectButton);
-    layoutMid.addView(mPrevButton);
-    layoutMid.addView(mMovesText);
-    layoutMid.addView(mNextButton);
     layoutRight.addView(mBackButton);
 
     LinearLayout layout = act.findViewById(R.id.lowerBar);
@@ -298,6 +65,6 @@ public class InfoScreen
     layout.addView(layoutMid);
     layout.addView(layoutRight);
 
-    mPane = new InfoScreenPane(act,mObjectOrdinal);
+    mPane = new InfoScreenPane(act,objectOrdinal);
     }
 }
diff --git a/src/main/java/org/distorted/main/MainActivity.java b/src/main/java/org/distorted/main/MainActivity.java
index e2d2d538..e00aae6a 100644
--- a/src/main/java/org/distorted/main/MainActivity.java
+++ b/src/main/java/org/distorted/main/MainActivity.java
@@ -34,6 +34,7 @@ import com.google.firebase.analytics.FirebaseAnalytics;
 import com.google.firebase.inappmessaging.FirebaseInAppMessaging;
 
 import org.distorted.bandaged.BandagedActivity;
+import org.distorted.config.ConfigActivity;
 import org.distorted.info.InfoActivity;
 import org.distorted.dialogs.RubikDialogAbout;
 import org.distorted.dialogs.RubikDialogCreators;
@@ -402,6 +403,15 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
       startActivity(intent);
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchToConfig(int objectOrdinal)
+      {
+      Intent intent = new Intent(this, ConfigActivity.class);
+      intent.putExtra("obj", objectOrdinal);
+      startActivity(intent);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     public void switchToBandagedCreator(int objectOrdinal)
diff --git a/src/main/java/org/distorted/main/MainObjectPopup.java b/src/main/java/org/distorted/main/MainObjectPopup.java
index d614b1f6..db315b1f 100644
--- a/src/main/java/org/distorted/main/MainObjectPopup.java
+++ b/src/main/java/org/distorted/main/MainObjectPopup.java
@@ -64,6 +64,7 @@ public class MainObjectPopup
 
     RubikObject object = RubikObjectList.getObject(ordinal);
 
+    ///////// SOLVER //////////////////////////////////////////////////
     Button b1 = layout.findViewById(R.id.objectSolver);
 
     if( object!=null && object.hasSolver() )
@@ -87,6 +88,7 @@ public class MainObjectPopup
       }
     else b1.setVisibility(GONE);
 
+    ///////// PATTERN /////////////////////////////////////////////////
     Button b2 = layout.findViewById(R.id.objectPattern);
 
     if( object!=null && object.hasPatterns() )
@@ -110,6 +112,7 @@ public class MainObjectPopup
       }
     else b2.setVisibility(GONE);
 
+    ///////// TUTORIALS ///////////////////////////////////////////////
     Button b3 = layout.findViewById(R.id.objectTutorial);
 
     if( object!=null && object.hasExtras() )
@@ -133,11 +136,12 @@ public class MainObjectPopup
       }
     else b3.setVisibility(GONE);
 
+    ///////// INFO ////////////////////////////////////////////////////
     Button b4 = layout.findViewById(R.id.objectInfo);
 
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,levelHeight);
-    params.setMargins(marginH,firstButtonShown ? marginV : marginH,marginH,marginV);
-    b4.setLayoutParams(params);
+    LinearLayout.LayoutParams paramsInfo = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,levelHeight);
+    paramsInfo.setMargins(marginH,firstButtonShown ? marginV : marginH,marginH,marginV);
+    b4.setLayoutParams(paramsInfo);
     b4.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
 
     b4.setOnClickListener( new View.OnClickListener()
@@ -150,6 +154,25 @@ public class MainObjectPopup
         }
       });
 
+    ///////// CONFIG //////////////////////////////////////////////////
+    Button b5 = layout.findViewById(R.id.objectConfig);
+
+    LinearLayout.LayoutParams paramsConfig = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,levelHeight);
+    paramsConfig.setMargins(marginH,marginV,marginH,marginV);
+    b5.setLayoutParams(paramsConfig);
+    b5.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
+
+    b5.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        mPopup.dismiss();
+        act.switchToConfig(ordinal);
+        }
+      });
+
+    ///////// LEVELS //////////////////////////////////////////////////
     TextView levels = layout.findViewById(R.id.objectLevels);
     levels.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
 
diff --git a/src/main/res/layout/config.xml b/src/main/res/layout/config.xml
new file mode 100644
index 00000000..1282b385
--- /dev/null
+++ b/src/main/res/layout/config.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/mainLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/light_grey"
+    android:orientation="vertical">
+
+    <org.distorted.config.ConfigSurfaceView
+        android:id="@+id/configSurfaceView"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1.3"/>
+
+    <LinearLayout
+        android:id="@+id/configLayout"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1.0"
+        android:layout_marginLeft="5dp"
+        android:layout_marginRight="5dp"
+        android:layout_marginBottom="5dp"
+        android:paddingLeft="5dp"
+        android:paddingRight="5dp"
+        android:background="@color/grey"
+        android:orientation="vertical" >
+
+        <TextView
+            android:id="@+id/configTextStickers"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:textSize="26sp"
+            android:text="@string/config_name"/>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/lowerBar"
+        android:layout_width="match_parent"
+        android:layout_height="100dp"
+        android:layout_gravity="end"
+        android:orientation="horizontal"
+        android:background="@color/light_grey">
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/src/main/res/layout/object_popup.xml b/src/main/res/layout/object_popup.xml
index 03f2d123..529cced2 100644
--- a/src/main/res/layout/object_popup.xml
+++ b/src/main/res/layout/object_popup.xml
@@ -50,6 +50,17 @@
         android:backgroundTint="@color/dark_grey"
         android:gravity="center"/>
 
+    <Button
+        android:id="@+id/objectConfig"
+        android:text="@string/object_config"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingRight="10dp"
+        android:paddingLeft="10dp"
+        android:singleLine="true"
+        android:backgroundTint="@color/dark_grey"
+        android:gravity="center"/>
+
    <TextView
        android:id="@+id/objectLevels"
        android:layout_width="match_parent"
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index fe62613a..ff5de134 100755
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -60,6 +60,7 @@
     <string name="object_pattern">Muster</string>
     <string name="object_tutorial">Tutorials</string>
     <string name="object_info">Info</string>
+    <string name="object_config">Konfig</string>
 
     <string name="stars">Sterne</string>
     <string name="scores">Highscores</string>
diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml
index 3215543c..ce13fd78 100755
--- a/src/main/res/values-es/strings.xml
+++ b/src/main/res/values-es/strings.xml
@@ -60,6 +60,7 @@
     <string name="object_pattern">Patrones</string>
     <string name="object_tutorial">Tutoriales</string>
     <string name="object_info">Información</string>
+    <string name="object_config">Config</string>
 
     <string name="stars">Estrellas</string>
     <string name="scores">Leaderboard</string>
diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml
index 5997645d..b9db7731 100755
--- a/src/main/res/values-fr/strings.xml
+++ b/src/main/res/values-fr/strings.xml
@@ -60,6 +60,7 @@
     <string name="object_pattern">Motifs</string>
     <string name="object_tutorial">Tutoriels</string>
     <string name="object_info">Info</string>
+    <string name="object_config">Config</string>
 
     <string name="stars">Étoiles</string>
     <string name="scores">Meilleurs scores</string>
diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml
index bfe05c07..05743578 100755
--- a/src/main/res/values-ja/strings.xml
+++ b/src/main/res/values-ja/strings.xml
@@ -60,6 +60,7 @@
     <string name="object_pattern">パターン</string>
     <string name="object_tutorial">チュートリアル</string>
     <string name="object_info">情報</string>
+    <string name="object_config">構成</string>
 
     <string name="stars">星</string>
     <string name="scores">ハイスコア</string>
diff --git a/src/main/res/values-ko/strings.xml b/src/main/res/values-ko/strings.xml
index ce6384c5..12e12e21 100755
--- a/src/main/res/values-ko/strings.xml
+++ b/src/main/res/values-ko/strings.xml
@@ -60,6 +60,7 @@
     <string name="object_pattern">패턴</string>
     <string name="object_tutorial">튜토리얼</string>
     <string name="object_info">정보</string>
+    <string name="object_config">구성</string>
 
     <string name="stars">별</string>
     <string name="scores">고득점</string>
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index f6e5cc51..1a2e273a 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -60,6 +60,7 @@
     <string name="object_pattern">Wzory</string>
     <string name="object_tutorial">Tutoriale</string>
     <string name="object_info">Info</string>
+    <string name="object_config">Konfig</string>
 
     <string name="stars">Gwiazdki</string>
     <string name="scores">Lista najlepszych</string>
diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml
index f388a612..e20e4add 100755
--- a/src/main/res/values-ru/strings.xml
+++ b/src/main/res/values-ru/strings.xml
@@ -60,6 +60,7 @@
     <string name="object_pattern">Узоры</string>
     <string name="object_tutorial">Учебники</string>
     <string name="object_info">Инфо</string>
+    <string name="object_config">Конфиг</string>
 
     <string name="stars">Звезды</string>
     <string name="scores">Высокие баллы</string>
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index 1d14705d..3e6098c6 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -60,6 +60,7 @@
     <string name="object_pattern">图案</string>
     <string name="object_tutorial">教程</string>
     <string name="object_info">信息</string>
+    <string name="object_config">配置</string>
 
     <string name="stars">星星</string>
     <string name="scores">高分</string>
diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml
index d6e2784d..efec07c5 100644
--- a/src/main/res/values-zh-rTW/strings.xml
+++ b/src/main/res/values-zh-rTW/strings.xml
@@ -60,6 +60,7 @@
     <string name="object_pattern">圖案</string>
     <string name="object_tutorial">教學</string>
     <string name="object_info">資訊</string>
+    <string name="object_config">配置</string>
 
     <string name="stars">星星</string>
     <string name="scores">高分</string>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 309e46c7..5bdc8d5f 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -77,6 +77,7 @@
     <string name="object_pattern">Patterns</string>
     <string name="object_tutorial">Tutorials</string>
     <string name="object_info">Info</string>
+    <string name="object_config">Config</string>
 
     <string name="level0" translatable="false">0</string>
     <string name="level1" translatable="false">1</string>
