1 module dlangui.platforms.android.androidapp; 2 3 version(Android): 4 5 import core.stdc.stdlib : malloc; 6 import core.stdc.string : memset; 7 import dlangui.core.logger; 8 9 import dlangui.widgets.styles; 10 import dlangui.widgets.widget; 11 import dlangui.graphics.drawbuf; 12 import dlangui.graphics.gldrawbuf; 13 import dlangui.graphics.glsupport; 14 import dlangui.platforms.common.platform; 15 16 import android.input, android.looper : ALooper_pollAll; 17 import android.native_window : ANativeWindow_setBuffersGeometry; 18 import android.configuration; 19 import android.log, android.android_native_app_glue; 20 21 /** 22 * Window abstraction layer. Widgets can be shown only inside window. 23 * 24 */ 25 class AndroidWindow : Window { 26 // Abstract methods : override in platform implementatino 27 28 /// show window 29 override void show() { 30 // TODO 31 _visible = true; 32 _platform.drawWindow(this); 33 } 34 bool _visible; 35 36 protected dstring _caption; 37 /// returns window caption 38 override @property dstring windowCaption() { 39 return _caption; 40 } 41 /// sets window caption 42 override @property void windowCaption(dstring caption) { 43 _caption = caption; 44 } 45 /// sets window icon 46 override @property void windowIcon(DrawBufRef icon) { 47 // not supported 48 } 49 uint _lastRedrawEventCode; 50 /// request window redraw 51 override void invalidate() { 52 _platform.sendRedrawEvent(this, ++_lastRedrawEventCode); 53 } 54 55 void processRedrawEvent(uint code) { 56 //if (code == _lastRedrawEventCode) 57 // redraw(); 58 } 59 /// close window 60 override void close() { 61 _platform.closeWindow(this); 62 } 63 64 protected AndroidPlatform _platform; 65 this(AndroidPlatform platform) { 66 super(); 67 _platform = platform; 68 } 69 ~this() { 70 } 71 72 /// after drawing, call to schedule redraw if animation is active 73 override void scheduleAnimation() { 74 // override if necessary 75 // TODO 76 } 77 78 ushort lastFlags; 79 short lastx; 80 short lasty; 81 protected ButtonDetails _lbutton; 82 protected ButtonDetails _mbutton; 83 protected ButtonDetails _rbutton; 84 void processMouseEvent(MouseAction action, uint button, uint state, int x, int y) { 85 MouseEvent event = null; 86 lastFlags = 0; //convertMouseFlags(state); 87 if (_keyFlags & KeyFlag.Shift) 88 lastFlags |= MouseFlag.Shift; 89 if (_keyFlags & KeyFlag.Control) 90 lastFlags |= MouseFlag.Control; 91 if (_keyFlags & KeyFlag.Alt) 92 lastFlags |= MouseFlag.Alt; 93 lastx = cast(short)x; 94 lasty = cast(short)y; 95 MouseButton btn = MouseButton.Left; // convertMouseButton(button); 96 event = new MouseEvent(action, btn, lastFlags, lastx, lasty); 97 if (event) { 98 ButtonDetails * pbuttonDetails = null; 99 if (button == MouseButton.Left) 100 pbuttonDetails = &_lbutton; 101 else if (button == MouseButton.Right) 102 pbuttonDetails = &_rbutton; 103 else if (button == MouseButton.Middle) 104 pbuttonDetails = &_mbutton; 105 if (pbuttonDetails) { 106 if (action == MouseAction.ButtonDown) { 107 pbuttonDetails.down(cast(short)x, cast(short)y, lastFlags); 108 } else if (action == MouseAction.ButtonUp) { 109 pbuttonDetails.up(cast(short)x, cast(short)y, lastFlags); 110 } 111 } 112 event.lbutton = _lbutton; 113 event.rbutton = _rbutton; 114 event.mbutton = _mbutton; 115 bool res = dispatchMouseEvent(event); 116 if (res) { 117 debug(mouse) Log.d("Calling update() after mouse event"); 118 invalidate(); 119 } 120 } 121 } 122 uint _keyFlags; 123 124 /** 125 * Process the next input event. 126 */ 127 int handle_input(AInputEvent* event) { 128 Log.i("handle input, event=", AInputEvent_getType(event)); 129 auto et = AInputEvent_getType(event); 130 if (et == AINPUT_EVENT_TYPE_MOTION) { 131 auto action = AMotionEvent_getAction(event); 132 int x = cast(int)AMotionEvent_getX(event, 0); 133 int y = cast(int)AMotionEvent_getY(event, 0); 134 switch(action) { 135 case AMOTION_EVENT_ACTION_DOWN: 136 processMouseEvent(MouseAction.ButtonDown, 0, 0, x, y); 137 break; 138 case AMOTION_EVENT_ACTION_UP: 139 processMouseEvent(MouseAction.ButtonUp, 0, 0, x, y); 140 break; 141 case AMOTION_EVENT_ACTION_MOVE: 142 processMouseEvent(MouseAction.Move, 0, 0, x, y); 143 break; 144 case AMOTION_EVENT_ACTION_CANCEL: 145 processMouseEvent(MouseAction.Cancel, 0, 0, x, y); 146 break; 147 case AMOTION_EVENT_ACTION_OUTSIDE: 148 //processMouseEvent(MouseAction.Down, 0, 0, x, y); 149 break; 150 case AMOTION_EVENT_ACTION_POINTER_DOWN: 151 processMouseEvent(MouseAction.ButtonDown, 0, 0, x, y); 152 break; 153 case AMOTION_EVENT_ACTION_POINTER_UP: 154 processMouseEvent(MouseAction.ButtonUp, 0, 0, x, y); 155 break; 156 default: 157 break; 158 } 159 return 1; 160 } else if (et == AINPUT_EVENT_TYPE_KEY) { 161 Log.d("AINPUT_EVENT_TYPE_KEY"); 162 return 0; 163 } 164 return 0; 165 } 166 } 167 168 /** 169 * Platform abstraction layer. 170 * 171 * Represents application. 172 * 173 * 174 * 175 */ 176 class AndroidPlatform : Platform { 177 178 protected AndroidWindow[] _windows; 179 protected AndroidWindow _activeWindow; 180 engine _engine; 181 protected android_app* _appstate; 182 protected EGLDisplay _display; 183 protected EGLSurface _surface; 184 protected EGLContext _context; 185 protected int _width; 186 protected int _height; 187 188 this(android_app* state) { 189 Log.d("AndroidPlatform.this()"); 190 _appstate = state; 191 memset(&_engine, 0, engine.sizeof); 192 Log.d("AndroidPlatform.this() - setting handlers"); 193 state.userData = cast(void*)this; 194 state.onAppCmd = &engine_handle_cmd; 195 state.onInputEvent = &engine_handle_input; 196 197 //Log.d("AndroidPlatform.this() - restoring saved state"); 198 //if (state.savedState != null) { 199 // // We are starting with a previous saved state; restore from it. 200 // _engine.state = *cast(saved_state*)state.savedState; 201 //} 202 Log.d("AndroidPlatform.this() - done"); 203 } 204 205 ~this() { 206 foreach_reverse(w; _windows) { 207 destroy(w); 208 } 209 _windows.length = 0; 210 termDisplay(); 211 } 212 213 214 /** 215 * Initialize an EGL context for the current display. 216 */ 217 int initDisplay() { 218 // initialize OpenGL ES and EGL 219 Log.i("initDisplay"); 220 221 /* 222 * Here specify the attributes of the desired configuration. 223 * Below, we select an EGLConfig with at least 8 bits per color 224 * component compatible with on-screen windows 225 */ 226 const(EGLint)[9] attribs = [ 227 EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 228 EGL_BLUE_SIZE, 8, 229 EGL_GREEN_SIZE, 8, 230 EGL_RED_SIZE, 8, 231 EGL_NONE 232 ]; 233 EGLint w, h, dummy, format; 234 EGLint numConfigs; 235 EGLConfig config; 236 EGLSurface surface; 237 EGLContext context; 238 239 EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); 240 241 eglInitialize(display, null, null); 242 243 /* Here, the application chooses the configuration it desires. In this 244 * sample, we have a very simplified selection process, where we pick 245 * the first EGLConfig that matches our criteria */ 246 eglChooseConfig(display, attribs.ptr, &config, 1, &numConfigs); 247 248 /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is 249 * guaranteed to be accepted by ANativeWindow_setBuffersGeometry(). 250 * As soon as we picked a EGLConfig, we can safely reconfigure the 251 * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */ 252 eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); 253 254 ANativeWindow_setBuffersGeometry(_appstate.window, 0, 0, format); 255 256 surface = eglCreateWindowSurface(display, config, _appstate.window, null); 257 EGLint[3] contextAttrs = [EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE]; 258 context = eglCreateContext(display, config, null, contextAttrs.ptr); 259 260 if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { 261 LOGW("Unable to eglMakeCurrent"); 262 return -1; 263 } 264 265 eglQuerySurface(display, surface, EGL_WIDTH, &w); 266 eglQuerySurface(display, surface, EGL_HEIGHT, &h); 267 268 Log.i("surface created: ", _width, "x", _height); 269 270 _display = display; 271 _context = context; 272 _surface = surface; 273 _width = w; 274 _height = h; 275 _engine.state.angle = 0; 276 277 // Initialize GL state. 278 //glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); 279 glEnable(GL_CULL_FACE); 280 //glShadeModel(GL_SMOOTH); 281 glDisable(GL_DEPTH_TEST); 282 283 Log.i("calling initGLSupport"); 284 initGLSupport(false); 285 286 return 0; 287 } 288 289 290 /** 291 * Tear down the EGL context currently associated with the display. 292 */ 293 void termDisplay() { 294 Log.i("termDisplay"); 295 if (_display != EGL_NO_DISPLAY) { 296 eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 297 if (_context != EGL_NO_CONTEXT) { 298 eglDestroyContext(_display, _context); 299 } 300 if (_surface != EGL_NO_SURFACE) { 301 eglDestroySurface(_display, _surface); 302 } 303 eglTerminate(_display); 304 } 305 //_engine.animating = 0; 306 _display = EGL_NO_DISPLAY; 307 _context = EGL_NO_CONTEXT; 308 _surface = EGL_NO_SURFACE; 309 } 310 311 /** 312 * Process the next input event. 313 */ 314 int handle_input(AInputEvent* event) { 315 Log.i("handle input, event=", AInputEvent_getType(event)); 316 auto w = activeWindow; 317 if (!w) 318 return 0; 319 return w.handle_input(event); 320 } 321 322 323 bool _appFocused; 324 /** 325 * Process the next main command. 326 */ 327 void handle_cmd(int cmd) { 328 if (_appstate.destroyRequested != 0) { 329 Log.w("handle_cmd: destroyRequested is set!!!"); 330 } 331 switch (cmd) { 332 case APP_CMD_SAVE_STATE: 333 Log.d("APP_CMD_SAVE_STATE"); 334 // The system has asked us to save our current state. Do so. 335 _appstate.savedState = malloc(saved_state.sizeof); 336 *(cast(saved_state*)_appstate.savedState) = _engine.state; 337 _appstate.savedStateSize = saved_state.sizeof; 338 break; 339 case APP_CMD_INIT_WINDOW: 340 Log.d("APP_CMD_INIT_WINDOW"); 341 // The window is being shown, get it ready. 342 if (_appstate.window != null) { 343 initDisplay(); 344 drawWindow(); 345 } 346 break; 347 case APP_CMD_TERM_WINDOW: 348 Log.d("APP_CMD_TERM_WINDOW"); 349 // The window is being hidden or closed, clean it up. 350 termDisplay(); 351 break; 352 case APP_CMD_GAINED_FOCUS: 353 Log.d("APP_CMD_GAINED_FOCUS"); 354 // When our app gains focus 355 _appFocused = true; 356 break; 357 case APP_CMD_LOST_FOCUS: 358 Log.d("APP_CMD_LOST_FOCUS"); 359 // When our app loses focus 360 // This is to avoid consuming battery while not being used. 361 // Also stop animating. 362 //_engine.animating = 0; 363 _appFocused = false; 364 drawWindow(); 365 break; 366 case APP_CMD_INPUT_CHANGED: 367 Log.d("APP_CMD_INPUT_CHANGED"); 368 break; 369 case APP_CMD_WINDOW_RESIZED: 370 Log.d("APP_CMD_WINDOW_RESIZED"); 371 break; 372 case APP_CMD_WINDOW_REDRAW_NEEDED: 373 Log.d("APP_CMD_WINDOW_REDRAW_NEEDED"); 374 drawWindow(); 375 break; 376 case APP_CMD_CONTENT_RECT_CHANGED: 377 Log.d("APP_CMD_CONTENT_RECT_CHANGED"); 378 drawWindow(); 379 break; 380 case APP_CMD_CONFIG_CHANGED: 381 Log.d("APP_CMD_CONFIG_CHANGED"); 382 break; 383 case APP_CMD_LOW_MEMORY: 384 Log.d("APP_CMD_LOW_MEMORY"); 385 break; 386 case APP_CMD_START: 387 Log.d("APP_CMD_START"); 388 break; 389 case APP_CMD_RESUME: 390 Log.d("APP_CMD_RESUME"); 391 break; 392 case APP_CMD_PAUSE: 393 Log.d("APP_CMD_PAUSE"); 394 break; 395 case APP_CMD_STOP: 396 Log.d("APP_CMD_STOP"); 397 break; 398 case APP_CMD_DESTROY: 399 Log.d("APP_CMD_DESTROY"); 400 break; 401 default: 402 Log.i("unknown APP_CMD_XXX=", cmd); 403 break; 404 } 405 } 406 407 @property bool isAnimationActive() { 408 auto w = activeWindow; 409 return (w && w.isAnimationActive && _appFocused); 410 } 411 412 413 414 void sendRedrawEvent(AndroidWindow w, uint redrawEventCode) { 415 import core.stdc.stdio; 416 import core.sys.posix.unistd; 417 if (w && w is activeWindow) { 418 // request update 419 _appstate.redrawNeeded = true; 420 Log.d("sending APP_CMD_WINDOW_REDRAW_NEEDED"); 421 ubyte cmd = APP_CMD_WINDOW_REDRAW_NEEDED; 422 write(_appstate.msgwrite, &cmd, cmd.sizeof); 423 } 424 } 425 426 /** 427 * create window 428 * Args: 429 * windowCaption = window caption text 430 * parent = parent Window, or null if no parent 431 * flags = WindowFlag bit set, combination of Resizable, Modal, Fullscreen 432 * width = window width 433 * height = window height 434 * 435 * Window w/o Resizable nor Fullscreen will be created with size based on measurement of its content widget 436 */ 437 override Window createWindow(dstring windowCaption, Window parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0) { 438 AndroidWindow w = new AndroidWindow(this); 439 _windows ~= w; 440 return w; 441 } 442 443 /** 444 * close window 445 * 446 * Closes window earlier created with createWindow() 447 */ 448 override void closeWindow(Window w) { 449 import std.algorithm : remove; 450 for (int i = 0; i < _windows.length; i++) { 451 if (_windows[i] is w) { 452 _windows = _windows.remove(i); 453 break; 454 } 455 } 456 if (_windows.length == 0) { 457 _appstate.destroyRequested = true; 458 } 459 } 460 461 @property AndroidWindow activeWindow() { 462 for (int i = cast(int)_windows.length - 1; i >= 0; i++) 463 if (_windows[i]._visible) 464 return _windows[i]; 465 return null; 466 } 467 468 GLDrawBuf _drawbuf; 469 void drawWindow(AndroidWindow w = null) { 470 Log.i("drawWindow"); 471 if (w is null) 472 w = activeWindow; 473 else if (!(activeWindow is w)) 474 return; 475 if (_display == null) { 476 // No display. 477 return; 478 } 479 480 // Just fill the screen with a color. 481 if (!w) { 482 glClearColor(0, 0, 0, 1); 483 glClear(GL_COLOR_BUFFER_BIT); 484 } else { 485 w.onResize(_width, _height); 486 glDisable(GL_DEPTH_TEST); 487 glViewport(0, 0, _width, _height); 488 float a = 1.0f; 489 float r = ((w.backgroundColor >> 16) & 255) / 255.0f; 490 float g = ((w.backgroundColor >> 8) & 255) / 255.0f; 491 float b = ((w.backgroundColor >> 0) & 255) / 255.0f; 492 glClearColor(r, g, b, a); 493 glClear(GL_COLOR_BUFFER_BIT); 494 if (!_drawbuf) 495 _drawbuf = new GLDrawBuf(_width, _height); 496 _drawbuf.resize(_width, _height); 497 _drawbuf.beforeDrawing(); 498 w.onDraw(_drawbuf); 499 _drawbuf.afterDrawing(); 500 } 501 502 eglSwapBuffers(_display, _surface); 503 } 504 505 /** 506 * Starts application message loop. 507 * 508 * When returned from this method, application is shutting down. 509 */ 510 override int enterMessageLoop() { 511 while (1) { 512 // Read all pending events. 513 int ident; 514 int events; 515 android_poll_source* source; 516 517 // If not animating, we will block forever waiting for events. 518 // If animating, we loop until all events are read, then continue 519 // to draw the next frame of animation. 520 while ((ident=ALooper_pollAll(isAnimationActive ? 0 : -1, null, &events, 521 cast(void**)&source)) >= 0) { 522 523 // Process this event. 524 if (source != null) { 525 source.process(_appstate, source); 526 } 527 528 // If a sensor has data, process it now. 529 if (ident == LOOPER_ID_USER) { 530 /* 531 if (_accelerometerSensor != null) { 532 ASensorEvent event; 533 while (ASensorEventQueue_getEvents(_sensorEventQueue, 534 &event, 1) > 0) { 535 LOGI("accelerometer: x=%f y=%f z=%f", 536 event.acceleration.x, event.acceleration.y, 537 event.acceleration.z); 538 } 539 } 540 */ 541 } 542 543 // Check if we are exiting. 544 if (_appstate.destroyRequested != 0) { 545 Log.w("destroyRequested is set: exiting message loop"); 546 return 0; 547 } 548 } 549 550 if (isAnimationActive) { 551 // Done with events; draw next animation frame. 552 _engine.state.angle += .01f; 553 if (_engine.state.angle > 1) { 554 _engine.state.angle = 0; 555 } 556 557 // Drawing is throttled to the screen update rate, so there 558 // is no need to do timing here. 559 drawWindow(); 560 } 561 } 562 } 563 564 protected dstring _clipboardText; 565 /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux) 566 override dstring getClipboardText(bool mouseBuffer = false) { 567 return _clipboardText; 568 } 569 570 /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux) 571 override void setClipboardText(dstring text, bool mouseBuffer = false) { 572 _clipboardText = text; 573 } 574 575 /// calls request layout for all windows 576 override void requestLayout() { 577 } 578 579 /// handle theme change: e.g. reload some themed resources 580 override void onThemeChanged() { 581 // override and call dispatchThemeChange for all windows 582 } 583 584 } 585 586 587 588 /** 589 * Our saved state data. 590 */ 591 struct saved_state { 592 float angle; 593 float x; 594 float y; 595 } 596 597 /** 598 * Shared state for our app. 599 */ 600 struct engine { 601 //int animating; 602 saved_state state; 603 } 604 605 /** 606 * Process the next input event. 607 */ 608 extern(C) int engine_handle_input(android_app* app, AInputEvent* event) { 609 AndroidPlatform p = cast(AndroidPlatform)app.userData; 610 return p.handle_input(event); 611 } 612 613 /** 614 * Process the next main command. 615 */ 616 extern(C) void engine_handle_cmd(android_app* app, int cmd) { 617 AndroidPlatform p = cast(AndroidPlatform)app.userData; 618 p.handle_cmd(cmd); 619 } 620 621 void main(){} 622 623 int getDensityDpi(android_app * app) { 624 AConfiguration * config = AConfiguration_new(); 625 AConfiguration_fromAssetManager(config, app.activity.assetManager); 626 int res = AConfiguration_getDensity(config); 627 AConfiguration_delete(config); 628 return res; 629 } 630 631 __gshared AndroidPlatform _platform; 632 633 /** 634 * This is the main entry point of a native application that is using 635 * android_native_app_glue. It runs in its own thread, with its own 636 * event loop for receiving input events and doing other things. 637 */ 638 extern (C) void android_main(android_app* state) { 639 //import dlangui.platforms.common.startup : initLogs, initFontManager, initResourceManagers, ; 640 LOGI("Inside android_main"); 641 initLogs(); 642 Log.i("Testing logger - Log.i"); 643 Log.fi("Testing logger - Log.fi %d %s", 12345, "asdfgh"); 644 645 if (!initFontManager()) { 646 Log.e("******************************************************************"); 647 Log.e("No font files found!!!"); 648 Log.e("Currently, only hardcoded font paths implemented."); 649 Log.e("******************************************************************"); 650 assert(false); 651 } 652 initResourceManagers(); 653 SCREEN_DPI = getDensityDpi(state); 654 TOUCH_MODE = true; 655 Log.i("SCREEN_DPI=", SCREEN_DPI); 656 657 //currentTheme = createDefaultTheme(); 658 659 660 _platform = new AndroidPlatform(state); 661 Platform.setInstance(_platform); 662 663 _platform.uiTheme = "theme_default"; 664 665 // Make sure glue isn't stripped. 666 app_dummy(); 667 668 int res = 0; 669 670 version (unittest) { 671 } else { 672 Log.i("Calling UIAppMain"); 673 res = UIAppMain([]); 674 Log.i("UIAppMain returned with resultCode=", res); 675 } 676 677 // loop waiting for stuff to do. 678 Log.d("Destroying Android platform"); 679 Platform.setInstance(null); 680 681 releaseResourcesOnAppExit(); 682 683 Log.d("Exiting main"); 684 685 686 } 687