2019-08-17 12:00:37 +02:00
// 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.
2021-06-10 18:03:35 +02:00
//go:build ebitenmobilegobind
2019-08-17 12:00:37 +02:00
// +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 )
}
}
2019-09-23 11:53:46 +02:00
func invokeOriginalGobind ( lang string ) ( pkgName string , err error ) {
2019-08-17 12:00:37 +02:00
cmd := exec . Command ( "gobind-original" , os . Args [ 1 : ] ... )
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
if err := cmd . Run ( ) ; err != nil {
2019-09-23 11:53:46 +02:00
return "" , err
2019-08-17 12:00:37 +02:00
}
2019-09-23 11:53:46 +02:00
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 ] )
2019-08-17 12:00:37 +02:00
if err != nil {
2019-09-23 11:53:46 +02:00
return "" , err
2019-08-17 12:00:37 +02:00
}
2019-09-23 11:53:46 +02:00
return pkgs [ 0 ] . Name , nil
}
2019-08-17 12:00:37 +02:00
2019-09-23 11:53:46 +02:00
func run ( ) error {
2019-08-17 12:00:37 +02:00
writeFile := func ( filename string , content string ) error {
2022-09-14 19:45:36 +02:00
if err := os . WriteFile ( filepath . Join ( * outdir , filename ) , [ ] byte ( content ) , 0644 ) ; err != nil {
2019-08-17 12:00:37 +02:00
return err
}
return nil
}
// Add additional files.
langs := strings . Split ( * lang , "," )
for _ , lang := range langs {
2019-09-23 11:53:46 +02:00
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
}
2019-08-17 12:00:37 +02:00
switch lang {
case "objc" :
// iOS
2019-08-18 08:53:25 +02:00
if err := writeFile ( filepath . Join ( "src" , "gobind" , prefixLower + "ebitenviewcontroller_ios.m" ) , replacePrefixes ( objcM ) ) ; err != nil {
2019-08-17 12:00:37 +02:00
return err
}
2022-01-04 19:06:22 +01:00
if err := writeFile ( filepath . Join ( "src" , "gobind" , prefixLower + "ebitenviewcontroller_ios.go" ) , ` package main
// #cgo CFLAGS: -DGLES_SILENCE_DEPRECATION
import "C" ` ) ; err != nil {
return err
}
2019-08-17 12:00:37 +02:00
case "java" :
// Android
2019-08-18 08:53:25 +02:00
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
}
2019-08-17 12:00:37 +02:00
case "go" :
// Do nothing.
default :
panic ( fmt . Sprintf ( "unsupported language: %s" , lang ) )
}
}
return nil
}
const objcM = ` // Code generated by ebitenmobile. DO NOT EDIT.
2019-09-02 21:08:58 +02:00
# import < TargetConditionals . h >
2019-08-17 12:00:37 +02:00
# import < stdint . h >
# import < UIKit / UIKit . h >
2022-07-09 10:31:21 +02:00
# import < GLKit / GLKit . h >
2019-09-02 21:08:58 +02:00
2019-08-17 12:00:37 +02:00
# import "Ebitenmobileview.objc.h"
2021-07-22 18:07:28 +02:00
@ interface { { . PrefixUpper } } EbitenViewController : UIViewController < EbitenmobileviewRenderRequester >
2019-08-17 12:00:37 +02:00
@ end
@ implementation { { . PrefixUpper } } EbitenViewController {
2021-07-22 18:07:28 +02:00
UIView * metalView_ ;
GLKView * glkView_ ;
bool started_ ;
bool active_ ;
bool error_ ;
CADisplayLink * displayLink_ ;
bool explicitRendering_ ;
2019-08-17 12:00:37 +02:00
}
2019-09-02 21:08:58 +02:00
- ( UIView * ) metalView {
if ( ! metalView_ ) {
metalView_ = [ [ UIView alloc ] init ] ;
metalView_ . multipleTouchEnabled = YES ;
}
return metalView_ ;
}
2019-08-17 12:00:37 +02:00
- ( GLKView * ) glkView {
if ( ! glkView_ ) {
glkView_ = [ [ GLKView alloc ] init ] ;
glkView_ . multipleTouchEnabled = YES ;
}
return glkView_ ;
}
- ( void ) viewDidLoad {
[ super viewDidLoad ] ;
2019-09-12 19:41:43 +02:00
if ( ! started_ ) {
@ synchronized ( self ) {
active_ = true ;
}
started_ = true ;
}
2022-03-22 16:00:45 +01:00
if ( EbitenmobileviewIsGL ( ) ) {
self . glkView . delegate = ( id < GLKViewDelegate > ) ( self ) ;
[ self . view addSubview : self . glkView ] ;
2019-08-17 12:00:37 +02:00
2022-03-22 16:00:45 +01:00
EAGLContext * context = [ [ EAGLContext alloc ] initWithAPI : kEAGLRenderingAPIOpenGLES2 ] ;
[ self glkView ] . context = context ;
2019-08-17 12:00:37 +02:00
2022-03-22 16:00:45 +01:00
[ EAGLContext setCurrentContext : context ] ;
} else {
[ self . view addSubview : self . metalView ] ;
EbitenmobileviewSetUIView ( ( uintptr_t ) ( self . metalView ) ) ;
}
2019-09-02 21:08:58 +02:00
2021-07-22 18:07:28 +02:00
displayLink_ = [ CADisplayLink displayLinkWithTarget : self selector : @ selector ( drawFrame ) ] ;
[ displayLink_ addToRunLoop : [ NSRunLoop currentRunLoop ] forMode : NSDefaultRunLoopMode ] ;
EbitenmobileviewSetRenderRequester ( self ) ;
2019-08-17 12:00:37 +02:00
}
2020-02-11 12:20:13 +01:00
- ( void ) viewWillLayoutSubviews {
CGRect viewRect = [ [ self view ] frame ] ;
2022-03-22 16:00:45 +01:00
if ( EbitenmobileviewIsGL ( ) ) {
[ [ self glkView ] setFrame : viewRect ] ;
} else {
[ [ self metalView ] setFrame : viewRect ] ;
}
2020-02-11 12:20:13 +01:00
}
2019-08-17 12:00:37 +02:00
- ( void ) viewDidLayoutSubviews {
[ super viewDidLayoutSubviews ] ;
CGRect viewRect = [ [ self view ] frame ] ;
2020-02-11 09:53:50 +01:00
EbitenmobileviewLayout ( viewRect . size . width , viewRect . size . height ) ;
2019-08-17 12:00:37 +02:00
}
- ( 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 ;
}
2022-10-25 06:36:54 +02:00
}
2019-09-12 20:26:13 +02:00
2022-10-25 06:36:54 +02:00
if ( EbitenmobileviewIsGL ( ) ) {
[ [ self glkView ] setNeedsDisplay ] ;
} else {
[ self updateEbiten ] ;
}
2021-07-22 18:07:28 +02:00
2022-10-25 06:36:54 +02:00
@ synchronized ( self ) {
2021-07-22 18:07:28 +02:00
if ( explicitRendering_ ) {
[ displayLink_ setPaused : YES ] ;
}
2019-09-13 04:47:45 +02:00
}
2019-08-17 12:00:37 +02:00
}
- ( void ) glkView : ( GLKView * ) view drawInRect : ( CGRect ) rect {
2022-10-25 06:36:54 +02:00
[ self updateEbiten ] ;
2019-09-02 21:08:58 +02:00
}
- ( void ) updateEbiten {
if ( error_ ) {
return ;
}
2022-10-25 06:36:54 +02:00
2019-09-02 21:08:58 +02:00
NSError * err = nil ;
EbitenmobileviewUpdate ( & err ) ;
if ( err != nil ) {
[ self performSelectorOnMainThread : @ selector ( onErrorOnGameUpdate : )
withObject : err
waitUntilDone : NO ] ;
error_ = true ;
2019-08-17 12:00:37 +02:00
}
}
2019-08-18 13:55:33 +02:00
- ( void ) onErrorOnGameUpdate : ( NSError * ) err {
NSLog ( @ "Error: %@" , err ) ;
}
2019-08-17 12:00:37 +02:00
- ( void ) updateTouches : ( NSSet * ) touches {
for ( UITouch * touch in touches ) {
2022-03-22 16:00:45 +01:00
if ( EbitenmobileviewIsGL ( ) ) {
if ( touch . view != [ self glkView ] ) {
continue ;
}
} else {
if ( touch . view != [ self metalView ] ) {
continue ;
}
2019-08-17 12:00:37 +02:00
}
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 ] ;
}
2019-09-02 21:08:58 +02:00
- ( void ) suspendGame {
2020-06-09 17:33:39 +02:00
NSAssert ( started_ , @ "suspendGame must not be called before viewDidLoad is called" ) ;
2019-09-12 19:41:43 +02:00
@ synchronized ( self ) {
active_ = false ;
2022-10-25 06:36:54 +02:00
}
NSError * err = nil ;
EbitenmobileviewSuspend ( & err ) ;
if ( err != nil ) {
[ self onErrorOnGameUpdate : err ] ;
2019-09-12 19:41:43 +02:00
}
}
2019-09-02 21:08:58 +02:00
- ( void ) resumeGame {
2020-06-09 17:33:39 +02:00
NSAssert ( started_ , @ "resumeGame must not be called before viewDidLoad is called" ) ;
2019-09-12 19:41:43 +02:00
@ synchronized ( self ) {
active_ = true ;
2022-10-25 06:36:54 +02:00
}
NSError * err = nil ;
EbitenmobileviewResume ( & err ) ;
if ( err != nil ) {
[ self onErrorOnGameUpdate : err ] ;
2019-09-12 19:41:43 +02:00
}
}
2021-07-22 18:07:28 +02:00
- ( 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 ] ;
}
}
}
2019-08-17 12:00:37 +02:00
@ end
`
2019-08-18 08:53:25 +02:00
const viewJava = ` // Code generated by ebitenmobile. DO NOT EDIT.
package { { . JavaPkg } } . { { . PrefixLower } } ;
2022-09-08 05:29:02 +02:00
import java . util . ArrayList ;
import java . util . Collections ;
import java . util . Comparator ;
import java . util . List ;
2019-08-18 08:53:25 +02:00
import android . content . Context ;
2020-03-22 11:02:56 +01:00
import android . hardware . input . InputManager ;
2019-09-14 05:32:02 +02:00
import android . os . Handler ;
import android . os . Looper ;
2019-08-18 08:53:25 +02:00
import android . util . AttributeSet ;
2021-02-05 16:27:53 +01:00
import android . util . DisplayMetrics ;
2019-08-18 13:22:40 +02:00
import android . util . Log ;
2021-02-05 16:27:53 +01:00
import android . view . Display ;
2020-02-18 16:57:19 +01:00
import android . view . KeyEvent ;
2020-03-22 11:02:56 +01:00
import android . view . InputDevice ;
2020-02-24 09:50:01 +01:00
import android . view . MotionEvent ;
2019-08-18 08:53:25 +02:00
import android . view . ViewGroup ;
2021-02-05 16:27:53 +01:00
import android . view . WindowManager ;
2019-08-18 08:53:25 +02:00
import { { . JavaPkg } } . ebitenmobileview . Ebitenmobileview ;
2020-03-22 11:02:56 +01:00
public class EbitenView extends ViewGroup implements InputManager . InputDeviceListener {
2022-09-08 05:29:02 +02:00
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 ;
}
}
2021-04-10 19:30:35 +02:00
private static double pxToDp ( double x ) {
return x / Ebitenmobileview . deviceScale ( ) ;
2019-08-18 08:53:25 +02:00
}
public EbitenView ( Context context ) {
super ( context ) ;
2020-03-22 11:02:56 +01:00
initialize ( context ) ;
2019-08-18 08:53:25 +02:00
}
public EbitenView ( Context context , AttributeSet attrs ) {
super ( context , attrs ) ;
2020-03-22 11:02:56 +01:00
initialize ( context ) ;
2019-10-18 19:10:32 +02:00
}
2020-03-22 11:02:56 +01:00
private void initialize ( Context context ) {
2022-09-08 05:29:02 +02:00
this . gamepads = new ArrayList < Gamepad > ( ) ;
2020-03-22 11:02:56 +01:00
this . ebitenSurfaceView = new EbitenSurfaceView ( getContext ( ) ) ;
2020-02-11 09:53:50 +01:00
LayoutParams params = new LayoutParams ( LayoutParams . MATCH_PARENT , LayoutParams . MATCH_PARENT ) ;
2020-03-22 11:02:56 +01:00
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 ) ;
}
2019-08-18 08:53:25 +02:00
}
@ Override
protected void onLayout ( boolean changed , int left , int top , int right , int bottom ) {
2020-03-22 11:02:56 +01:00
this . ebitenSurfaceView . layout ( 0 , 0 , right - left , bottom - top ) ;
2020-02-11 09:53:50 +01:00
double widthInDp = pxToDp ( right - left ) ;
double heightInDp = pxToDp ( bottom - top ) ;
Ebitenmobileview . layout ( widthInDp , heightInDp ) ;
2019-08-18 08:53:25 +02:00
}
2020-02-18 16:57:19 +01:00
@ Override
public boolean onKeyDown ( int keyCode , KeyEvent event ) {
2020-03-22 11:02:56 +01:00
Ebitenmobileview . onKeyDownOnAndroid ( keyCode , event . getUnicodeChar ( ) , event . getSource ( ) , event . getDeviceId ( ) ) ;
2020-02-18 16:57:19 +01:00
return true ;
}
@ Override
public boolean onKeyUp ( int keyCode , KeyEvent event ) {
2020-03-22 11:02:56 +01:00
Ebitenmobileview . onKeyUpOnAndroid ( keyCode , event . getSource ( ) , event . getDeviceId ( ) ) ;
2020-02-18 16:57:19 +01:00
return true ;
}
2020-02-24 09:50:01 +01:00
@ Override
public boolean onTouchEvent ( MotionEvent e ) {
2022-07-29 10:38:24 +02:00
// 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 ) ;
2022-07-29 10:38:24 +02:00
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 ;
}
2022-09-08 05:29:02 +02:00
private Gamepad getGamepad ( int deviceId ) {
for ( Gamepad gamepad : this . gamepads ) {
if ( gamepad . deviceId == deviceId ) {
return gamepad ;
}
}
return null ;
}
2020-03-23 18:06:56 +01:00
@ 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 ) ;
}
2022-09-08 05:29:02 +02:00
// 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.0 f - 1.0 f ;
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 ) ;
2020-03-23 18:06:56 +01:00
}
return true ;
}
2020-03-22 11:02:56 +01:00
@ Override
public void onInputDeviceAdded ( int deviceId ) {
InputDevice inputDevice = this . inputManager . getInputDevice ( deviceId ) ;
2020-09-10 07:10:35 +02:00
// The InputDevice can be null on some deivces (#1342).
if ( inputDevice == null ) {
return ;
}
2021-03-14 15:26:02 +01:00
// A fingerprint reader is unexpectedly recognized as a joystick. Skip this (#1542).
if ( inputDevice . getName ( ) . equals ( "uinput-fpc" ) ) {
return ;
}
2020-03-22 11:02:56 +01:00
int sources = inputDevice . getSources ( ) ;
if ( ( sources & InputDevice . SOURCE_GAMEPAD ) != InputDevice . SOURCE_GAMEPAD &&
( sources & InputDevice . SOURCE_JOYSTICK ) != InputDevice . SOURCE_JOYSTICK ) {
return ;
}
2020-03-23 18:06:56 +01:00
2022-09-08 05:29:02 +02:00
// 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 ) {
2022-01-04 15:14:15 +01:00
if ( range . getAxis ( ) == MotionEvent . AXIS_HAT_X || range . getAxis ( ) == MotionEvent . AXIS_HAT_Y ) {
2022-09-08 05:29:02 +02:00
gamepad . hats . add ( range ) ;
2022-01-04 15:14:15 +01:00
} else {
2022-09-08 05:29:02 +02:00
gamepad . axes . add ( range ) ;
2022-01-04 15:14:15 +01:00
}
2020-03-23 18:06:56 +01:00
}
2022-09-08 05:29:02 +02:00
this . gamepads . add ( gamepad ) ;
2020-03-23 18:06:56 +01:00
2020-03-24 17:40:25 +01:00
String descriptor = inputDevice . getDescriptor ( ) ;
int vendorId = inputDevice . getVendorId ( ) ;
int productId = inputDevice . getProductId ( ) ;
// These values are required to calculate SDL's GUID.
2022-09-08 05:29:02 +02:00
int buttonMask = getButtonMask ( inputDevice , gamepad . hats . size ( ) / 2 ) ;
2020-03-24 17:40:25 +01:00
int axisMask = getAxisMask ( inputDevice ) ;
2022-09-08 05:29:02 +02:00
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 ) ;
}
2020-03-24 17:40:25 +01:00
}
// 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
2022-09-08 05:29:02 +02:00
private static int getButtonMask ( InputDevice joystickDevice , int nhats ) {
2022-09-07 17:04:43 +02:00
int buttonMask = 0 ;
2020-03-24 17:40:25 +01:00
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 -> ??
} ;
2022-09-07 17:04:43 +02:00
boolean [ ] hasKeys = joystickDevice . hasKeys ( keys ) ;
2020-03-24 17:40:25 +01:00
for ( int i = 0 ; i < keys . length ; ++ i ) {
2022-09-07 17:04:43 +02:00
if ( hasKeys [ i ] ) {
buttonMask |= masks [ i ] ;
2020-03-24 17:40:25 +01:00
}
}
2022-09-07 17:04:43 +02:00
// 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 ;
2020-03-24 17:40:25 +01:00
}
2022-09-08 05:29:02 +02:00
private static int getAxisMask ( InputDevice joystickDevice ) {
2020-03-24 17:40:25 +01:00
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
2020-03-24 17:40:25 +01:00
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 ;
2020-03-22 11:02:56 +01:00
}
@ Override
public void onInputDeviceChanged ( int deviceId ) {
// Do nothing.
}
@ Override
public void onInputDeviceRemoved ( int deviceId ) {
2020-06-09 16:34:07 +02:00
// Do not call inputManager.getInputDevice(), which returns null (#1185).
Ebitenmobileview . onInputDeviceRemoved ( deviceId ) ;
2022-09-08 05:29:02 +02:00
this . gamepads . remove ( this . getGamepad ( deviceId ) ) ;
2020-03-22 11:02:56 +01:00
}
2019-09-14 08:33:35 +02:00
// 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 ( ) {
2020-03-22 11:02:56 +01:00
this . inputManager . unregisterInputDeviceListener ( this ) ;
this . ebitenSurfaceView . onPause ( ) ;
2021-05-04 15:24:31 +02:00
try {
Ebitenmobileview . suspend ( ) ;
} catch ( final Exception e ) {
onErrorOnGameUpdate ( e ) ;
}
2019-08-18 08:53:25 +02:00
}
2019-09-14 08:33:35 +02:00
// 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 ( ) {
2020-03-22 11:02:56 +01:00
this . inputManager . registerInputDeviceListener ( this , null ) ;
this . ebitenSurfaceView . onResume ( ) ;
2021-05-04 15:24:31 +02:00
try {
Ebitenmobileview . resume ( ) ;
} catch ( final Exception e ) {
onErrorOnGameUpdate ( e ) ;
}
2019-08-18 08:53:25 +02:00
}
2019-09-14 08:33:35 +02:00
// 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.
2019-08-18 13:22:40 +02:00
protected void onErrorOnGameUpdate ( Exception e ) {
Log . e ( "Go" , e . toString ( ) ) ;
}
2020-03-22 11:02:56 +01:00
private EbitenSurfaceView ebitenSurfaceView ;
private InputManager inputManager ;
2022-09-08 05:29:02 +02:00
private ArrayList < Gamepad > gamepads ;
2019-08-18 08:53:25 +02:00
}
`
const surfaceViewJava = ` // Code generated by ebitenmobile. DO NOT EDIT.
package { { . JavaPkg } } . { { . PrefixLower } } ;
import android . content . Context ;
import android . opengl . GLSurfaceView ;
2019-08-18 13:22:40 +02:00
import android . os . Handler ;
import android . os . Looper ;
2019-08-18 08:53:25 +02:00
import android . util . AttributeSet ;
2020-03-22 11:02:56 +01:00
import android . util . Log ;
2019-08-18 08:53:25 +02:00
import javax . microedition . khronos . egl . EGLConfig ;
import javax . microedition . khronos . opengles . GL10 ;
import { { . JavaPkg } } . ebitenmobileview . Ebitenmobileview ;
2021-07-22 18:07:28 +02:00
import { { . JavaPkg } } . ebitenmobileview . RenderRequester ;
2019-08-18 13:22:40 +02:00
import { { . JavaPkg } } . { { . PrefixLower } } . EbitenView ;
2019-08-18 08:53:25 +02:00
2021-07-22 18:07:28 +02:00
class EbitenSurfaceView extends GLSurfaceView implements RenderRequester {
2019-08-18 08:53:25 +02:00
2019-08-18 13:22:40 +02:00
private class EbitenRenderer implements GLSurfaceView . Renderer {
2019-08-18 08:53:25 +02:00
private boolean errored_ = false ;
@ Override
public void onDrawFrame ( GL10 gl ) {
if ( errored_ ) {
return ;
}
try {
Ebitenmobileview . update ( ) ;
2019-08-18 13:22:40 +02:00
} catch ( final Exception e ) {
new Handler ( Looper . getMainLooper ( ) ) . post ( new Runnable ( ) {
@ Override
public void run ( ) {
onErrorOnGameUpdate ( e ) ;
}
} ) ;
2019-08-18 08:53:25 +02:00
errored_ = true ;
}
}
@ Override
public void onSurfaceCreated ( GL10 gl , EGLConfig config ) {
2020-08-14 18:37:51 +02:00
Ebitenmobileview . onContextLost ( ) ;
2019-08-18 08:53:25 +02:00
}
@ 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 ( ) ) ;
2021-07-22 18:07:28 +02:00
Ebitenmobileview . setRenderRequester ( this ) ;
2019-08-18 08:53:25 +02:00
}
2019-08-18 13:22:40 +02:00
private void onErrorOnGameUpdate ( Exception e ) {
( ( EbitenView ) getParent ( ) ) . onErrorOnGameUpdate ( e ) ;
}
2021-07-22 18:07:28 +02:00
@ Override
2022-08-03 13:52:23 +02:00
public synchronized void setExplicitRenderingMode ( boolean explicitRendering ) {
if ( explicitRendering ) {
2021-07-22 18:07:28 +02:00
setRenderMode ( RENDERMODE_WHEN_DIRTY ) ;
} else {
setRenderMode ( RENDERMODE_CONTINUOUSLY ) ;
}
}
@ Override
public synchronized void requestRenderIfNeeded ( ) {
if ( getRenderMode ( ) == RENDERMODE_WHEN_DIRTY ) {
requestRender ( ) ;
}
}
2019-08-18 08:53:25 +02:00
}
`