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