mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-12 12:08:58 +01:00
parent
0afb6fd22a
commit
7bf822bdb1
101
cmd/ebitenmobile/_files/EbitenSurfaceView.java
Normal file
101
cmd/ebitenmobile/_files/EbitenSurfaceView.java
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2022 The Ebitengine Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package {{.JavaPkg}}.{{.PrefixLower}};
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.opengl.GLSurfaceView;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import javax.microedition.khronos.egl.EGLConfig;
|
||||||
|
import javax.microedition.khronos.opengles.GL10;
|
||||||
|
|
||||||
|
import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;
|
||||||
|
import {{.JavaPkg}}.ebitenmobileview.RenderRequester;
|
||||||
|
import {{.JavaPkg}}.{{.PrefixLower}}.EbitenView;
|
||||||
|
|
||||||
|
class EbitenSurfaceView extends GLSurfaceView implements RenderRequester {
|
||||||
|
|
||||||
|
private class EbitenRenderer implements GLSurfaceView.Renderer {
|
||||||
|
|
||||||
|
private boolean errored_ = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawFrame(GL10 gl) {
|
||||||
|
if (errored_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Ebitenmobileview.update();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onErrorOnGameUpdate(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
errored_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
|
||||||
|
Ebitenmobileview.onContextLost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceChanged(GL10 gl, int width, int height) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EbitenSurfaceView(Context context) {
|
||||||
|
super(context);
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EbitenSurfaceView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialize() {
|
||||||
|
setEGLContextClientVersion(2);
|
||||||
|
setEGLConfigChooser(8, 8, 8, 8, 0, 0);
|
||||||
|
setRenderer(new EbitenRenderer());
|
||||||
|
Ebitenmobileview.setRenderRequester(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onErrorOnGameUpdate(Exception e) {
|
||||||
|
((EbitenView)getParent()).onErrorOnGameUpdate(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setExplicitRenderingMode(boolean explicitRendering) {
|
||||||
|
if (explicitRendering) {
|
||||||
|
setRenderMode(RENDERMODE_WHEN_DIRTY);
|
||||||
|
} else {
|
||||||
|
setRenderMode(RENDERMODE_CONTINUOUSLY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void requestRenderIfNeeded() {
|
||||||
|
if (getRenderMode() == RENDERMODE_WHEN_DIRTY) {
|
||||||
|
requestRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
401
cmd/ebitenmobile/_files/EbitenView.java
Normal file
401
cmd/ebitenmobile/_files/EbitenView.java
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
// Copyright 2022 The Ebitengine Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package {{.JavaPkg}}.{{.PrefixLower}};
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.input.InputManager;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.InputDevice;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;
|
||||||
|
|
||||||
|
public class EbitenView extends ViewGroup implements InputManager.InputDeviceListener {
|
||||||
|
static class Gamepad {
|
||||||
|
public int deviceId;
|
||||||
|
public ArrayList<InputDevice.MotionRange> axes;
|
||||||
|
public ArrayList<InputDevice.MotionRange> hats;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L154-L173
|
||||||
|
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
|
||||||
|
@Override
|
||||||
|
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
|
||||||
|
int arg0Axis = arg0.getAxis();
|
||||||
|
int arg1Axis = arg1.getAxis();
|
||||||
|
if (arg0Axis == MotionEvent.AXIS_GAS) {
|
||||||
|
arg0Axis = MotionEvent.AXIS_BRAKE;
|
||||||
|
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
|
||||||
|
arg0Axis = MotionEvent.AXIS_GAS;
|
||||||
|
}
|
||||||
|
if (arg1Axis == MotionEvent.AXIS_GAS) {
|
||||||
|
arg1Axis = MotionEvent.AXIS_BRAKE;
|
||||||
|
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
|
||||||
|
arg1Axis = MotionEvent.AXIS_GAS;
|
||||||
|
}
|
||||||
|
return arg0Axis - arg1Axis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double pxToDp(double x) {
|
||||||
|
return x / Ebitenmobileview.deviceScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
public EbitenView(Context context) {
|
||||||
|
super(context);
|
||||||
|
initialize(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EbitenView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
initialize(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initialize(Context context) {
|
||||||
|
this.gamepads = new ArrayList<Gamepad>();
|
||||||
|
|
||||||
|
this.ebitenSurfaceView = new EbitenSurfaceView(getContext());
|
||||||
|
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||||
|
addView(this.ebitenSurfaceView, params);
|
||||||
|
|
||||||
|
this.inputManager = (InputManager)context.getSystemService(Context.INPUT_SERVICE);
|
||||||
|
this.inputManager.registerInputDeviceListener(this, null);
|
||||||
|
for (int id : this.inputManager.getInputDeviceIds()) {
|
||||||
|
this.onInputDeviceAdded(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||||
|
this.ebitenSurfaceView.layout(0, 0, right - left, bottom - top);
|
||||||
|
double widthInDp = pxToDp(right - left);
|
||||||
|
double heightInDp = pxToDp(bottom - top);
|
||||||
|
Ebitenmobileview.layout(widthInDp, heightInDp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
|
Ebitenmobileview.onKeyDownOnAndroid(keyCode, event.getUnicodeChar(), event.getSource(), event.getDeviceId());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||||
|
Ebitenmobileview.onKeyUpOnAndroid(keyCode, event.getSource(), event.getDeviceId());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent e) {
|
||||||
|
// getActionIndex returns a valid value only for the action whose index is the returned value of getActionIndex (#2220).
|
||||||
|
// See https://developer.android.com/reference/android/view/MotionEvent#getActionMasked().
|
||||||
|
// For other pointers, treat their actions as MotionEvent.ACTION_MOVE.
|
||||||
|
int touchIndex = e.getActionIndex();
|
||||||
|
for (int i = 0; i < e.getPointerCount(); i++) {
|
||||||
|
int id = e.getPointerId(i);
|
||||||
|
int x = (int)e.getX(i);
|
||||||
|
int y = (int)e.getY(i);
|
||||||
|
int action = (i == touchIndex) ? e.getActionMasked() : MotionEvent.ACTION_MOVE;
|
||||||
|
Ebitenmobileview.updateTouchesOnAndroid(action, id, (int)pxToDp(x), (int)pxToDp(y));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Gamepad getGamepad(int deviceId) {
|
||||||
|
for (Gamepad gamepad : this.gamepads) {
|
||||||
|
if (gamepad.deviceId == deviceId) {
|
||||||
|
return gamepad;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||||
|
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
|
||||||
|
return super.onGenericMotionEvent(event);
|
||||||
|
}
|
||||||
|
if (event.getAction() != MotionEvent.ACTION_MOVE) {
|
||||||
|
return super.onGenericMotionEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L256-L277
|
||||||
|
Gamepad gamepad = this.getGamepad(event.getDeviceId());
|
||||||
|
if (gamepad == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int actionPointerIndex = event.getActionIndex();
|
||||||
|
for (int i = 0; i < gamepad.axes.size(); i++) {
|
||||||
|
InputDevice.MotionRange range = gamepad.axes.get(i);
|
||||||
|
float axisValue = event.getAxisValue(range.getAxis(), actionPointerIndex);
|
||||||
|
float value = (axisValue - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
||||||
|
Ebitenmobileview.onGamepadAxisChanged(gamepad.deviceId, i, value);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < gamepad.hats.size() / 2; i++) {
|
||||||
|
int hatX = Math.round(event.getAxisValue(gamepad.hats.get(2*i).getAxis(), actionPointerIndex));
|
||||||
|
int hatY = Math.round(event.getAxisValue(gamepad.hats.get(2*i+1).getAxis(), actionPointerIndex));
|
||||||
|
Ebitenmobileview.onGamepadHatChanged(gamepad.deviceId, i, hatX, hatY);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceAdded(int deviceId) {
|
||||||
|
InputDevice inputDevice = this.inputManager.getInputDevice(deviceId);
|
||||||
|
// The InputDevice can be null on some deivces (#1342).
|
||||||
|
if (inputDevice == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A fingerprint reader is unexpectedly recognized as a joystick. Skip this (#1542).
|
||||||
|
if (inputDevice.getName().equals("uinput-fpc")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sources = inputDevice.getSources();
|
||||||
|
if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD &&
|
||||||
|
(sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L182-L216
|
||||||
|
List<InputDevice.MotionRange> ranges = inputDevice.getMotionRanges();
|
||||||
|
Collections.sort(ranges, new RangeComparator());
|
||||||
|
|
||||||
|
Gamepad gamepad = new Gamepad();
|
||||||
|
gamepad.deviceId = deviceId;
|
||||||
|
gamepad.axes = new ArrayList<InputDevice.MotionRange>();
|
||||||
|
gamepad.hats = new ArrayList<InputDevice.MotionRange>();
|
||||||
|
for (InputDevice.MotionRange range : ranges) {
|
||||||
|
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
||||||
|
gamepad.hats.add(range);
|
||||||
|
} else {
|
||||||
|
gamepad.axes.add(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.gamepads.add(gamepad);
|
||||||
|
|
||||||
|
String descriptor = inputDevice.getDescriptor();
|
||||||
|
int vendorId = inputDevice.getVendorId();
|
||||||
|
int productId = inputDevice.getProductId();
|
||||||
|
|
||||||
|
// These values are required to calculate SDL's GUID.
|
||||||
|
int buttonMask = getButtonMask(inputDevice, gamepad.hats.size()/2);
|
||||||
|
int axisMask = getAxisMask(inputDevice);
|
||||||
|
|
||||||
|
Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), gamepad.axes.size(), gamepad.hats.size()/2, descriptor, vendorId, productId, buttonMask, axisMask);
|
||||||
|
|
||||||
|
// Initialize the trigger axes values explicitly, or the initial button values would be 0.5 instead of 0.
|
||||||
|
if (gamepad.axes.size() >= 6) {
|
||||||
|
Ebitenmobileview.onGamepadAxisChanged(deviceId, 4, -1);
|
||||||
|
Ebitenmobileview.onGamepadAxisChanged(deviceId, 5, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The implementation is copied from SDL:
|
||||||
|
// https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L308
|
||||||
|
private static int getButtonMask(InputDevice joystickDevice, int nhats) {
|
||||||
|
int buttonMask = 0;
|
||||||
|
int[] keys = new int[] {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y,
|
||||||
|
KeyEvent.KEYCODE_BACK,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_MODE,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBL,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBR,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L1,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R1,
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP,
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT,
|
||||||
|
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||||
|
|
||||||
|
// These don't map into any SDL controller buttons directly
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L2,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R2,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_C,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Z,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_1,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_2,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_3,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_4,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_5,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_6,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_7,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_8,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_9,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_10,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_11,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_12,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_13,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_14,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_15,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_16,
|
||||||
|
};
|
||||||
|
int[] masks = new int[] {
|
||||||
|
(1 << 0), // A -> A
|
||||||
|
(1 << 1), // B -> B
|
||||||
|
(1 << 2), // X -> X
|
||||||
|
(1 << 3), // Y -> Y
|
||||||
|
(1 << 4), // BACK -> BACK
|
||||||
|
(1 << 5), // MODE -> GUIDE
|
||||||
|
(1 << 6), // START -> START
|
||||||
|
(1 << 7), // THUMBL -> LEFTSTICK
|
||||||
|
(1 << 8), // THUMBR -> RIGHTSTICK
|
||||||
|
(1 << 9), // L1 -> LEFTSHOULDER
|
||||||
|
(1 << 10), // R1 -> RIGHTSHOULDER
|
||||||
|
(1 << 11), // DPAD_UP -> DPAD_UP
|
||||||
|
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
|
||||||
|
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
|
||||||
|
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
|
||||||
|
(1 << 4), // SELECT -> BACK
|
||||||
|
(1 << 0), // DPAD_CENTER -> A
|
||||||
|
(1 << 15), // L2 -> ??
|
||||||
|
(1 << 16), // R2 -> ??
|
||||||
|
(1 << 17), // C -> ??
|
||||||
|
(1 << 18), // Z -> ??
|
||||||
|
(1 << 20), // 1 -> ??
|
||||||
|
(1 << 21), // 2 -> ??
|
||||||
|
(1 << 22), // 3 -> ??
|
||||||
|
(1 << 23), // 4 -> ??
|
||||||
|
(1 << 24), // 5 -> ??
|
||||||
|
(1 << 25), // 6 -> ??
|
||||||
|
(1 << 26), // 7 -> ??
|
||||||
|
(1 << 27), // 8 -> ??
|
||||||
|
(1 << 28), // 9 -> ??
|
||||||
|
(1 << 29), // 10 -> ??
|
||||||
|
(1 << 30), // 11 -> ??
|
||||||
|
(1 << 31), // 12 -> ??
|
||||||
|
// We're out of room...
|
||||||
|
0xFFFFFFFF, // 13 -> ??
|
||||||
|
0xFFFFFFFF, // 14 -> ??
|
||||||
|
0xFFFFFFFF, // 15 -> ??
|
||||||
|
0xFFFFFFFF, // 16 -> ??
|
||||||
|
};
|
||||||
|
boolean[] hasKeys = joystickDevice.hasKeys(keys);
|
||||||
|
for (int i = 0; i < keys.length; ++i) {
|
||||||
|
if (hasKeys[i]) {
|
||||||
|
buttonMask |= masks[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// https://github.com/libsdl-org/SDL/blob/47f2373dc13b66c48bf4024fcdab53cd0bdd59bb/src/joystick/android/SDL_sysjoystick.c#L360-L367
|
||||||
|
if (nhats > 0) {
|
||||||
|
// Add Dpad buttons.
|
||||||
|
buttonMask |= 1 << 11;
|
||||||
|
buttonMask |= 1 << 12;
|
||||||
|
buttonMask |= 1 << 13;
|
||||||
|
buttonMask |= 1 << 14;
|
||||||
|
}
|
||||||
|
return buttonMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getAxisMask(InputDevice joystickDevice) {
|
||||||
|
final int SDL_CONTROLLER_AXIS_LEFTX = 0;
|
||||||
|
final int SDL_CONTROLLER_AXIS_LEFTY = 1;
|
||||||
|
final int SDL_CONTROLLER_AXIS_RIGHTX = 2;
|
||||||
|
final int SDL_CONTROLLER_AXIS_RIGHTY = 3;
|
||||||
|
final int SDL_CONTROLLER_AXIS_TRIGGERLEFT = 4;
|
||||||
|
final int SDL_CONTROLLER_AXIS_TRIGGERRIGHT = 5;
|
||||||
|
|
||||||
|
int naxes = 0;
|
||||||
|
for (InputDevice.MotionRange range : joystickDevice.getMotionRanges()) {
|
||||||
|
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||||
|
if (range.getAxis() != MotionEvent.AXIS_HAT_X && range.getAxis() != MotionEvent.AXIS_HAT_Y) {
|
||||||
|
naxes++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The variable is_accelerometer seems always false, then skip the checking:
|
||||||
|
// https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L207
|
||||||
|
int axisMask = 0;
|
||||||
|
if (naxes >= 2) {
|
||||||
|
axisMask |= ((1 << SDL_CONTROLLER_AXIS_LEFTX) | (1 << SDL_CONTROLLER_AXIS_LEFTY));
|
||||||
|
}
|
||||||
|
if (naxes >= 4) {
|
||||||
|
axisMask |= ((1 << SDL_CONTROLLER_AXIS_RIGHTX) | (1 << SDL_CONTROLLER_AXIS_RIGHTY));
|
||||||
|
}
|
||||||
|
if (naxes >= 6) {
|
||||||
|
axisMask |= ((1 << SDL_CONTROLLER_AXIS_TRIGGERLEFT) | (1 << SDL_CONTROLLER_AXIS_TRIGGERRIGHT));
|
||||||
|
}
|
||||||
|
return axisMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceChanged(int deviceId) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputDeviceRemoved(int deviceId) {
|
||||||
|
// Do not call inputManager.getInputDevice(), which returns null (#1185).
|
||||||
|
Ebitenmobileview.onInputDeviceRemoved(deviceId);
|
||||||
|
this.gamepads.remove(this.getGamepad(deviceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// suspendGame suspends the game.
|
||||||
|
// It is recommended to call this when the application is being suspended e.g.,
|
||||||
|
// Activity's onPause is called.
|
||||||
|
public void suspendGame() {
|
||||||
|
this.inputManager.unregisterInputDeviceListener(this);
|
||||||
|
this.ebitenSurfaceView.onPause();
|
||||||
|
try {
|
||||||
|
Ebitenmobileview.suspend();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
onErrorOnGameUpdate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resumeGame resumes the game.
|
||||||
|
// It is recommended to call this when the application is being resumed e.g.,
|
||||||
|
// Activity's onResume is called.
|
||||||
|
public void resumeGame() {
|
||||||
|
this.inputManager.registerInputDeviceListener(this, null);
|
||||||
|
this.ebitenSurfaceView.onResume();
|
||||||
|
try {
|
||||||
|
Ebitenmobileview.resume();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
onErrorOnGameUpdate(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// onErrorOnGameUpdate is called on the main thread when an error happens when updating a game.
|
||||||
|
// You can define your own error handler, e.g., using Crashlytics, by overriding this method.
|
||||||
|
protected void onErrorOnGameUpdate(Exception e) {
|
||||||
|
Log.e("Go", e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private EbitenSurfaceView ebitenSurfaceView;
|
||||||
|
private InputManager inputManager;
|
||||||
|
private ArrayList<Gamepad> gamepads;
|
||||||
|
}
|
33
cmd/ebitenmobile/_files/EbitenViewController.h
Normal file
33
cmd/ebitenmobile/_files/EbitenViewController.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2022 The Ebitengine Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@interface {{.PrefixUpper}}EbitenViewController : UIViewController
|
||||||
|
|
||||||
|
// onErrorOnGameUpdate is called on the main thread when an error happens when updating a game.
|
||||||
|
// You can define your own error handler, e.g., using Crashlytics, by overwriting this method.
|
||||||
|
- (void)onErrorOnGameUpdate:(NSError*)err;
|
||||||
|
|
||||||
|
// suspendGame suspends the game.
|
||||||
|
// It is recommended to call this when the application is being suspended e.g.,
|
||||||
|
// UIApplicationDelegate's applicationWillResignActive is called.
|
||||||
|
- (void)suspendGame;
|
||||||
|
|
||||||
|
// resumeGame resumes the game.
|
||||||
|
// It is recommended to call this when the application is being resumed e.g.,
|
||||||
|
// UIApplicationDelegate's applicationDidBecomeActive is called.
|
||||||
|
- (void)resumeGame;
|
||||||
|
|
||||||
|
@end
|
224
cmd/ebitenmobile/_files/EbitenViewController.m
Normal file
224
cmd/ebitenmobile/_files/EbitenViewController.m
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
// Copyright 2022 The Ebitengine Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#import <TargetConditionals.h>
|
||||||
|
|
||||||
|
#import <stdint.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <GLKit/GLKit.h>
|
||||||
|
|
||||||
|
#import "Ebitenmobileview.objc.h"
|
||||||
|
|
||||||
|
@interface {{.PrefixUpper}}EbitenViewController : UIViewController<EbitenmobileviewRenderRequester>
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation {{.PrefixUpper}}EbitenViewController {
|
||||||
|
UIView* metalView_;
|
||||||
|
GLKView* glkView_;
|
||||||
|
bool started_;
|
||||||
|
bool active_;
|
||||||
|
bool error_;
|
||||||
|
CADisplayLink* displayLink_;
|
||||||
|
bool explicitRendering_;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (UIView*)metalView {
|
||||||
|
if (!metalView_) {
|
||||||
|
metalView_ = [[UIView alloc] init];
|
||||||
|
metalView_.multipleTouchEnabled = YES;
|
||||||
|
}
|
||||||
|
return metalView_;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (GLKView*)glkView {
|
||||||
|
if (!glkView_) {
|
||||||
|
glkView_ = [[GLKView alloc] init];
|
||||||
|
glkView_.multipleTouchEnabled = YES;
|
||||||
|
}
|
||||||
|
return glkView_;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
|
||||||
|
if (!started_) {
|
||||||
|
@synchronized(self) {
|
||||||
|
active_ = true;
|
||||||
|
}
|
||||||
|
started_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EbitenmobileviewIsGL()) {
|
||||||
|
self.glkView.delegate = (id<GLKViewDelegate>)(self);
|
||||||
|
[self.view addSubview: self.glkView];
|
||||||
|
|
||||||
|
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
|
||||||
|
[self glkView].context = context;
|
||||||
|
|
||||||
|
[EAGLContext setCurrentContext:context];
|
||||||
|
} else {
|
||||||
|
[self.view addSubview: self.metalView];
|
||||||
|
EbitenmobileviewSetUIView((uintptr_t)(self.metalView));
|
||||||
|
}
|
||||||
|
|
||||||
|
displayLink_ = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
|
||||||
|
[displayLink_ addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
|
||||||
|
EbitenmobileviewSetRenderRequester(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewWillLayoutSubviews {
|
||||||
|
CGRect viewRect = [[self view] frame];
|
||||||
|
if (EbitenmobileviewIsGL()) {
|
||||||
|
[[self glkView] setFrame:viewRect];
|
||||||
|
} else {
|
||||||
|
[[self metalView] setFrame:viewRect];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)viewDidLayoutSubviews {
|
||||||
|
[super viewDidLayoutSubviews];
|
||||||
|
CGRect viewRect = [[self view] frame];
|
||||||
|
|
||||||
|
EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)didReceiveMemoryWarning {
|
||||||
|
[super didReceiveMemoryWarning];
|
||||||
|
// Dispose of any resources that can be recreated.
|
||||||
|
// TODO: Notify this to Go world?
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)drawFrame{
|
||||||
|
@synchronized(self) {
|
||||||
|
if (!active_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EbitenmobileviewIsGL()) {
|
||||||
|
[[self glkView] setNeedsDisplay];
|
||||||
|
} else {
|
||||||
|
[self updateEbiten];
|
||||||
|
}
|
||||||
|
|
||||||
|
@synchronized(self) {
|
||||||
|
if (explicitRendering_) {
|
||||||
|
[displayLink_ setPaused:YES];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)glkView:(GLKView*)view drawInRect:(CGRect)rect {
|
||||||
|
[self updateEbiten];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateEbiten {
|
||||||
|
if (error_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* err = nil;
|
||||||
|
EbitenmobileviewUpdate(&err);
|
||||||
|
if (err != nil) {
|
||||||
|
[self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:)
|
||||||
|
withObject:err
|
||||||
|
waitUntilDone:NO];
|
||||||
|
error_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)onErrorOnGameUpdate:(NSError*)err {
|
||||||
|
NSLog(@"Error: %@", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateTouches:(NSSet*)touches {
|
||||||
|
for (UITouch* touch in touches) {
|
||||||
|
if (EbitenmobileviewIsGL()) {
|
||||||
|
if (touch.view != [self glkView]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (touch.view != [self metalView]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CGPoint location = [touch locationInView:touch.view];
|
||||||
|
EbitenmobileviewUpdateTouchesOnIOS(touch.phase, (uintptr_t)touch, location.x, location.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
|
||||||
|
[self updateTouches:touches];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
|
||||||
|
[self updateTouches:touches];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
|
||||||
|
[self updateTouches:touches];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
|
||||||
|
[self updateTouches:touches];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)suspendGame {
|
||||||
|
NSAssert(started_, @"suspendGame must not be called before viewDidLoad is called");
|
||||||
|
|
||||||
|
@synchronized(self) {
|
||||||
|
active_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* err = nil;
|
||||||
|
EbitenmobileviewSuspend(&err);
|
||||||
|
if (err != nil) {
|
||||||
|
[self onErrorOnGameUpdate:err];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resumeGame {
|
||||||
|
NSAssert(started_, @"resumeGame must not be called before viewDidLoad is called");
|
||||||
|
|
||||||
|
@synchronized(self) {
|
||||||
|
active_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSError* err = nil;
|
||||||
|
EbitenmobileviewResume(&err);
|
||||||
|
if (err != nil) {
|
||||||
|
[self onErrorOnGameUpdate:err];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setExplicitRenderingMode:(BOOL)explicitRendering {
|
||||||
|
@synchronized(self) {
|
||||||
|
explicitRendering_ = explicitRendering;
|
||||||
|
if (explicitRendering_) {
|
||||||
|
[displayLink_ setPaused:YES];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)requestRenderIfNeeded {
|
||||||
|
@synchronized(self) {
|
||||||
|
if (explicitRendering_) {
|
||||||
|
// Resume the callback temporarily.
|
||||||
|
// This is paused again soon in drawFrame.
|
||||||
|
[displayLink_ setPaused:NO];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
@ -18,6 +18,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "embed"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -29,6 +30,15 @@ import (
|
|||||||
"golang.org/x/tools/go/packages"
|
"golang.org/x/tools/go/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed _files/EbitenViewController.m
|
||||||
|
var objcM string
|
||||||
|
|
||||||
|
//go:embed _files/EbitenView.java
|
||||||
|
var viewJava string
|
||||||
|
|
||||||
|
//go:embed _files/EbitenSurfaceView.java
|
||||||
|
var surfaceViewJava string
|
||||||
|
|
||||||
var (
|
var (
|
||||||
lang = flag.String("lang", "", "")
|
lang = flag.String("lang", "", "")
|
||||||
outdir = flag.String("outdir", "", "")
|
outdir = flag.String("outdir", "", "")
|
||||||
@ -133,699 +143,3 @@ import "C"`); err != nil {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const objcM = `// Code generated by ebitenmobile. DO NOT EDIT.
|
|
||||||
|
|
||||||
#import <TargetConditionals.h>
|
|
||||||
|
|
||||||
#import <stdint.h>
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
#import <GLKit/GLKit.h>
|
|
||||||
|
|
||||||
#import "Ebitenmobileview.objc.h"
|
|
||||||
|
|
||||||
@interface {{.PrefixUpper}}EbitenViewController : UIViewController<EbitenmobileviewRenderRequester>
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation {{.PrefixUpper}}EbitenViewController {
|
|
||||||
UIView* metalView_;
|
|
||||||
GLKView* glkView_;
|
|
||||||
bool started_;
|
|
||||||
bool active_;
|
|
||||||
bool error_;
|
|
||||||
CADisplayLink* displayLink_;
|
|
||||||
bool explicitRendering_;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (UIView*)metalView {
|
|
||||||
if (!metalView_) {
|
|
||||||
metalView_ = [[UIView alloc] init];
|
|
||||||
metalView_.multipleTouchEnabled = YES;
|
|
||||||
}
|
|
||||||
return metalView_;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (GLKView*)glkView {
|
|
||||||
if (!glkView_) {
|
|
||||||
glkView_ = [[GLKView alloc] init];
|
|
||||||
glkView_.multipleTouchEnabled = YES;
|
|
||||||
}
|
|
||||||
return glkView_;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewDidLoad {
|
|
||||||
[super viewDidLoad];
|
|
||||||
|
|
||||||
if (!started_) {
|
|
||||||
@synchronized(self) {
|
|
||||||
active_ = true;
|
|
||||||
}
|
|
||||||
started_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EbitenmobileviewIsGL()) {
|
|
||||||
self.glkView.delegate = (id<GLKViewDelegate>)(self);
|
|
||||||
[self.view addSubview: self.glkView];
|
|
||||||
|
|
||||||
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
|
|
||||||
[self glkView].context = context;
|
|
||||||
|
|
||||||
[EAGLContext setCurrentContext:context];
|
|
||||||
} else {
|
|
||||||
[self.view addSubview: self.metalView];
|
|
||||||
EbitenmobileviewSetUIView((uintptr_t)(self.metalView));
|
|
||||||
}
|
|
||||||
|
|
||||||
displayLink_ = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
|
|
||||||
[displayLink_ addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
|
|
||||||
EbitenmobileviewSetRenderRequester(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewWillLayoutSubviews {
|
|
||||||
CGRect viewRect = [[self view] frame];
|
|
||||||
if (EbitenmobileviewIsGL()) {
|
|
||||||
[[self glkView] setFrame:viewRect];
|
|
||||||
} else {
|
|
||||||
[[self metalView] setFrame:viewRect];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)viewDidLayoutSubviews {
|
|
||||||
[super viewDidLayoutSubviews];
|
|
||||||
CGRect viewRect = [[self view] frame];
|
|
||||||
|
|
||||||
EbitenmobileviewLayout(viewRect.size.width, viewRect.size.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)didReceiveMemoryWarning {
|
|
||||||
[super didReceiveMemoryWarning];
|
|
||||||
// Dispose of any resources that can be recreated.
|
|
||||||
// TODO: Notify this to Go world?
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)drawFrame{
|
|
||||||
@synchronized(self) {
|
|
||||||
if (!active_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EbitenmobileviewIsGL()) {
|
|
||||||
[[self glkView] setNeedsDisplay];
|
|
||||||
} else {
|
|
||||||
[self updateEbiten];
|
|
||||||
}
|
|
||||||
|
|
||||||
@synchronized(self) {
|
|
||||||
if (explicitRendering_) {
|
|
||||||
[displayLink_ setPaused:YES];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)glkView:(GLKView*)view drawInRect:(CGRect)rect {
|
|
||||||
[self updateEbiten];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateEbiten {
|
|
||||||
if (error_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* err = nil;
|
|
||||||
EbitenmobileviewUpdate(&err);
|
|
||||||
if (err != nil) {
|
|
||||||
[self performSelectorOnMainThread:@selector(onErrorOnGameUpdate:)
|
|
||||||
withObject:err
|
|
||||||
waitUntilDone:NO];
|
|
||||||
error_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)onErrorOnGameUpdate:(NSError*)err {
|
|
||||||
NSLog(@"Error: %@", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateTouches:(NSSet*)touches {
|
|
||||||
for (UITouch* touch in touches) {
|
|
||||||
if (EbitenmobileviewIsGL()) {
|
|
||||||
if (touch.view != [self glkView]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (touch.view != [self metalView]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CGPoint location = [touch locationInView:touch.view];
|
|
||||||
EbitenmobileviewUpdateTouchesOnIOS(touch.phase, (uintptr_t)touch, location.x, location.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
|
|
||||||
[self updateTouches:touches];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
|
|
||||||
[self updateTouches:touches];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
|
|
||||||
[self updateTouches:touches];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
|
|
||||||
[self updateTouches:touches];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)suspendGame {
|
|
||||||
NSAssert(started_, @"suspendGame must not be called before viewDidLoad is called");
|
|
||||||
|
|
||||||
@synchronized(self) {
|
|
||||||
active_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* err = nil;
|
|
||||||
EbitenmobileviewSuspend(&err);
|
|
||||||
if (err != nil) {
|
|
||||||
[self onErrorOnGameUpdate:err];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)resumeGame {
|
|
||||||
NSAssert(started_, @"resumeGame must not be called before viewDidLoad is called");
|
|
||||||
|
|
||||||
@synchronized(self) {
|
|
||||||
active_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError* err = nil;
|
|
||||||
EbitenmobileviewResume(&err);
|
|
||||||
if (err != nil) {
|
|
||||||
[self onErrorOnGameUpdate:err];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setExplicitRenderingMode:(BOOL)explicitRendering {
|
|
||||||
@synchronized(self) {
|
|
||||||
explicitRendering_ = explicitRendering;
|
|
||||||
if (explicitRendering_) {
|
|
||||||
[displayLink_ setPaused:YES];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)requestRenderIfNeeded {
|
|
||||||
@synchronized(self) {
|
|
||||||
if (explicitRendering_) {
|
|
||||||
// Resume the callback temporarily.
|
|
||||||
// This is paused again soon in drawFrame.
|
|
||||||
[displayLink_ setPaused:NO];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
`
|
|
||||||
|
|
||||||
const viewJava = `// Code generated by ebitenmobile. DO NOT EDIT.
|
|
||||||
|
|
||||||
package {{.JavaPkg}}.{{.PrefixLower}};
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.hardware.input.InputManager;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Display;
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.InputDevice;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
|
|
||||||
import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;
|
|
||||||
|
|
||||||
public class EbitenView extends ViewGroup implements InputManager.InputDeviceListener {
|
|
||||||
static class Gamepad {
|
|
||||||
public int deviceId;
|
|
||||||
public ArrayList<InputDevice.MotionRange> axes;
|
|
||||||
public ArrayList<InputDevice.MotionRange> hats;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L154-L173
|
|
||||||
static class RangeComparator implements Comparator<InputDevice.MotionRange> {
|
|
||||||
@Override
|
|
||||||
public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
|
|
||||||
int arg0Axis = arg0.getAxis();
|
|
||||||
int arg1Axis = arg1.getAxis();
|
|
||||||
if (arg0Axis == MotionEvent.AXIS_GAS) {
|
|
||||||
arg0Axis = MotionEvent.AXIS_BRAKE;
|
|
||||||
} else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
|
|
||||||
arg0Axis = MotionEvent.AXIS_GAS;
|
|
||||||
}
|
|
||||||
if (arg1Axis == MotionEvent.AXIS_GAS) {
|
|
||||||
arg1Axis = MotionEvent.AXIS_BRAKE;
|
|
||||||
} else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
|
|
||||||
arg1Axis = MotionEvent.AXIS_GAS;
|
|
||||||
}
|
|
||||||
return arg0Axis - arg1Axis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double pxToDp(double x) {
|
|
||||||
return x / Ebitenmobileview.deviceScale();
|
|
||||||
}
|
|
||||||
|
|
||||||
public EbitenView(Context context) {
|
|
||||||
super(context);
|
|
||||||
initialize(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public EbitenView(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
initialize(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialize(Context context) {
|
|
||||||
this.gamepads = new ArrayList<Gamepad>();
|
|
||||||
|
|
||||||
this.ebitenSurfaceView = new EbitenSurfaceView(getContext());
|
|
||||||
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
|
||||||
addView(this.ebitenSurfaceView, params);
|
|
||||||
|
|
||||||
this.inputManager = (InputManager)context.getSystemService(Context.INPUT_SERVICE);
|
|
||||||
this.inputManager.registerInputDeviceListener(this, null);
|
|
||||||
for (int id : this.inputManager.getInputDeviceIds()) {
|
|
||||||
this.onInputDeviceAdded(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
||||||
this.ebitenSurfaceView.layout(0, 0, right - left, bottom - top);
|
|
||||||
double widthInDp = pxToDp(right - left);
|
|
||||||
double heightInDp = pxToDp(bottom - top);
|
|
||||||
Ebitenmobileview.layout(widthInDp, heightInDp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
||||||
Ebitenmobileview.onKeyDownOnAndroid(keyCode, event.getUnicodeChar(), event.getSource(), event.getDeviceId());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
|
||||||
Ebitenmobileview.onKeyUpOnAndroid(keyCode, event.getSource(), event.getDeviceId());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouchEvent(MotionEvent e) {
|
|
||||||
// getActionIndex returns a valid value only for the action whose index is the returned value of getActionIndex (#2220).
|
|
||||||
// See https://developer.android.com/reference/android/view/MotionEvent#getActionMasked().
|
|
||||||
// For other pointers, treat their actions as MotionEvent.ACTION_MOVE.
|
|
||||||
int touchIndex = e.getActionIndex();
|
|
||||||
for (int i = 0; i < e.getPointerCount(); i++) {
|
|
||||||
int id = e.getPointerId(i);
|
|
||||||
int x = (int)e.getX(i);
|
|
||||||
int y = (int)e.getY(i);
|
|
||||||
int action = (i == touchIndex) ? e.getActionMasked() : MotionEvent.ACTION_MOVE;
|
|
||||||
Ebitenmobileview.updateTouchesOnAndroid(action, id, (int)pxToDp(x), (int)pxToDp(y));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Gamepad getGamepad(int deviceId) {
|
|
||||||
for (Gamepad gamepad : this.gamepads) {
|
|
||||||
if (gamepad.deviceId == deviceId) {
|
|
||||||
return gamepad;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
|
||||||
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
|
|
||||||
return super.onGenericMotionEvent(event);
|
|
||||||
}
|
|
||||||
if (event.getAction() != MotionEvent.ACTION_MOVE) {
|
|
||||||
return super.onGenericMotionEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L256-L277
|
|
||||||
Gamepad gamepad = this.getGamepad(event.getDeviceId());
|
|
||||||
if (gamepad == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int actionPointerIndex = event.getActionIndex();
|
|
||||||
for (int i = 0; i < gamepad.axes.size(); i++) {
|
|
||||||
InputDevice.MotionRange range = gamepad.axes.get(i);
|
|
||||||
float axisValue = event.getAxisValue(range.getAxis(), actionPointerIndex);
|
|
||||||
float value = (axisValue - range.getMin()) / range.getRange() * 2.0f - 1.0f;
|
|
||||||
Ebitenmobileview.onGamepadAxisChanged(gamepad.deviceId, i, value);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < gamepad.hats.size() / 2; i++) {
|
|
||||||
int hatX = Math.round(event.getAxisValue(gamepad.hats.get(2*i).getAxis(), actionPointerIndex));
|
|
||||||
int hatY = Math.round(event.getAxisValue(gamepad.hats.get(2*i+1).getAxis(), actionPointerIndex));
|
|
||||||
Ebitenmobileview.onGamepadHatChanged(gamepad.deviceId, i, hatX, hatY);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInputDeviceAdded(int deviceId) {
|
|
||||||
InputDevice inputDevice = this.inputManager.getInputDevice(deviceId);
|
|
||||||
// The InputDevice can be null on some deivces (#1342).
|
|
||||||
if (inputDevice == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A fingerprint reader is unexpectedly recognized as a joystick. Skip this (#1542).
|
|
||||||
if (inputDevice.getName().equals("uinput-fpc")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sources = inputDevice.getSources();
|
|
||||||
if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD &&
|
|
||||||
(sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L182-L216
|
|
||||||
List<InputDevice.MotionRange> ranges = inputDevice.getMotionRanges();
|
|
||||||
Collections.sort(ranges, new RangeComparator());
|
|
||||||
|
|
||||||
Gamepad gamepad = new Gamepad();
|
|
||||||
gamepad.deviceId = deviceId;
|
|
||||||
gamepad.axes = new ArrayList<InputDevice.MotionRange>();
|
|
||||||
gamepad.hats = new ArrayList<InputDevice.MotionRange>();
|
|
||||||
for (InputDevice.MotionRange range : ranges) {
|
|
||||||
if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
|
|
||||||
gamepad.hats.add(range);
|
|
||||||
} else {
|
|
||||||
gamepad.axes.add(range);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.gamepads.add(gamepad);
|
|
||||||
|
|
||||||
String descriptor = inputDevice.getDescriptor();
|
|
||||||
int vendorId = inputDevice.getVendorId();
|
|
||||||
int productId = inputDevice.getProductId();
|
|
||||||
|
|
||||||
// These values are required to calculate SDL's GUID.
|
|
||||||
int buttonMask = getButtonMask(inputDevice, gamepad.hats.size()/2);
|
|
||||||
int axisMask = getAxisMask(inputDevice);
|
|
||||||
|
|
||||||
Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), gamepad.axes.size(), gamepad.hats.size()/2, descriptor, vendorId, productId, buttonMask, axisMask);
|
|
||||||
|
|
||||||
// Initialize the trigger axes values explicitly, or the initial button values would be 0.5 instead of 0.
|
|
||||||
if (gamepad.axes.size() >= 6) {
|
|
||||||
Ebitenmobileview.onGamepadAxisChanged(deviceId, 4, -1);
|
|
||||||
Ebitenmobileview.onGamepadAxisChanged(deviceId, 5, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The implementation is copied from SDL:
|
|
||||||
// https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L308
|
|
||||||
private static int getButtonMask(InputDevice joystickDevice, int nhats) {
|
|
||||||
int buttonMask = 0;
|
|
||||||
int[] keys = new int[] {
|
|
||||||
KeyEvent.KEYCODE_BUTTON_A,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_B,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_X,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_Y,
|
|
||||||
KeyEvent.KEYCODE_BACK,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_MODE,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_START,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_THUMBL,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_THUMBR,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_L1,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_R1,
|
|
||||||
KeyEvent.KEYCODE_DPAD_UP,
|
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_SELECT,
|
|
||||||
KeyEvent.KEYCODE_DPAD_CENTER,
|
|
||||||
|
|
||||||
// These don't map into any SDL controller buttons directly
|
|
||||||
KeyEvent.KEYCODE_BUTTON_L2,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_R2,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_C,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_Z,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_1,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_2,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_3,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_4,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_5,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_6,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_7,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_8,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_9,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_10,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_11,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_12,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_13,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_14,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_15,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_16,
|
|
||||||
};
|
|
||||||
int[] masks = new int[] {
|
|
||||||
(1 << 0), // A -> A
|
|
||||||
(1 << 1), // B -> B
|
|
||||||
(1 << 2), // X -> X
|
|
||||||
(1 << 3), // Y -> Y
|
|
||||||
(1 << 4), // BACK -> BACK
|
|
||||||
(1 << 5), // MODE -> GUIDE
|
|
||||||
(1 << 6), // START -> START
|
|
||||||
(1 << 7), // THUMBL -> LEFTSTICK
|
|
||||||
(1 << 8), // THUMBR -> RIGHTSTICK
|
|
||||||
(1 << 9), // L1 -> LEFTSHOULDER
|
|
||||||
(1 << 10), // R1 -> RIGHTSHOULDER
|
|
||||||
(1 << 11), // DPAD_UP -> DPAD_UP
|
|
||||||
(1 << 12), // DPAD_DOWN -> DPAD_DOWN
|
|
||||||
(1 << 13), // DPAD_LEFT -> DPAD_LEFT
|
|
||||||
(1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
|
|
||||||
(1 << 4), // SELECT -> BACK
|
|
||||||
(1 << 0), // DPAD_CENTER -> A
|
|
||||||
(1 << 15), // L2 -> ??
|
|
||||||
(1 << 16), // R2 -> ??
|
|
||||||
(1 << 17), // C -> ??
|
|
||||||
(1 << 18), // Z -> ??
|
|
||||||
(1 << 20), // 1 -> ??
|
|
||||||
(1 << 21), // 2 -> ??
|
|
||||||
(1 << 22), // 3 -> ??
|
|
||||||
(1 << 23), // 4 -> ??
|
|
||||||
(1 << 24), // 5 -> ??
|
|
||||||
(1 << 25), // 6 -> ??
|
|
||||||
(1 << 26), // 7 -> ??
|
|
||||||
(1 << 27), // 8 -> ??
|
|
||||||
(1 << 28), // 9 -> ??
|
|
||||||
(1 << 29), // 10 -> ??
|
|
||||||
(1 << 30), // 11 -> ??
|
|
||||||
(1 << 31), // 12 -> ??
|
|
||||||
// We're out of room...
|
|
||||||
0xFFFFFFFF, // 13 -> ??
|
|
||||||
0xFFFFFFFF, // 14 -> ??
|
|
||||||
0xFFFFFFFF, // 15 -> ??
|
|
||||||
0xFFFFFFFF, // 16 -> ??
|
|
||||||
};
|
|
||||||
boolean[] hasKeys = joystickDevice.hasKeys(keys);
|
|
||||||
for (int i = 0; i < keys.length; ++i) {
|
|
||||||
if (hasKeys[i]) {
|
|
||||||
buttonMask |= masks[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// https://github.com/libsdl-org/SDL/blob/47f2373dc13b66c48bf4024fcdab53cd0bdd59bb/src/joystick/android/SDL_sysjoystick.c#L360-L367
|
|
||||||
if (nhats > 0) {
|
|
||||||
// Add Dpad buttons.
|
|
||||||
buttonMask |= 1 << 11;
|
|
||||||
buttonMask |= 1 << 12;
|
|
||||||
buttonMask |= 1 << 13;
|
|
||||||
buttonMask |= 1 << 14;
|
|
||||||
}
|
|
||||||
return buttonMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getAxisMask(InputDevice joystickDevice) {
|
|
||||||
final int SDL_CONTROLLER_AXIS_LEFTX = 0;
|
|
||||||
final int SDL_CONTROLLER_AXIS_LEFTY = 1;
|
|
||||||
final int SDL_CONTROLLER_AXIS_RIGHTX = 2;
|
|
||||||
final int SDL_CONTROLLER_AXIS_RIGHTY = 3;
|
|
||||||
final int SDL_CONTROLLER_AXIS_TRIGGERLEFT = 4;
|
|
||||||
final int SDL_CONTROLLER_AXIS_TRIGGERRIGHT = 5;
|
|
||||||
|
|
||||||
int naxes = 0;
|
|
||||||
for (InputDevice.MotionRange range : joystickDevice.getMotionRanges()) {
|
|
||||||
if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
|
||||||
if (range.getAxis() != MotionEvent.AXIS_HAT_X && range.getAxis() != MotionEvent.AXIS_HAT_Y) {
|
|
||||||
naxes++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The variable is_accelerometer seems always false, then skip the checking:
|
|
||||||
// https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L207
|
|
||||||
int axisMask = 0;
|
|
||||||
if (naxes >= 2) {
|
|
||||||
axisMask |= ((1 << SDL_CONTROLLER_AXIS_LEFTX) | (1 << SDL_CONTROLLER_AXIS_LEFTY));
|
|
||||||
}
|
|
||||||
if (naxes >= 4) {
|
|
||||||
axisMask |= ((1 << SDL_CONTROLLER_AXIS_RIGHTX) | (1 << SDL_CONTROLLER_AXIS_RIGHTY));
|
|
||||||
}
|
|
||||||
if (naxes >= 6) {
|
|
||||||
axisMask |= ((1 << SDL_CONTROLLER_AXIS_TRIGGERLEFT) | (1 << SDL_CONTROLLER_AXIS_TRIGGERRIGHT));
|
|
||||||
}
|
|
||||||
return axisMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInputDeviceChanged(int deviceId) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onInputDeviceRemoved(int deviceId) {
|
|
||||||
// Do not call inputManager.getInputDevice(), which returns null (#1185).
|
|
||||||
Ebitenmobileview.onInputDeviceRemoved(deviceId);
|
|
||||||
this.gamepads.remove(this.getGamepad(deviceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// suspendGame suspends the game.
|
|
||||||
// It is recommended to call this when the application is being suspended e.g.,
|
|
||||||
// Activity's onPause is called.
|
|
||||||
public void suspendGame() {
|
|
||||||
this.inputManager.unregisterInputDeviceListener(this);
|
|
||||||
this.ebitenSurfaceView.onPause();
|
|
||||||
try {
|
|
||||||
Ebitenmobileview.suspend();
|
|
||||||
} catch (final Exception e) {
|
|
||||||
onErrorOnGameUpdate(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// resumeGame resumes the game.
|
|
||||||
// It is recommended to call this when the application is being resumed e.g.,
|
|
||||||
// Activity's onResume is called.
|
|
||||||
public void resumeGame() {
|
|
||||||
this.inputManager.registerInputDeviceListener(this, null);
|
|
||||||
this.ebitenSurfaceView.onResume();
|
|
||||||
try {
|
|
||||||
Ebitenmobileview.resume();
|
|
||||||
} catch (final Exception e) {
|
|
||||||
onErrorOnGameUpdate(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// onErrorOnGameUpdate is called on the main thread when an error happens when updating a game.
|
|
||||||
// You can define your own error handler, e.g., using Crashlytics, by overriding this method.
|
|
||||||
protected void onErrorOnGameUpdate(Exception e) {
|
|
||||||
Log.e("Go", e.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private EbitenSurfaceView ebitenSurfaceView;
|
|
||||||
private InputManager inputManager;
|
|
||||||
private ArrayList<Gamepad> gamepads;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const surfaceViewJava = `// Code generated by ebitenmobile. DO NOT EDIT.
|
|
||||||
|
|
||||||
package {{.JavaPkg}}.{{.PrefixLower}};
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.opengl.GLSurfaceView;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import javax.microedition.khronos.egl.EGLConfig;
|
|
||||||
import javax.microedition.khronos.opengles.GL10;
|
|
||||||
|
|
||||||
import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;
|
|
||||||
import {{.JavaPkg}}.ebitenmobileview.RenderRequester;
|
|
||||||
import {{.JavaPkg}}.{{.PrefixLower}}.EbitenView;
|
|
||||||
|
|
||||||
class EbitenSurfaceView extends GLSurfaceView implements RenderRequester {
|
|
||||||
|
|
||||||
private class EbitenRenderer implements GLSurfaceView.Renderer {
|
|
||||||
|
|
||||||
private boolean errored_ = false;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDrawFrame(GL10 gl) {
|
|
||||||
if (errored_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Ebitenmobileview.update();
|
|
||||||
} catch (final Exception e) {
|
|
||||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
onErrorOnGameUpdate(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
errored_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
|
|
||||||
Ebitenmobileview.onContextLost();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSurfaceChanged(GL10 gl, int width, int height) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public EbitenSurfaceView(Context context) {
|
|
||||||
super(context);
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
public EbitenSurfaceView(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialize() {
|
|
||||||
setEGLContextClientVersion(2);
|
|
||||||
setEGLConfigChooser(8, 8, 8, 8, 0, 0);
|
|
||||||
setRenderer(new EbitenRenderer());
|
|
||||||
Ebitenmobileview.setRenderRequester(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onErrorOnGameUpdate(Exception e) {
|
|
||||||
((EbitenView)getParent()).onErrorOnGameUpdate(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setExplicitRenderingMode(boolean explicitRendering) {
|
|
||||||
if (explicitRendering) {
|
|
||||||
setRenderMode(RENDERMODE_WHEN_DIRTY);
|
|
||||||
} else {
|
|
||||||
setRenderMode(RENDERMODE_CONTINUOUSLY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void requestRenderIfNeeded() {
|
|
||||||
if (getRenderMode() == RENDERMODE_WHEN_DIRTY) {
|
|
||||||
requestRender();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
@ -30,6 +30,15 @@ import (
|
|||||||
//go:embed gobind.go
|
//go:embed gobind.go
|
||||||
var gobind_go []byte
|
var gobind_go []byte
|
||||||
|
|
||||||
|
//go:embed _files/EbitenViewController.m
|
||||||
|
var objcM []byte
|
||||||
|
|
||||||
|
//go:embed _files/EbitenView.java
|
||||||
|
var viewJava []byte
|
||||||
|
|
||||||
|
//go:embed _files/EbitenSurfaceView.java
|
||||||
|
var surfaceViewJava []byte
|
||||||
|
|
||||||
func runCommand(command string, args []string, env []string) error {
|
func runCommand(command string, args []string, env []string) error {
|
||||||
if buildX || buildN {
|
if buildX || buildN {
|
||||||
for _, e := range env {
|
for _, e := range env {
|
||||||
@ -169,6 +178,19 @@ import (
|
|||||||
return tmp, err
|
return tmp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := os.Mkdir(filepath.Join("src", "_files"), 0755); err != nil {
|
||||||
|
return tmp, err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join("src", "_files", "EbitenViewController.m"), objcM, 0644); err != nil {
|
||||||
|
return tmp, err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join("src", "_files", "EbitenView.java"), viewJava, 0644); err != nil {
|
||||||
|
return tmp, err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join("src", "_files", "EbitenSurfaceView.java"), surfaceViewJava, 0644); err != nil {
|
||||||
|
return tmp, err
|
||||||
|
}
|
||||||
|
|
||||||
if err := runGo("build", "-o", exe(filepath.Join("bin", "gobind")), "-tags", "ebitenmobilegobind", filepath.Join("src", "gobind.go")); err != nil {
|
if err := runGo("build", "-o", exe(filepath.Join("bin", "gobind")), "-tags", "ebitenmobilegobind", filepath.Join("src", "gobind.go")); err != nil {
|
||||||
return tmp, err
|
return tmp, err
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "embed"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -37,6 +38,9 @@ const (
|
|||||||
ebitenmobileCommand = "ebitenmobile"
|
ebitenmobileCommand = "ebitenmobile"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed _files/EbitenViewController.h
|
||||||
|
var objcH string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
// This message is copied from `gomobile bind -h`
|
// This message is copied from `gomobile bind -h`
|
||||||
@ -287,29 +291,6 @@ func doBind(args []string, flagset *flag.FlagSet, buildOS string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const objcH = `// Code generated by ebitenmobile. DO NOT EDIT.
|
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
|
||||||
|
|
||||||
@interface {{.PrefixUpper}}EbitenViewController : UIViewController
|
|
||||||
|
|
||||||
// onErrorOnGameUpdate is called on the main thread when an error happens when updating a game.
|
|
||||||
// You can define your own error handler, e.g., using Crashlytics, by overwriting this method.
|
|
||||||
- (void)onErrorOnGameUpdate:(NSError*)err;
|
|
||||||
|
|
||||||
// suspendGame suspends the game.
|
|
||||||
// It is recommended to call this when the application is being suspended e.g.,
|
|
||||||
// UIApplicationDelegate's applicationWillResignActive is called.
|
|
||||||
- (void)suspendGame;
|
|
||||||
|
|
||||||
// resumeGame resumes the game.
|
|
||||||
// It is recommended to call this when the application is being resumed e.g.,
|
|
||||||
// UIApplicationDelegate's applicationDidBecomeActive is called.
|
|
||||||
- (void)resumeGame;
|
|
||||||
|
|
||||||
@end
|
|
||||||
`
|
|
||||||
|
|
||||||
var iosModuleMapTmpl = template.Must(template.New("iosmmap").Parse(`framework module "{{.Module}}" {
|
var iosModuleMapTmpl = template.Must(template.New("iosmmap").Parse(`framework module "{{.Module}}" {
|
||||||
{{range .Headers}} header "{{.}}"
|
{{range .Headers}} header "{{.}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
Loading…
Reference in New Issue
Block a user