# limitations under the License.
# Support services for Gamnit on Android
-module gamnit_android
+module gamnit_android is
+ android_api_min 15
+ android_api_target 15
+ android_manifest_activity """android:theme="@android:style/Theme.NoTitleBar.Fullscreen""""
+ android_manifest_activity """android:configChanges="orientation|screenSize|keyboard|keyboardHidden""""
+end
import android
intrude import gamnit
intrude import android::input_events
+import egl
+
+private import realtime
+
+# Print Android lifecycle events to the log?
+fun print_lifecycle_events: Bool do return true
redef class App
+
+ # ---
+ # User inputs
+
redef fun feed_events do app.poll_looper 0
redef fun native_input_key(event) do return accept_event(event)
redef fun native_input_motion(event)
do
+ if not scene_created then return false
+
var ie = new AndroidMotionEvent(event)
var handled = accept_event(ie)
return handled
end
+
+ # ---
+ # Handle OS lifecycle and set current state flag
+
+ # State between `init_window` and `term_window`
+ private var window_created = false
+
+ # State between `gained_focus` and `lost_focus`
+ private var focused = false
+
+ # State between `resume` and `pause`
+ private var resumed = false
+
+ # Stage after `destroy`
+ private var destroyed = false
+
+ redef fun init_window
+ do
+ if print_lifecycle_events then print "+ init_window"
+ window_created = true
+ set_active
+ super
+ end
+
+ redef fun term_window
+ do
+ if print_lifecycle_events then print "+ term_window"
+ window_created = false
+ set_inactive
+ super
+ end
+
+ redef fun resume
+ do
+ if print_lifecycle_events then print "+ resume"
+ resumed = true
+ set_active
+ super
+ end
+
+ redef fun pause
+ do
+ if print_lifecycle_events then print "+ pause"
+ resumed = false
+ set_inactive
+ super
+ end
+
+ redef fun gained_focus
+ do
+ if print_lifecycle_events then print "+ gained_focus"
+ focused = true
+ set_active
+ super
+ end
+
+ redef fun lost_focus
+ do
+ if print_lifecycle_events then print "+ lost_focus"
+ focused = false
+ super
+ end
+
+ redef fun destroy
+ do
+ if print_lifecycle_events then print "+ destroy"
+ destroyed = true
+ super
+ end
+
+ redef fun start
+ do
+ if print_lifecycle_events then print "+ start"
+ super
+ end
+
+ redef fun stop
+ do
+ if print_lifecycle_events then print "+ stop"
+ set_inactive
+ super
+ end
+
+ redef fun config_changed
+ do
+ if print_lifecycle_events then print "+ config_changed"
+ super
+ end
+
+ redef fun window_resized
+ do
+ if print_lifecycle_events then print "+ window_resized"
+ super
+ end
+
+ redef fun content_rect_changed
+ do
+ if print_lifecycle_events then print "+ content_rect_changed"
+ super
+ end
+
+ # ---
+ # Update gamnit app
+
+ # The app is fully visible and focused
+ private var active = false
+
+ # The scene was set up
+ private var scene_created = false
+
+ private fun set_active
+ do
+ assert not destroyed
+ if window_created and resumed and focused and not active then
+ var display = display
+ if display == null then
+ # Initial create
+ create_display
+ create_gamnit
+ display = self.display
+ else
+ # Try to reuse the EGL context
+ var native_window = app.native_app_glue.window
+ assert not native_window.address_is_null
+ var needs_recreate = display.check_egl_context(native_window)
+ if needs_recreate then
+
+ # Skip frame
+ if display.native_window_is_invalid then
+ print_error "the native window is invalid, skip frame"
+ return
+ end
+
+ # The context was lost, reload everything
+ create_gamnit
+ recreate_gamnit
+ end
+ end
+
+ # Update screen dimensions
+ assert display != null
+ display.update_size
+ app.on_resize display
+
+ if not scene_created then
+ # Initial launch
+ if debug_gamnit then print "set_active: create"
+ create_scene
+ scene_created = true
+ on_restore_state
+ else
+ # Next to first launch, reload
+ if debug_gamnit then print "set_active: recreate"
+ end
+
+ active = true
+ end
+ end
+
+ private fun set_inactive
+ do
+ active = false
+ end
+
+ # ---
+ # Implement gamnit entry points
+
+ redef fun recreate_gamnit
+ do
+ super
+
+ # Reload all textures
+ if debug_gamnit then print "recreate_gamnit: reloading {all_root_textures.length} textures"
+ for texture in all_root_textures do
+ if debug_gamnit then print "recreate_gamnit: loading {texture}"
+ texture.load true
+ var gamnit_error = texture.error
+ if gamnit_error != null then print_error gamnit_error
+ end
+ end
+
+ redef fun run
+ do
+ if debug_gamnit then print "run: start"
+ scene_created = false
+
+ while not destroyed do
+ if not active then
+ if debug_gamnit then print "run: wait"
+ app.poll_looper_pause -1
+
+ else
+ if debug_gamnit then print "run: frame"
+
+ var native_window = app.native_app_glue.window
+ assert not native_window.address_is_null
+
+ var display = display
+ assert display != null
+
+ var needs_recreate = display.check_egl_context(native_window)
+ if needs_recreate then
+ if display.native_window_is_invalid then
+ # This should be rare and may cause more issues, log it
+ print "The native window is invalid, skip frame"
+ set_inactive
+ continue
+ end
+
+ # The context was lost, reload everything
+ create_gamnit
+ recreate_gamnit
+ end
+
+ assert scene_created
+ frame_full
+ end
+ end
+
+ if debug_gamnit then print "run: exit"
+ exit 0
+ end
+end
+
+redef class GamnitDisplay
+
+ redef var width = 1080
+ redef var height = 720
+
+ # Update `width` and `height`
+ private fun update_size
+ do
+ var context = app.native_activity
+ self.width = context.window_width
+ self.height = context.window_height
+ end
+end
+
+redef class NativeActivity
+
+ private fun window_height: Int in "Java" `{
+ android.view.View view = self.getWindow().getDecorView();
+ return view.getBottom() - view.getTop();
+ `}
+
+ private fun window_width: Int in "Java" `{
+ android.view.View view = self.getWindow().getDecorView();
+ return view.getRight() - view.getLeft();
+ `}
+
+ private fun orientation: Int in "Java" `{
+ return self.getResources().getConfiguration().orientation;
+ `}
end