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 }