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 }