lib: import the android module from mnit_android
[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 android_opengles1
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
30 in "C" `{
31 #include <EGL/egl.h>
32 #include <GLES/gl.h>
33 #define GL_GLEXT_PROTOTYPES 1
34 #include <GLES/glext.h>
35 #include <errno.h>
36
37 #define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "mnit", __VA_ARGS__))
38 #ifdef DEBUG
39 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "mnit", __VA_ARGS__))
40 #else
41 #define LOGI(...) (void)0
42 #endif
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 end
259
260 interface AndroidInputEvent
261 super InputEvent
262 end
263
264 class AndroidMotionEvent
265 super AndroidInputEvent
266 super MotionEvent
267
268 private init(ie: InnerAndroidMotionEvent) do inner_event = ie
269 private var inner_event: InnerAndroidMotionEvent
270
271 private var pointers_cache: nullable Array[AndroidPointerEvent] = null
272 fun pointers: Array[AndroidPointerEvent]
273 do
274 if pointers_cache != null then
275 return pointers_cache.as(not null)
276 else
277 var pointers = new Array[AndroidPointerEvent]
278 var pointers_count = inner_event.pointers_count
279 for i in [0 .. pointers_count [do
280 var pointer_event = new AndroidPointerEvent(self, i)
281 pointers.add(pointer_event)
282 end
283 pointers_cache = pointers
284 return pointers
285 end
286 end
287
288 redef fun just_went_down: Bool do return inner_event.just_went_down
289 fun edge: Int do return inner_event.edge
290
291 redef fun down_pointer: nullable AndroidPointerEvent
292 do
293 var i = inner_event.index_down_pointer
294 if i > 0 then
295 return pointers[i]
296 else
297 return null
298 end
299 end
300 end
301
302 class AndroidPointerEvent
303 super PointerEvent
304 super AndroidInputEvent
305
306 protected var motion_event: AndroidMotionEvent
307 protected var pointer_id: Int
308
309 redef fun x: Float do return extern_x(motion_event.inner_event, pointer_id)
310 private fun extern_x(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
311 return ((int) AMotionEvent_getX(motion_event, pointer_id) * mnit_zoom);
312 `}
313
314 redef fun y: Float do return extern_y(motion_event.inner_event, pointer_id)
315 private fun extern_y(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
316 return ((int) AMotionEvent_getY(motion_event, pointer_id) * mnit_zoom) + 32;
317 `}
318
319 fun pressure: Float do return extern_pressure(motion_event.inner_event, pointer_id)
320 private fun extern_pressure(motion_event: InnerAndroidMotionEvent, pointer_id: Int): Float is extern `{
321 return AMotionEvent_getPressure(motion_event, pointer_id);
322 `}
323
324 redef fun pressed do return true
325 redef fun depressed do return false
326 end
327
328 extern AndroidKeyEvent in "C" `{AInputEvent *`}
329 super KeyEvent
330 super AndroidInputEvent
331
332 fun action: Int is extern `{
333 return AKeyEvent_getAction(recv);
334 `}
335 redef fun is_down: Bool do return action == 0
336 redef fun is_up: Bool do return action == 1
337
338 fun key_code: Int is extern `{
339 return AKeyEvent_getKeyCode(recv);
340 `}
341
342 fun key_char: Char is extern `{
343 int code = AKeyEvent_getKeyCode(recv);
344 if (code >= AKEYCODE_0 && code <= AKEYCODE_9)
345 return '0'+code-AKEYCODE_0;
346 if (code >= AKEYCODE_A && code <= AKEYCODE_Z)
347 return 'a'+code-AKEYCODE_A;
348 return 0;
349 `}
350
351 fun is_back_key: Bool do return key_code == 2
352 fun is_menu_key: Bool do return key_code == 82
353 fun is_search_key: Bool do return key_code == 84
354 end
355
356 redef class Object
357 # Uses Android logs for every print
358 redef fun print(text: Object) is extern import Object.to_s, String.to_cstring `{
359 __android_log_print(ANDROID_LOG_INFO, "mnit print", "%s", String_to_cstring(Object_to_s(object)));
360 `}
361 end
362
363 redef class App
364 redef type IE: AndroidInputEvent
365 redef type D: Opengles1Display
366
367 redef fun log_warning(msg) is extern import String.to_cstring `{
368 LOGW("%s", String_to_cstring(msg));
369 `}
370 redef fun log_info(msg) is extern import String.to_cstring `{
371 LOGI("%s", String_to_cstring(msg));
372 `}
373
374 redef fun init_window
375 do
376 super
377
378 display = new Opengles1Display
379 end
380
381 # these two are used as a callback from native to type incoming events
382 private fun extern_input_key(event: AndroidKeyEvent): Bool
383 do
384 return input(event)
385 end
386 private fun extern_input_motion(event: InnerAndroidMotionEvent): Bool
387 do
388 var ie = new AndroidMotionEvent(event)
389 var handled = input(ie)
390
391 if not handled then
392 for pe in ie.pointers do
393 input(pe)
394 end
395 end
396
397 return handled
398 end
399
400 redef fun main_loop is extern import full_frame, save, pause, resume, gained_focus, lost_focus, init_window, term_window, extern_input_key, extern_input_motion `{
401 LOGI("nitni loop");
402
403 nit_app = recv;
404
405 mnit_java_app->userData = &nit_app;
406 mnit_java_app->onAppCmd = mnit_handle_cmd;
407 mnit_java_app->onInputEvent = mnit_handle_input;
408
409 while (1) {
410 int ident;
411 int events;
412 static int block = 0;
413 struct android_poll_source* source;
414
415 while ((ident=ALooper_pollAll(0, NULL, &events,
416 (void**)&source)) >= 0) { /* first 0 is for non-blocking */
417
418 // Process this event.
419 if (source != NULL)
420 source->process(mnit_java_app, source);
421
422 // Check if we are exiting.
423 if (mnit_java_app->destroyRequested != 0) {
424 mnit_term_display();
425 return;
426 }
427 }
428
429 if (mnit_animating == 1) {
430 mnit_frame();
431 LOGI("frame at loop end 1");
432 }
433 }
434
435 /* App_exit(); // this is unreachable anyway*/
436 `}
437 end
438