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