gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/GioView.java (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package org.gioui;
     4  
     5  import android.content.Context;
     6  import android.graphics.Rect;
     7  import android.os.Build;
     8  import android.os.Handler;
     9  import android.util.AttributeSet;
    10  import android.text.Editable;
    11  import android.view.Choreographer;
    12  import android.view.KeyCharacterMap;
    13  import android.view.KeyEvent;
    14  import android.view.MotionEvent;
    15  import android.view.View;
    16  import android.view.WindowInsets;
    17  import android.view.Surface;
    18  import android.view.SurfaceView;
    19  import android.view.SurfaceHolder;
    20  import android.view.inputmethod.BaseInputConnection;
    21  import android.view.inputmethod.InputConnection;
    22  import android.view.inputmethod.InputMethodManager;
    23  import android.view.inputmethod.EditorInfo;
    24  
    25  import java.io.UnsupportedEncodingException;
    26  
    27  public class GioView extends SurfaceView implements Choreographer.FrameCallback {
    28  	private final static Object initLock = new Object();
    29  	private static boolean jniLoaded;
    30  
    31  	private final SurfaceHolder.Callback callbacks;
    32  	private final InputMethodManager imm;
    33  	private final Handler handler;
    34  	private long nhandle;
    35  
    36  	private static synchronized void initialize(Context appCtx) {
    37  		synchronized (initLock) {
    38  			if (jniLoaded) {
    39  				return;
    40  			}
    41  			String dataDir = appCtx.getFilesDir().getAbsolutePath();
    42  			byte[] dataDirUTF8;
    43  			try {
    44  				dataDirUTF8 = dataDir.getBytes("UTF-8");
    45  			} catch (UnsupportedEncodingException e) {
    46  				throw new RuntimeException(e);
    47  			}
    48  			System.loadLibrary("gio");
    49  			runGoMain(dataDirUTF8);
    50  			jniLoaded = true;
    51  		}
    52  	}
    53  
    54  	public GioView(Context context) {
    55  		this(context, null);
    56  	}
    57  
    58  	public GioView(Context context, AttributeSet attrs) {
    59  		super(context, attrs);
    60  		// Late initialization of the Go runtime to wait for a valid context.
    61  		initialize(context.getApplicationContext());
    62  
    63  		nhandle = onCreateView(this);
    64  		handler = new Handler();
    65  		imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
    66  		setFocusable(true);
    67  		setFocusableInTouchMode(true);
    68  		setOnFocusChangeListener(new View.OnFocusChangeListener() {
    69  			@Override public void onFocusChange(View v, boolean focus) {
    70  				GioView.this.onFocusChange(nhandle, focus);
    71  			}
    72  		});
    73  		callbacks = new SurfaceHolder.Callback() {
    74  			@Override public void surfaceCreated(SurfaceHolder holder) {
    75  				// Ignore; surfaceChanged is guaranteed to be called immediately after this.
    76  			}
    77  			@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    78  				onSurfaceChanged(nhandle, getHolder().getSurface());
    79  			}
    80  			@Override public void surfaceDestroyed(SurfaceHolder holder) {
    81  				onSurfaceDestroyed(nhandle);
    82  			}
    83  		};
    84  		getHolder().addCallback(callbacks);
    85  	}
    86  
    87  	@Override public boolean onKeyDown(int keyCode, KeyEvent event) {
    88  		onKeyEvent(nhandle, keyCode, event.getUnicodeChar(), event.getEventTime());
    89  		return false;
    90  	}
    91  
    92  	@Override public boolean onTouchEvent(MotionEvent event) {
    93  		// Ask for unbuffered events. Flutter and Chrome does it
    94  		// so I assume its good for us as well.
    95  		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    96  			requestUnbufferedDispatch(event);
    97  		}
    98  
    99  		for (int j = 0; j < event.getHistorySize(); j++) {
   100  			long time = event.getHistoricalEventTime(j);
   101  			for (int i = 0; i < event.getPointerCount(); i++) {
   102  				onTouchEvent(
   103  						nhandle,
   104  						event.ACTION_MOVE,
   105  						event.getPointerId(i),
   106  						event.getToolType(i),
   107  						event.getHistoricalX(i, j),
   108  						event.getHistoricalY(i, j),
   109  						time);
   110  			}
   111  		}
   112  		int act = event.getActionMasked();
   113  		int idx = event.getActionIndex();
   114  		for (int i = 0; i < event.getPointerCount(); i++) {
   115  			int pact = event.ACTION_MOVE;
   116  			if (i == idx) {
   117  				pact = act;
   118  			}
   119  			onTouchEvent(
   120  					nhandle,
   121  					act,
   122  					event.getPointerId(i),
   123  					event.getToolType(i),
   124  					event.getX(i),
   125  					event.getY(i),
   126  					event.getEventTime());
   127  		}
   128  		return true;
   129  	}
   130  
   131  	@Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
   132  		return new InputConnection(this);
   133  	}
   134  
   135  	void showTextInput() {
   136  		post(new Runnable() {
   137  			@Override public void run() {
   138  				GioView.this.requestFocus();
   139  				imm.showSoftInput(GioView.this, 0);
   140  			}
   141  		});
   142  	}
   143  
   144  	void hideTextInput() {
   145  		post(new Runnable() {
   146  			@Override public void run() {
   147  				imm.hideSoftInputFromWindow(getWindowToken(), 0);
   148  			}
   149  		});
   150  	}
   151  
   152  	void postFrameCallbackOnMainThread() {
   153  		handler.post(new Runnable() {
   154  			@Override public void run() {
   155  				postFrameCallback();
   156  			}
   157  		});
   158  	}
   159  
   160  	@Override protected boolean fitSystemWindows(Rect insets) {
   161  		onWindowInsets(nhandle, insets.top, insets.right, insets.bottom, insets.left);
   162  		return true;
   163  	}
   164  
   165  	void postFrameCallback() {
   166  		Choreographer.getInstance().removeFrameCallback(this);
   167  		Choreographer.getInstance().postFrameCallback(this);
   168  	}
   169  
   170  	@Override public void doFrame(long nanos) {
   171  		onFrameCallback(nhandle, nanos);
   172  	}
   173  
   174  	int getDensity() {
   175  		return getResources().getDisplayMetrics().densityDpi;
   176  	}
   177  
   178  	float getFontScale() {
   179  		return getResources().getConfiguration().fontScale;
   180  	}
   181  
   182  	void start() {
   183  		onStartView(nhandle);
   184  	}
   185  
   186  	void stop() {
   187  		onStopView(nhandle);
   188  	}
   189  
   190  	void destroy() {
   191  		getHolder().removeCallback(callbacks);
   192  		onDestroyView(nhandle);
   193  		nhandle = 0;
   194  	}
   195  
   196  	void configurationChanged() {
   197  		onConfigurationChanged(nhandle);
   198  	}
   199  
   200  	void lowMemory() {
   201  		onLowMemory();
   202  	}
   203  
   204  	boolean backPressed() {
   205  		return onBack(nhandle);
   206  	}
   207  
   208  	static private native long onCreateView(GioView view);
   209  	static private native void onDestroyView(long handle);
   210  	static private native void onStartView(long handle);
   211  	static private native void onStopView(long handle);
   212  	static private native void onSurfaceDestroyed(long handle);
   213  	static private native void onSurfaceChanged(long handle, Surface surface);
   214  	static private native void onConfigurationChanged(long handle);
   215  	static private native void onWindowInsets(long handle, int top, int right, int bottom, int left);
   216  	static private native void onLowMemory();
   217  	static private native void onTouchEvent(long handle, int action, int pointerID, int tool, float x, float y, long time);
   218  	static private native void onKeyEvent(long handle, int code, int character, long time);
   219  	static private native void onFrameCallback(long handle, long nanos);
   220  	static private native boolean onBack(long handle);
   221  	static private native void onFocusChange(long handle, boolean focus);
   222  	static private native void runGoMain(byte[] dataDir);
   223  
   224  	private static class InputConnection extends BaseInputConnection {
   225  		private final Editable editable;
   226  
   227  		InputConnection(View view) {
   228  			// Passing false enables "dummy mode", where the BaseInputConnection
   229  			// attempts to convert IME operations to key events.
   230  			super(view, false);
   231  			editable = Editable.Factory.getInstance().newEditable("");
   232  		}
   233  
   234  		@Override public Editable getEditable() {
   235  			return editable;
   236  		}
   237  	}
   238  }