ebiten/cmd/ebitenmobile/gobind.go

833 lines
26 KiB
Go
Raw Normal View History

// Copyright 2019 The Ebiten 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.
//go:build ebitenmobilegobind
// +build ebitenmobilegobind
// gobind is a wrapper of the original gobind. This command adds extra files like a view controller.
package main
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"golang.org/x/tools/go/packages"
)
var (
lang = flag.String("lang", "", "")
outdir = flag.String("outdir", "", "")
javaPkg = flag.String("javapkg", "", "")
prefix = flag.String("prefix", "", "")
bootclasspath = flag.String("bootclasspath", "", "")
classpath = flag.String("classpath", "", "")
tags = flag.String("tags", "", "")
)
var usage = `The Gobind tool generates Java language bindings for Go.
For usage details, see doc.go.`
func main() {
flag.Parse()
if err := run(); err != nil {
log.Fatal(err)
}
}
func invokeOriginalGobind(lang string) (pkgName string, err error) {
cmd := exec.Command("gobind-original", os.Args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return "", err
}
cfgtags := strings.Join(strings.Split(*tags, ","), " ")
cfg := &packages.Config{}
switch lang {
case "java":
cfg.Env = append(os.Environ(), "GOOS=android")
case "objc":
cfg.Env = append(os.Environ(), "GOOS=darwin")
if cfgtags != "" {
cfgtags += " "
}
cfgtags += "ios"
}
cfg.BuildFlags = []string{"-tags", cfgtags}
pkgs, err := packages.Load(cfg, flag.Args()[0])
if err != nil {
return "", err
}
return pkgs[0].Name, nil
}
func run() error {
writeFile := func(filename string, content string) error {
if err := os.WriteFile(filepath.Join(*outdir, filename), []byte(content), 0644); err != nil {
return err
}
return nil
}
// Add additional files.
langs := strings.Split(*lang, ",")
for _, lang := range langs {
pkgName, err := invokeOriginalGobind(lang)
if err != nil {
return err
}
prefixLower := *prefix + pkgName
prefixUpper := strings.Title(*prefix) + strings.Title(pkgName)
replacePrefixes := func(content string) string {
content = strings.ReplaceAll(content, "{{.PrefixUpper}}", prefixUpper)
content = strings.ReplaceAll(content, "{{.PrefixLower}}", prefixLower)
content = strings.ReplaceAll(content, "{{.JavaPkg}}", *javaPkg)
return content
}
switch lang {
case "objc":
// iOS
if err := writeFile(filepath.Join("src", "gobind", prefixLower+"ebitenviewcontroller_ios.m"), replacePrefixes(objcM)); err != nil {
return err
}
if err := writeFile(filepath.Join("src", "gobind", prefixLower+"ebitenviewcontroller_ios.go"), `package main
// #cgo CFLAGS: -DGLES_SILENCE_DEPRECATION
import "C"`); err != nil {
return err
}
case "java":
// Android
dir := filepath.Join(strings.Split(*javaPkg, ".")...)
dir = filepath.Join(dir, prefixLower)
if err := writeFile(filepath.Join("java", dir, "EbitenView.java"), replacePrefixes(viewJava)); err != nil {
return err
}
if err := writeFile(filepath.Join("java", dir, "EbitenSurfaceView.java"), replacePrefixes(surfaceViewJava)); err != nil {
return err
}
case "go":
// Do nothing.
default:
panic(fmt.Sprintf("unsupported language: %s", lang))
}
}
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{
2019-09-12 20:26:13 +02:00
@synchronized(self) {
if (!active_) {
return;
}
}
2019-09-12 20:26:13 +02:00
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 {
2020-06-09 17:33:39 +02:00
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 {
2020-06-09 17:33:39 +02:00
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;
2020-02-24 09:50:01 +01:00
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;
}
2020-02-24 09:50:01 +01:00
@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();
2020-02-24 09:50:01 +01:00
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));
2020-02-24 09:50:01 +01:00
}
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:
2021-07-19 16:00:41 +02:00
// 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:
2021-07-19 16:00:41 +02:00
// 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.
2020-08-28 20:14:19 +02:00
// 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();
}
}
}
`