Merge branch 'dump_rta'
[nit.git] / lib / mnit_android / android_app.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Copyright 2012-2014 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Impements the services of `mnit:app` using the API from the Android ndk
18 module android_app
19
20 import mnit
21 import android
22
23 in "C header" `{
24 #include <jni.h>
25 #include <errno.h>
26 #include <android/log.h>
27 #include <android_native_app_glue.h>
28
29 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "mnit", __VA_ARGS__))
30 #ifdef DEBUG
31 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "mnit", __VA_ARGS__))
32 #else
33 #define LOGI(...) (void)0
34 #endif
35 `}
36
37 in "C" `{
38 #include <EGL/egl.h>
39 #include <GLES/gl.h>
40 #define GL_GLEXT_PROTOTYPES 1
41 #include <GLES/glext.h>
42 #include <errno.h>
43
44 extern EGLDisplay mnit_display;
45 extern EGLSurface mnit_surface;
46 extern EGLContext mnit_context;
47 extern EGLConfig mnit_config;
48 extern int32_t mnit_width;
49 extern int32_t mnit_height;
50 extern float mnit_zoom;
51
52 int mnit_orientation_changed;
53 float mnit_zoom;
54 int mnit_animating = 0;
55
56 /* This is confusing; the type come from android_native_app_glue.h
57 and so identifies the java part of the app */
58 struct android_app *mnit_java_app;
59
60 /* This is the pure Nit App */
61 App nit_app;
62
63 /* The main of the Nit application, compiled somewhere else */
64 extern int main(int, char**);
65
66 /* Wraps App_full_frame() and check for orientation. */
67 void mnit_frame();
68
69 void mnit_term_display()
70 {
71 // At this point we have nothing to do
72 }
73
74 /* Handle inputs from the Android platform and sort them before
75 sending them in the Nit App */
76 static int32_t mnit_handle_input(struct android_app* app, AInputEvent* event) {
77 LOGI("handle input %i", (int)pthread_self());
78 if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY) {
79 LOGI("key");
80 return App_extern_input_key(nit_app, event);
81 }
82 else if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {
83 LOGI("motion");
84 return App_extern_input_motion(nit_app, event);
85 }
86
87 return 0;
88 }
89
90 static void mnit_handle_cmd(struct android_app* app, int32_t cmd) {
91
92 mnit_java_app = app;
93 AConfiguration_setOrientation(mnit_java_app->config, ACONFIGURATION_ORIENTATION_LAND);
94 LOGI("cmd %i", (int)pthread_self());
95
96 switch (cmd) {
97 case APP_CMD_SAVE_STATE:
98 LOGI ("save state");
99 mnit_java_app->savedStateSize = 1;
100 mnit_java_app->savedState = malloc(1);
101 App_save(nit_app);
102 break;
103
104 case APP_CMD_INIT_WINDOW:
105 LOGI ("init window");
106 if (mnit_java_app->window != NULL) {
107 LOGI("init window in");
108 App_init_window(nit_app);
109 mnit_frame();
110 mnit_animating = 1;
111 }
112 break;
113
114 case APP_CMD_TERM_WINDOW:
115 LOGI ("term window");
116 mnit_term_display();
117 App_term_window(nit_app);
118 break;
119
120 case APP_CMD_GAINED_FOCUS:
121 LOGI ("gain foc");
122 mnit_animating = 1;
123 App_gained_focus(nit_app);
124 LOGI ("gain foc 1");
125 break;
126
127 case APP_CMD_LOST_FOCUS:
128 LOGI ("lost foc");
129 mnit_animating = 0;
130 App_lost_focus(nit_app);
131 mnit_frame();
132 break;
133
134 case APP_CMD_PAUSE:
135 LOGI ("app pause");
136 App_pause(nit_app);
137 break;
138
139 /*
140 case APP_CMD_STOP:
141 LOGI ("app stop");
142 App_stop(nit_app);
143 break;
144
145 case APP_CMD_DESTROY:
146 LOGI ("app destrop");
147 App_destroy(nit_app);
148 break;
149
150 case APP_CMD_START:
151 LOGI ("app start");
152 App_start(nit_app);
153 break;
154 */
155
156 case APP_CMD_RESUME:
157 LOGI ("app resume");
158 App_resume(nit_app);
159 break;
160
161 case APP_CMD_LOW_MEMORY:
162 LOGI ("app low mem");
163 break;
164
165 case APP_CMD_CONFIG_CHANGED:
166 LOGI ("app cmd conf ch");
167 break;
168
169 case APP_CMD_INPUT_CHANGED:
170 LOGI ("app cmd in ch");
171 break;
172
173 case APP_CMD_WINDOW_RESIZED:
174 mnit_orientation_changed = 1;
175 LOGI ("app win res");
176 break;
177
178 case APP_CMD_WINDOW_REDRAW_NEEDED:
179 LOGI ("app win redraw needed");
180 break;
181
182 case APP_CMD_CONTENT_RECT_CHANGED:
183 LOGI ("app content rect ch");
184 break;
185 }
186 }
187
188 void android_main(struct android_app* app)
189 {
190 mnit_java_app = app;
191
192 app_dummy();
193
194 main(0, NULL);
195 }
196
197 void mnit_frame()
198 {
199 if (mnit_display == EGL_NO_DISPLAY) {
200 LOGI("no frame");
201 return;
202 }
203
204 if (mnit_orientation_changed)
205 {
206 mnit_orientation_changed = 0;
207
208 if (mnit_surface != EGL_NO_SURFACE) {
209 eglDestroySurface(mnit_display, mnit_surface);
210 }
211 EGLSurface surface = eglCreateWindowSurface(mnit_display, mnit_config, mnit_java_app->window, NULL);
212
213 if (eglMakeCurrent(mnit_display, surface, surface, mnit_context) == EGL_FALSE) {
214 LOGW("Unable to eglMakeCurrent");
215 }
216
217 eglQuerySurface(mnit_display, surface, EGL_WIDTH, &mnit_width);
218 eglQuerySurface(mnit_display, surface, EGL_HEIGHT, &mnit_height);
219
220 mnit_surface = surface;
221
222 glViewport(0, 0, mnit_width, mnit_height);
223 glMatrixMode(GL_PROJECTION);
224 glLoadIdentity();
225 glOrthof(0.0f, mnit_width, mnit_height, 0.0f, 0.0f, 1.0f);
226 glMatrixMode(GL_MODELVIEW);
227 }
228
229 LOGI("frame");
230
231 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
232 glClear(GL_COLOR_BUFFER_BIT); // | GL_DEPTH_BUFFER_BIT);
233
234 App_full_frame(nit_app);
235
236 LOGI("frame b");
237 }
238 `}
239
240
241 extern InnerAndroidMotionEvent in "C" `{AInputEvent *`}
242 super Pointer
243 private fun pointers_count: Int is extern `{
244 return AMotionEvent_getPointerCount(recv);
245 `}
246 private fun just_went_down: Bool is extern `{
247 return (AMotionEvent_getAction(recv) & AMOTION_EVENT_ACTION_MASK) == AMOTION_EVENT_ACTION_DOWN;
248 `}
249 private fun edge: Int is extern `{
250 return AMotionEvent_getEdgeFlags(recv);
251 `}
252 private fun index_down_pointer: Int is extern `{
253 int a = AMotionEvent_getAction(recv);
254 if ((a & AMOTION_EVENT_ACTION_MASK) == AMOTION_EVENT_ACTION_POINTER_DOWN)
255 return (a & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
256 else return -1;
257 `}
258
259 private fun action: AMotionEventAction `{ return AMotionEvent_getAction(recv); `}
260 end
261
262 extern class AMotionEventAction `{ int32_t `}
263 protected fun action: Int `{ return recv & AMOTION_EVENT_ACTION_MASK; `}
264 fun is_down: Bool do return action == 0
265 fun is_up: Bool do return action == 1
266 fun is_move: Bool do return action == 2
267 fun is_cancel: Bool do return action == 3
268 fun is_outside: Bool do return action == 4
269 fun is_pointer_down: Bool do return action == 5
270 fun is_pointer_up: Bool do return action == 6
271 end
272
273 interface AndroidInputEvent
274 super InputEvent
275 end
276
277 class AndroidMotionEvent
278 super AndroidInputEvent
279 super MotionEvent
280
281 private init(ie: InnerAndroidMotionEvent) do inner_event = ie
282 private var inner_event: InnerAndroidMotionEvent
283
284 private var pointers_cache: nullable Array[AndroidPointerEvent] = null
285 fun pointers: Array[AndroidPointerEvent]
286 do
287 if pointers_cache != null then
288 return pointers_cache.as(not null)
289 else
290 var pointers = new Array[AndroidPointerEvent]
291 var pointers_count = inner_event.pointers_count
292 for i in [0 .. pointers_count [do
293 var pointer_event = new AndroidPointerEvent(self, i)
294 pointers.add(pointer_event)
295 end
296 pointers_cache = pointers
297 return pointers
298 end
299 end
300
301 redef fun just_went_down: Bool do return inner_event.just_went_down
302 fun edge: Int do return inner_event.edge
303
304 redef fun down_pointer: nullable AndroidPointerEvent
305 do
306 var i = inner_event.index_down_pointer
307 if i > 0 then
308 return pointers[i]
309 else
310 return null
311 end
312 end
313 end
314
315 class AndroidPointerEvent
316 super PointerEvent
317 super AndroidInputEvent
318
319 protected var motion_event: AndroidMotionEvent
320 protected var pointer_id: Int
321
322 redef fun x: Float do return extern_x(motion_event.inner_event, pointer_id)
323 private fun extern_x(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
324 return ((int) AMotionEvent_getX(motion_event, pointer_id) * mnit_zoom);
325 `}
326
327 redef fun y: Float do return extern_y(motion_event.inner_event, pointer_id)
328 private fun extern_y(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
329 return ((int) AMotionEvent_getY(motion_event, pointer_id) * mnit_zoom) + 32;
330 `}
331
332 fun pressure: Float do return extern_pressure(motion_event.inner_event, pointer_id)
333 private fun extern_pressure(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
334 return AMotionEvent_getPressure(motion_event, pointer_id);
335 `}
336
337 redef fun pressed
338 do
339 var action = motion_event.inner_event.action
340 return action.is_down or action.is_move
341 end
342
343 redef fun depressed do return not pressed
344 end
345
346 extern AndroidKeyEvent in "C" `{AInputEvent *`}
347 super KeyEvent
348 super AndroidInputEvent
349
350 fun action: Int is extern `{
351 return AKeyEvent_getAction(recv);
352 `}
353 redef fun is_down: Bool do return action == 0
354 redef fun is_up: Bool do return action == 1
355
356 fun key_code: Int is extern `{
357 return AKeyEvent_getKeyCode(recv);
358 `}
359
360 fun key_char: Char is extern `{
361 int code = AKeyEvent_getKeyCode(recv);
362 if (code >= AKEYCODE_0 && code <= AKEYCODE_9)
363 return '0'+code-AKEYCODE_0;
364 if (code >= AKEYCODE_A && code <= AKEYCODE_Z)
365 return 'a'+code-AKEYCODE_A;
366 return 0;
367 `}
368
369 fun is_back_key: Bool do return key_code == 2
370 fun is_menu_key: Bool do return key_code == 82
371 fun is_search_key: Bool do return key_code == 84
372 end
373
374 redef class Object
375 # Uses Android logs for every print
376 redef fun print(text: Object) is extern import Object.to_s, String.to_cstring `{
377 __android_log_print(ANDROID_LOG_INFO, "mnit print", "%s", String_to_cstring(Object_to_s(text)));
378 `}
379 end
380
381 redef class App
382 redef type IE: AndroidInputEvent
383 redef type D: Opengles1Display
384
385 redef fun log_warning(msg) is extern import String.to_cstring `{
386 LOGW("%s", String_to_cstring(msg));
387 `}
388 redef fun log_info(msg) is extern import String.to_cstring `{
389 LOGI("%s", String_to_cstring(msg));
390 `}
391
392 redef fun init_window
393 do
394 super
395
396 display = new Opengles1Display
397 end
398
399 # these two are used as a callback from native to type incoming events
400 private fun extern_input_key(event: AndroidKeyEvent): Bool
401 do
402 return input(event)
403 end
404 private fun extern_input_motion(event: InnerAndroidMotionEvent): Bool
405 do
406 var ie = new AndroidMotionEvent(event)
407 var handled = input(ie)
408
409 if not handled then
410 for pe in ie.pointers do
411 input(pe)
412 end
413 end
414
415 return handled
416 end
417
418 redef fun main_loop is extern import full_frame, generate_input `{
419 LOGI("nitni loop");
420
421 nit_app = recv;
422
423 mnit_java_app->userData = &nit_app;
424 mnit_java_app->onAppCmd = mnit_handle_cmd;
425 mnit_java_app->onInputEvent = mnit_handle_input;
426
427 while (1) {
428 App_generate_input(recv);
429
430 if (mnit_java_app->destroyRequested != 0) return;
431
432 if (mnit_animating == 1) {
433 mnit_frame();
434 LOGI("frame at loop end 1");
435 }
436 }
437
438 /* App_exit(); // this is unreachable anyway*/
439 `}
440
441 redef fun generate_input import save, pause, resume, gained_focus, lost_focus, init_window, term_window, extern_input_key, extern_input_motion `{
442 int ident;
443 int events;
444 static int block = 0;
445 struct android_poll_source* source;
446
447 while ((ident=ALooper_pollAll(0, NULL, &events,
448 (void**)&source)) >= 0) { /* first 0 is for non-blocking */
449
450 // Process this event.
451 if (source != NULL)
452 source->process(mnit_java_app, source);
453
454 // Check if we are exiting.
455 if (mnit_java_app->destroyRequested != 0) {
456 mnit_term_display();
457 return;
458 }
459 }
460 `}
461 end
462