package TARGET_PACKAGE_NAME; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.app.Activity; import android.os.Bundle; import android.os.Build; import android.util.Log; import android.view.View; import android.view.Surface; import android.view.Window; import android.view.WindowInsets; import android.view.WindowManager.LayoutParams; import android.view.SurfaceView; import android.view.SurfaceHolder; import android.view.MotionEvent; import android.view.KeyEvent; import android.view.inputmethod.InputMethodManager; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Insets; import android.view.inputmethod.InputConnection; import android.view.inputmethod.EditorInfo; import android.widget.LinearLayout; import quad_native.QuadNative; // note: //% is a special miniquad's pre-processor for plugins // when there are no plugins - //% whatever will be replaced to an empty string // before compiling //% IMPORTS class QuadSurface extends SurfaceView implements View.OnTouchListener, View.OnKeyListener, SurfaceHolder.Callback { public QuadSurface(Context context){ super(context); getHolder().addCallback(this); setFocusable(true); setFocusableInTouchMode(true); requestFocus(); setOnTouchListener(this); setOnKeyListener(this); } @Override public void surfaceCreated(SurfaceHolder holder) { Log.i("SAPP", "surfaceCreated"); Surface surface = holder.getSurface(); QuadNative.surfaceOnSurfaceCreated(surface); } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.i("SAPP", "surfaceDestroyed"); Surface surface = holder.getSurface(); QuadNative.surfaceOnSurfaceDestroyed(surface); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.i("SAPP", "surfaceChanged"); Surface surface = holder.getSurface(); QuadNative.surfaceOnSurfaceChanged(surface, width, height); } @Override public boolean onTouch(View v, MotionEvent event) { int pointerCount = event.getPointerCount(); int action = event.getActionMasked(); switch(action) { case MotionEvent.ACTION_MOVE: { for (int i = 0; i < pointerCount; i++) { final int id = event.getPointerId(i); final float x = event.getX(i); final float y = event.getY(i); QuadNative.surfaceOnTouch(id, 0, x, y); } break; } case MotionEvent.ACTION_UP: { final int id = event.getPointerId(0); final float x = event.getX(0); final float y = event.getY(0); QuadNative.surfaceOnTouch(id, 1, x, y); break; } case MotionEvent.ACTION_DOWN: { final int id = event.getPointerId(0); final float x = event.getX(0); final float y = event.getY(0); QuadNative.surfaceOnTouch(id, 2, x, y); break; } case MotionEvent.ACTION_POINTER_UP: { final int pointerIndex = event.getActionIndex(); final int id = event.getPointerId(pointerIndex); final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); QuadNative.surfaceOnTouch(id, 1, x, y); break; } case MotionEvent.ACTION_POINTER_DOWN: { final int pointerIndex = event.getActionIndex(); final int id = event.getPointerId(pointerIndex); final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); QuadNative.surfaceOnTouch(id, 2, x, y); break; } case MotionEvent.ACTION_CANCEL: { for (int i = 0; i < pointerCount; i++) { final int id = event.getPointerId(i); final float x = event.getX(i); final float y = event.getY(i); QuadNative.surfaceOnTouch(id, 3, x, y); } break; } default: break; } return true; } // docs says getCharacters are deprecated // but somehow on non-latyn input all keyCode and all the relevant fields in the KeyEvent are zeros // and only getCharacters has some usefull data @SuppressWarnings("deprecation") @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode != 0) { QuadNative.surfaceOnKeyDown(keyCode); } if (event.getAction() == KeyEvent.ACTION_UP && keyCode != 0) { QuadNative.surfaceOnKeyUp(keyCode); } if (event.getAction() == KeyEvent.ACTION_UP || event.getAction() == KeyEvent.ACTION_MULTIPLE) { int character = event.getUnicodeChar(); if (character == 0) { String characters = event.getCharacters(); if (characters != null && characters.length() >= 0) { character = characters.charAt(0); } } if (character != 0) { QuadNative.surfaceOnCharacter(character); } } return true; } // There is an Android bug when screen is in landscape, // the keyboard inset height is reported as 0. // This code is a workaround which fixes the bug. // See https://groups.google.com/g/android-developers/c/50XcWooqk7I // For some reason it only works if placed here and not in the parent layout. @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { InputConnection connection = super.onCreateInputConnection(outAttrs); outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN; return connection; } public Surface getNativeSurface() { return getHolder().getSurface(); } } class ResizingLayout extends LinearLayout implements View.OnApplyWindowInsetsListener { public ResizingLayout(Context context){ super(context); // When viewing in landscape mode with keyboard shown, there are // gaps on both sides so we fill the negative space with black. setBackgroundColor(Color.BLACK); setOnApplyWindowInsetsListener(this); } @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { if (Build.VERSION.SDK_INT >= 30) { Insets imeInsets = insets.getInsets(WindowInsets.Type.ime()); Insets sysInsets = insets.getInsets(WindowInsets.Type.systemBars()); // The sys insets change when orientation changes and sys bars // change position. v.setPadding( sysInsets.left, sysInsets.top, sysInsets.right, imeInsets.bottom + sysInsets.bottom ); } return insets; } } public class MainActivity extends Activity { //% MAIN_ACTIVITY_BODY private QuadSurface view; static { System.loadLibrary("LIBRARY_NAME"); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.requestWindowFeature(Window.FEATURE_NO_TITLE); view = new QuadSurface(this); // Put it inside a parent layout which can resize it using padding ResizingLayout layout = new ResizingLayout(this); layout.addView(view); setContentView(layout); QuadNative.activityOnCreate(this); //% MAIN_ACTIVITY_ON_CREATE } @Override protected void onResume() { super.onResume(); QuadNative.activityOnResume(); //% MAIN_ACTIVITY_ON_RESUME } @Override public void onBackPressed() { Log.w("SAPP", "onBackPressed"); // TODO: here is the place to handle request_quit/order_quit/cancel_quit super.onBackPressed(); } @Override protected void onStop() { super.onStop(); } @Override protected void onDestroy() { super.onDestroy(); QuadNative.activityOnDestroy(); } @Override protected void onPause() { super.onPause(); QuadNative.activityOnPause(); //% MAIN_ACTIVITY_ON_PAUSE } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { //% MAIN_ACTIVITY_ON_ACTIVITY_RESULT } public void setFullScreen(final boolean fullscreen) { runOnUiThread(new Runnable() { @Override public void run() { View decorView = getWindow().getDecorView(); if (fullscreen) { getWindow().setFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS, LayoutParams.FLAG_LAYOUT_NO_LIMITS); if (Build.VERSION.SDK_INT >= 28) { getWindow().getAttributes().layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; } if (Build.VERSION.SDK_INT >= 30) { getWindow().setDecorFitsSystemWindows(false); } else { int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; decorView.setSystemUiVisibility(uiOptions); } } else { if (Build.VERSION.SDK_INT >= 30) { getWindow().setDecorFitsSystemWindows(true); } else { decorView.setSystemUiVisibility(0); } } } }); } public void showKeyboard(final boolean show) { runOnUiThread(new Runnable() { @Override public void run() { if (show) { InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(view, 0); } else { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(),0); } } }); } }