commit 7ee8337bc2f6de2fe0629b2f9fb1243d3647d9f6
Author: leszek <leszek@koltunski.pl>
Date:   Mon Nov 13 16:37:25 2023 +0100

    Progress with PlayActivity

diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index e0604685..5fc75c60 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -32,11 +32,11 @@
 
         <activity android:name="org.distorted.tutorials.TutorialActivity" android:exported="false" android:screenOrientation="portrait"/>
         <activity android:name="org.distorted.config.ConfigActivity" android:exported="false" android:screenOrientation="portrait"/>
-        <activity android:name="org.distorted.bandaged.BandagedCreatorActivity" android:exported="false" android:screenOrientation="portrait"/>
-        <activity android:name="org.distorted.bandaged.BandagedPlayActivity" android:exported="false" android:screenOrientation="portrait"/>
+        <activity android:name="org.distorted.bandaged.BandagedActivity" android:exported="false" android:screenOrientation="portrait"/>
         <activity android:name="org.distorted.purchase.PurchaseActivity" android:exported="false" android:screenOrientation="portrait"/>
         <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"/>
 
         <service
             android:name="org.distorted.messaging.RubikMessagingService"
diff --git a/src/main/java/org/distorted/bandaged/BandagedActivity.java b/src/main/java/org/distorted/bandaged/BandagedActivity.java
new file mode 100644
index 00000000..8840c959
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedActivity.java
@@ -0,0 +1,350 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 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.bandaged;
+
+import static android.view.View.LAYOUT_DIRECTION_RTL;
+
+import java.io.InputStream;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+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 androidx.preference.PreferenceManager;
+
+import org.distorted.dialogs.RubikDialogError;
+import org.distorted.external.RubikFiles;
+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.TwistyJson;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.os.OSInterface;
+import org.distorted.playui.PlayActivity;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedActivity extends AppCompatActivity
+{
+    private static final int ACTIVITY_NUMBER    = 3;
+    private static final float RATIO_BAR        = MainActivity.RATIO_BAR;
+    private static final float RATIO_BUT        = 0.07f;
+    static final float RATIO_SCROLL             = 0.30f;
+    public static final float SPINNER_TEXT_SIZE = 0.03f;
+    private static final float PADDING          = 0.010f;
+    public static final int FLAGS               = MainActivity.FLAGS;
+    private static final int NUM_SCRAMBLES      = 300;
+
+    private static int mScreenWidth, mScreenHeight;
+    private int mCurrentApiVersion;
+    private BandagedScreen mScreen;
+    private boolean mRTL;
+    private int mObjectOrdinal;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected void onCreate(Bundle savedState)
+      {
+      super.onCreate(savedState);
+
+      Bundle b = getIntent().getExtras();
+      mObjectOrdinal = (b != null) ? b.getInt("obj") : 0;
+
+      DistortedLibrary.onCreate(ACTIVITY_NUMBER);
+      setTheme(R.style.MaterialThemeNoActionBar);
+      setContentView(R.layout.bandaged);
+
+      DisplayMetrics displaymetrics = new DisplayMetrics();
+      getWindowManager().getDefaultDisplay().getRealMetrics(displaymetrics);
+      mScreenWidth =displaymetrics.widthPixels;
+      mScreenHeight=displaymetrics.heightPixels;
+
+      final Configuration config = getResources().getConfiguration();
+      final int layoutDirection = config.getLayoutDirection();
+      mRTL = layoutDirection==LAYOUT_DIRECTION_RTL;
+
+      hideNavigationBar();
+      cutoutHack();
+      computeHeights();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// this does not include possible insets
+
+    private void computeHeights()
+      {
+      int barHeight    = (int)(mScreenHeight*RATIO_BAR);
+      int butHeight    = (int)(mScreenHeight*RATIO_BUT);
+      int viewHeight   = (int)(mScreenHeight*RATIO_SCROLL);
+      int objectHeight = (int)(mScreenHeight*(1-RATIO_SCROLL+RATIO_BAR));
+      int padding      = (int)(mScreenHeight*PADDING);
+
+      LinearLayout botLayout = findViewById(R.id.lowerBar);
+      ViewGroup.LayoutParams paramsL = botLayout.getLayoutParams();
+      paramsL.height = barHeight;
+      botLayout.setLayoutParams(paramsL);
+
+      LinearLayout butLayout = findViewById(R.id.bandagedCreatorButtons);
+      ViewGroup.LayoutParams paramsB = butLayout.getLayoutParams();
+      paramsB.height = butHeight;
+      butLayout.setPadding(padding,0,padding,padding);
+      butLayout.setLayoutParams(paramsB);
+
+      LinearLayout topLayout = findViewById(R.id.bandagedCreatorTopView);
+      ViewGroup.LayoutParams paramsT = topLayout.getLayoutParams();
+      paramsT.height = viewHeight;
+      topLayout.setPadding(padding,padding,padding,padding);
+      topLayout.setLayoutParams(paramsT);
+
+      BandagedView creator = findViewById(R.id.bandagedCreatorObjectView);
+      ViewGroup.LayoutParams paramsC = creator.getLayoutParams();
+      paramsC.height = objectHeight;
+      creator.setLayoutParams(paramsC);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    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();
+      BandagedView view = findViewById(R.id.bandagedCreatorObjectView);
+      view.onPause();
+      DistortedLibrary.onPause(ACTIVITY_NUMBER);
+      savePreferences();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onResume() 
+      {
+      super.onResume();
+
+      DistortedLibrary.onResume(ACTIVITY_NUMBER);
+      BandagedView view = findViewById(R.id.bandagedCreatorObjectView);
+      view.onResume();
+
+      if( mScreen==null )
+        {
+        int ordinal = getObjectOrdinal();
+        mScreen = new BandagedScreen(ordinal);
+        }
+
+      mScreen.onAttachedToWindow(this);
+
+      restorePreferences();
+      BandagedWorkerThread.create(this);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onDestroy() 
+      {
+      super.onDestroy();
+      DistortedLibrary.onDestroy(ACTIVITY_NUMBER);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void OpenGLError()
+      {
+      RubikDialogError errDiag = new RubikDialogError();
+      errDiag.show(getSupportFragmentManager(), null);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void savePreferences()
+      {
+      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+      SharedPreferences.Editor editor = preferences.edit();
+      mScreen.savePreferences(editor);
+      editor.apply();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void restorePreferences()
+      {
+      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+      mScreen.restorePreferences(this,preferences);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void changeObject(int x, int y, int z)
+      {
+      BandagedRenderer renderer = getRenderer();
+      renderer.changeObject(x,y,z);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public boolean isRTL()
+      {
+      return mRTL;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public BandagedRenderer getRenderer()
+      {
+      BandagedView view = findViewById(R.id.bandagedCreatorObjectView);
+      return view.getRenderer();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public boolean objectDoesntExist(String name)
+      {
+      return mScreen.objectDoesntExist(name);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void addObject(String name)
+      {
+      mScreen.addObject(this,name);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void deleteObject(String name)
+      {
+      RubikFiles files = RubikFiles.getInstance();
+      InputStream jsonStream = files.openFile(this,name+"_object.json");
+      InitAssets assets = new InitAssets(jsonStream,null,null);
+
+      if( !assets.noJsonStream() )
+        {
+        TwistyObject object = new TwistyJson( TwistyObject.MODE_NORM, null, null, 1.0f, assets);
+
+        if( !object.getError() )
+          {
+          SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+          SharedPreferences.Editor editor = preferences.edit();
+          OSInterface os = new OSInterface(this,null);
+          os.setEditor(editor);
+          object.removePreferences(os);
+          editor.apply();
+          }
+        }
+
+      mScreen.deleteObject(this,name);
+      files.deleteIcon(this,name);
+      files.deleteJsonObject(this,name);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void playObject(String name)
+      {
+      Intent intent = new Intent(this, PlayActivity.class);
+      intent.putExtra("name", name);
+      intent.putExtra("scrambles", NUM_SCRAMBLES);
+      intent.putExtra("local", true);
+      intent.putExtra("ordinal", 0);
+      intent.putExtra("free", true);
+      startActivity(intent);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void iconCreationDone(Bitmap bmp)
+      {
+      mScreen.iconCreationDone(this,bmp);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenWidthInPixels()
+      {
+      return mScreenWidth;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenHeightInPixels()
+      {
+      return mScreenHeight;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getObjectOrdinal()
+      {
+      return mObjectOrdinal;
+      }
+}
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorActivity.java b/src/main/java/org/distorted/bandaged/BandagedCreatorActivity.java
deleted file mode 100644
index d9203705..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorActivity.java
+++ /dev/null
@@ -1,344 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.bandaged;
-
-import static android.view.View.LAYOUT_DIRECTION_RTL;
-
-import java.io.InputStream;
-
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.res.Configuration;
-import android.graphics.Bitmap;
-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 androidx.preference.PreferenceManager;
-
-import org.distorted.dialogs.RubikDialogError;
-import org.distorted.external.RubikFiles;
-import org.distorted.library.main.DistortedLibrary;
-import org.distorted.main.MainActivity;
-import org.distorted.main.R;
-import org.distorted.main_old.RubikActivity;
-import org.distorted.objectlib.main.InitAssets;
-import org.distorted.objectlib.main.TwistyJson;
-import org.distorted.objectlib.main.TwistyObject;
-import org.distorted.os.OSInterface;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class BandagedCreatorActivity extends AppCompatActivity
-{
-    private static final int ACTIVITY_NUMBER    = 3;
-    private static final float RATIO_BAR        = MainActivity.RATIO_BAR;
-    private static final float RATIO_BUT        = 0.07f;
-    static final float RATIO_SCROLL             = 0.30f;
-    public static final float SPINNER_TEXT_SIZE = 0.03f;
-    public static final int FLAGS               = RubikActivity.FLAGS;
-
-    private static int mScreenWidth, mScreenHeight;
-    private int mCurrentApiVersion;
-    private BandagedCreatorScreen mScreen;
-    private boolean mRTL;
-    private int mObjectOrdinal;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    protected void onCreate(Bundle savedState)
-      {
-      super.onCreate(savedState);
-
-      Bundle b = getIntent().getExtras();
-      mObjectOrdinal = (b != null) ? b.getInt("obj") : 0;
-
-      DistortedLibrary.onCreate(ACTIVITY_NUMBER);
-      setTheme(R.style.MaterialThemeNoActionBar);
-      setContentView(R.layout.bandaged);
-
-      DisplayMetrics displaymetrics = new DisplayMetrics();
-      getWindowManager().getDefaultDisplay().getRealMetrics(displaymetrics);
-      mScreenWidth =displaymetrics.widthPixels;
-      mScreenHeight=displaymetrics.heightPixels;
-
-      final Configuration config = getResources().getConfiguration();
-      final int layoutDirection = config.getLayoutDirection();
-      mRTL = layoutDirection==LAYOUT_DIRECTION_RTL;
-
-      hideNavigationBar();
-      cutoutHack();
-      computeHeights();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// this does not include possible insets
-
-    private void computeHeights()
-      {
-      int barHeight    = (int)(mScreenHeight*RATIO_BAR);
-      int butHeight    = (int)(mScreenHeight*RATIO_BUT);
-      int viewHeight   = (int)(mScreenHeight*RATIO_SCROLL);
-      int objectHeight = (int)(mScreenHeight*(1-RATIO_SCROLL+RATIO_BAR));
-      int padding      = (int)(mScreenHeight*RubikActivity.PADDING);
-
-      LinearLayout botLayout = findViewById(R.id.lowerBar);
-      ViewGroup.LayoutParams paramsL = botLayout.getLayoutParams();
-      paramsL.height = barHeight;
-      botLayout.setLayoutParams(paramsL);
-
-      LinearLayout butLayout = findViewById(R.id.bandagedCreatorButtons);
-      ViewGroup.LayoutParams paramsB = butLayout.getLayoutParams();
-      paramsB.height = butHeight;
-      butLayout.setPadding(padding,0,padding,padding);
-      butLayout.setLayoutParams(paramsB);
-
-      LinearLayout topLayout = findViewById(R.id.bandagedCreatorTopView);
-      ViewGroup.LayoutParams paramsT = topLayout.getLayoutParams();
-      paramsT.height = viewHeight;
-      topLayout.setPadding(padding,padding,padding,padding);
-      topLayout.setLayoutParams(paramsT);
-
-      BandagedCreatorView creator = findViewById(R.id.bandagedCreatorObjectView);
-      ViewGroup.LayoutParams paramsC = creator.getLayoutParams();
-      paramsC.height = objectHeight;
-      creator.setLayoutParams(paramsC);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    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();
-      BandagedCreatorView view = findViewById(R.id.bandagedCreatorObjectView);
-      view.onPause();
-      DistortedLibrary.onPause(ACTIVITY_NUMBER);
-      savePreferences();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onResume() 
-      {
-      super.onResume();
-
-      DistortedLibrary.onResume(ACTIVITY_NUMBER);
-      BandagedCreatorView view = findViewById(R.id.bandagedCreatorObjectView);
-      view.onResume();
-
-      if( mScreen==null )
-        {
-        int ordinal = getObjectOrdinal();
-        mScreen = new BandagedCreatorScreen(ordinal);
-        }
-
-      mScreen.onAttachedToWindow(this);
-
-      restorePreferences();
-      BandagedCreatorWorkerThread.create(this);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onDestroy() 
-      {
-      super.onDestroy();
-      DistortedLibrary.onDestroy(ACTIVITY_NUMBER);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void OpenGLError()
-      {
-      RubikDialogError errDiag = new RubikDialogError();
-      errDiag.show(getSupportFragmentManager(), null);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void savePreferences()
-      {
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      SharedPreferences.Editor editor = preferences.edit();
-      mScreen.savePreferences(editor);
-      editor.apply();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void restorePreferences()
-      {
-      SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-      mScreen.restorePreferences(this,preferences);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void changeObject(int x, int y, int z)
-      {
-      BandagedCreatorRenderer renderer = getRenderer();
-      renderer.changeObject(x,y,z);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public boolean isRTL()
-      {
-      return mRTL;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public BandagedCreatorRenderer getRenderer()
-      {
-      BandagedCreatorView view = findViewById(R.id.bandagedCreatorObjectView);
-      return view.getRenderer();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public boolean objectDoesntExist(String name)
-      {
-      return mScreen.objectDoesntExist(name);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void addObject(String name)
-      {
-      mScreen.addObject(this,name);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void deleteObject(String name)
-      {
-      RubikFiles files = RubikFiles.getInstance();
-      InputStream jsonStream = files.openFile(this,name+"_object.json");
-      InitAssets assets = new InitAssets(jsonStream,null,null);
-
-      if( !assets.noJsonStream() )
-        {
-        TwistyObject object = new TwistyJson( TwistyObject.MODE_NORM, null, null, 1.0f, assets);
-
-        if( !object.getError() )
-          {
-          SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-          SharedPreferences.Editor editor = preferences.edit();
-          OSInterface os = new OSInterface(this,null);
-          os.setEditor(editor);
-          object.removePreferences(os);
-          editor.apply();
-          }
-        }
-
-      mScreen.deleteObject(this,name);
-      files.deleteIcon(this,name);
-      files.deleteJsonObject(this,name);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void playObject(String name)
-      {
-      Intent intent = new Intent(this, BandagedPlayActivity.class);
-      intent.putExtra("name", name);
-      startActivity(intent);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void iconCreationDone(Bitmap bmp)
-      {
-      mScreen.iconCreationDone(this,bmp);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getScreenWidthInPixels()
-      {
-      return mScreenWidth;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getScreenHeightInPixels()
-      {
-      return mScreenHeight;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getObjectOrdinal()
-      {
-      return mObjectOrdinal;
-      }
-}
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorObjectView.java b/src/main/java/org/distorted/bandaged/BandagedCreatorObjectView.java
deleted file mode 100644
index 11b98323..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorObjectView.java
+++ /dev/null
@@ -1,120 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.bandaged;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import org.distorted.dialogs.RubikDialogBandagedDelete;
-import org.distorted.helpers.TransparentButton;
-import org.distorted.helpers.TransparentImageButton;
-import org.distorted.main.R;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class BandagedCreatorObjectView
-{
-  private static final float MARGIN    = 0.015f;
-  private static final float TEXT_SIZE = 0.032f;
-  private static final float RATIO_ICON= 0.15f;
-
-  private final LinearLayout mPane;
-  private final String mName;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public BandagedCreatorObjectView(final BandagedCreatorActivity act, String name, boolean leftmost)
-    {
-    mName = name;
-    LayoutInflater inflater = act.getLayoutInflater();
-    mPane = (LinearLayout) inflater.inflate(R.layout.bandaged_pane, null);
-
-    int height   = act.getScreenHeightInPixels();
-    int textSize = (int)(height*TEXT_SIZE);
-
-    LinearLayout.LayoutParams params = createPaneParams(height,leftmost,act.isRTL());
-    mPane.setLayoutParams(params);
-
-    LinearLayout bottom = mPane.findViewById(R.id.bandagedCreatorObjectLayout);
-
-    TransparentButton plaButton = new TransparentButton(act, R.string.play, textSize);
-    plaButton.setSingleLine();
-
-    final int icon = R.drawable.ui_trash;
-    LinearLayout.LayoutParams paramsB = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,3.0f);
-    TransparentImageButton delButton = new TransparentImageButton(act,icon,paramsB);
-
-    bottom.addView(plaButton);
-    bottom.addView(delButton);
-
-    plaButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        act.playObject(mName);
-        }
-      });
-
-    delButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        Bundle bundle = new Bundle();
-        bundle.putString("argument", mName );
-        RubikDialogBandagedDelete dialog = new RubikDialogBandagedDelete();
-        dialog.setArguments(bundle);
-        dialog.show( act.getSupportFragmentManager(), RubikDialogBandagedDelete.getDialogTag() );
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static LinearLayout.LayoutParams createPaneParams(int height, boolean leftmost, boolean rtl)
-    {
-    int margin = (int)(height*MARGIN);
-    int length = (int)(height*BandagedCreatorActivity.RATIO_SCROLL*0.65f);
-
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( length, LinearLayout.LayoutParams.MATCH_PARENT);
-    params.bottomMargin = margin;
-    params.topMargin    = margin;
-
-    if( !rtl )
-      {
-      params.leftMargin   = leftmost ? margin : 0;
-      params.rightMargin  = margin;
-      }
-    else
-      {
-      params.rightMargin  = leftmost ? margin : 0;
-      params.leftMargin   = margin;
-      }
-
-    return params;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public LinearLayout getPane()
-    {
-    return mPane;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public String getName()
-    {
-    return mName;
-    }
-}
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorRenderer.java b/src/main/java/org/distorted/bandaged/BandagedCreatorRenderer.java
deleted file mode 100644
index 45bc29f5..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorRenderer.java
+++ /dev/null
@@ -1,534 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.bandaged;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.opengles.GL10;
-
-import android.app.Activity;
-import android.content.res.Resources;
-import android.opengl.GLES31;
-import android.opengl.GLSurfaceView;
-import android.widget.Toast;
-
-import org.distorted.dialogs.RubikDialogBandagedSave;
-import org.distorted.library.effect.EffectType;
-import org.distorted.library.effect.FragmentEffectBrightness;
-import org.distorted.library.effect.PostprocessEffectBorder;
-import org.distorted.library.main.DistortedEffects;
-import org.distorted.library.main.DistortedFramebuffer;
-import org.distorted.library.main.DistortedLibrary;
-import org.distorted.library.main.DistortedNode;
-import org.distorted.library.main.DistortedScreen;
-import org.distorted.library.main.InternalOutputSurface;
-import org.distorted.library.mesh.MeshBase;
-import org.distorted.library.type.Static1D;
-import org.distorted.library.type.Static3D;
-import org.distorted.library.type.Static4D;
-import org.distorted.objectlib.bandaged.BandagedObject;
-import org.distorted.objectlib.bandaged.LocallyBandagedList;
-import org.distorted.objectlib.json.JsonWriter;
-import org.distorted.objectlib.main.ObjectControl;
-import org.distorted.objectlib.main.TwistyObject;
-
-import org.json.JSONException;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class BandagedCreatorRenderer implements GLSurfaceView.Renderer, DistortedLibrary.LibraryUser
-{
-   public static final float BRIGHTNESS = 0.333f;
-   private static final int RESET_DURATION = 1000;
-   private static final float MAX_SIZE_CHANGE = 1.70f;
-   private static final float MIN_SIZE_CHANGE = 0.50f;
-
-   private final BandagedCreatorView mView;
-   private final Resources mResources;
-   private final DistortedScreen mScreen;
-   private final Static3D mScale;
-   private final Static4D mQuatT, mQuatA;
-   private final BandagedObject mObject;
-   private final float mInitRatio;
-
-   private boolean mInitialPhase;
-   private long mStartTime;
-   private float mQuatX, mQuatY, mQuatZ, mQuatW;
-   private boolean mResetQuats, mSetQuatT, mResettingObject, mConnectingCubits, mCreatingCubits, mRescaling;
-   private int mIndex1, mIndex2;
-   private int mSaveIcon;
-   private DistortedFramebuffer mFramebuffer;
-   private String mPath;
-   private boolean mCubitsCreated;
-   private int mWidth, mHeight;
-   private float mScaleValue, mObjectScreenRatio;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   BandagedCreatorRenderer(BandagedCreatorView v, int ordinal)
-     {
-     mView = v;
-     mResources = v.getResources();
-
-     mQuatT = new Static4D(0,0,0,1);
-     mQuatA = new Static4D(-0.25189602f,0.3546389f,0.009657208f,0.90038127f);
-
-     mResetQuats       = false;
-     mSetQuatT         = false;
-     mResettingObject  = false;
-     mConnectingCubits = false;
-     mCubitsCreated    = false;
-     mCreatingCubits   = false;
-     mRescaling        = false;
-
-     mSaveIcon = -1;
-
-     mScreen = new DistortedScreen();
-     mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
-     mScale = new Static3D(1,1,1);
-     mObject= LocallyBandagedList.create(ordinal,mScreen);
-
-     mInitRatio = mObject.getScreenRatio();
-     mObjectScreenRatio= mInitRatio;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   @Override
-   public void onDrawFrame(GL10 glUnused)
-     {
-     long time = System.currentTimeMillis();
-     mScreen.render(time);
-
-     if( mSetQuatT )
-       {
-       mSetQuatT = false;
-       mQuatT.set(mQuatX,mQuatY,mQuatZ,mQuatW);
-       }
-
-     if( mResetQuats )
-       {
-       mResetQuats = false;
-
-       float qx = mQuatT.get0();
-       float qy = mQuatT.get1();
-       float qz = mQuatT.get2();
-       float qw = mQuatT.get3();
-
-       float rx = mQuatA.get0();
-       float ry = mQuatA.get1();
-       float rz = mQuatA.get2();
-       float rw = mQuatA.get3();
-
-       float tx = rw*qx - rz*qy + ry*qz + rx*qw;
-       float ty = rw*qy + rz*qx + ry*qw - rx*qz;
-       float tz = rw*qz + rz*qw - ry*qx + rx*qy;
-       float tw = rw*qw - rz*qz - ry*qy - rx*qx;
-
-       mQuatT.set(0f, 0f, 0f, 1f);
-       mQuatA.set(tx, ty, tz, tw);
-       }
-
-     if( mResettingObject )
-       {
-       boolean done = continueResetting(time);
-       if( done ) mResettingObject = false;
-       }
-
-     if( mSaveIcon>=0 )
-       {
-       renderIcon(time); // for some reason we need to call render() twice here, otherwise the
-       mSaveIcon++;      // icon turns out black. Probably some problem with binding the texture.
-       }
-     if( mSaveIcon>=2 )
-       {
-       saveIcon();
-       mSaveIcon = -1;
-       }
-
-     if( mConnectingCubits )
-       {
-       mObject.tryConnectingCubits(mIndex1,mIndex2,mScaleValue);
-       mConnectingCubits = false;
-       }
-
-     if( mCreatingCubits )
-       {
-       rescaleObject();
-
-       if( mCubitsCreated )
-         {
-         mObject.createCubits(mQuatT,mQuatA,mScale);
-         float[] dist = mObject.getDist3D();
-         BandagedCreatorTouchControl control = mView.getTouchControl();
-         control.setDist3D(dist);
-         mScreen.detachAll();
-         mView.resetCubits();
-         mObject.attachCubits(mScaleValue);
-         }
-
-       mCreatingCubits = false;
-       }
-
-     if( mRescaling )
-       {
-       rescaleObject();
-       mObject.scaleCubits(mScaleValue);
-       BandagedCreatorTouchControl control = mView.getTouchControl();
-       control.setObjectRatio(mObjectScreenRatio);
-       mRescaling = false;
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   @Override
-   public void onSurfaceChanged(GL10 glUnused, int width, int height)
-      {
-      if( width!=mWidth || height!=mHeight )
-        {
-        mWidth = width;
-        mHeight= height;
-        rescaleObject();
-        mScreen.detachAll();
-        int touched = mView.getTouched();
-        mObject.attachAndMarkCubits(mScaleValue,touched);
-        mView.setScreenSize(width,height);
-        mScreen.resize(width,height);
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   @Override
-   public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
-      {
-      DistortedLibrary.setMax(EffectType.VERTEX,0);
-      MeshBase.setMaxEffComponents(ObjectControl.MAX_MOVING_PARTS);
-      FragmentEffectBrightness.enable();
-      DistortedLibrary.onSurfaceCreated(this,1);
-      DistortedLibrary.setCull(true);
-      mObject.recreateCubits(mQuatT,mQuatA,mScale);
-      mCubitsCreated = true;
-      mWidth = 0;
-      mHeight= 0;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public DistortedScreen getScreen()
-     {
-     return mScreen;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void setConnecting(int index1, int index2)
-     {
-     mIndex1 = index1;
-     mIndex2 = index2;
-     mConnectingCubits = true;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public Static4D getQuatAccu()
-     {
-     return mQuatA;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void setQuatTemp(float x, float y, float z, float w)
-     {
-     mSetQuatT = false;
-
-     mQuatX = x;
-     mQuatY = y;
-     mQuatZ = z;
-     mQuatW = w;
-
-     mSetQuatT = true;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void resetQuats()
-     {
-     mResetQuats = true;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public boolean isBusy()
-     {
-     return (mResettingObject || mCreatingCubits || mConnectingCubits);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void saveObject()
-     {
-     TwistyObject obj = mObject.createObject(TwistyObject.MODE_NORM, 1.0f );
-     String name = obj.getShortName();
-     BandagedCreatorActivity act = (BandagedCreatorActivity) mView.getContext();
-
-     if( act.objectDoesntExist(name) && createObjectJson(obj,act) )
-       {
-       setupIconCreation(act);
-       act.addObject(obj.getShortName());
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private boolean createObjectJson(TwistyObject object, Activity act)
-     {
-     final String name = object.getShortName()+"_object.json";
-     File file = new File(act.getFilesDir(), name);
-     String filename = file.getAbsolutePath();
-
-     try
-       {
-       JsonWriter writer = JsonWriter.getInstance();
-       String json = writer.createObjectString(object,24,0);
-       writer.write(filename,json);
-       return true;
-       }
-     catch(JSONException ex)
-       {
-       act.runOnUiThread(new Runnable()
-         {
-         public void run()
-           {
-           String message = "JSON Exception saving to \n\n"+filename+"\n\n failed:\n\n"+ex.getMessage();
-           Toast.makeText(act,message,Toast.LENGTH_LONG).show();
-           }
-         });
-
-       return false;
-       }
-     catch(FileNotFoundException ex)
-       {
-       act.runOnUiThread(new Runnable()
-         {
-         public void run()
-           {
-           String message = "FileNotFound exception saving to \n\n"+filename+"\n\n failed:\n\n"+ex.getMessage();
-           Toast.makeText(act,message,Toast.LENGTH_LONG).show();
-           }
-         });
-
-       return false;
-       }
-     catch(IOException ex)
-       {
-       act.runOnUiThread(new Runnable()
-         {
-         public void run()
-           {
-           String message = "IO exception saving to \n\n"+filename+"\n\n failed:\n\n"+ex.getMessage();
-           Toast.makeText(act,message,Toast.LENGTH_LONG).show();
-           }
-         });
-
-       return false;
-       }
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void setupIconCreation(Activity act)
-     {
-     final float R=1.0f;
-     final int FBO_WIDTH  = (int)(R*720);
-     final int FBO_HEIGHT = (int)(R*1280);
-     final float OBJECT_SIZE = R*0.60f;
-
-     TwistyObject obj = mObject.createObject(TwistyObject.MODE_ICON, OBJECT_SIZE );
-     DistortedEffects effects = obj.getObjectEffects();
-     DistortedNode node = obj.getNode();
-
-     if( mFramebuffer==null )
-       {
-       mFramebuffer = new DistortedFramebuffer(FBO_WIDTH,FBO_HEIGHT,1, InternalOutputSurface.DEPTH_NO_STENCIL);
-       mFramebuffer.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
-       }
-
-     mFramebuffer.setProjection( mObject.computeProjectionAngle() ,0.1f);
-     mFramebuffer.detachAll();
-     mFramebuffer.attach(node);
-
-     Static1D halo = new Static1D(5);
-     Static4D color = new Static4D(0,0,0,1);
-     PostprocessEffectBorder border = new PostprocessEffectBorder(halo,color);
-     border.setHaloDepth(false);
-     effects.apply(border);
-
-     final String name = obj.getShortName()+".png";
-     File file = new File(act.getFilesDir(), name);
-     String filename = file.getAbsolutePath();
-
-     mSaveIcon = 0;
-     mPath = filename;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void renderIcon(long time)
-     {
-     mFramebuffer.render(time);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void saveIcon()
-     {
-     int fW = mFramebuffer.getWidth();
-     int fH = mFramebuffer.getHeight();
-
-     ByteBuffer buf = ByteBuffer.allocateDirect(fW*fH*4);
-     buf.order(ByteOrder.LITTLE_ENDIAN);
-
-     mFramebuffer.setAsReadFramebuffer(0);
-     GLES31.glReadBuffer(GLES31.GL_COLOR_ATTACHMENT0);
-     GLES31.glReadPixels( 0, 0, fW, fH, GLES31.GL_RGBA, GLES31.GL_UNSIGNED_BYTE, buf);
-     BandagedCreatorWorkerThread.newBuffer(buf,fW,fH,6,mPath);
-     GLES31.glBindFramebuffer(GLES31.GL_READ_FRAMEBUFFER, 0);
-
-     mSaveIcon = -1;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   void mulObjectRatio(float ratio)
-     {
-     mObjectScreenRatio *= ratio;
-
-     if( mObjectScreenRatio>MAX_SIZE_CHANGE*mInitRatio) mObjectScreenRatio = MAX_SIZE_CHANGE*mInitRatio;
-     if( mObjectScreenRatio<MIN_SIZE_CHANGE*mInitRatio) mObjectScreenRatio = MIN_SIZE_CHANGE*mInitRatio;
-
-     mRescaling = true;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   float getObjectRatio()
-     {
-     return mObjectScreenRatio;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   private void rescaleObject()
-     {
-     float size = mObject.getMaxSize();
-     final float Q = mObjectScreenRatio/size;
-     mScaleValue = mWidth<mHeight ? Q*mWidth : Q*mHeight;
-     mScale.set( mScaleValue,mScaleValue,mScaleValue );
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void changeObject(int x, int y, int z)
-     {
-     if( mObject.tryChangeObject(x,y,z) ) mCreatingCubits = true;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void displaySavingDialog()
-     {
-     BandagedCreatorActivity act = (BandagedCreatorActivity)mView.getContext();
-     RubikDialogBandagedSave saveDiag = new RubikDialogBandagedSave();
-     saveDiag.show(act.getSupportFragmentManager(), null);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void setupReset()
-     {
-     mResettingObject = true;
-     mInitialPhase    = true;
-     mStartTime       = System.currentTimeMillis();
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public boolean continueResetting(long time)
-     {
-     long diff = time-mStartTime;
-     float quotient = ((float)diff)/RESET_DURATION;
-
-     if( mInitialPhase && quotient>0.5f )
-       {
-       mInitialPhase=false;
-       mView.resetCubits();
-       mObject.resetObject(mScaleValue);
-       }
-
-     double angle = 2*Math.PI*quotient*quotient*(3-2*quotient);
-
-     float sinA = (float)Math.sin(angle);
-     float cosA = (float)Math.cos(angle);
-
-     mQuatT.set(0, -sinA, 0, cosA);
-
-     return quotient>1.0f;
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void touchCubit(int index)
-    {
-    mObject.touchCubit(index);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void untouchCubit(int index)
-    {
-    mObject.untouchCubit(index);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public BandagedCreatorTouchControl createTouchControl()
-    {
-    return new BandagedCreatorTouchControl( getObjectRatio() , mScreen.getFOV(), mObject );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void distortedException(Exception ex)
-    {
-    android.util.Log.e("BandagedCreator", "unexpected exception: "+ex.getMessage() );
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public InputStream localFile(int fileID)
-    {
-    return mResources.openRawResource(fileID);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void logMessage(String message)
-    {
-    android.util.Log.e("BandagedCreator", message );
-    }
-}
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java b/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java
deleted file mode 100644
index ab98a725..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorScreen.java
+++ /dev/null
@@ -1,411 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.bandaged;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.content.SharedPreferences;
-import android.graphics.Bitmap;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import org.distorted.external.RubikFiles;
-import org.distorted.helpers.TransparentImageButton;
-import org.distorted.main.R;
-import org.distorted.main_old.RubikActivity;
-import org.distorted.objectlib.bandaged.LocallyBandagedList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class BandagedCreatorScreen implements AdapterView.OnItemSelectedListener
-{
-  private WeakReference<BandagedCreatorActivity> mAct;
-  private TransparentImageButton mBackButton, mResetButton, mDoneButton;
-  private LinearLayout mObjectView;
-  private int mNumObjects, mX, mY, mZ;
-  private final ArrayList<BandagedCreatorObjectView> mViews;
-  private Spinner mSpinnerX, mSpinnerY, mSpinnerZ;
-  private boolean mSpinnersReady;
-  private float mTextSize;
-  private final int mOrdinal;
-  private final int mDimension;
-  private final int mMaxSupported;
-  private final int mMinSupported;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public BandagedCreatorScreen(int ordinal)
-    {
-    mOrdinal      = ordinal;
-    mMinSupported = LocallyBandagedList.getMinSupported(ordinal);
-    mMaxSupported = LocallyBandagedList.getMaxSupported(ordinal);
-    mDimension    = LocallyBandagedList.getDimension(ordinal);
-    mSpinnersReady= false;
-    mNumObjects = 0;
-    mViews = new ArrayList<>();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupBackButton(final BandagedCreatorActivity 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 setupDoneButton(final BandagedCreatorActivity act)
-    {
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
-    mDoneButton = new TransparentImageButton(act,R.drawable.ui_done,params);
-
-    mDoneButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        BandagedCreatorRenderer renderer = act.getRenderer();
-        if( !renderer.isBusy() ) renderer.displaySavingDialog();
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupResetButton(final BandagedCreatorActivity 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)
-        {
-        BandagedCreatorRenderer renderer = act.getRenderer();
-        if( !renderer.isBusy() ) renderer.setupReset();
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String[] createSizes(String mark)
-    {
-    int num = mMaxSupported-mMinSupported+1;
-    String[] ret = new String[num];
-
-    for(int i=1; i<=num; i++)
-      {
-      ret[i-1] = mark+" : "+(mMinSupported+i-1);
-      }
-
-    return ret;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void changeObject()
-    {
-    if( mSpinnersReady )
-      {
-      BandagedCreatorActivity act = mAct.get();
-      if( act!=null ) act.changeObject(mX+mMinSupported,mY+mMinSupported,mZ+mMinSupported);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void onAttachedToWindow(final BandagedCreatorActivity act)
-    {
-    mAct = new WeakReference<>(act);
-    mObjectView = act.findViewById(R.id.bandagedCreatorView);
-    mTextSize = act.getScreenHeightInPixels()*BandagedCreatorActivity.SPINNER_TEXT_SIZE;
-
-    int width  = act.getScreenWidthInPixels();
-    int height = act.getScreenHeightInPixels();
-    int padding= (int)(height*RubikActivity.PADDING/3);
-
-    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);
-    layoutRight.addView(mBackButton);
-    setupDoneButton(act);
-    layoutMid.addView(mDoneButton);
-    setupResetButton(act);
-    layoutLeft.addView(mResetButton);
-
-    LinearLayout layout = act.findViewById(R.id.lowerBar);
-    layout.removeAllViews();
-    layout.addView(layoutLeft);
-    layout.addView(layoutMid);
-    layout.addView(layoutRight);
-
-    mSpinnerX = act.findViewById(R.id.bandagedCreatorX);
-    mSpinnerY = act.findViewById(R.id.bandagedCreatorY);
-    mSpinnerZ = act.findViewById(R.id.bandagedCreatorZ);
-
-    if( mDimension>=1 )
-      {
-      mSpinnerX.setOnItemSelectedListener(this);
-      ArrayAdapter<String> adX=new ArrayAdapter<>(act, R.layout.spinner_item, createSizes("X"));
-      mSpinnerX.setAdapter(adX);
-      ViewGroup.MarginLayoutParams paramsX=(ViewGroup.MarginLayoutParams) mSpinnerX.getLayoutParams();
-      paramsX.setMargins(0, 0, 2*padding, 0);
-      }
-    else
-      {
-      mSpinnerX.setVisibility(View.INVISIBLE);
-      }
-
-    if( mDimension>=2 )
-      {
-      mSpinnerY.setOnItemSelectedListener(this);
-      ArrayAdapter<String> adY=new ArrayAdapter<>(act, R.layout.spinner_item, createSizes("Y"));
-      mSpinnerY.setAdapter(adY);
-      ViewGroup.MarginLayoutParams paramsY=(ViewGroup.MarginLayoutParams) mSpinnerY.getLayoutParams();
-      paramsY.setMargins(padding, 0, padding, 0);
-      }
-    else
-      {
-      mSpinnerY.setVisibility(View.INVISIBLE);
-      }
-
-    if( mDimension>=3 )
-      {
-      mSpinnerZ.setOnItemSelectedListener(this);
-      ArrayAdapter<String> adZ=new ArrayAdapter<>(act, R.layout.spinner_item, createSizes("Z"));
-      mSpinnerZ.setAdapter(adZ);
-      ViewGroup.MarginLayoutParams paramsZ=(ViewGroup.MarginLayoutParams) mSpinnerZ.getLayoutParams();
-      paramsZ.setMargins(2*padding, 0, 0, 0);
-      }
-    else
-      {
-      mSpinnerZ.setVisibility(View.INVISIBLE);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  boolean objectDoesntExist(String name)
-    {
-    for( BandagedCreatorObjectView view : mViews )
-      {
-      String viewName = view.getName();
-      if( viewName.equals(name) ) return false;
-      }
-
-    return true;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void addObjects(BandagedCreatorActivity act, String objectString)
-    {
-    if( objectString.length()>0 )
-      {
-      String[] objects = objectString.split(" ");
-      RubikFiles files = RubikFiles.getInstance();
-
-      for(String object : objects)
-        {
-        if( objectDoesntExist(object) )
-          {
-          addObject(act, object);
-          Bitmap bmp = files.getIcon(act, object + ".png");
-          iconCreationDone(act, bmp);
-          }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void addObject(BandagedCreatorActivity act, String name)
-    {
-    BandagedCreatorObjectView view = new BandagedCreatorObjectView(act,name,mNumObjects==0);
-    LinearLayout pane = view.getPane();
-    mObjectView.addView(pane);
-    mNumObjects++;
-    mViews.add(view);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void deleteObject(BandagedCreatorActivity act, String name)
-    {
-    for(int v=0; v<mNumObjects; v++)
-      {
-      BandagedCreatorObjectView view = mViews.get(v);
-      String viewName = view.getName();
-
-      if( viewName.equals(name) )
-        {
-        LinearLayout pane = view.getPane();
-        mObjectView.removeView(pane);
-        mViews.remove(view);
-        mNumObjects--;
-
-        if( v==0 && mNumObjects>0 )
-          {
-          BandagedCreatorObjectView v2 = mViews.get(v);
-          LinearLayout p2 = v2.getPane();
-          int height = act.getScreenHeightInPixels();
-          LinearLayout.LayoutParams params = BandagedCreatorObjectView.createPaneParams(height,true,act.isRTL());
-          p2.setLayoutParams(params);
-          }
-
-        break;
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void iconCreationDone(Activity act, Bitmap bmp)
-    {
-    int numChildren = mObjectView.getChildCount();
-
-    if( numChildren>0 )
-      {
-      LinearLayout pane = (LinearLayout)mObjectView.getChildAt(numChildren-1);
-      ImageView view = pane.findViewById(R.id.bandagedCreatorObjectIcon);
-
-      if( view!=null )
-        {
-        act.runOnUiThread(new Runnable()
-          {
-          @Override
-          public void run()
-          {
-          view.setImageBitmap(bmp);
-          }
-          });
-        }
-      else
-        {
-        android.util.Log.e("D", "ImageView not found!");
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private String generateObjectStrings()
-    {
-    StringBuilder result = new StringBuilder();
-    int numViews = mViews.size();
-
-    for( int v=0; v<numViews; v++ )
-      {
-      BandagedCreatorObjectView view = mViews.get(v);
-      String name = view.getName();
-      if( v>0 ) result.append(' ');
-      result.append(name);
-      }
-
-    return result.toString();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @SuppressLint("SuspiciousIndentation")
-    @Override
-    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id)
-      {
-      int spinnerID = parent.getId();
-
-      if( view!=null ) ((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX,mTextSize);
-
-           if( spinnerID==R.id.bandagedCreatorX ) mX = pos;
-      else if( spinnerID==R.id.bandagedCreatorY ) mY = pos;
-      else if( spinnerID==R.id.bandagedCreatorZ ) mZ = pos;
-
-      changeObject();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public void onNothingSelected(AdapterView<?> parent) { }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private String generateObjectKey()
-      {
-      return mOrdinal==0 ? "bandagedObjects" : "bandagedObjects"+mOrdinal;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void savePreferences(SharedPreferences.Editor editor)
-      {
-      String objects = generateObjectStrings();
-      editor.putString(generateObjectKey(), objects );
-
-      editor.putInt("bandagedX"+mOrdinal, mX);
-      editor.putInt("bandagedY"+mOrdinal, mY);
-      editor.putInt("bandagedZ"+mOrdinal, mZ);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void restorePreferences(BandagedCreatorActivity act, SharedPreferences preferences)
-      {
-      String objects = preferences.getString(generateObjectKey(),"");
-      addObjects(act,objects);
-
-      mX = preferences.getInt("bandagedX"+mOrdinal, 2);
-      mY = preferences.getInt("bandagedY"+mOrdinal, 2);
-      mZ = preferences.getInt("bandagedZ"+mOrdinal, 2);
-
-      int max = mMaxSupported-mMinSupported+1;
-
-      if( mX>=max ) mX = 0;//max-1;
-      if( mY>=max ) mY = 0;//max-1;
-      if( mZ>=max ) mZ = 0;//max-1;
-
-      mSpinnerX.setSelection(mX);
-      mSpinnerY.setSelection(mY);
-      mSpinnerZ.setSelection(mZ);
-
-      mSpinnersReady = true;
-      changeObject();
-      }
-}
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorTouchControl.java b/src/main/java/org/distorted/bandaged/BandagedCreatorTouchControl.java
deleted file mode 100644
index 6aaed925..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorTouchControl.java
+++ /dev/null
@@ -1,199 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.bandaged;
-
-import org.distorted.library.helpers.QuatHelper;
-import org.distorted.library.type.Static3D;
-import org.distorted.library.type.Static4D;
-import org.distorted.objectlib.bandaged.BandagedObject;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class BandagedCreatorTouchControl
-{
-  private final BandagedObject mObject;
-  private final Static4D CAMERA_POINT;
-  private final float[] mPoint, mCamera, mTouch;
-  private final float[] mPoint2D;
-  private float mObjectRatio;
-  private final Static3D[] mFaceAxis;
-  private final int mNumFaces;
-
-  private float[] mDist3D;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Convert the 3D point3D into a 2D point on the same face surface, but in a different
-// coordinate system: a in-plane 2D coord where the origin is in the point where the axis intersects
-// the surface, and whose Y axis points 'north' i.e. is in the plane given by the 3D origin, the
-// original 3D Y axis and our 2D in-plane origin.
-// If those 3 points constitute a degenerate triangle which does not define a plane - which can only
-// happen if axis is vertical (or in theory when 2D origin and 3D origin meet, but that would have to
-// mean that the distance between the center of the Object and its faces is 0) - then we arbitrarily
-// decide that 2D Y = (0,0,-1) in the North Pole and (0,0,1) in the South Pole)
-// (ax,ay,az) - vector normal to the face surface.
-
-  void convertTo2Dcoords(float[] point3D, float ax, float ay, float az , float[] output)
-    {
-    float y0,y1,y2; // base Y vector of the 2D coord system
-
-    if( ax==0.0f && az==0.0f )
-      {
-      y0=0; y1=0; y2=-ay;
-      }
-    else if( ay==0.0f )
-      {
-      y0=0; y1=1; y2=0;
-      }
-    else
-      {
-      float norm = (float)(-ay/Math.sqrt(1-ay*ay));
-      y0 = norm*ax; y1= norm*(ay-1/ay); y2=norm*az;
-      }
-
-    float x0 = y1*az - y2*ay;  //
-    float x1 = y2*ax - y0*az;  // (2D coord baseY) x (axis) = 2D coord baseX
-    float x2 = y0*ay - y1*ax;  //
-
-    float originAlpha = point3D[0]*ax + point3D[1]*ay + point3D[2]*az;
-
-    float origin0 = originAlpha*ax; // coords of the point where axis
-    float origin1 = originAlpha*ay; // intersects surface plane i.e.
-    float origin2 = originAlpha*az; // the origin of our 2D coord system
-
-    float v0 = point3D[0] - origin0;
-    float v1 = point3D[1] - origin1;
-    float v2 = point3D[2] - origin2;
-
-    output[0] = v0*x0 + v1*x1 + v2*x2;
-    output[1] = v0*y0 + v1*y1 + v2*y2;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// given precomputed mCamera and mPoint, respectively camera and touch point positions in ScreenSpace,
-// compute point 'output[]' which:
-// 1) lies on a face of the Object, i.e. surface defined by (axis, distance from (0,0,0))
-// 2) is co-linear with mCamera and mPoint
-//
-// output = camera + alpha*(point-camera), where alpha = [dist-axis*camera] / [axis*(point-camera)]
-
-  private void castTouchPointOntoFace(int face, float[] output)
-    {
-    Static3D faceAxis = mFaceAxis[face];
-
-    float d0 = mPoint[0]-mCamera[0];
-    float d1 = mPoint[1]-mCamera[1];
-    float d2 = mPoint[2]-mCamera[2];
-    float a0 = faceAxis.get0();
-    float a1 = faceAxis.get1();
-    float a2 = faceAxis.get2();
-
-    float denom = a0*d0 + a1*d1 + a2*d2;
-
-    if( denom != 0.0f )
-      {
-      float axisCam = a0*mCamera[0] + a1*mCamera[1] + a2*mCamera[2];
-      float alpha = (mDist3D[face]-axisCam)/denom;
-
-      output[0] = mCamera[0] + d0*alpha;
-      output[1] = mCamera[1] + d1*alpha;
-      output[2] = mCamera[2] + d2*alpha;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean faceIsVisible(int face)
-    {
-    Static3D faceAxis = mFaceAxis[face];
-    float castCameraOnAxis = mCamera[0]*faceAxis.get0() + mCamera[1]*faceAxis.get1() + mCamera[2]*faceAxis.get2();
-    return castCameraOnAxis > mDist3D[face];
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private boolean objectTouched(Static4D rotatedTouchPoint, Static4D rotatedCamera)
-    {
-    mPoint[0]  = rotatedTouchPoint.get0()/mObjectRatio;
-    mPoint[1]  = rotatedTouchPoint.get1()/mObjectRatio;
-    mPoint[2]  = rotatedTouchPoint.get2()/mObjectRatio;
-
-    mCamera[0] = rotatedCamera.get0()/mObjectRatio;
-    mCamera[1] = rotatedCamera.get1()/mObjectRatio;
-    mCamera[2] = rotatedCamera.get2()/mObjectRatio;
-
-    for( int face=0; face<mNumFaces; face++)
-      {
-      if( faceIsVisible(face) )
-        {
-        castTouchPointOntoFace(face, mTouch);
-
-        float ax = mFaceAxis[face].get0();
-        float ay = mFaceAxis[face].get1();
-        float az = mFaceAxis[face].get2();
-
-        convertTo2Dcoords(mTouch, ax,ay,az, mPoint2D);
-
-        if( mObject.isInsideFace(face,mPoint2D) ) return true;
-        }
-      }
-
-    return false;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public BandagedCreatorTouchControl(float ratio, float fov, BandagedObject object)
-    {
-    mPoint   = new float[3];
-    mCamera  = new float[3];
-    mTouch   = new float[3];
-    mPoint2D = new float[2];
-
-    mObject   = object;
-    mFaceAxis = mObject.getFaceAxis();
-    mNumFaces = mFaceAxis.length;
-    mObjectRatio = ratio;
-
-    double halfFOV = fov * (Math.PI/360);
-    float tanHalf = (float)Math.tan(halfFOV);
-    float dist = 0.5f/tanHalf;
-
-    CAMERA_POINT = new Static4D(0,0,dist,0);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void setDist3D(float[] dist3d)
-    {
-    mDist3D = dist3d;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void setObjectRatio(float ratio)
-    {
-    mObjectRatio = ratio;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// return the index of the cubit touched; if none, return -1.
-
-  public int cubitTouched(float x, float y, Static4D quat)
-    {
-    Static4D touchPoint = new Static4D(x, y, 0, 0);
-    Static4D rotatedTouchPoint= QuatHelper.rotateVectorByInvertedQuat(touchPoint, quat);
-    Static4D rotatedCamera= QuatHelper.rotateVectorByInvertedQuat(CAMERA_POINT, quat);
-    boolean touched = objectTouched(rotatedTouchPoint,rotatedCamera);
-    return touched ? mObject.whichCubitTouched(mTouch) : -1;
-    }
-}
-
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorView.java b/src/main/java/org/distorted/bandaged/BandagedCreatorView.java
deleted file mode 100644
index d8d6010c..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorView.java
+++ /dev/null
@@ -1,335 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.bandaged;
-
-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;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class BandagedCreatorView extends GLSurfaceView
-{
-    public static final int INVALID_POINTER_ID = -1;
-
-    private BandagedCreatorRenderer mRenderer;
-    private BandagedCreatorTouchControl mTouchControl;
-    private int mScreenWidth, mScreenHeight, mScreenMin;
-    private int mTouchedIndex1, mTouchedIndex2;
-    private float mX1, mY1, mX2, mY2, mX, mY;
-    private int mPointer1, mPointer2;
-    private float mRotAngle, mInitDistance;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public BandagedCreatorView(Context context, AttributeSet attrs)
-      {
-      super(context,attrs);
-
-      mX = -1;
-      mY = -1;
-
-      mTouchedIndex1 = -1;
-      mTouchedIndex2 = -1;
-
-      if(!isInEditMode())
-        {
-        BandagedCreatorActivity act = (BandagedCreatorActivity)context;
-        mRenderer = new BandagedCreatorRenderer(this,act.getObjectOrdinal() );
-        mTouchControl = mRenderer.createTouchControl();
-
-        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);
-          }
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getTouched()
-      {
-      return mTouchedIndex1;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public BandagedCreatorTouchControl getTouchControl()
-      {
-      return mTouchControl;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public BandagedCreatorRenderer getRenderer()
-      {
-      return mRenderer;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void resetCubits()
-      {
-      if( mTouchedIndex1>=0 ) mRenderer.untouchCubit(mTouchedIndex1);
-      if( mTouchedIndex2>=0 ) mRenderer.untouchCubit(mTouchedIndex2);
-
-      mTouchedIndex1 = -1;
-      mTouchedIndex2 = -1;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public void setScreenSize(int width, int height)
-      {
-      mScreenWidth = width;
-      mScreenHeight= height;
-      mScreenMin   = Math.min(width, height);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private float getAngle(float x1, float y1, float x2, float y2)
-      {
-      return (float) Math.atan2(y1-y2, x1-x2);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void actionDown(MotionEvent event)
-      {
-      mPointer1 = event.getPointerId(0);
-      mX1 = event.getX();
-      mY1 = event.getY();
-      mPointer2 = INVALID_POINTER_ID;
-
-      float x1 = (mX1 -mScreenWidth*0.5f)/mScreenMin;
-      float y1 = (mScreenHeight*0.5f-mY1)/mScreenMin;
-
-      int index = mTouchControl.cubitTouched(x1,y1,mRenderer.getQuatAccu() );
-
-      if( index<0 )
-        {
-        mX = mX1;
-        mY = mY1;
-        }
-      else
-        {
-        mX = -1;
-        mY = -1;
-
-        if( mTouchedIndex1<0 )
-          {
-          mTouchedIndex1 = index;
-          mRenderer.touchCubit(mTouchedIndex1);
-          }
-        else
-          {
-          mTouchedIndex2 = index;
-
-          if( mTouchedIndex1 != index )
-            {
-            mRenderer.touchCubit(mTouchedIndex2);
-            }
-          }
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void actionMove(MotionEvent event)
-      {
-      int index1 = event.findPointerIndex(mPointer1);
-
-      if( index1>=0 )
-        {
-        mX1 = event.getX(index1);
-        mY1 = event.getY(index1);
-        }
-
-      int index2 = event.findPointerIndex(mPointer2);
-
-      if( index2>=0 )
-        {
-        mX2 = event.getX(index2);
-        mY2 = event.getY(index2);
-        }
-
-      if( mPointer1!=INVALID_POINTER_ID && mPointer2!=INVALID_POINTER_ID)
-        {
-        float angleNow = getAngle(mX1,mY1,mX2,mY2);
-        float angleDiff = angleNow-mRotAngle;
-        float sinA = (float)Math.sin(angleDiff);
-        float cosA = (float)Math.cos(angleDiff);
-        mRenderer.setQuatTemp(0,0,sinA,cosA);
-        mRenderer.resetQuats();
-        mRotAngle = angleNow;
-
-        float distNow  = (float)Math.sqrt( (mX1-mX2)*(mX1-mX2) + (mY1-mY2)*(mY1-mY2) );
-        float distQuot = mInitDistance<0 ? 1.0f : distNow/ mInitDistance;
-        mInitDistance = distNow;
-        mRenderer.mulObjectRatio(distQuot);
-        }
-      else
-        {
-        float x = event.getX();
-        float y = event.getY();
-
-        if( mX>=0 && mY>= 0 )
-          {
-          float px = mY-y;
-          float py = mX-x;
-          float pz = 0;
-          float plen = (float)Math.sqrt(px*px + py*py + pz*pz);
-
-          if( plen>0 )
-            {
-            px /= plen;
-            py /= plen;
-            pz /= plen;
-
-            float cosA = (float)Math.cos(plen*3.14f/mScreenMin);
-            float sinA = (float)Math.sqrt(1-cosA*cosA);
-
-            mRenderer.setQuatTemp(px*sinA, py*sinA, pz*sinA, cosA);
-            }
-          }
-
-        mRenderer.resetQuats();
-        mX = x;
-        mY = y;
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void actionUp()
-      {
-      mPointer1 = INVALID_POINTER_ID;
-      mPointer2 = INVALID_POINTER_ID;
-
-      if( mTouchedIndex2>=0 )
-        {
-        mRenderer.untouchCubit(mTouchedIndex1);
-
-        if( mTouchedIndex2!=mTouchedIndex1 )
-          {
-          mRenderer.untouchCubit(mTouchedIndex2);
-          mRenderer.setConnecting(mTouchedIndex1,mTouchedIndex2);
-          }
-
-        mTouchedIndex1 = -1;
-        mTouchedIndex2 = -1;
-        }
-
-      mX = -1;
-      mY = -1;
-
-      mRenderer.resetQuats();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void actionPointerDown(MotionEvent event)
-      {
-      int index = event.getActionIndex();
-
-      if( mPointer1==INVALID_POINTER_ID )
-        {
-        mPointer1 = event.getPointerId(index);
-        mX1 = event.getX(index);
-        mY1 = event.getY(index);
-        }
-      else if( mPointer2==INVALID_POINTER_ID )
-        {
-        mPointer2 = event.getPointerId(index);
-        mX2 = event.getX(index);
-        mY2 = event.getY(index);
-        }
-
-      mRotAngle = getAngle(mX1,mY1,mX2,mY2);
-      mInitDistance = -1;
-      mRenderer.resetQuats();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void actionPointerUp(MotionEvent event)
-      {
-      int index = event.getActionIndex();
-
-      if( index==event.findPointerIndex(mPointer1) )
-        {
-        mPointer1 = INVALID_POINTER_ID;
-        mX = mX2;
-        mY = mY2;
-        }
-      else if( index==event.findPointerIndex(mPointer2) )
-        {
-        mPointer2 = INVALID_POINTER_ID;
-        mX = mX1;
-        mY = mY1;
-        }
-
-      mRenderer.resetQuats();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @SuppressLint("ClickableViewAccessibility")
-    @Override
-    public boolean onTouchEvent(MotionEvent event)
-      {
-      if( mRenderer.isBusy() ) return true;
-
-      int action = event.getActionMasked();
-
-      switch(action)
-         {
-         case MotionEvent.ACTION_DOWN        : actionDown(event)       ; break;
-         case MotionEvent.ACTION_MOVE        : actionMove(event)       ; break;
-         case MotionEvent.ACTION_UP          : actionUp()              ; break;
-         case MotionEvent.ACTION_POINTER_DOWN: actionPointerDown(event); break;
-         case MotionEvent.ACTION_POINTER_UP  : actionPointerUp  (event); break;
-         }
-
-      return true;
-      }
-}
-
diff --git a/src/main/java/org/distorted/bandaged/BandagedCreatorWorkerThread.java b/src/main/java/org/distorted/bandaged/BandagedCreatorWorkerThread.java
deleted file mode 100644
index 2ccb1b08..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedCreatorWorkerThread.java
+++ /dev/null
@@ -1,326 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 Leszek Koltunski                                                               //
-//                                                                                               //
-// This file is part of Distorted.                                                               //
-//                                                                                               //
-// 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.bandaged;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.widget.Toast;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.Vector;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-class BandagedCreatorWorkerThread extends Thread
-  {
-  private static Vector<WorkLoad> mBuffers;
-  private static BandagedCreatorWorkerThread mThis=null;
-  private static WeakReference<Activity> mWeakAct;
-
-  private static class WorkLoad
-    {
-    ByteBuffer buffer;
-    int width;
-    int height;
-    int numColors;
-    String filename;
-
-    WorkLoad(ByteBuffer buf, int w, int h, int n, String name)
-      {
-      buffer   = buf;
-      width    = w;
-      height   = h;
-      numColors= n;
-      filename = name;
-      }
-    }
-
-  private int mCreatedBufSize;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private BandagedCreatorWorkerThread()
-    {
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void create(Activity act)
-    {
-    mWeakAct = new WeakReference<>(act);
-
-    if( mThis==null )
-      {
-      mBuffers = new Vector<>();
-      mThis = new BandagedCreatorWorkerThread();
-      mThis.start();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void run()
-    {
-    WorkLoad load;
-
-    while(true)
-      {
-      synchronized(mThis)
-        {
-        while( mBuffers.size()>0 )
-          {
-          load = mBuffers.remove(0);
-          process(load);
-          }
-
-        try  { mThis.wait(); }
-        catch(InterruptedException ignored) { }
-        }
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  static void newBuffer(ByteBuffer buffer, int width, int height, int numColors, String filename)
-    {
-    synchronized(mThis)
-      {
-      WorkLoad load = new WorkLoad(buffer,width,height,numColors,filename);
-      mBuffers.add(load);
-      mThis.notify();
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int firstBlackPixel(byte[] tmp, int width)
-    {
-    for(int i=0; i<width; i++)
-      {
-      if( tmp[4*i]==0 && tmp[4*i+3]==-1 ) return i;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int lastBlackPixel(byte[] tmp, int width)
-    {
-    for(int i=width-1; i>=0; i--)
-      {
-      if( tmp[4*i]==0 && tmp[4*i+3]==-1 ) return i;
-      }
-
-    return -1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeFirstRow(ByteBuffer buf, byte[] tmp, int width, int height)
-    {
-    int wBytes = 4*width;
-
-    for(int i=0; i<height; i++)
-      {
-      buf.position(i*wBytes);
-      buf.get(tmp);
-
-      if( firstBlackPixel(tmp,width)>=0 ) return i;
-      }
-
-    return 0;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeLastRow(ByteBuffer buf, byte[] tmp, int width, int height)
-    {
-    int wBytes = 4*width;
-
-    for(int i=height-1; i>=0; i--)
-      {
-      buf.position(i*wBytes);
-      buf.get(tmp);
-
-      if( firstBlackPixel(tmp,width)>=0 ) return i;
-      }
-
-    return width-1;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeLeftColumn(ByteBuffer buf, byte[] tmp, int width, int firstrow, int lastrow)
-    {
-    int wBytes = 4*width;
-    int currentBest = width;
-
-    for(int i=firstrow; i<lastrow; i++)
-      {
-      buf.position(i*wBytes);
-      buf.get(tmp);
-
-      int pixel = firstBlackPixel(tmp,width);
-      if( pixel>=0 && pixel<currentBest ) currentBest = pixel;
-      }
-
-    return currentBest;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private int computeRightColumn(ByteBuffer buf, byte[] tmp, int width, int firstrow, int lastrow)
-    {
-    int wBytes = 4*width;
-    int currentBest = 0;
-
-    for(int i=firstrow; i<lastrow; i++)
-      {
-      buf.position(i*wBytes);
-      buf.get(tmp);
-
-      int pixel = lastBlackPixel(tmp,width);
-      if( pixel>=0 && pixel>currentBest ) currentBest = pixel;
-      }
-
-    return currentBest;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// GL uses a coordinate system from mathematics; i.e. (0,0) is in the lower-left corner. 2D stuff
-// has the origin on the upper-left corner; we have to flip our bitmap upside down!
-//
-// We also need to figure out the topmost and bottommost rows where the object starts and cut out
-// a square section from the 'input' so that the object is vertically centralized.
-
-  private ByteBuffer adjustBuffer(ByteBuffer input, byte[] tmp, int width, int height)
-    {
-    int firstRow   = computeFirstRow(input,tmp,width,height);
-    int lastRow    = computeLastRow(input,tmp,width,height);
-    int leftColumn = computeLeftColumn(input,tmp,width,firstRow,lastRow);
-    int rightColumn= computeRightColumn(input,tmp,width,firstRow,lastRow);
-
-    int rowHeight = lastRow-firstRow;
-    int colWidth  = rightColumn-leftColumn;
-    int size      = Math.max(rowHeight,colWidth);
-    size = (int)(1.1f*size);
-    int half = size/2;
-    int centerX = (leftColumn+rightColumn)/2;
-    int centerY = (firstRow+lastRow)/2;
-    int sL=size, sR=size, sT=size, sB=size;
-
-    if( centerX-half<    0 )
-      {
-      android.util.Log.e("D", "buffer encroaches on the left!   centerX="+centerX+" centerY="+centerY+" size="+size);
-      sL = 2*centerX;
-      }
-    if( centerX+half>width )
-      {
-      android.util.Log.e("D", "buffer encroaches on the right!  centerX="+centerX+" centerY="+centerY+" size="+size);
-      sR = 2*(width-centerX);
-      }
-    if( centerY-half<    0 )
-      {
-      android.util.Log.e("D", "buffer encroaches on the top!    centerX="+centerX+" centerY="+centerY+" size="+size);
-      sT = 2*centerY;
-      }
-    if( centerY+half>height)
-      {
-      android.util.Log.e("D", "buffer encroaches on the bottom! centerX="+centerX+" centerY="+centerY+" size="+size);
-      sB = 2*(height-centerY);
-      }
-/*
-    if( sL==size && sR==size && sT==size && sB==size )
-      {
-      android.util.Log.e("D", "EVERYTHING ALL RIGHT centerX="+centerX+" centerY="+centerY+" size="+size);
-      }
-*/
-    int minH = Math.min(sL,sR);
-    int minV = Math.min(sT,sB);
-    mCreatedBufSize = Math.min(minH,minV);
-    half = mCreatedBufSize/2;
-    int wBytes = 4*mCreatedBufSize;
-    ByteBuffer output = ByteBuffer.allocateDirect(wBytes*mCreatedBufSize);
-    output.order(ByteOrder.LITTLE_ENDIAN);
-    int startRow = centerY+half;
-    int distR = width-centerX-half;
-
-    for(int i=0; i<mCreatedBufSize; i++)
-      {
-      input.position((startRow-i)*4*width + 4*distR );
-      input.get(tmp,0,wBytes);
-      output.position(i*wBytes);
-      output.put(tmp,0,wBytes);
-      }
-
-    output.rewind();
-    return output;
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void process(WorkLoad load)
-    {
-    int width  = load.width;
-    int height = load.height;
-
-    byte[] tmp = new byte[4*width];
-    ByteBuffer bufSquare = adjustBuffer(load.buffer,tmp,width,height);
-    final String filename = load.filename;
-    BufferedOutputStream bos =null;
-    final Activity act = mWeakAct.get();
-
-    try
-      {
-      bos = new BufferedOutputStream(new FileOutputStream(filename));
-      Bitmap bmp = Bitmap.createBitmap( mCreatedBufSize, mCreatedBufSize, Bitmap.Config.ARGB_8888);
-      bmp.copyPixelsFromBuffer(bufSquare);
-      bmp.compress(Bitmap.CompressFormat.PNG, 90, bos);
-
-      BandagedCreatorActivity cact = (BandagedCreatorActivity)act;
-      cact.iconCreationDone(bmp);
-      }
-    catch(final Exception ex)
-      {
-      act.runOnUiThread(new Runnable()
-        {
-        public void run()
-          {
-          Toast.makeText(act,
-              "Saving to \n\n"+filename+"\n\n failed: "+"\n\n"+ex.getMessage() ,
-              Toast.LENGTH_LONG).show();
-          }
-        });
-      }
-    finally
-      {
-      if(bos!=null)
-        {
-        try { bos.close(); }
-        catch(IOException ignored) {}
-        }
-      }
-
-    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
-    File f = new File(filename);
-    Uri contentUri = Uri.fromFile(f);
-    mediaScanIntent.setData(contentUri);
-    act.sendBroadcast(mediaScanIntent);
-    }
-  }
diff --git a/src/main/java/org/distorted/bandaged/BandagedObjectView.java b/src/main/java/org/distorted/bandaged/BandagedObjectView.java
new file mode 100644
index 00000000..5888a7af
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedObjectView.java
@@ -0,0 +1,119 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 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.bandaged;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import org.distorted.dialogs.RubikDialogBandagedDelete;
+import org.distorted.helpers.TransparentButton;
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedObjectView
+{
+  private static final float MARGIN    = 0.015f;
+  private static final float TEXT_SIZE = 0.032f;
+
+  private final LinearLayout mPane;
+  private final String mName;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public BandagedObjectView(final BandagedActivity act, String name, boolean leftmost)
+    {
+    mName = name;
+    LayoutInflater inflater = act.getLayoutInflater();
+    mPane = (LinearLayout) inflater.inflate(R.layout.bandaged_pane, null);
+
+    int height   = act.getScreenHeightInPixels();
+    int textSize = (int)(height*TEXT_SIZE);
+
+    LinearLayout.LayoutParams params = createPaneParams(height,leftmost,act.isRTL());
+    mPane.setLayoutParams(params);
+
+    LinearLayout bottom = mPane.findViewById(R.id.bandagedCreatorObjectLayout);
+
+    TransparentButton plaButton = new TransparentButton(act, R.string.play, textSize);
+    plaButton.setSingleLine();
+
+    final int icon = R.drawable.ui_trash;
+    LinearLayout.LayoutParams paramsB = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,3.0f);
+    TransparentImageButton delButton = new TransparentImageButton(act,icon,paramsB);
+
+    bottom.addView(plaButton);
+    bottom.addView(delButton);
+
+    plaButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        act.playObject(mName);
+        }
+      });
+
+    delButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        Bundle bundle = new Bundle();
+        bundle.putString("argument", mName );
+        RubikDialogBandagedDelete dialog = new RubikDialogBandagedDelete();
+        dialog.setArguments(bundle);
+        dialog.show( act.getSupportFragmentManager(), RubikDialogBandagedDelete.getDialogTag() );
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static LinearLayout.LayoutParams createPaneParams(int height, boolean leftmost, boolean rtl)
+    {
+    int margin = (int)(height*MARGIN);
+    int length = (int)(height*BandagedActivity.RATIO_SCROLL*0.65f);
+
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( length, LinearLayout.LayoutParams.MATCH_PARENT);
+    params.bottomMargin = margin;
+    params.topMargin    = margin;
+
+    if( !rtl )
+      {
+      params.leftMargin   = leftmost ? margin : 0;
+      params.rightMargin  = margin;
+      }
+    else
+      {
+      params.rightMargin  = leftmost ? margin : 0;
+      params.leftMargin   = margin;
+      }
+
+    return params;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public LinearLayout getPane()
+    {
+    return mPane;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getName()
+    {
+    return mName;
+    }
+}
diff --git a/src/main/java/org/distorted/bandaged/BandagedPlayActivity.java b/src/main/java/org/distorted/bandaged/BandagedPlayActivity.java
deleted file mode 100644
index 8787e322..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedPlayActivity.java
+++ /dev/null
@@ -1,276 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.bandaged;
-
-import android.content.SharedPreferences;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.DisplayMetrics;
-import android.view.DisplayCutout;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.LinearLayout;
-
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.preference.PreferenceManager;
-
-import org.distorted.dialogs.RubikDialogError;
-import org.distorted.external.RubikFiles;
-import org.distorted.library.main.DistortedLibrary;
-import org.distorted.main.MainActivity;
-import org.distorted.main.R;
-import org.distorted.main_old.RubikActivity;
-import org.distorted.objectlib.main.InitAssets;
-import org.distorted.objectlib.main.ObjectControl;
-import org.distorted.objectlib.main.TwistyObject;
-
-import java.io.InputStream;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class BandagedPlayActivity extends AppCompatActivity
-{
-    private static final int ACTIVITY_NUMBER = 4;
-    private static final float RATIO_BAR     = MainActivity.RATIO_BAR;
-    private static final float RATIO_INSET   = 0.09f;
-    public static final int FLAGS            = RubikActivity.FLAGS;
-
-    private static int mScreenWidth, mScreenHeight;
-    private int mCurrentApiVersion;
-    private BandagedPlayScreen mScreen;
-    private String mObjectName;
-    private int mHeightUpperBar;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    protected void onCreate(Bundle savedState)
-      {
-      super.onCreate(savedState);
-      DistortedLibrary.onCreate(ACTIVITY_NUMBER);
-      setTheme(R.style.MaterialThemeNoActionBar);
-      setContentView(R.layout.bandaged_play);
-
-      Bundle b = getIntent().getExtras();
-      mObjectName = b!=null ? b.getString("name") : "";
-
-      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);
-      mHeightUpperBar = barHeight;
-
-      LinearLayout layoutTop = findViewById(R.id.upperBar);
-      LinearLayout layoutBot = findViewById(R.id.lowerBar);
-
-      ViewGroup.LayoutParams paramsTop = layoutTop.getLayoutParams();
-      paramsTop.height = mHeightUpperBar;
-      layoutTop.setLayoutParams(paramsTop);
-      ViewGroup.LayoutParams paramsBot = layoutBot.getLayoutParams();
-      paramsBot.height = barHeight;
-      layoutBot.setLayoutParams(paramsBot);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    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);
-              }
-            }
-          });
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public void onAttachedToWindow()
-      {
-      super.onAttachedToWindow();
-
-      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
-        {
-        DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
-        int insetHeight = cutout!=null ? cutout.getSafeInsetTop() : 0;
-
-        LinearLayout layoutHid = findViewById(R.id.hiddenBar);
-        ViewGroup.LayoutParams paramsHid = layoutHid.getLayoutParams();
-        paramsHid.height = (int)(insetHeight*RATIO_INSET);
-        layoutHid.setLayoutParams(paramsHid);
-        mHeightUpperBar += paramsHid.height;
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// 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();
-      BandagedPlayView view = findViewById(R.id.bandagedPlayView);
-      view.onPause();
-      savePreferences();
-      DistortedLibrary.onPause(ACTIVITY_NUMBER);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onResume() 
-      {
-      super.onResume();
-      DistortedLibrary.onResume(ACTIVITY_NUMBER);
-      BandagedPlayView view = findViewById(R.id.bandagedPlayView);
-      view.onResume();
-
-      if( mScreen==null ) mScreen = new BandagedPlayScreen();
-      mScreen.onAttachedToWindow(this, mObjectName);
-      restorePreferences();
-
-      if( mObjectName.length()>0 )
-        {
-        changeIfDifferent(mObjectName,view.getObjectControl());
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-    
-    @Override
-    protected void onDestroy() 
-      {
-      super.onDestroy();
-      DistortedLibrary.onDestroy(ACTIVITY_NUMBER);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void savePreferences()
-    {
-    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-    SharedPreferences.Editor editor = preferences.edit();
-    mScreen.savePreferences(this,editor);
-
-    editor.apply();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void restorePreferences()
-    {
-    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
-    mScreen.restorePreferences(this,preferences);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void OpenGLError()
-      {
-      RubikDialogError errDiag = new RubikDialogError();
-      errDiag.show(getSupportFragmentManager(), null);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    private void changeIfDifferent(String name,ObjectControl control)
-      {
-      RubikFiles files = RubikFiles.getInstance();
-
-      int iconMode           = TwistyObject.MODE_NORM;
-      InputStream jsonStream = files.openFile(this,name+"_object.json");
-      InitAssets asset       = new InitAssets(jsonStream,null,null);
-      int ordinal            = 0; // if jsonStream!=null, this doesn't matter
-
-      control.changeIfDifferent(ordinal,name,iconMode,asset);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getScreenWidthInPixels()
-      {
-      return mScreenWidth;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public int getScreenHeightInPixels()
-      {
-      return mScreenHeight;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public ObjectControl getControl()
-      {
-      BandagedPlayView view = findViewById(R.id.bandagedPlayView);
-      return view.getObjectControl();
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public BandagedPlayScreen getScreen()
-      {
-      return mScreen;
-      }
-}
diff --git a/src/main/java/org/distorted/bandaged/BandagedPlayLibInterface.java b/src/main/java/org/distorted/bandaged/BandagedPlayLibInterface.java
deleted file mode 100644
index f00827d1..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedPlayLibInterface.java
+++ /dev/null
@@ -1,200 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.bandaged;
-
-import android.util.DisplayMetrics;
-
-import java.lang.ref.WeakReference;
-
-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.objects.RubikObject;
-import org.distorted.objects.RubikObjectList;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class BandagedPlayLibInterface implements ObjectLibInterface
-{
-  private final WeakReference<BandagedPlayActivity> mAct;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  BandagedPlayLibInterface(BandagedPlayActivity act)
-    {
-    mAct = new WeakReference<>(act);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  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 onReplaceModeDown(int cubit, int face) { }
-  public void onReplaceModeUp() { }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void onFinishRotation(int axis, int row, int angle)
-    {
-    BandagedPlayActivity act = mAct.get();
-
-    if( act!=null )
-      {
-      BandagedPlayScreen play = act.getScreen();
-      play.addMove(act,axis,row,angle);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void failedToDrag()
-    {
-    BandagedPlayActivity act = mAct.get();
-
-    if( act!=null )
-      {
-      BandagedPlayScreen play = act.getScreen();
-      play.reddenLock(act);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  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;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void reportJSONError(String error, int ordinal)
-    {
-    RubikObject object = RubikObjectList.getObject(ordinal);
-    String name = object==null ? "NULL" : object.getUpperName();
-
-    if( BuildConfig.DEBUG )
-       {
-       android.util.Log.e("libInterface", "name="+name+" JSON error: "+error);
-       }
-    else
-      {
-      Exception ex = new Exception(error);
-      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
-      crashlytics.setCustomKey("name" , name );
-      crashlytics.setCustomKey("JSONerror", error );
-      crashlytics.recordException(ex);
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public int getScreenDensity()
-    {
-    DisplayMetrics dm = new DisplayMetrics();
-    mAct.get().getWindowManager().getDefaultDisplay().getMetrics(dm);
-    return dm.densityDpi;
-    }
-}
diff --git a/src/main/java/org/distorted/bandaged/BandagedPlayRenderer.java b/src/main/java/org/distorted/bandaged/BandagedPlayRenderer.java
deleted file mode 100644
index e766a1df..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedPlayRenderer.java
+++ /dev/null
@@ -1,111 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.bandaged;
-
-import android.content.res.Resources;
-import android.opengl.GLSurfaceView;
-
-import org.distorted.library.effect.EffectType;
-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.mesh.MeshBase;
-import org.distorted.objectlib.effects.BaseEffect;
-import org.distorted.objectlib.main.ObjectControl;
-
-import java.io.InputStream;
-
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.opengles.GL10;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class BandagedPlayRenderer implements GLSurfaceView.Renderer, DistortedLibrary.LibraryUser
-{
-   private final BandagedPlayView mView;
-   private final Resources mResources;
-   private final DistortedScreen mScreen;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   BandagedPlayRenderer(BandagedPlayView v)
-     {
-     final float BRIGHTNESS = 0.333f;
-
-     mView = v;
-     mResources = v.getResources();
-     mScreen = new DistortedScreen();
-     mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   @Override
-   public void onDrawFrame(GL10 glUnused)
-     {
-     long time = System.currentTimeMillis();
-     mView.getObjectControl().preRender();
-     mScreen.render(time);
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   @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);
-      MeshBase.setMaxEffComponents(ObjectControl.MAX_MOVING_PARTS);
-
-      VertexEffectRotate.enable();
-      VertexEffectQuaternion.enable();
-      BaseEffect.Type.enableEffects();
-
-      DistortedLibrary.onSurfaceCreated(this,1);
-      DistortedLibrary.setCull(true);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void distortedException(Exception ex)
-     {
-     android.util.Log.e("BandagedPlay", "unexpected exception: "+ex.getMessage() );
-     }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public InputStream localFile(int fileID)
-      {
-      return mResources.openRawResource(fileID);
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-   public void logMessage(String message)
-      {
-      android.util.Log.e("BandagedPlay", message );
-      }
-}
diff --git a/src/main/java/org/distorted/bandaged/BandagedPlayScreen.java b/src/main/java/org/distorted/bandaged/BandagedPlayScreen.java
deleted file mode 100644
index 3b96713b..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedPlayScreen.java
+++ /dev/null
@@ -1,191 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.bandaged;
-
-import android.app.Activity;
-import android.content.SharedPreferences;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import org.distorted.helpers.LockController;
-import org.distorted.helpers.MovesController;
-import org.distorted.helpers.TransparentImageButton;
-import org.distorted.main.R;
-import org.distorted.objectlib.effects.BaseEffect;
-import org.distorted.objectlib.main.ObjectControl;
-import org.distorted.os.OSInterface;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class BandagedPlayScreen
-{
-  private static final int NUM_SCRAMBLES = 300;
-
-  private TransparentImageButton mBackButton, mScrambleButton, mSolveButton;
-  private final LockController mLockController;
-  private final MovesController mMovesController;
-  private String mKey;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public BandagedPlayScreen()
-    {
-    mLockController = new LockController();
-    mMovesController= new MovesController();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupBackButton(final BandagedPlayActivity 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 setupSolveButton(final BandagedPlayActivity act)
-    {
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
-    mSolveButton = new TransparentImageButton(act,R.drawable.ui_cube_solve,params);
-
-    mSolveButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        ObjectControl control = act.getControl();
-        control.solveObject();
-        mMovesController.clearMoves(act);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void setupScrambleButton(final BandagedPlayActivity act)
-    {
-    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
-    mScrambleButton = new TransparentImageButton(act,R.drawable.ui_cube_scramble,params);
-
-    mScrambleButton.setOnClickListener( new View.OnClickListener()
-      {
-      @Override
-      public void onClick(View v)
-        {
-        ObjectControl control = act.getControl();
-        int duration = BaseEffect.Type.FAST_SCRAMBLE.getDuration();
-        control.fastScrambleObject(duration,NUM_SCRAMBLES);
-        mMovesController.clearMoves(act);
-        }
-      });
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  void onAttachedToWindow(final BandagedPlayActivity act, String objectName)
-    {
-    mKey = "moveController_"+objectName;
-
-    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);
-    ObjectControl control = act.getControl();
-    mMovesController.setupButton(act,control);
-    layoutLeft.addView(mMovesController.getButton());
-    mLockController.setupButton(act,control);
-    layoutMid.addView(mLockController.getButton());
-
-    layoutRight.addView(mBackButton);
-
-    LinearLayout layoutLower = act.findViewById(R.id.lowerBar);
-    layoutLower.removeAllViews();
-    layoutLower.addView(layoutLeft);
-    layoutLower.addView(layoutMid);
-    layoutLower.addView(layoutRight);
-
-    setupSolveButton(act);
-    setupScrambleButton(act);
-
-    LinearLayout layoutUpper = act.findViewById(R.id.upperBar);
-
-    LinearLayout layoutLeftU = new LinearLayout(act);
-    layoutLeftU.setLayoutParams(paramsL);
-    LinearLayout layoutMidU  = new LinearLayout(act);
-    layoutMidU.setLayoutParams(paramsM);
-    LinearLayout layoutRightU= new LinearLayout(act);
-    layoutRightU.setLayoutParams(paramsR);
-
-    layoutLeftU.addView(mSolveButton);
-    layoutRightU.addView(mScrambleButton);
-
-    layoutUpper.removeAllViews();
-    layoutUpper.addView(layoutLeftU);
-    layoutUpper.addView(layoutMidU);
-    layoutUpper.addView(layoutRightU);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void reddenLock(final BandagedPlayActivity act)
-    {
-    ObjectControl control = act.getControl();
-    mLockController.reddenLock(act,control);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void addMove(Activity act, int axis, int row, int angle)
-    {
-    mMovesController.addMove(act,axis,row,angle);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void savePreferences(BandagedPlayActivity act, SharedPreferences.Editor editor)
-    {
-    mMovesController.savePreferences(mKey,editor);
-    ObjectControl control = act.getControl();
-    OSInterface os = (OSInterface)control.getOS();
-    os.setEditor(editor);
-    control.savePreferences();
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  public void restorePreferences(BandagedPlayActivity act, SharedPreferences preferences)
-    {
-    mMovesController.restorePreferences(act,mKey,preferences);
-    ObjectControl control = act.getControl();
-    OSInterface os = (OSInterface)control.getOS();
-    os.setPreferences(preferences);
-    control.restorePreferences();
-    }
-}
diff --git a/src/main/java/org/distorted/bandaged/BandagedPlayView.java b/src/main/java/org/distorted/bandaged/BandagedPlayView.java
deleted file mode 100644
index fa71c27e..00000000
--- a/src/main/java/org/distorted/bandaged/BandagedPlayView.java
+++ /dev/null
@@ -1,146 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// Copyright 2022 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.bandaged;
-
-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;
-
-import static org.distorted.objectlib.main.ObjectControl.MODE_ROTATE;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-public class BandagedPlayView extends GLSurfaceView
-{
-    private ObjectControl mObjectController;
-    private OSInterface mInterface;
-    private BandagedPlayRenderer mRenderer;
-    private int mScreenWidth, mScreenHeight;
-    private boolean mCreated;
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    void setScreenSize(int width, int height)
-      {
-      mScreenWidth = width;
-      mScreenHeight= height;
-      mObjectController.setScreenSizeAndScaling(width,height, Math.min(width, (int)(0.75f*height)) );
-      mObjectController.setObjectScale(1.00f);
-
-      if( !mCreated )
-        {
-        mCreated = true;
-        mObjectController.createNode(width,height);
-        TwistyObjectNode objectNode = mObjectController.getNode();
-        mRenderer.getScreen().attach(objectNode);
-        }
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    boolean isVertical()
-      {
-      return mScreenHeight>mScreenWidth;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    ObjectControl getObjectControl()
-      {
-      return mObjectController;
-      }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-// PUBLIC API
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public BandagedPlayView(Context context, AttributeSet attrs)
-      {
-      super(context,attrs);
-
-      mCreated = false;
-
-      if(!isInEditMode())
-        {
-        BandagedPlayActivity act = (BandagedPlayActivity)context;
-        BandagedPlayLibInterface ref = new BandagedPlayLibInterface(act);
-        mInterface = new OSInterface(act,ref);
-        mObjectController = new ObjectControl(mInterface);
-        mObjectController.setRotateOnCreation(true);
-        mRenderer = new BandagedPlayRenderer(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_ROTATE);
-      }
-}
-
diff --git a/src/main/java/org/distorted/bandaged/BandagedRenderer.java b/src/main/java/org/distorted/bandaged/BandagedRenderer.java
new file mode 100644
index 00000000..8991b7f4
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedRenderer.java
@@ -0,0 +1,534 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 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.bandaged;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.opengl.GLES31;
+import android.opengl.GLSurfaceView;
+import android.widget.Toast;
+
+import org.distorted.dialogs.RubikDialogBandagedSave;
+import org.distorted.library.effect.EffectType;
+import org.distorted.library.effect.FragmentEffectBrightness;
+import org.distorted.library.effect.PostprocessEffectBorder;
+import org.distorted.library.main.DistortedEffects;
+import org.distorted.library.main.DistortedFramebuffer;
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.library.main.DistortedNode;
+import org.distorted.library.main.DistortedScreen;
+import org.distorted.library.main.InternalOutputSurface;
+import org.distorted.library.mesh.MeshBase;
+import org.distorted.library.type.Static1D;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.objectlib.bandaged.BandagedObject;
+import org.distorted.objectlib.bandaged.LocallyBandagedList;
+import org.distorted.objectlib.json.JsonWriter;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObject;
+
+import org.json.JSONException;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedRenderer implements GLSurfaceView.Renderer, DistortedLibrary.LibraryUser
+{
+   public static final float BRIGHTNESS = 0.333f;
+   private static final int RESET_DURATION = 1000;
+   private static final float MAX_SIZE_CHANGE = 1.70f;
+   private static final float MIN_SIZE_CHANGE = 0.50f;
+
+   private final BandagedView mView;
+   private final Resources mResources;
+   private final DistortedScreen mScreen;
+   private final Static3D mScale;
+   private final Static4D mQuatT, mQuatA;
+   private final BandagedObject mObject;
+   private final float mInitRatio;
+
+   private boolean mInitialPhase;
+   private long mStartTime;
+   private float mQuatX, mQuatY, mQuatZ, mQuatW;
+   private boolean mResetQuats, mSetQuatT, mResettingObject, mConnectingCubits, mCreatingCubits, mRescaling;
+   private int mIndex1, mIndex2;
+   private int mSaveIcon;
+   private DistortedFramebuffer mFramebuffer;
+   private String mPath;
+   private boolean mCubitsCreated;
+   private int mWidth, mHeight;
+   private float mScaleValue, mObjectScreenRatio;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   BandagedRenderer(BandagedView v, int ordinal)
+     {
+     mView = v;
+     mResources = v.getResources();
+
+     mQuatT = new Static4D(0,0,0,1);
+     mQuatA = new Static4D(-0.25189602f,0.3546389f,0.009657208f,0.90038127f);
+
+     mResetQuats       = false;
+     mSetQuatT         = false;
+     mResettingObject  = false;
+     mConnectingCubits = false;
+     mCubitsCreated    = false;
+     mCreatingCubits   = false;
+     mRescaling        = false;
+
+     mSaveIcon = -1;
+
+     mScreen = new DistortedScreen();
+     mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
+     mScale = new Static3D(1,1,1);
+     mObject= LocallyBandagedList.create(ordinal,mScreen);
+
+     mInitRatio = mObject.getScreenRatio();
+     mObjectScreenRatio= mInitRatio;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onDrawFrame(GL10 glUnused)
+     {
+     long time = System.currentTimeMillis();
+     mScreen.render(time);
+
+     if( mSetQuatT )
+       {
+       mSetQuatT = false;
+       mQuatT.set(mQuatX,mQuatY,mQuatZ,mQuatW);
+       }
+
+     if( mResetQuats )
+       {
+       mResetQuats = false;
+
+       float qx = mQuatT.get0();
+       float qy = mQuatT.get1();
+       float qz = mQuatT.get2();
+       float qw = mQuatT.get3();
+
+       float rx = mQuatA.get0();
+       float ry = mQuatA.get1();
+       float rz = mQuatA.get2();
+       float rw = mQuatA.get3();
+
+       float tx = rw*qx - rz*qy + ry*qz + rx*qw;
+       float ty = rw*qy + rz*qx + ry*qw - rx*qz;
+       float tz = rw*qz + rz*qw - ry*qx + rx*qy;
+       float tw = rw*qw - rz*qz - ry*qy - rx*qx;
+
+       mQuatT.set(0f, 0f, 0f, 1f);
+       mQuatA.set(tx, ty, tz, tw);
+       }
+
+     if( mResettingObject )
+       {
+       boolean done = continueResetting(time);
+       if( done ) mResettingObject = false;
+       }
+
+     if( mSaveIcon>=0 )
+       {
+       renderIcon(time); // for some reason we need to call render() twice here, otherwise the
+       mSaveIcon++;      // icon turns out black. Probably some problem with binding the texture.
+       }
+     if( mSaveIcon>=2 )
+       {
+       saveIcon();
+       mSaveIcon = -1;
+       }
+
+     if( mConnectingCubits )
+       {
+       mObject.tryConnectingCubits(mIndex1,mIndex2,mScaleValue);
+       mConnectingCubits = false;
+       }
+
+     if( mCreatingCubits )
+       {
+       rescaleObject();
+
+       if( mCubitsCreated )
+         {
+         mObject.createCubits(mQuatT,mQuatA,mScale);
+         float[] dist = mObject.getDist3D();
+         BandagedTouchControl control = mView.getTouchControl();
+         control.setDist3D(dist);
+         mScreen.detachAll();
+         mView.resetCubits();
+         mObject.attachCubits(mScaleValue);
+         }
+
+       mCreatingCubits = false;
+       }
+
+     if( mRescaling )
+       {
+       rescaleObject();
+       mObject.scaleCubits(mScaleValue);
+       BandagedTouchControl control = mView.getTouchControl();
+       control.setObjectRatio(mObjectScreenRatio);
+       mRescaling = false;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onSurfaceChanged(GL10 glUnused, int width, int height)
+      {
+      if( width!=mWidth || height!=mHeight )
+        {
+        mWidth = width;
+        mHeight= height;
+        rescaleObject();
+        mScreen.detachAll();
+        int touched = mView.getTouched();
+        mObject.attachAndMarkCubits(mScaleValue,touched);
+        mView.setScreenSize(width,height);
+        mScreen.resize(width,height);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
+      {
+      DistortedLibrary.setMax(EffectType.VERTEX,0);
+      MeshBase.setMaxEffComponents(ObjectControl.MAX_MOVING_PARTS);
+      FragmentEffectBrightness.enable();
+      DistortedLibrary.onSurfaceCreated(this,1);
+      DistortedLibrary.setCull(true);
+      mObject.recreateCubits(mQuatT,mQuatA,mScale);
+      mCubitsCreated = true;
+      mWidth = 0;
+      mHeight= 0;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public DistortedScreen getScreen()
+     {
+     return mScreen;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void setConnecting(int index1, int index2)
+     {
+     mIndex1 = index1;
+     mIndex2 = index2;
+     mConnectingCubits = true;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public Static4D getQuatAccu()
+     {
+     return mQuatA;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void setQuatTemp(float x, float y, float z, float w)
+     {
+     mSetQuatT = false;
+
+     mQuatX = x;
+     mQuatY = y;
+     mQuatZ = z;
+     mQuatW = w;
+
+     mSetQuatT = true;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void resetQuats()
+     {
+     mResetQuats = true;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public boolean isBusy()
+     {
+     return (mResettingObject || mCreatingCubits || mConnectingCubits);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void saveObject()
+     {
+     TwistyObject obj = mObject.createObject(TwistyObject.MODE_NORM, 1.0f );
+     String name = obj.getShortName();
+     BandagedActivity act = (BandagedActivity) mView.getContext();
+
+     if( act.objectDoesntExist(name) && createObjectJson(obj,act) )
+       {
+       setupIconCreation(act);
+       act.addObject(obj.getShortName());
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private boolean createObjectJson(TwistyObject object, Activity act)
+     {
+     final String name = object.getShortName()+"_object.json";
+     File file = new File(act.getFilesDir(), name);
+     String filename = file.getAbsolutePath();
+
+     try
+       {
+       JsonWriter writer = JsonWriter.getInstance();
+       String json = writer.createObjectString(object,24,0);
+       writer.write(filename,json);
+       return true;
+       }
+     catch(JSONException ex)
+       {
+       act.runOnUiThread(new Runnable()
+         {
+         public void run()
+           {
+           String message = "JSON Exception saving to \n\n"+filename+"\n\n failed:\n\n"+ex.getMessage();
+           Toast.makeText(act,message,Toast.LENGTH_LONG).show();
+           }
+         });
+
+       return false;
+       }
+     catch(FileNotFoundException ex)
+       {
+       act.runOnUiThread(new Runnable()
+         {
+         public void run()
+           {
+           String message = "FileNotFound exception saving to \n\n"+filename+"\n\n failed:\n\n"+ex.getMessage();
+           Toast.makeText(act,message,Toast.LENGTH_LONG).show();
+           }
+         });
+
+       return false;
+       }
+     catch(IOException ex)
+       {
+       act.runOnUiThread(new Runnable()
+         {
+         public void run()
+           {
+           String message = "IO exception saving to \n\n"+filename+"\n\n failed:\n\n"+ex.getMessage();
+           Toast.makeText(act,message,Toast.LENGTH_LONG).show();
+           }
+         });
+
+       return false;
+       }
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void setupIconCreation(Activity act)
+     {
+     final float R=1.0f;
+     final int FBO_WIDTH  = (int)(R*720);
+     final int FBO_HEIGHT = (int)(R*1280);
+     final float OBJECT_SIZE = R*0.60f;
+
+     TwistyObject obj = mObject.createObject(TwistyObject.MODE_ICON, OBJECT_SIZE );
+     DistortedEffects effects = obj.getObjectEffects();
+     DistortedNode node = obj.getNode();
+
+     if( mFramebuffer==null )
+       {
+       mFramebuffer = new DistortedFramebuffer(FBO_WIDTH,FBO_HEIGHT,1, InternalOutputSurface.DEPTH_NO_STENCIL);
+       mFramebuffer.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
+       }
+
+     mFramebuffer.setProjection( mObject.computeProjectionAngle() ,0.1f);
+     mFramebuffer.detachAll();
+     mFramebuffer.attach(node);
+
+     Static1D halo = new Static1D(5);
+     Static4D color = new Static4D(0,0,0,1);
+     PostprocessEffectBorder border = new PostprocessEffectBorder(halo,color);
+     border.setHaloDepth(false);
+     effects.apply(border);
+
+     final String name = obj.getShortName()+".png";
+     File file = new File(act.getFilesDir(), name);
+     String filename = file.getAbsolutePath();
+
+     mSaveIcon = 0;
+     mPath = filename;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void renderIcon(long time)
+     {
+     mFramebuffer.render(time);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void saveIcon()
+     {
+     int fW = mFramebuffer.getWidth();
+     int fH = mFramebuffer.getHeight();
+
+     ByteBuffer buf = ByteBuffer.allocateDirect(fW*fH*4);
+     buf.order(ByteOrder.LITTLE_ENDIAN);
+
+     mFramebuffer.setAsReadFramebuffer(0);
+     GLES31.glReadBuffer(GLES31.GL_COLOR_ATTACHMENT0);
+     GLES31.glReadPixels( 0, 0, fW, fH, GLES31.GL_RGBA, GLES31.GL_UNSIGNED_BYTE, buf);
+     BandagedWorkerThread.newBuffer(buf,fW,fH,6,mPath);
+     GLES31.glBindFramebuffer(GLES31.GL_READ_FRAMEBUFFER, 0);
+
+     mSaveIcon = -1;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   void mulObjectRatio(float ratio)
+     {
+     mObjectScreenRatio *= ratio;
+
+     if( mObjectScreenRatio>MAX_SIZE_CHANGE*mInitRatio) mObjectScreenRatio = MAX_SIZE_CHANGE*mInitRatio;
+     if( mObjectScreenRatio<MIN_SIZE_CHANGE*mInitRatio) mObjectScreenRatio = MIN_SIZE_CHANGE*mInitRatio;
+
+     mRescaling = true;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   float getObjectRatio()
+     {
+     return mObjectScreenRatio;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   private void rescaleObject()
+     {
+     float size = mObject.getMaxSize();
+     final float Q = mObjectScreenRatio/size;
+     mScaleValue = mWidth<mHeight ? Q*mWidth : Q*mHeight;
+     mScale.set( mScaleValue,mScaleValue,mScaleValue );
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void changeObject(int x, int y, int z)
+     {
+     if( mObject.tryChangeObject(x,y,z) ) mCreatingCubits = true;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void displaySavingDialog()
+     {
+     BandagedActivity act = (BandagedActivity)mView.getContext();
+     RubikDialogBandagedSave saveDiag = new RubikDialogBandagedSave();
+     saveDiag.show(act.getSupportFragmentManager(), null);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void setupReset()
+     {
+     mResettingObject = true;
+     mInitialPhase    = true;
+     mStartTime       = System.currentTimeMillis();
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public boolean continueResetting(long time)
+     {
+     long diff = time-mStartTime;
+     float quotient = ((float)diff)/RESET_DURATION;
+
+     if( mInitialPhase && quotient>0.5f )
+       {
+       mInitialPhase=false;
+       mView.resetCubits();
+       mObject.resetObject(mScaleValue);
+       }
+
+     double angle = 2*Math.PI*quotient*quotient*(3-2*quotient);
+
+     float sinA = (float)Math.sin(angle);
+     float cosA = (float)Math.cos(angle);
+
+     mQuatT.set(0, -sinA, 0, cosA);
+
+     return quotient>1.0f;
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void touchCubit(int index)
+    {
+    mObject.touchCubit(index);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void untouchCubit(int index)
+    {
+    mObject.untouchCubit(index);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public BandagedTouchControl createTouchControl()
+    {
+    return new BandagedTouchControl( getObjectRatio() , mScreen.getFOV(), mObject );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void distortedException(Exception ex)
+    {
+    android.util.Log.e("BandagedCreator", "unexpected exception: "+ex.getMessage() );
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public InputStream localFile(int fileID)
+    {
+    return mResources.openRawResource(fileID);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void logMessage(String message)
+    {
+    android.util.Log.e("BandagedCreator", message );
+    }
+}
diff --git a/src/main/java/org/distorted/bandaged/BandagedScreen.java b/src/main/java/org/distorted/bandaged/BandagedScreen.java
new file mode 100644
index 00000000..4d9859b6
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedScreen.java
@@ -0,0 +1,411 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 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.bandaged;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import org.distorted.external.RubikFiles;
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+import org.distorted.main_old.RubikActivity;
+import org.distorted.objectlib.bandaged.LocallyBandagedList;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedScreen implements AdapterView.OnItemSelectedListener
+{
+  private WeakReference<BandagedActivity> mAct;
+  private TransparentImageButton mBackButton, mResetButton, mDoneButton;
+  private LinearLayout mObjectView;
+  private int mNumObjects, mX, mY, mZ;
+  private final ArrayList<BandagedObjectView> mViews;
+  private Spinner mSpinnerX, mSpinnerY, mSpinnerZ;
+  private boolean mSpinnersReady;
+  private float mTextSize;
+  private final int mOrdinal;
+  private final int mDimension;
+  private final int mMaxSupported;
+  private final int mMinSupported;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public BandagedScreen(int ordinal)
+    {
+    mOrdinal      = ordinal;
+    mMinSupported = LocallyBandagedList.getMinSupported(ordinal);
+    mMaxSupported = LocallyBandagedList.getMaxSupported(ordinal);
+    mDimension    = LocallyBandagedList.getDimension(ordinal);
+    mSpinnersReady= false;
+    mNumObjects = 0;
+    mViews = new ArrayList<>();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final BandagedActivity 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 setupDoneButton(final BandagedActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
+    mDoneButton = new TransparentImageButton(act,R.drawable.ui_done,params);
+
+    mDoneButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        BandagedRenderer renderer = act.getRenderer();
+        if( !renderer.isBusy() ) renderer.displaySavingDialog();
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupResetButton(final BandagedActivity 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)
+        {
+        BandagedRenderer renderer = act.getRenderer();
+        if( !renderer.isBusy() ) renderer.setupReset();
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String[] createSizes(String mark)
+    {
+    int num = mMaxSupported-mMinSupported+1;
+    String[] ret = new String[num];
+
+    for(int i=1; i<=num; i++)
+      {
+      ret[i-1] = mark+" : "+(mMinSupported+i-1);
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void changeObject()
+    {
+    if( mSpinnersReady )
+      {
+      BandagedActivity act = mAct.get();
+      if( act!=null ) act.changeObject(mX+mMinSupported,mY+mMinSupported,mZ+mMinSupported);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void onAttachedToWindow(final BandagedActivity act)
+    {
+    mAct = new WeakReference<>(act);
+    mObjectView = act.findViewById(R.id.bandagedCreatorView);
+    mTextSize = act.getScreenHeightInPixels()*BandagedActivity.SPINNER_TEXT_SIZE;
+
+    int width  = act.getScreenWidthInPixels();
+    int height = act.getScreenHeightInPixels();
+    int padding= (int)(height*RubikActivity.PADDING/3);
+
+    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);
+    layoutRight.addView(mBackButton);
+    setupDoneButton(act);
+    layoutMid.addView(mDoneButton);
+    setupResetButton(act);
+    layoutLeft.addView(mResetButton);
+
+    LinearLayout layout = act.findViewById(R.id.lowerBar);
+    layout.removeAllViews();
+    layout.addView(layoutLeft);
+    layout.addView(layoutMid);
+    layout.addView(layoutRight);
+
+    mSpinnerX = act.findViewById(R.id.bandagedCreatorX);
+    mSpinnerY = act.findViewById(R.id.bandagedCreatorY);
+    mSpinnerZ = act.findViewById(R.id.bandagedCreatorZ);
+
+    if( mDimension>=1 )
+      {
+      mSpinnerX.setOnItemSelectedListener(this);
+      ArrayAdapter<String> adX=new ArrayAdapter<>(act, R.layout.spinner_item, createSizes("X"));
+      mSpinnerX.setAdapter(adX);
+      ViewGroup.MarginLayoutParams paramsX=(ViewGroup.MarginLayoutParams) mSpinnerX.getLayoutParams();
+      paramsX.setMargins(0, 0, 2*padding, 0);
+      }
+    else
+      {
+      mSpinnerX.setVisibility(View.INVISIBLE);
+      }
+
+    if( mDimension>=2 )
+      {
+      mSpinnerY.setOnItemSelectedListener(this);
+      ArrayAdapter<String> adY=new ArrayAdapter<>(act, R.layout.spinner_item, createSizes("Y"));
+      mSpinnerY.setAdapter(adY);
+      ViewGroup.MarginLayoutParams paramsY=(ViewGroup.MarginLayoutParams) mSpinnerY.getLayoutParams();
+      paramsY.setMargins(padding, 0, padding, 0);
+      }
+    else
+      {
+      mSpinnerY.setVisibility(View.INVISIBLE);
+      }
+
+    if( mDimension>=3 )
+      {
+      mSpinnerZ.setOnItemSelectedListener(this);
+      ArrayAdapter<String> adZ=new ArrayAdapter<>(act, R.layout.spinner_item, createSizes("Z"));
+      mSpinnerZ.setAdapter(adZ);
+      ViewGroup.MarginLayoutParams paramsZ=(ViewGroup.MarginLayoutParams) mSpinnerZ.getLayoutParams();
+      paramsZ.setMargins(2*padding, 0, 0, 0);
+      }
+    else
+      {
+      mSpinnerZ.setVisibility(View.INVISIBLE);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  boolean objectDoesntExist(String name)
+    {
+    for( BandagedObjectView view : mViews )
+      {
+      String viewName = view.getName();
+      if( viewName.equals(name) ) return false;
+      }
+
+    return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void addObjects(BandagedActivity act, String objectString)
+    {
+    if( objectString.length()>0 )
+      {
+      String[] objects = objectString.split(" ");
+      RubikFiles files = RubikFiles.getInstance();
+
+      for(String object : objects)
+        {
+        if( objectDoesntExist(object) )
+          {
+          addObject(act, object);
+          Bitmap bmp = files.getIcon(act, object + ".png");
+          iconCreationDone(act, bmp);
+          }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void addObject(BandagedActivity act, String name)
+    {
+    BandagedObjectView view = new BandagedObjectView(act,name,mNumObjects==0);
+    LinearLayout pane = view.getPane();
+    mObjectView.addView(pane);
+    mNumObjects++;
+    mViews.add(view);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void deleteObject(BandagedActivity act, String name)
+    {
+    for(int v=0; v<mNumObjects; v++)
+      {
+      BandagedObjectView view = mViews.get(v);
+      String viewName = view.getName();
+
+      if( viewName.equals(name) )
+        {
+        LinearLayout pane = view.getPane();
+        mObjectView.removeView(pane);
+        mViews.remove(view);
+        mNumObjects--;
+
+        if( v==0 && mNumObjects>0 )
+          {
+          BandagedObjectView v2 = mViews.get(v);
+          LinearLayout p2 = v2.getPane();
+          int height = act.getScreenHeightInPixels();
+          LinearLayout.LayoutParams params = BandagedObjectView.createPaneParams(height,true,act.isRTL());
+          p2.setLayoutParams(params);
+          }
+
+        break;
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void iconCreationDone(Activity act, Bitmap bmp)
+    {
+    int numChildren = mObjectView.getChildCount();
+
+    if( numChildren>0 )
+      {
+      LinearLayout pane = (LinearLayout)mObjectView.getChildAt(numChildren-1);
+      ImageView view = pane.findViewById(R.id.bandagedCreatorObjectIcon);
+
+      if( view!=null )
+        {
+        act.runOnUiThread(new Runnable()
+          {
+          @Override
+          public void run()
+          {
+          view.setImageBitmap(bmp);
+          }
+          });
+        }
+      else
+        {
+        android.util.Log.e("D", "ImageView not found!");
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private String generateObjectStrings()
+    {
+    StringBuilder result = new StringBuilder();
+    int numViews = mViews.size();
+
+    for( int v=0; v<numViews; v++ )
+      {
+      BandagedObjectView view = mViews.get(v);
+      String name = view.getName();
+      if( v>0 ) result.append(' ');
+      result.append(name);
+      }
+
+    return result.toString();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @SuppressLint("SuspiciousIndentation")
+    @Override
+    public void onItemSelected(AdapterView<?> parent, View view, int pos, long id)
+      {
+      int spinnerID = parent.getId();
+
+      if( view!=null ) ((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_PX,mTextSize);
+
+           if( spinnerID==R.id.bandagedCreatorX ) mX = pos;
+      else if( spinnerID==R.id.bandagedCreatorY ) mY = pos;
+      else if( spinnerID==R.id.bandagedCreatorZ ) mZ = pos;
+
+      changeObject();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onNothingSelected(AdapterView<?> parent) { }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private String generateObjectKey()
+      {
+      return mOrdinal==0 ? "bandagedObjects" : "bandagedObjects"+mOrdinal;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void savePreferences(SharedPreferences.Editor editor)
+      {
+      String objects = generateObjectStrings();
+      editor.putString(generateObjectKey(), objects );
+
+      editor.putInt("bandagedX"+mOrdinal, mX);
+      editor.putInt("bandagedY"+mOrdinal, mY);
+      editor.putInt("bandagedZ"+mOrdinal, mZ);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void restorePreferences(BandagedActivity act, SharedPreferences preferences)
+      {
+      String objects = preferences.getString(generateObjectKey(),"");
+      addObjects(act,objects);
+
+      mX = preferences.getInt("bandagedX"+mOrdinal, 2);
+      mY = preferences.getInt("bandagedY"+mOrdinal, 2);
+      mZ = preferences.getInt("bandagedZ"+mOrdinal, 2);
+
+      int max = mMaxSupported-mMinSupported+1;
+
+      if( mX>=max ) mX = 0;//max-1;
+      if( mY>=max ) mY = 0;//max-1;
+      if( mZ>=max ) mZ = 0;//max-1;
+
+      mSpinnerX.setSelection(mX);
+      mSpinnerY.setSelection(mY);
+      mSpinnerZ.setSelection(mZ);
+
+      mSpinnersReady = true;
+      changeObject();
+      }
+}
diff --git a/src/main/java/org/distorted/bandaged/BandagedTouchControl.java b/src/main/java/org/distorted/bandaged/BandagedTouchControl.java
new file mode 100644
index 00000000..10d022a8
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedTouchControl.java
@@ -0,0 +1,199 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 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.bandaged;
+
+import org.distorted.library.helpers.QuatHelper;
+import org.distorted.library.type.Static3D;
+import org.distorted.library.type.Static4D;
+import org.distorted.objectlib.bandaged.BandagedObject;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedTouchControl
+{
+  private final BandagedObject mObject;
+  private final Static4D CAMERA_POINT;
+  private final float[] mPoint, mCamera, mTouch;
+  private final float[] mPoint2D;
+  private float mObjectRatio;
+  private final Static3D[] mFaceAxis;
+  private final int mNumFaces;
+
+  private float[] mDist3D;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Convert the 3D point3D into a 2D point on the same face surface, but in a different
+// coordinate system: a in-plane 2D coord where the origin is in the point where the axis intersects
+// the surface, and whose Y axis points 'north' i.e. is in the plane given by the 3D origin, the
+// original 3D Y axis and our 2D in-plane origin.
+// If those 3 points constitute a degenerate triangle which does not define a plane - which can only
+// happen if axis is vertical (or in theory when 2D origin and 3D origin meet, but that would have to
+// mean that the distance between the center of the Object and its faces is 0) - then we arbitrarily
+// decide that 2D Y = (0,0,-1) in the North Pole and (0,0,1) in the South Pole)
+// (ax,ay,az) - vector normal to the face surface.
+
+  void convertTo2Dcoords(float[] point3D, float ax, float ay, float az , float[] output)
+    {
+    float y0,y1,y2; // base Y vector of the 2D coord system
+
+    if( ax==0.0f && az==0.0f )
+      {
+      y0=0; y1=0; y2=-ay;
+      }
+    else if( ay==0.0f )
+      {
+      y0=0; y1=1; y2=0;
+      }
+    else
+      {
+      float norm = (float)(-ay/Math.sqrt(1-ay*ay));
+      y0 = norm*ax; y1= norm*(ay-1/ay); y2=norm*az;
+      }
+
+    float x0 = y1*az - y2*ay;  //
+    float x1 = y2*ax - y0*az;  // (2D coord baseY) x (axis) = 2D coord baseX
+    float x2 = y0*ay - y1*ax;  //
+
+    float originAlpha = point3D[0]*ax + point3D[1]*ay + point3D[2]*az;
+
+    float origin0 = originAlpha*ax; // coords of the point where axis
+    float origin1 = originAlpha*ay; // intersects surface plane i.e.
+    float origin2 = originAlpha*az; // the origin of our 2D coord system
+
+    float v0 = point3D[0] - origin0;
+    float v1 = point3D[1] - origin1;
+    float v2 = point3D[2] - origin2;
+
+    output[0] = v0*x0 + v1*x1 + v2*x2;
+    output[1] = v0*y0 + v1*y1 + v2*y2;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// given precomputed mCamera and mPoint, respectively camera and touch point positions in ScreenSpace,
+// compute point 'output[]' which:
+// 1) lies on a face of the Object, i.e. surface defined by (axis, distance from (0,0,0))
+// 2) is co-linear with mCamera and mPoint
+//
+// output = camera + alpha*(point-camera), where alpha = [dist-axis*camera] / [axis*(point-camera)]
+
+  private void castTouchPointOntoFace(int face, float[] output)
+    {
+    Static3D faceAxis = mFaceAxis[face];
+
+    float d0 = mPoint[0]-mCamera[0];
+    float d1 = mPoint[1]-mCamera[1];
+    float d2 = mPoint[2]-mCamera[2];
+    float a0 = faceAxis.get0();
+    float a1 = faceAxis.get1();
+    float a2 = faceAxis.get2();
+
+    float denom = a0*d0 + a1*d1 + a2*d2;
+
+    if( denom != 0.0f )
+      {
+      float axisCam = a0*mCamera[0] + a1*mCamera[1] + a2*mCamera[2];
+      float alpha = (mDist3D[face]-axisCam)/denom;
+
+      output[0] = mCamera[0] + d0*alpha;
+      output[1] = mCamera[1] + d1*alpha;
+      output[2] = mCamera[2] + d2*alpha;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean faceIsVisible(int face)
+    {
+    Static3D faceAxis = mFaceAxis[face];
+    float castCameraOnAxis = mCamera[0]*faceAxis.get0() + mCamera[1]*faceAxis.get1() + mCamera[2]*faceAxis.get2();
+    return castCameraOnAxis > mDist3D[face];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private boolean objectTouched(Static4D rotatedTouchPoint, Static4D rotatedCamera)
+    {
+    mPoint[0]  = rotatedTouchPoint.get0()/mObjectRatio;
+    mPoint[1]  = rotatedTouchPoint.get1()/mObjectRatio;
+    mPoint[2]  = rotatedTouchPoint.get2()/mObjectRatio;
+
+    mCamera[0] = rotatedCamera.get0()/mObjectRatio;
+    mCamera[1] = rotatedCamera.get1()/mObjectRatio;
+    mCamera[2] = rotatedCamera.get2()/mObjectRatio;
+
+    for( int face=0; face<mNumFaces; face++)
+      {
+      if( faceIsVisible(face) )
+        {
+        castTouchPointOntoFace(face, mTouch);
+
+        float ax = mFaceAxis[face].get0();
+        float ay = mFaceAxis[face].get1();
+        float az = mFaceAxis[face].get2();
+
+        convertTo2Dcoords(mTouch, ax,ay,az, mPoint2D);
+
+        if( mObject.isInsideFace(face,mPoint2D) ) return true;
+        }
+      }
+
+    return false;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public BandagedTouchControl(float ratio, float fov, BandagedObject object)
+    {
+    mPoint   = new float[3];
+    mCamera  = new float[3];
+    mTouch   = new float[3];
+    mPoint2D = new float[2];
+
+    mObject   = object;
+    mFaceAxis = mObject.getFaceAxis();
+    mNumFaces = mFaceAxis.length;
+    mObjectRatio = ratio;
+
+    double halfFOV = fov * (Math.PI/360);
+    float tanHalf = (float)Math.tan(halfFOV);
+    float dist = 0.5f/tanHalf;
+
+    CAMERA_POINT = new Static4D(0,0,dist,0);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setDist3D(float[] dist3d)
+    {
+    mDist3D = dist3d;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void setObjectRatio(float ratio)
+    {
+    mObjectRatio = ratio;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// return the index of the cubit touched; if none, return -1.
+
+  public int cubitTouched(float x, float y, Static4D quat)
+    {
+    Static4D touchPoint = new Static4D(x, y, 0, 0);
+    Static4D rotatedTouchPoint= QuatHelper.rotateVectorByInvertedQuat(touchPoint, quat);
+    Static4D rotatedCamera= QuatHelper.rotateVectorByInvertedQuat(CAMERA_POINT, quat);
+    boolean touched = objectTouched(rotatedTouchPoint,rotatedCamera);
+    return touched ? mObject.whichCubitTouched(mTouch) : -1;
+    }
+}
+
diff --git a/src/main/java/org/distorted/bandaged/BandagedView.java b/src/main/java/org/distorted/bandaged/BandagedView.java
new file mode 100644
index 00000000..2ea0e6f5
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedView.java
@@ -0,0 +1,335 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 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.bandaged;
+
+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;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class BandagedView extends GLSurfaceView
+{
+    public static final int INVALID_POINTER_ID = -1;
+
+    private BandagedRenderer mRenderer;
+    private BandagedTouchControl mTouchControl;
+    private int mScreenWidth, mScreenHeight, mScreenMin;
+    private int mTouchedIndex1, mTouchedIndex2;
+    private float mX1, mY1, mX2, mY2, mX, mY;
+    private int mPointer1, mPointer2;
+    private float mRotAngle, mInitDistance;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public BandagedView(Context context, AttributeSet attrs)
+      {
+      super(context,attrs);
+
+      mX = -1;
+      mY = -1;
+
+      mTouchedIndex1 = -1;
+      mTouchedIndex2 = -1;
+
+      if(!isInEditMode())
+        {
+        BandagedActivity act = (BandagedActivity)context;
+        mRenderer = new BandagedRenderer(this,act.getObjectOrdinal() );
+        mTouchControl = mRenderer.createTouchControl();
+
+        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);
+          }
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getTouched()
+      {
+      return mTouchedIndex1;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public BandagedTouchControl getTouchControl()
+      {
+      return mTouchControl;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public BandagedRenderer getRenderer()
+      {
+      return mRenderer;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void resetCubits()
+      {
+      if( mTouchedIndex1>=0 ) mRenderer.untouchCubit(mTouchedIndex1);
+      if( mTouchedIndex2>=0 ) mRenderer.untouchCubit(mTouchedIndex2);
+
+      mTouchedIndex1 = -1;
+      mTouchedIndex2 = -1;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void setScreenSize(int width, int height)
+      {
+      mScreenWidth = width;
+      mScreenHeight= height;
+      mScreenMin   = Math.min(width, height);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private float getAngle(float x1, float y1, float x2, float y2)
+      {
+      return (float) Math.atan2(y1-y2, x1-x2);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void actionDown(MotionEvent event)
+      {
+      mPointer1 = event.getPointerId(0);
+      mX1 = event.getX();
+      mY1 = event.getY();
+      mPointer2 = INVALID_POINTER_ID;
+
+      float x1 = (mX1 -mScreenWidth*0.5f)/mScreenMin;
+      float y1 = (mScreenHeight*0.5f-mY1)/mScreenMin;
+
+      int index = mTouchControl.cubitTouched(x1,y1,mRenderer.getQuatAccu() );
+
+      if( index<0 )
+        {
+        mX = mX1;
+        mY = mY1;
+        }
+      else
+        {
+        mX = -1;
+        mY = -1;
+
+        if( mTouchedIndex1<0 )
+          {
+          mTouchedIndex1 = index;
+          mRenderer.touchCubit(mTouchedIndex1);
+          }
+        else
+          {
+          mTouchedIndex2 = index;
+
+          if( mTouchedIndex1 != index )
+            {
+            mRenderer.touchCubit(mTouchedIndex2);
+            }
+          }
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void actionMove(MotionEvent event)
+      {
+      int index1 = event.findPointerIndex(mPointer1);
+
+      if( index1>=0 )
+        {
+        mX1 = event.getX(index1);
+        mY1 = event.getY(index1);
+        }
+
+      int index2 = event.findPointerIndex(mPointer2);
+
+      if( index2>=0 )
+        {
+        mX2 = event.getX(index2);
+        mY2 = event.getY(index2);
+        }
+
+      if( mPointer1!=INVALID_POINTER_ID && mPointer2!=INVALID_POINTER_ID)
+        {
+        float angleNow = getAngle(mX1,mY1,mX2,mY2);
+        float angleDiff = angleNow-mRotAngle;
+        float sinA = (float)Math.sin(angleDiff);
+        float cosA = (float)Math.cos(angleDiff);
+        mRenderer.setQuatTemp(0,0,sinA,cosA);
+        mRenderer.resetQuats();
+        mRotAngle = angleNow;
+
+        float distNow  = (float)Math.sqrt( (mX1-mX2)*(mX1-mX2) + (mY1-mY2)*(mY1-mY2) );
+        float distQuot = mInitDistance<0 ? 1.0f : distNow/ mInitDistance;
+        mInitDistance = distNow;
+        mRenderer.mulObjectRatio(distQuot);
+        }
+      else
+        {
+        float x = event.getX();
+        float y = event.getY();
+
+        if( mX>=0 && mY>= 0 )
+          {
+          float px = mY-y;
+          float py = mX-x;
+          float pz = 0;
+          float plen = (float)Math.sqrt(px*px + py*py + pz*pz);
+
+          if( plen>0 )
+            {
+            px /= plen;
+            py /= plen;
+            pz /= plen;
+
+            float cosA = (float)Math.cos(plen*3.14f/mScreenMin);
+            float sinA = (float)Math.sqrt(1-cosA*cosA);
+
+            mRenderer.setQuatTemp(px*sinA, py*sinA, pz*sinA, cosA);
+            }
+          }
+
+        mRenderer.resetQuats();
+        mX = x;
+        mY = y;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void actionUp()
+      {
+      mPointer1 = INVALID_POINTER_ID;
+      mPointer2 = INVALID_POINTER_ID;
+
+      if( mTouchedIndex2>=0 )
+        {
+        mRenderer.untouchCubit(mTouchedIndex1);
+
+        if( mTouchedIndex2!=mTouchedIndex1 )
+          {
+          mRenderer.untouchCubit(mTouchedIndex2);
+          mRenderer.setConnecting(mTouchedIndex1,mTouchedIndex2);
+          }
+
+        mTouchedIndex1 = -1;
+        mTouchedIndex2 = -1;
+        }
+
+      mX = -1;
+      mY = -1;
+
+      mRenderer.resetQuats();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void actionPointerDown(MotionEvent event)
+      {
+      int index = event.getActionIndex();
+
+      if( mPointer1==INVALID_POINTER_ID )
+        {
+        mPointer1 = event.getPointerId(index);
+        mX1 = event.getX(index);
+        mY1 = event.getY(index);
+        }
+      else if( mPointer2==INVALID_POINTER_ID )
+        {
+        mPointer2 = event.getPointerId(index);
+        mX2 = event.getX(index);
+        mY2 = event.getY(index);
+        }
+
+      mRotAngle = getAngle(mX1,mY1,mX2,mY2);
+      mInitDistance = -1;
+      mRenderer.resetQuats();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void actionPointerUp(MotionEvent event)
+      {
+      int index = event.getActionIndex();
+
+      if( index==event.findPointerIndex(mPointer1) )
+        {
+        mPointer1 = INVALID_POINTER_ID;
+        mX = mX2;
+        mY = mY2;
+        }
+      else if( index==event.findPointerIndex(mPointer2) )
+        {
+        mPointer2 = INVALID_POINTER_ID;
+        mX = mX1;
+        mY = mY1;
+        }
+
+      mRenderer.resetQuats();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @SuppressLint("ClickableViewAccessibility")
+    @Override
+    public boolean onTouchEvent(MotionEvent event)
+      {
+      if( mRenderer.isBusy() ) return true;
+
+      int action = event.getActionMasked();
+
+      switch(action)
+         {
+         case MotionEvent.ACTION_DOWN        : actionDown(event)       ; break;
+         case MotionEvent.ACTION_MOVE        : actionMove(event)       ; break;
+         case MotionEvent.ACTION_UP          : actionUp()              ; break;
+         case MotionEvent.ACTION_POINTER_DOWN: actionPointerDown(event); break;
+         case MotionEvent.ACTION_POINTER_UP  : actionPointerUp  (event); break;
+         }
+
+      return true;
+      }
+}
+
diff --git a/src/main/java/org/distorted/bandaged/BandagedWorkerThread.java b/src/main/java/org/distorted/bandaged/BandagedWorkerThread.java
new file mode 100644
index 00000000..825dbbee
--- /dev/null
+++ b/src/main/java/org/distorted/bandaged/BandagedWorkerThread.java
@@ -0,0 +1,326 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Distorted.                                                               //
+//                                                                                               //
+// 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.bandaged;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.widget.Toast;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Vector;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class BandagedWorkerThread extends Thread
+  {
+  private static Vector<WorkLoad> mBuffers;
+  private static BandagedWorkerThread mThis=null;
+  private static WeakReference<Activity> mWeakAct;
+
+  private static class WorkLoad
+    {
+    ByteBuffer buffer;
+    int width;
+    int height;
+    int numColors;
+    String filename;
+
+    WorkLoad(ByteBuffer buf, int w, int h, int n, String name)
+      {
+      buffer   = buf;
+      width    = w;
+      height   = h;
+      numColors= n;
+      filename = name;
+      }
+    }
+
+  private int mCreatedBufSize;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private BandagedWorkerThread()
+    {
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void create(Activity act)
+    {
+    mWeakAct = new WeakReference<>(act);
+
+    if( mThis==null )
+      {
+      mBuffers = new Vector<>();
+      mThis = new BandagedWorkerThread();
+      mThis.start();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void run()
+    {
+    WorkLoad load;
+
+    while(true)
+      {
+      synchronized(mThis)
+        {
+        while( mBuffers.size()>0 )
+          {
+          load = mBuffers.remove(0);
+          process(load);
+          }
+
+        try  { mThis.wait(); }
+        catch(InterruptedException ignored) { }
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  static void newBuffer(ByteBuffer buffer, int width, int height, int numColors, String filename)
+    {
+    synchronized(mThis)
+      {
+      WorkLoad load = new WorkLoad(buffer,width,height,numColors,filename);
+      mBuffers.add(load);
+      mThis.notify();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int firstBlackPixel(byte[] tmp, int width)
+    {
+    for(int i=0; i<width; i++)
+      {
+      if( tmp[4*i]==0 && tmp[4*i+3]==-1 ) return i;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int lastBlackPixel(byte[] tmp, int width)
+    {
+    for(int i=width-1; i>=0; i--)
+      {
+      if( tmp[4*i]==0 && tmp[4*i+3]==-1 ) return i;
+      }
+
+    return -1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeFirstRow(ByteBuffer buf, byte[] tmp, int width, int height)
+    {
+    int wBytes = 4*width;
+
+    for(int i=0; i<height; i++)
+      {
+      buf.position(i*wBytes);
+      buf.get(tmp);
+
+      if( firstBlackPixel(tmp,width)>=0 ) return i;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeLastRow(ByteBuffer buf, byte[] tmp, int width, int height)
+    {
+    int wBytes = 4*width;
+
+    for(int i=height-1; i>=0; i--)
+      {
+      buf.position(i*wBytes);
+      buf.get(tmp);
+
+      if( firstBlackPixel(tmp,width)>=0 ) return i;
+      }
+
+    return width-1;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeLeftColumn(ByteBuffer buf, byte[] tmp, int width, int firstrow, int lastrow)
+    {
+    int wBytes = 4*width;
+    int currentBest = width;
+
+    for(int i=firstrow; i<lastrow; i++)
+      {
+      buf.position(i*wBytes);
+      buf.get(tmp);
+
+      int pixel = firstBlackPixel(tmp,width);
+      if( pixel>=0 && pixel<currentBest ) currentBest = pixel;
+      }
+
+    return currentBest;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private int computeRightColumn(ByteBuffer buf, byte[] tmp, int width, int firstrow, int lastrow)
+    {
+    int wBytes = 4*width;
+    int currentBest = 0;
+
+    for(int i=firstrow; i<lastrow; i++)
+      {
+      buf.position(i*wBytes);
+      buf.get(tmp);
+
+      int pixel = lastBlackPixel(tmp,width);
+      if( pixel>=0 && pixel>currentBest ) currentBest = pixel;
+      }
+
+    return currentBest;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// GL uses a coordinate system from mathematics; i.e. (0,0) is in the lower-left corner. 2D stuff
+// has the origin on the upper-left corner; we have to flip our bitmap upside down!
+//
+// We also need to figure out the topmost and bottommost rows where the object starts and cut out
+// a square section from the 'input' so that the object is vertically centralized.
+
+  private ByteBuffer adjustBuffer(ByteBuffer input, byte[] tmp, int width, int height)
+    {
+    int firstRow   = computeFirstRow(input,tmp,width,height);
+    int lastRow    = computeLastRow(input,tmp,width,height);
+    int leftColumn = computeLeftColumn(input,tmp,width,firstRow,lastRow);
+    int rightColumn= computeRightColumn(input,tmp,width,firstRow,lastRow);
+
+    int rowHeight = lastRow-firstRow;
+    int colWidth  = rightColumn-leftColumn;
+    int size      = Math.max(rowHeight,colWidth);
+    size = (int)(1.1f*size);
+    int half = size/2;
+    int centerX = (leftColumn+rightColumn)/2;
+    int centerY = (firstRow+lastRow)/2;
+    int sL=size, sR=size, sT=size, sB=size;
+
+    if( centerX-half<    0 )
+      {
+      android.util.Log.e("D", "buffer encroaches on the left!   centerX="+centerX+" centerY="+centerY+" size="+size);
+      sL = 2*centerX;
+      }
+    if( centerX+half>width )
+      {
+      android.util.Log.e("D", "buffer encroaches on the right!  centerX="+centerX+" centerY="+centerY+" size="+size);
+      sR = 2*(width-centerX);
+      }
+    if( centerY-half<    0 )
+      {
+      android.util.Log.e("D", "buffer encroaches on the top!    centerX="+centerX+" centerY="+centerY+" size="+size);
+      sT = 2*centerY;
+      }
+    if( centerY+half>height)
+      {
+      android.util.Log.e("D", "buffer encroaches on the bottom! centerX="+centerX+" centerY="+centerY+" size="+size);
+      sB = 2*(height-centerY);
+      }
+/*
+    if( sL==size && sR==size && sT==size && sB==size )
+      {
+      android.util.Log.e("D", "EVERYTHING ALL RIGHT centerX="+centerX+" centerY="+centerY+" size="+size);
+      }
+*/
+    int minH = Math.min(sL,sR);
+    int minV = Math.min(sT,sB);
+    mCreatedBufSize = Math.min(minH,minV);
+    half = mCreatedBufSize/2;
+    int wBytes = 4*mCreatedBufSize;
+    ByteBuffer output = ByteBuffer.allocateDirect(wBytes*mCreatedBufSize);
+    output.order(ByteOrder.LITTLE_ENDIAN);
+    int startRow = centerY+half;
+    int distR = width-centerX-half;
+
+    for(int i=0; i<mCreatedBufSize; i++)
+      {
+      input.position((startRow-i)*4*width + 4*distR );
+      input.get(tmp,0,wBytes);
+      output.position(i*wBytes);
+      output.put(tmp,0,wBytes);
+      }
+
+    output.rewind();
+    return output;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void process(WorkLoad load)
+    {
+    int width  = load.width;
+    int height = load.height;
+
+    byte[] tmp = new byte[4*width];
+    ByteBuffer bufSquare = adjustBuffer(load.buffer,tmp,width,height);
+    final String filename = load.filename;
+    BufferedOutputStream bos =null;
+    final Activity act = mWeakAct.get();
+
+    try
+      {
+      bos = new BufferedOutputStream(new FileOutputStream(filename));
+      Bitmap bmp = Bitmap.createBitmap( mCreatedBufSize, mCreatedBufSize, Bitmap.Config.ARGB_8888);
+      bmp.copyPixelsFromBuffer(bufSquare);
+      bmp.compress(Bitmap.CompressFormat.PNG, 90, bos);
+
+      BandagedActivity cact = (BandagedActivity)act;
+      cact.iconCreationDone(bmp);
+      }
+    catch(final Exception ex)
+      {
+      act.runOnUiThread(new Runnable()
+        {
+        public void run()
+          {
+          Toast.makeText(act,
+              "Saving to \n\n"+filename+"\n\n failed: "+"\n\n"+ex.getMessage() ,
+              Toast.LENGTH_LONG).show();
+          }
+        });
+      }
+    finally
+      {
+      if(bos!=null)
+        {
+        try { bos.close(); }
+        catch(IOException ignored) {}
+        }
+      }
+
+    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+    File f = new File(filename);
+    Uri contentUri = Uri.fromFile(f);
+    mediaScanIntent.setData(contentUri);
+    act.sendBroadcast(mediaScanIntent);
+    }
+  }
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogBandagedDelete.java b/src/main/java/org/distorted/dialogs/RubikDialogBandagedDelete.java
index af0c4828..5ce25295 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogBandagedDelete.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogBandagedDelete.java
@@ -16,7 +16,7 @@ import android.widget.TextView;
 
 import androidx.fragment.app.FragmentActivity;
 
-import org.distorted.bandaged.BandagedCreatorActivity;
+import org.distorted.bandaged.BandagedActivity;
 import org.distorted.main.R;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -33,7 +33,7 @@ public class RubikDialogBandagedDelete extends RubikDialogAbstract
 
   public void positiveAction()
     {
-    BandagedCreatorActivity bact = (BandagedCreatorActivity)getContext();
+    BandagedActivity bact = (BandagedActivity)getContext();
     if( bact!=null ) bact.deleteObject(mArgument);
     }
 
diff --git a/src/main/java/org/distorted/dialogs/RubikDialogBandagedSave.java b/src/main/java/org/distorted/dialogs/RubikDialogBandagedSave.java
index f26382f3..e708affd 100644
--- a/src/main/java/org/distorted/dialogs/RubikDialogBandagedSave.java
+++ b/src/main/java/org/distorted/dialogs/RubikDialogBandagedSave.java
@@ -16,8 +16,8 @@ import android.widget.TextView;
 
 import androidx.fragment.app.FragmentActivity;
 
-import org.distorted.bandaged.BandagedCreatorActivity;
-import org.distorted.bandaged.BandagedCreatorRenderer;
+import org.distorted.bandaged.BandagedActivity;
+import org.distorted.bandaged.BandagedRenderer;
 import org.distorted.main.R;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34,11 +34,11 @@ public class RubikDialogBandagedSave extends RubikDialogAbstract
 
   public void positiveAction()
     {
-    BandagedCreatorActivity bact = (BandagedCreatorActivity)getContext();
+    BandagedActivity bact = (BandagedActivity)getContext();
 
     if( bact!=null )
       {
-      BandagedCreatorRenderer rend = bact.getRenderer();
+      BandagedRenderer rend = bact.getRenderer();
       rend.saveObject();
       }
     }
diff --git a/src/main/java/org/distorted/main/MainActivity.java b/src/main/java/org/distorted/main/MainActivity.java
index 0bae410d..51f9fc78 100644
--- a/src/main/java/org/distorted/main/MainActivity.java
+++ b/src/main/java/org/distorted/main/MainActivity.java
@@ -33,7 +33,7 @@ import androidx.preference.PreferenceManager;
 import com.google.firebase.analytics.FirebaseAnalytics;
 import com.google.firebase.inappmessaging.FirebaseInAppMessaging;
 
-import org.distorted.bandaged.BandagedCreatorActivity;
+import org.distorted.bandaged.BandagedActivity;
 import org.distorted.config.ConfigActivity;
 import org.distorted.dialogs.RubikDialogAbout;
 import org.distorted.dialogs.RubikDialogCreators;
@@ -44,8 +44,10 @@ import org.distorted.external.RubikNetwork;
 import org.distorted.external.RubikScores;
 import org.distorted.external.RubikUpdates;
 import org.distorted.messaging.RubikInAppMessanging;
+import org.distorted.objects.RubikObject;
 import org.distorted.objects.RubikObjectList;
 import org.distorted.patternui.PatternActivity;
+import org.distorted.playui.PlayActivity;
 import org.distorted.solverui.SolverActivity;
 import org.distorted.tutorials.TutorialActivity;
 
@@ -379,7 +381,7 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
 
     public void switchToBandagedCreator(int objectOrdinal)
       {
-      Intent intent = new Intent(this, BandagedCreatorActivity.class);
+      Intent intent = new Intent(this, BandagedActivity.class);
       intent.putExtra("obj", objectOrdinal);
       startActivity(intent);
       }
@@ -402,6 +404,19 @@ public class MainActivity extends AppCompatActivity implements RubikNetwork.Upda
       startActivity(intent);
       }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public void switchToPlay(RubikObject object, int scrambles, boolean free)
+      {
+      Intent intent = new Intent(this, PlayActivity.class);
+      intent.putExtra("name", object.getLowerName());
+      intent.putExtra("scrambles", scrambles);
+      intent.putExtra("local", object.isLocal() );
+      intent.putExtra("ordinal", object.getObjectOrdinal() );
+      intent.putExtra("free", free );
+      startActivity(intent);
+      }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
     public void onScores(View v)
diff --git a/src/main/java/org/distorted/main/MainObjectPopup.java b/src/main/java/org/distorted/main/MainObjectPopup.java
index a4f5b0b8..b0a34cd0 100644
--- a/src/main/java/org/distorted/main/MainObjectPopup.java
+++ b/src/main/java/org/distorted/main/MainObjectPopup.java
@@ -11,7 +11,6 @@ package org.distorted.main;
 
 import static android.view.View.GONE;
 
-import android.app.Activity;
 import android.content.Context;
 import android.util.TypedValue;
 import android.view.Gravity;
@@ -154,7 +153,7 @@ public class MainObjectPopup
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void setupLevelButtons(Activity act, RubikObject object, View layout, int width,int padding, int margin)
+  private void setupLevelButtons(MainActivity act, RubikObject object, View layout, int width,int padding, int margin)
     {
     int layoutWidth = (int)(width*MENU_WIDTH);
     int levelHeight = (int)(width*BUTTON_HEIGHT);
@@ -175,7 +174,7 @@ public class MainObjectPopup
     level[7] = layout.findViewById(R.id.level7);
     level[8] = layout.findViewById(R.id.levelM);
 
-    int numScramble = object==null ? 1 : object.getNumScramble();
+    int numScramble  = object==null ? 1 : object.getNumScramble();
     int min = Math.min(numScramble,LEVELS_SHOWN);
 
     if( numScramble>=1 && numScramble<=7 )
@@ -186,17 +185,20 @@ public class MainObjectPopup
 
     for(int i=0; i<=min; i++)
       {
-      final int ii = i;
       level[i].setTextSize(TypedValue.COMPLEX_UNIT_PX, mMenuTextSize);
       level[i].setLayoutParams(params);
       level[i].setPadding(0,0,0,0);
 
+      boolean free = i==0;
+      int scrambles = (i>0 && i<min) ? i : numScramble;
+
       level[i].setOnClickListener( new View.OnClickListener()
         {
         @Override
         public void onClick(View v)
           {
-
+          mPopup.dismiss();
+          act.switchToPlay(object,scrambles,free);
           }
         });
       }
diff --git a/src/main/java/org/distorted/main_old/RubikActivity.java b/src/main/java/org/distorted/main_old/RubikActivity.java
index 89bbb73b..203e0da0 100644
--- a/src/main/java/org/distorted/main_old/RubikActivity.java
+++ b/src/main/java/org/distorted/main_old/RubikActivity.java
@@ -33,7 +33,7 @@ import com.google.firebase.analytics.FirebaseAnalytics;
 import com.google.firebase.inappmessaging.FirebaseInAppMessaging;
 
 import org.distorted.config.ConfigActivity;
-import org.distorted.bandaged.BandagedCreatorActivity;
+import org.distorted.bandaged.BandagedActivity;
 import org.distorted.dialogs.RubikDialogAbout;
 import org.distorted.library.main.DistortedLibrary;
 
@@ -571,7 +571,6 @@ public class RubikActivity extends AppCompatActivity
     public void changeIfDifferent(int ordinal, ObjectControl control)
       {
       RubikObject object = RubikObjectList.getObject(ordinal);
-      //int meshState = object!=null ? object.getMeshState() : MESH_NICE;
       int iconMode  = TwistyObject.MODE_NORM;
       InputStream jsonStream = object==null ? null : object.getObjectStream(this);
       InputStream meshStream = object==null ? null : object.getMeshStream(this);
@@ -606,7 +605,7 @@ public class RubikActivity extends AppCompatActivity
 
     public void switchToBandagedCreator(int objectOrdinal)
       {
-      Intent intent = new Intent(this, BandagedCreatorActivity.class);
+      Intent intent = new Intent(this, BandagedActivity.class);
       intent.putExtra("obj", objectOrdinal);
       startActivity(intent);
       }
diff --git a/src/main/java/org/distorted/objects/RubikObject.java b/src/main/java/org/distorted/objects/RubikObject.java
index 38f21223..5aef3f6c 100644
--- a/src/main/java/org/distorted/objects/RubikObject.java
+++ b/src/main/java/org/distorted/objects/RubikObject.java
@@ -43,19 +43,21 @@ public class RubikObject
   private final int mIconID;
   private final String[][] mPatterns;
   private final int mPrice;
+  private final int mSolverOrdinal;
+  private final int mObjectOrdinal;
+  private final boolean mIsLocal;
 
   private boolean mIsFree;
   private int mJsonID, mMeshID, mExtrasID;
   private int mObjectVersion, mExtrasVersion;
   private int mNumScramble;
   private int mExtrasOrdinal;
-  private int mSolverOrdinal;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   RubikObject(ObjectType type)
     {
-    int ordinal= type.ordinal();
+    mObjectOrdinal= type.ordinal();
 
     mUpperName   = type.name();
     mLowerName   = type.name().toLowerCase(Locale.ENGLISH);
@@ -63,18 +65,19 @@ public class RubikObject
     mPrice       = type.getPrice();
     mIsFree      = mPrice==0;
     mIconID      = type.getIconID();
-    mJsonID      = ObjectJson.getObjectJsonID(ordinal);
-    mMeshID      = ObjectMesh.getMeshID(ordinal);
-    mExtrasID    = ObjectJson.getExtrasJsonID(ordinal);
+    mJsonID      = ObjectJson.getObjectJsonID(mObjectOrdinal);
+    mMeshID      = ObjectMesh.getMeshID(mObjectOrdinal);
+    mExtrasID    = ObjectJson.getExtrasJsonID(mObjectOrdinal);
+    mIsLocal     = false;
 
-    int patternOrdinal  = RubikPatternList.getOrdinal(ordinal);
+    int patternOrdinal  = RubikPatternList.getOrdinal(mObjectOrdinal);
     mPatterns = RubikPatternList.getPatterns(patternOrdinal);
 
-    mSolverOrdinal = ImplementedSolversList.getSolverOrdinal(ordinal);
+    mSolverOrdinal = ImplementedSolversList.getSolverOrdinal(mObjectOrdinal);
     mExtrasOrdinal = -1;
 
-    mObjectVersion = ObjectType.getObjectVersion(ordinal);
-    mExtrasVersion = ObjectType.getExtrasVersion(ordinal);
+    mObjectVersion = ObjectType.getObjectVersion(mObjectOrdinal);
+    mExtrasVersion = ObjectType.getExtrasVersion(mObjectOrdinal);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -94,6 +97,8 @@ public class RubikObject
     mPatterns      = null;
     mExtrasOrdinal = -1;
     mSolverOrdinal = -1;
+    mObjectOrdinal = -1;
+    mIsLocal       = true;
 
     mMeshID        =  0;
     mJsonID        = object.object ? -1 : 0;
@@ -304,6 +309,20 @@ public class RubikObject
     return mSolverOrdinal;
     }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getObjectOrdinal()
+    {
+    return mObjectOrdinal;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public boolean isLocal()
+    {
+    return mIsLocal;
+    }
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public boolean hasPatterns()
diff --git a/src/main/java/org/distorted/playui/PlayActivity.java b/src/main/java/org/distorted/playui/PlayActivity.java
new file mode 100644
index 00000000..0d87a6fe
--- /dev/null
+++ b/src/main/java/org/distorted/playui/PlayActivity.java
@@ -0,0 +1,319 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 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.playui;
+
+import java.io.InputStream;
+
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.DisplayCutout;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.PreferenceManager;
+
+import org.distorted.library.main.DistortedLibrary;
+import org.distorted.objectlib.main.InitAssets;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.objectlib.main.TwistyObject;
+import org.distorted.dialogs.RubikDialogError;
+import org.distorted.external.RubikFiles;
+import org.distorted.main.MainActivity;
+import org.distorted.main.R;
+import org.distorted.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+import org.distorted.os.OSInterface;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class PlayActivity extends AppCompatActivity
+{
+    private static final int ACTIVITY_NUMBER = 4;
+    private static final float RATIO_BAR     = MainActivity.RATIO_BAR;
+    private static final float RATIO_INSET   = 0.09f;
+    public static final int FLAGS            = MainActivity.FLAGS;
+
+    private static int mScreenWidth, mScreenHeight;
+    private int mCurrentApiVersion;
+    private PlayScreen mScreen;
+    private String mObjectName;
+    private int mNumScrambles;
+    private int mHeightUpperBar;
+    private boolean mObjectLocal;
+    private int mObjectOrdinal;
+    private boolean mModeFree;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected void onCreate(Bundle savedState)
+      {
+      super.onCreate(savedState);
+      DistortedLibrary.onCreate(ACTIVITY_NUMBER);
+      setTheme(R.style.MaterialThemeNoActionBar);
+      setContentView(R.layout.play);
+
+      Bundle b = getIntent().getExtras();
+
+      if( b!=null )
+        {
+        mObjectName    = b.getString("name");
+        mNumScrambles  = b.getInt("scrambles");
+        mObjectLocal   = b.getBoolean("local");
+        mObjectOrdinal = b.getInt("ordinal");
+        mModeFree      = b.getBoolean("free");
+        }
+      else
+        {
+        mObjectName = "";
+        mNumScrambles = 0;
+        mObjectLocal = true;
+        mObjectOrdinal = 0;
+        mModeFree = true;
+        }
+
+      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);
+      mHeightUpperBar = barHeight;
+
+      LinearLayout layoutTop = findViewById(R.id.upperBar);
+      LinearLayout layoutBot = findViewById(R.id.lowerBar);
+
+      ViewGroup.LayoutParams paramsTop = layoutTop.getLayoutParams();
+      paramsTop.height = mHeightUpperBar;
+      layoutTop.setLayoutParams(paramsTop);
+      ViewGroup.LayoutParams paramsBot = layoutBot.getLayoutParams();
+      paramsBot.height = barHeight;
+      layoutBot.setLayoutParams(paramsBot);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    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);
+              }
+            }
+          });
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void onAttachedToWindow()
+      {
+      super.onAttachedToWindow();
+
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
+        {
+        DisplayCutout cutout = getWindow().getDecorView().getRootWindowInsets().getDisplayCutout();
+        int insetHeight = cutout!=null ? cutout.getSafeInsetTop() : 0;
+
+        LinearLayout layoutHid = findViewById(R.id.hiddenBar);
+        ViewGroup.LayoutParams paramsHid = layoutHid.getLayoutParams();
+        paramsHid.height = (int)(insetHeight*RATIO_INSET);
+        layoutHid.setLayoutParams(paramsHid);
+        mHeightUpperBar += paramsHid.height;
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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();
+      PlayView view = findViewById(R.id.playView);
+      view.onPause();
+      savePreferences();
+      DistortedLibrary.onPause(ACTIVITY_NUMBER);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onResume() 
+      {
+      super.onResume();
+      DistortedLibrary.onResume(ACTIVITY_NUMBER);
+      PlayView view = findViewById(R.id.playView);
+      view.onResume();
+
+      if( mScreen==null ) mScreen = new PlayScreen();
+      mScreen.onAttachedToWindow(this, mObjectName);
+      restorePreferences();
+
+      if( mObjectName.length()>0 )
+        {
+        changeIfDifferent(mObjectName,mObjectLocal,mObjectOrdinal,view.getObjectControl());
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+    
+    @Override
+    protected void onDestroy() 
+      {
+      super.onDestroy();
+      DistortedLibrary.onDestroy(ACTIVITY_NUMBER);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void savePreferences()
+    {
+    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+    SharedPreferences.Editor editor = preferences.edit();
+    mScreen.savePreferences(this,editor);
+
+    editor.apply();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void restorePreferences()
+    {
+    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+    mScreen.restorePreferences(this,preferences);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void OpenGLError()
+      {
+      RubikDialogError errDiag = new RubikDialogError();
+      errDiag.show(getSupportFragmentManager(), null);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void changeIfDifferent(String upperName, boolean local, int ordinal, ObjectControl control)
+      {
+      android.util.Log.e("D", "changing to "+upperName+" local: "+local+" ordinal: "+ordinal);
+
+      if( local )
+        {
+        RubikFiles files = RubikFiles.getInstance();
+        int iconMode = TwistyObject.MODE_NORM;
+        String lowerName = upperName.toLowerCase();
+        InputStream jsonStream = files.openFile(this, lowerName+"_object.json");
+        InitAssets asset = new InitAssets(jsonStream, null, null);
+        control.changeIfDifferent(ordinal,upperName,iconMode,asset);
+        }
+      else
+        {
+        RubikObject object = RubikObjectList.getObject(ordinal);
+        int iconMode = TwistyObject.MODE_NORM;
+        InputStream jsonStream = object==null ? null : object.getObjectStream(this);
+        InputStream meshStream = object==null ? null : object.getMeshStream(this);
+        PlayView view = findViewById(R.id.playView);
+        OSInterface os = view.getInterface();
+        InitAssets asset = new InitAssets(jsonStream, meshStream, os);
+        control.changeIfDifferent(ordinal, upperName, iconMode, asset);
+        }
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenWidthInPixels()
+      {
+      return mScreenWidth;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getScreenHeightInPixels()
+      {
+      return mScreenHeight;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public int getNumScrambles()
+      {
+      return mNumScrambles;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public ObjectControl getControl()
+      {
+      PlayView view = findViewById(R.id.playView);
+      return view.getObjectControl();
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public PlayScreen getScreen()
+      {
+      return mScreen;
+      }
+}
diff --git a/src/main/java/org/distorted/playui/PlayLibInterface.java b/src/main/java/org/distorted/playui/PlayLibInterface.java
new file mode 100644
index 00000000..8f63aeab
--- /dev/null
+++ b/src/main/java/org/distorted/playui/PlayLibInterface.java
@@ -0,0 +1,189 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.playui;
+
+import java.lang.ref.WeakReference;
+
+import com.google.firebase.crashlytics.FirebaseCrashlytics;
+
+import org.distorted.library.message.EffectMessageSender;
+import org.distorted.objectlib.helpers.BlockController;
+import org.distorted.objectlib.helpers.ObjectLibInterface;
+import org.distorted.objects.RubikObject;
+import org.distorted.objects.RubikObjectList;
+import org.distorted.main.BuildConfig;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class PlayLibInterface implements ObjectLibInterface
+{
+  private final WeakReference<PlayActivity> mAct;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  PlayLibInterface(PlayActivity act)
+    {
+    mAct = new WeakReference<>(act);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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 onReplaceModeDown(int cubit, int face) { }
+  public void onReplaceModeUp() { }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void onFinishRotation(int axis, int row, int angle)
+    {
+    PlayActivity act = mAct.get();
+
+    if( act!=null )
+      {
+      PlayScreen play = act.getScreen();
+      play.addMove(act,axis,row,angle);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void failedToDrag()
+    {
+    PlayActivity act = mAct.get();
+
+    if( act!=null )
+      {
+      PlayScreen play = act.getScreen();
+      play.reddenLock(act);
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  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;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void reportJSONError(String error, int ordinal)
+    {
+    RubikObject object = RubikObjectList.getObject(ordinal);
+    String name = object==null ? "NULL" : object.getUpperName();
+
+    if( BuildConfig.DEBUG )
+       {
+       android.util.Log.e("libInterface", "name="+name+" JSON error: "+error);
+       }
+    else
+      {
+      Exception ex = new Exception(error);
+      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
+      crashlytics.setCustomKey("name" , name );
+      crashlytics.setCustomKey("JSONerror", error );
+      crashlytics.recordException(ex);
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/playui/PlayRenderer.java b/src/main/java/org/distorted/playui/PlayRenderer.java
new file mode 100644
index 00000000..ed1064f5
--- /dev/null
+++ b/src/main/java/org/distorted/playui/PlayRenderer.java
@@ -0,0 +1,111 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2022 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.playui;
+
+import java.io.InputStream;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.content.res.Resources;
+import android.opengl.GLSurfaceView;
+
+import org.distorted.library.effect.EffectType;
+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.mesh.MeshBase;
+import org.distorted.objectlib.effects.BaseEffect;
+import org.distorted.objectlib.main.ObjectControl;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class PlayRenderer implements GLSurfaceView.Renderer, DistortedLibrary.LibraryUser
+{
+   private final PlayView mView;
+   private final Resources mResources;
+   private final DistortedScreen mScreen;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   PlayRenderer(PlayView v)
+     {
+     final float BRIGHTNESS = 0.333f;
+
+     mView = v;
+     mResources = v.getResources();
+     mScreen = new DistortedScreen();
+     mScreen.glClearColor(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS, 1.0f);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @Override
+   public void onDrawFrame(GL10 glUnused)
+     {
+     long time = System.currentTimeMillis();
+     mView.getObjectControl().preRender();
+     mScreen.render(time);
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   @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);
+      MeshBase.setMaxEffComponents(ObjectControl.MAX_MOVING_PARTS);
+
+      VertexEffectRotate.enable();
+      VertexEffectQuaternion.enable();
+      BaseEffect.Type.enableEffects();
+
+      DistortedLibrary.onSurfaceCreated(this,1);
+      DistortedLibrary.setCull(true);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void distortedException(Exception ex)
+     {
+     android.util.Log.e("Play", "unexpected exception: "+ex.getMessage() );
+     }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public InputStream localFile(int fileID)
+      {
+      return mResources.openRawResource(fileID);
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+   public void logMessage(String message)
+      {
+      android.util.Log.e("Play", message );
+      }
+}
diff --git a/src/main/java/org/distorted/playui/PlayScreen.java b/src/main/java/org/distorted/playui/PlayScreen.java
new file mode 100644
index 00000000..2eb7b379
--- /dev/null
+++ b/src/main/java/org/distorted/playui/PlayScreen.java
@@ -0,0 +1,190 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.playui;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import org.distorted.objectlib.effects.BaseEffect;
+import org.distorted.objectlib.main.ObjectControl;
+import org.distorted.os.OSInterface;
+import org.distorted.helpers.LockController;
+import org.distorted.helpers.MovesController;
+import org.distorted.helpers.TransparentImageButton;
+import org.distorted.main.R;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class PlayScreen
+{
+  private TransparentImageButton mBackButton, mScrambleButton, mSolveButton;
+  private final LockController mLockController;
+  private final MovesController mMovesController;
+  private String mKey;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public PlayScreen()
+    {
+    mLockController = new LockController();
+    mMovesController= new MovesController();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupBackButton(final PlayActivity 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 setupSolveButton(final PlayActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mSolveButton = new TransparentImageButton(act,R.drawable.ui_cube_solve,params);
+
+    mSolveButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ObjectControl control = act.getControl();
+        control.solveObject();
+        mMovesController.clearMoves(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void setupScrambleButton(final PlayActivity act)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT,1.0f);
+    mScrambleButton = new TransparentImageButton(act,R.drawable.ui_cube_scramble,params);
+
+    mScrambleButton.setOnClickListener( new View.OnClickListener()
+      {
+      @Override
+      public void onClick(View v)
+        {
+        ObjectControl control = act.getControl();
+        int duration = BaseEffect.Type.FAST_SCRAMBLE.getDuration();
+        int numScrambles = act.getNumScrambles();
+        control.fastScrambleObject(duration,numScrambles);
+        mMovesController.clearMoves(act);
+        }
+      });
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void onAttachedToWindow(final PlayActivity act, String objectName)
+    {
+    mKey = "moveController_"+objectName;
+
+    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);
+    ObjectControl control = act.getControl();
+    mMovesController.setupButton(act,control);
+    layoutLeft.addView(mMovesController.getButton());
+    mLockController.setupButton(act,control);
+    layoutMid.addView(mLockController.getButton());
+
+    layoutRight.addView(mBackButton);
+
+    LinearLayout layoutLower = act.findViewById(R.id.lowerBar);
+    layoutLower.removeAllViews();
+    layoutLower.addView(layoutLeft);
+    layoutLower.addView(layoutMid);
+    layoutLower.addView(layoutRight);
+
+    setupSolveButton(act);
+    setupScrambleButton(act);
+
+    LinearLayout layoutUpper = act.findViewById(R.id.upperBar);
+
+    LinearLayout layoutLeftU = new LinearLayout(act);
+    layoutLeftU.setLayoutParams(paramsL);
+    LinearLayout layoutMidU  = new LinearLayout(act);
+    layoutMidU.setLayoutParams(paramsM);
+    LinearLayout layoutRightU= new LinearLayout(act);
+    layoutRightU.setLayoutParams(paramsR);
+
+    layoutLeftU.addView(mSolveButton);
+    layoutRightU.addView(mScrambleButton);
+
+    layoutUpper.removeAllViews();
+    layoutUpper.addView(layoutLeftU);
+    layoutUpper.addView(layoutMidU);
+    layoutUpper.addView(layoutRightU);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void reddenLock(final PlayActivity act)
+    {
+    ObjectControl control = act.getControl();
+    mLockController.reddenLock(act,control);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void addMove(Activity act, int axis, int row, int angle)
+    {
+    mMovesController.addMove(act,axis,row,angle);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void savePreferences(PlayActivity act, SharedPreferences.Editor editor)
+    {
+    mMovesController.savePreferences(mKey,editor);
+    ObjectControl control = act.getControl();
+    OSInterface os = (OSInterface)control.getOS();
+    os.setEditor(editor);
+    control.savePreferences();
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void restorePreferences(PlayActivity act, SharedPreferences preferences)
+    {
+    mMovesController.restorePreferences(act,mKey,preferences);
+    ObjectControl control = act.getControl();
+    OSInterface os = (OSInterface)control.getOS();
+    os.setPreferences(preferences);
+    control.restorePreferences();
+    }
+}
diff --git a/src/main/java/org/distorted/playui/PlayView.java b/src/main/java/org/distorted/playui/PlayView.java
new file mode 100644
index 00000000..e8d14a87
--- /dev/null
+++ b/src/main/java/org/distorted/playui/PlayView.java
@@ -0,0 +1,143 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.playui;
+
+import static org.distorted.objectlib.main.ObjectControl.MODE_ROTATE;
+
+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 PlayView extends GLSurfaceView
+{
+    private ObjectControl mObjectController;
+    private OSInterface mInterface;
+    private PlayRenderer mRenderer;
+    private boolean mCreated;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    void setScreenSize(int width, int height)
+      {
+      mObjectController.setScreenSizeAndScaling(width,height, Math.min(width, (int)(0.75f*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;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    OSInterface getInterface()
+      {
+      return mInterface;
+      }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// PUBLIC API
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+    public PlayView(Context context, AttributeSet attrs)
+      {
+      super(context,attrs);
+
+      mCreated = false;
+
+      if(!isInEditMode())
+        {
+        PlayActivity act = (PlayActivity)context;
+        PlayLibInterface ref = new PlayLibInterface(act);
+        mInterface = new OSInterface(act,ref);
+        mObjectController = new ObjectControl(mInterface);
+        mObjectController.setRotateOnCreation(true);
+        mRenderer = new PlayRenderer(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_ROTATE);
+      }
+}
+
diff --git a/src/main/res/layout/bandaged.xml b/src/main/res/layout/bandaged.xml
index 2f70b009..d4fc8b05 100644
--- a/src/main/res/layout/bandaged.xml
+++ b/src/main/res/layout/bandaged.xml
@@ -32,7 +32,7 @@
        android:layout_width="match_parent"
        android:layout_height="match_parent">
 
-       <org.distorted.bandaged.BandagedCreatorView
+       <org.distorted.bandaged.BandagedView
            android:id="@+id/bandagedCreatorObjectView"
            android:layout_width="match_parent"
            android:layout_height="0dp"
diff --git a/src/main/res/layout/bandaged_play.xml b/src/main/res/layout/bandaged_play.xml
deleted file mode 100644
index 6199bf35..00000000
--- a/src/main/res/layout/bandaged_play.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/relativeLayout"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <org.distorted.bandaged.BandagedPlayView
-        android:id="@+id/bandagedPlayView"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_alignParentStart="true"
-        android:layout_alignParentTop="true"/>
-
-    <LinearLayout
-        android:id="@+id/hiddenBar"
-        android:layout_alignParentTop="true"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:gravity="center"
-        android:orientation="horizontal"
-        android:background="@android:color/transparent">
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/upperBar"
-        android:layout_below="@id/hiddenBar"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:gravity="center"
-        android:orientation="horizontal"
-        android:background="@android:color/transparent">
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/lowerBar"
-        android:layout_alignParentBottom="true"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:orientation="horizontal"
-        android:background="@android:color/transparent">
-    </LinearLayout>
-
-</RelativeLayout>
diff --git a/src/main/res/layout/play.xml b/src/main/res/layout/play.xml
new file mode 100644
index 00000000..e7f38ace
--- /dev/null
+++ b/src/main/res/layout/play.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/relativeLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <org.distorted.playui.PlayView
+        android:id="@+id/playView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentStart="true"
+        android:layout_alignParentTop="true"/>
+
+    <LinearLayout
+        android:id="@+id/hiddenBar"
+        android:layout_alignParentTop="true"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/upperBar"
+        android:layout_below="@id/hiddenBar"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/lowerBar"
+        android:layout_alignParentBottom="true"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:orientation="horizontal"
+        android:background="@android:color/transparent">
+    </LinearLayout>
+
+</RelativeLayout>
