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