github.com/rajveermalviya/gamen@v0.1.2-0.20220930195403-9be15877c1aa/internal/android/display.go (about)

     1  //go:build android
     2  
     3  package android
     4  
     5  import (
     6  	"runtime"
     7  	"time"
     8  
     9  	"github.com/rajveermalviya/gamen/dpi"
    10  	"github.com/rajveermalviya/gamen/events"
    11  	"github.com/rajveermalviya/gamen/internal/common/atomicx"
    12  )
    13  
    14  /*
    15  
    16  #cgo CXXFLAGS: -isystem ${SRCDIR}/game-activity/include/
    17  #cgo CFLAGS: -isystem ${SRCDIR}/game-activity/include/
    18  #cgo LDFLAGS: -static-libstdc++ -landroid -llog
    19  
    20  #include <game-activity/native_app_glue/android_native_app_glue.h>
    21  
    22  extern void display_poll(int timeoutMillis);
    23  extern void display_set_handler(struct android_app* app);
    24  
    25  */
    26  import "C"
    27  
    28  var androidApp atomicx.Pointer[C.struct_android_app]
    29  
    30  //go:linkname main_main main.main
    31  func main_main()
    32  
    33  //export android_main
    34  func android_main(app *C.struct_android_app) {
    35  	androidApp.Store(app)
    36  
    37  	C.display_set_handler(app)
    38  
    39  	main_main()
    40  }
    41  
    42  type Display struct{}
    43  
    44  func NewDisplay() (*Display, error) {
    45  	return &Display{}, nil
    46  }
    47  
    48  func (d *Display) Destroy() {
    49  	if app := androidApp.Load(); app != nil {
    50  		C.GameActivity_finish(app.activity)
    51  	}
    52  }
    53  func (d *Display) Wait() bool {
    54  	C.display_poll(-1)
    55  
    56  	if app := androidApp.Load(); app != nil {
    57  		if app.destroyRequested != 0 {
    58  			return false
    59  		}
    60  	}
    61  
    62  	clearInputBuffers()
    63  	return true
    64  }
    65  func (d *Display) Poll() bool {
    66  	C.display_poll(0)
    67  
    68  	if app := androidApp.Load(); app != nil {
    69  		if app.destroyRequested != 0 {
    70  			return false
    71  		}
    72  	}
    73  
    74  	clearInputBuffers()
    75  	return true
    76  }
    77  func (d *Display) WaitTimeout(t time.Duration) bool {
    78  	C.display_poll(C.int(t.Milliseconds()))
    79  
    80  	if app := androidApp.Load(); app != nil {
    81  		if app.destroyRequested != 0 {
    82  			return false
    83  		}
    84  	}
    85  
    86  	clearInputBuffers()
    87  	return true
    88  }
    89  
    90  var (
    91  	windowSurfaceCreatedCb          atomicx.Pointer[events.WindowSurfaceCreatedCallback]
    92  	windowSurfaceDestroyedCb        atomicx.Pointer[events.WindowSurfaceDestroyedCallback]
    93  	windowResizedCallback           atomicx.Pointer[events.WindowResizedCallback]
    94  	windowFocusedCb                 atomicx.Pointer[events.WindowFocusedCallback]
    95  	windowUnfocusedCb               atomicx.Pointer[events.WindowUnfocusedCallback]
    96  	windowTouchInputCb              atomicx.Pointer[events.WindowTouchInputCallback]
    97  	windowKeyboardInputCb           atomicx.Pointer[events.WindowKeyboardInputCallback]
    98  	windowReceivedCharacterCallback atomicx.Pointer[events.WindowReceivedCharacterCallback]
    99  )
   100  
   101  func runResizedCallback() {
   102  	if app := androidApp.Load(); app != nil {
   103  		window := app.window
   104  		config := app.config
   105  
   106  		if cb := windowResizedCallback.Load(); cb != nil {
   107  			if cb := (*cb); cb != nil {
   108  				if window != nil {
   109  					newWidth := C.ANativeWindow_getWidth(window)
   110  					newHeight := C.ANativeWindow_getHeight(window)
   111  					scaleFactor := float64(1)
   112  					if config != nil {
   113  						density := C.AConfiguration_getDensity(config)
   114  						scaleFactor = float64(density) / float64(160)
   115  					}
   116  
   117  					cb(uint32(newWidth), uint32(newHeight), scaleFactor)
   118  				}
   119  			}
   120  		}
   121  	}
   122  }
   123  
   124  //export display_handle_command
   125  func display_handle_command(app *C.struct_android_app, cmd C.int32_t) {
   126  	switch cmd {
   127  	case C.APP_CMD_INIT_WINDOW:
   128  		if cb := windowSurfaceCreatedCb.Load(); cb != nil {
   129  			if cb := (*cb); cb != nil {
   130  				cb()
   131  			}
   132  		}
   133  
   134  	case C.APP_CMD_TERM_WINDOW:
   135  		if cb := windowSurfaceDestroyedCb.Load(); cb != nil {
   136  			if cb := (*cb); cb != nil {
   137  				cb()
   138  			}
   139  		}
   140  
   141  	case C.APP_CMD_WINDOW_RESIZED,
   142  		C.APP_CMD_CONFIG_CHANGED,
   143  		C.APP_CMD_WINDOW_INSETS_CHANGED,
   144  		C.APP_CMD_CONTENT_RECT_CHANGED:
   145  		runResizedCallback()
   146  
   147  	case C.APP_CMD_LOW_MEMORY:
   148  		runtime.GC()
   149  
   150  	case C.APP_CMD_GAINED_FOCUS:
   151  		if cb := windowFocusedCb.Load(); cb != nil {
   152  			if cb := (*cb); cb != nil {
   153  				cb()
   154  			}
   155  		}
   156  
   157  	case C.APP_CMD_LOST_FOCUS:
   158  		if cb := windowUnfocusedCb.Load(); cb != nil {
   159  			if cb := (*cb); cb != nil {
   160  				cb()
   161  			}
   162  		}
   163  
   164  	case C.APP_CMD_WINDOW_REDRAW_NEEDED:
   165  	case C.APP_CMD_START:
   166  	case C.APP_CMD_RESUME:
   167  	case C.APP_CMD_SAVE_STATE:
   168  	case C.APP_CMD_PAUSE:
   169  	case C.APP_CMD_STOP:
   170  	case C.APP_CMD_DESTROY:
   171  	}
   172  }
   173  
   174  //export display_handle_key_event
   175  func display_handle_key_event(event *C.GameActivityKeyEvent) C.bool {
   176  	var state events.ButtonState
   177  
   178  	switch event.action {
   179  	case C.AKEY_EVENT_ACTION_DOWN,
   180  		C.AKEY_EVENT_ACTION_MULTIPLE:
   181  		state = events.ButtonStatePressed
   182  	case C.AKEY_EVENT_ACTION_UP:
   183  		state = events.ButtonStateReleased
   184  
   185  	default:
   186  		panic("unreachable")
   187  	}
   188  
   189  	if cb := windowKeyboardInputCb.Load(); cb != nil {
   190  		if cb := (*cb); cb != nil {
   191  			cb(
   192  				state,
   193  				// TODO: current release of GameActivity doesn't expose scancode,
   194  				// but it's available in master, we'll have to wait for next release
   195  				events.ScanCode(0),
   196  				mapKeycode(event.keyCode),
   197  			)
   198  		}
   199  	}
   200  
   201  	if event.unicodeChar != 0 && state == events.ButtonStatePressed {
   202  		if cb := windowReceivedCharacterCallback.Load(); cb != nil {
   203  			if cb := (*cb); cb != nil {
   204  				cb(rune(event.unicodeChar))
   205  			}
   206  		}
   207  	}
   208  
   209  	return true
   210  }
   211  
   212  var oldPosXs, oldPosYs [C.GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT]C.float
   213  
   214  //export display_handle_motion_event
   215  func display_handle_motion_event(event *C.GameActivityMotionEvent) C.bool {
   216  	mask := event.action & C.AMOTION_EVENT_ACTION_MASK
   217  	var ptrIdx C.int32_t = C.GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT
   218  	var phase events.TouchPhase
   219  
   220  	switch mask {
   221  	case C.AMOTION_EVENT_ACTION_POINTER_DOWN:
   222  		ptrIdx = (event.action & C.AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> C.AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT
   223  		phase = events.TouchPhaseStarted
   224  
   225  	case C.AMOTION_EVENT_ACTION_POINTER_UP:
   226  		ptrIdx = (event.action & C.AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> C.AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT
   227  		phase = events.TouchPhaseEnded
   228  
   229  	case C.AMOTION_EVENT_ACTION_DOWN:
   230  		ptrIdx = 0
   231  		phase = events.TouchPhaseStarted
   232  
   233  	case C.AMOTION_EVENT_ACTION_UP:
   234  		ptrIdx = 0
   235  		phase = events.TouchPhaseEnded
   236  
   237  	case C.AMOTION_EVENT_ACTION_MOVE:
   238  		for ptrIdx, ptr := range event.pointers {
   239  			if ptr.rawX == 0 && ptr.rawY == 0 {
   240  				continue
   241  			}
   242  
   243  			oldX := oldPosXs[ptrIdx]
   244  			oldY := oldPosYs[ptrIdx]
   245  			newX := C.GameActivityPointerAxes_getAxisValue(&ptr, C.AMOTION_EVENT_AXIS_X)
   246  			newY := C.GameActivityPointerAxes_getAxisValue(&ptr, C.AMOTION_EVENT_AXIS_Y)
   247  
   248  			if oldX != newX && oldY != newY {
   249  				oldPosXs[ptrIdx] = newX
   250  				oldPosYs[ptrIdx] = newY
   251  
   252  				if cb := windowTouchInputCb.Load(); cb != nil {
   253  					if cb := (*cb); cb != nil {
   254  						cb(
   255  							events.TouchPhaseMoved,
   256  							dpi.PhysicalPosition[float64]{
   257  								X: float64(newX),
   258  								Y: float64(newY),
   259  							},
   260  							events.TouchPointerID(ptr.id),
   261  						)
   262  					}
   263  				}
   264  			}
   265  		}
   266  	}
   267  
   268  	if ptrIdx != C.GAMEACTIVITY_MAX_NUM_POINTERS_IN_MOTION_EVENT {
   269  		ptr := event.pointers[ptrIdx]
   270  		x := C.GameActivityPointerAxes_getAxisValue(&ptr, C.AMOTION_EVENT_AXIS_X)
   271  		y := C.GameActivityPointerAxes_getAxisValue(&ptr, C.AMOTION_EVENT_AXIS_Y)
   272  
   273  		if phase == events.TouchPhaseEnded {
   274  			oldPosXs[ptrIdx] = 0
   275  			oldPosYs[ptrIdx] = 0
   276  		} else {
   277  			oldPosXs[ptrIdx] = x
   278  			oldPosYs[ptrIdx] = y
   279  		}
   280  
   281  		if cb := windowTouchInputCb.Load(); cb != nil {
   282  			if cb := (*cb); cb != nil {
   283  				cb(
   284  					phase,
   285  					dpi.PhysicalPosition[float64]{
   286  						X: float64(x),
   287  						Y: float64(y),
   288  					},
   289  					events.TouchPointerID(ptr.id),
   290  				)
   291  			}
   292  		}
   293  	}
   294  
   295  	return true
   296  }
   297  
   298  func clearInputBuffers() {
   299  	if app := androidApp.Load(); app != nil {
   300  		ib := C.android_app_swap_input_buffers(app)
   301  		if ib == nil {
   302  			return
   303  		}
   304  		if ib.motionEventsCount > 0 {
   305  			C.android_app_clear_motion_events(ib)
   306  		}
   307  		if ib.keyEventsCount > 0 {
   308  			C.android_app_clear_key_events(ib)
   309  		}
   310  	}
   311  }