Merge: doc: fixed some typos and other misc. corrections
[nit.git] / lib / gamnit / gamnit_android.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Support services for Gamnit on Android
16 module gamnit_android is
17 android_api_min 15
18 android_api_target 15
19 android_manifest_activity """android:theme="@android:style/Theme.NoTitleBar.Fullscreen""""
20 android_manifest_activity """android:configChanges="orientation|screenSize|keyboard|keyboardHidden""""
21 end
22
23 import android
24
25 intrude import gamnit
26 intrude import android::input_events
27 import egl
28
29 private import realtime
30
31 # Print Android lifecycle events to the log?
32 fun print_lifecycle_events: Bool do return true
33
34 redef class App
35
36 # ---
37 # User inputs
38
39 redef fun feed_events do app.poll_looper 0
40
41 redef fun native_input_key(event) do return accept_event(event)
42
43 redef fun native_input_motion(event)
44 do
45 if not scene_created then return false
46
47 var ie = new AndroidMotionEvent(event)
48 var handled = accept_event(ie)
49
50 if not handled then accept_event ie.acting_pointer
51
52 return handled
53 end
54
55 # ---
56 # Handle OS lifecycle and set current state flag
57
58 # State between `init_window` and `term_window`
59 private var window_created = false
60
61 # State between `gained_focus` and `lost_focus`
62 private var focused = false
63
64 # State between `resume` and `pause`
65 private var resumed = false
66
67 # Stage after `destroy`
68 private var destroyed = false
69
70 redef fun init_window
71 do
72 if print_lifecycle_events then print "+ init_window"
73 window_created = true
74 set_active
75 super
76 end
77
78 redef fun term_window
79 do
80 if print_lifecycle_events then print "+ term_window"
81 window_created = false
82 set_inactive
83 super
84 end
85
86 redef fun resume
87 do
88 if print_lifecycle_events then print "+ resume"
89 resumed = true
90 set_active
91 super
92 end
93
94 redef fun pause
95 do
96 if print_lifecycle_events then print "+ pause"
97 resumed = false
98 set_inactive
99 super
100 end
101
102 redef fun gained_focus
103 do
104 if print_lifecycle_events then print "+ gained_focus"
105 focused = true
106 set_active
107 super
108 end
109
110 redef fun lost_focus
111 do
112 if print_lifecycle_events then print "+ lost_focus"
113 focused = false
114 super
115 end
116
117 redef fun destroy
118 do
119 if print_lifecycle_events then print "+ destroy"
120 destroyed = true
121 super
122 end
123
124 redef fun start
125 do
126 if print_lifecycle_events then print "+ start"
127 super
128 end
129
130 redef fun stop
131 do
132 if print_lifecycle_events then print "+ stop"
133 set_inactive
134 super
135 end
136
137 redef fun config_changed
138 do
139 if print_lifecycle_events then print "+ config_changed"
140 super
141 end
142
143 redef fun window_resized
144 do
145 if print_lifecycle_events then print "+ window_resized"
146 super
147 end
148
149 redef fun content_rect_changed
150 do
151 if print_lifecycle_events then print "+ content_rect_changed"
152 super
153 end
154
155 # ---
156 # Update gamnit app
157
158 # The app is fully visible and focused
159 private var active = false
160
161 # The scene was set up
162 private var scene_created = false
163
164 private fun set_active
165 do
166 assert not destroyed
167 if window_created and resumed and focused and not active then
168 var display = display
169 if display == null then
170 # Initial create
171 create_display
172 create_gamnit
173 display = self.display
174 else
175 # Try to reuse the EGL context
176 var native_window = app.native_app_glue.window
177 assert not native_window.address_is_null
178 var needs_recreate = display.check_egl_context(native_window)
179 if needs_recreate then
180
181 # Skip frame
182 if display.native_window_is_invalid then
183 print_error "the native window is invalid, skip frame"
184 return
185 end
186
187 # The context was lost, reload everything
188 create_gamnit
189 recreate_gamnit
190 end
191 end
192
193 # Update screen dimensions
194 assert display != null
195 display.update_size
196 app.on_resize display
197
198 if not scene_created then
199 # Initial launch
200 if debug_gamnit then print "set_active: create"
201 create_scene
202 scene_created = true
203 on_restore_state
204 else
205 # Next to first launch, reload
206 if debug_gamnit then print "set_active: recreate"
207 end
208
209 active = true
210 end
211 end
212
213 private fun set_inactive
214 do
215 active = false
216 end
217
218 # ---
219 # Implement gamnit entry points
220
221 redef fun recreate_gamnit
222 do
223 super
224
225 # Reload all textures
226 if debug_gamnit then print "recreate_gamnit: reloading {all_root_textures.length} textures"
227 for texture in all_root_textures do
228 if debug_gamnit then print "recreate_gamnit: loading {texture}"
229 texture.load true
230 var gamnit_error = texture.error
231 if gamnit_error != null then print_error gamnit_error
232 end
233 end
234
235 redef fun run
236 do
237 if debug_gamnit then print "run: start"
238 scene_created = false
239
240 while not destroyed do
241 if not active then
242 if debug_gamnit then print "run: wait"
243 app.poll_looper_pause -1
244
245 else
246 if debug_gamnit then print "run: frame"
247
248 var native_window = app.native_app_glue.window
249 assert not native_window.address_is_null
250
251 var display = display
252 assert display != null
253
254 var needs_recreate = display.check_egl_context(native_window)
255 if needs_recreate then
256 if display.native_window_is_invalid then
257 # This should be rare and may cause more issues, log it
258 print "The native window is invalid, skip frame"
259 set_inactive
260 continue
261 end
262
263 # The context was lost, reload everything
264 create_gamnit
265 recreate_gamnit
266 end
267
268 assert scene_created
269 frame_full
270 end
271 end
272
273 if debug_gamnit then print "run: exit"
274 exit 0
275 end
276 end
277
278 redef class GamnitDisplay
279
280 redef var width = 1080
281 redef var height = 720
282
283 # Update `width` and `height`
284 private fun update_size
285 do
286 var context = app.native_activity
287 self.width = context.window_width
288 self.height = context.window_height
289 end
290 end
291
292 redef class NativeActivity
293
294 private fun window_height: Int in "Java" `{
295 android.view.View view = self.getWindow().getDecorView();
296 return view.getBottom() - view.getTop();
297 `}
298
299 private fun window_width: Int in "Java" `{
300 android.view.View view = self.getWindow().getDecorView();
301 return view.getRight() - view.getLeft();
302 `}
303
304 private fun orientation: Int in "Java" `{
305 return self.getResources().getConfiguration().orientation;
306 `}
307 end