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