8
8
import android .os .Handler ;
9
9
import android .util .AttributeSet ;
10
10
import android .widget .RelativeLayout ;
11
-
11
+ import androidx .annotation .NonNull ;
12
+ import androidx .annotation .Nullable ;
13
+ import androidx .lifecycle .Lifecycle ;
14
+ import androidx .lifecycle .LifecycleEventObserver ;
15
+ import androidx .lifecycle .LifecycleOwner ;
12
16
import com .jme3 .app .LegacyApplication ;
13
17
import com .jme3 .audio .AudioRenderer ;
14
18
import com .jme3 .input .JoyInput ;
17
21
import com .jme3 .system .SystemListener ;
18
22
import com .jme3 .system .android .JmeAndroidSystem ;
19
23
import com .jme3 .system .android .OGLESContext ;
20
-
21
24
import java .util .concurrent .atomic .AtomicInteger ;
22
25
import java .util .logging .Level ;
23
26
import java .util .logging .Logger ;
24
27
25
- import androidx .annotation .NonNull ;
26
- import androidx .annotation .Nullable ;
27
-
28
28
/**
29
29
* <b>A RelativeLayout Class Holder that holds a #{{@link GLSurfaceView}} using #{{@link OGLESContext}} as a renderer to render
30
30
* a JME game on an android view for custom xmL designs.</b>
34
34
* an image or even play a preface game music of choice.
35
35
* @author pavl_g.
36
36
*/
37
- public class JmeSurfaceView extends RelativeLayout implements SystemListener , DialogInterface .OnClickListener {
37
+ public class JmeSurfaceView extends RelativeLayout implements SystemListener , DialogInterface .OnClickListener , LifecycleEventObserver {
38
38
39
39
/*using #{@link LegacyApplication} instead of #{@link SimpleApplication} to include all classes extends LegacyApplication*/
40
40
private LegacyApplication legacyApplication ;
@@ -59,6 +59,7 @@ public class JmeSurfaceView extends RelativeLayout implements SystemListener , D
59
59
public static final int NO_DELAY = 1 ;
60
60
private int delayMillis = NO_DELAY ;
61
61
private static final int TOLERANCE_TIMER = 100 ;
62
+ private boolean showErrorDialog = true ;
62
63
63
64
public JmeSurfaceView (@ NonNull Context context ) {
64
65
super (context );
@@ -80,45 +81,66 @@ public JmeSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, in
80
81
*/
81
82
public void startRenderer (int delayMillis ) {
82
83
this .delayMillis = Math .max (NO_DELAY , delayMillis );
83
- if (legacyApplication != null ) {
84
- try {
85
- /*initialize App Settings & start the Game*/
86
- appSettings = new AppSettings (true );
87
- appSettings .setAudioRenderer (audioRendererType );
88
- appSettings .setResolution (JmeSurfaceView .this .getLayoutParams ().width , JmeSurfaceView .this .getLayoutParams ().height );
89
- appSettings .setAlphaBits (eglAlphaBits );
90
- appSettings .setDepthBits (eglDepthBits );
91
- appSettings .setSamples (eglSamples );
92
- appSettings .setStencilBits (eglStencilBits );
93
- appSettings .setBitsPerPixel (eglBitsPerPixel );
94
- appSettings .setEmulateKeyboard (emulateKeyBoard );
95
- appSettings .setEmulateMouse (emulateMouse );
96
- appSettings .setUseJoysticks (useJoyStickEvents );
97
- legacyApplication .setSettings (appSettings );
98
- /*start jme game context*/
99
- legacyApplication .start ();
100
- /*attach the game to JmE OpenGL.Renderer context */
101
- OGLESContext oglesContext = (OGLESContext ) legacyApplication .getContext ();
102
- /*create a glSurfaceView that will hold the renderer thread*/
103
- glSurfaceView = oglesContext .createView (JmeSurfaceView .this .getContext ());
104
- /*set the current view as the system engine thread view for future uses*/
105
- JmeAndroidSystem .setView (JmeSurfaceView .this );
106
- /*set JME system Listener to initialize game , update , requestClose & destroy on closure*/
107
- oglesContext .setSystemListener (JmeSurfaceView .this );
108
- /* set the glSurfaceView to fit the widget */
109
- glSurfaceView .setLayoutParams (new LayoutParams (JmeSurfaceView .this .getLayoutParams ().width , JmeSurfaceView .this .getLayoutParams ().height ));
110
- /*post delay the renderer join into the UI thread*/
111
- handler .postDelayed (new RendererThread () , delayMillis );
112
- } catch (Exception e ) {
113
- jmeSurfaceViewLogger .log (Level .WARNING , e .getMessage ());
114
- showErrorDialog (e , e .getMessage ());
115
- if (onExceptionThrown != null ) {
116
- onExceptionThrown .onExceptionThrown (e );
117
- }
84
+ if (legacyApplication == null ) {
85
+ throw new IllegalStateException ("Cannot build a SurfaceView for a null app, make sure to use setLegacyApplication() to pass in your app !" );
86
+ }
87
+ try {
88
+ /*initialize App Settings & start the Game*/
89
+ appSettings = new AppSettings (true );
90
+ appSettings .setAudioRenderer (audioRendererType );
91
+ appSettings .setResolution (JmeSurfaceView .this .getLayoutParams ().width , JmeSurfaceView .this .getLayoutParams ().height );
92
+ appSettings .setAlphaBits (eglAlphaBits );
93
+ appSettings .setDepthBits (eglDepthBits );
94
+ appSettings .setSamples (eglSamples );
95
+ appSettings .setStencilBits (eglStencilBits );
96
+ appSettings .setBitsPerPixel (eglBitsPerPixel );
97
+ appSettings .setEmulateKeyboard (emulateKeyBoard );
98
+ appSettings .setEmulateMouse (emulateMouse );
99
+ appSettings .setUseJoysticks (useJoyStickEvents );
100
+ legacyApplication .setSettings (appSettings );
101
+ /*start jme game context*/
102
+ legacyApplication .start ();
103
+ /*attach the game to JmE OpenGL.Renderer context */
104
+ OGLESContext oglesContext = (OGLESContext ) legacyApplication .getContext ();
105
+ /*create a glSurfaceView that will hold the renderer thread*/
106
+ glSurfaceView = oglesContext .createView (JmeSurfaceView .this .getContext ());
107
+ /*set the current view as the system engine thread view for future uses*/
108
+ JmeAndroidSystem .setView (JmeSurfaceView .this );
109
+ /*set JME system Listener to initialize game , update , requestClose & destroy on closure*/
110
+ oglesContext .setSystemListener (JmeSurfaceView .this );
111
+ /* set the glSurfaceView to fit the widget */
112
+ glSurfaceView .setLayoutParams (new LayoutParams (JmeSurfaceView .this .getLayoutParams ().width , JmeSurfaceView .this .getLayoutParams ().height ));
113
+ /*post delay the renderer join into the UI thread*/
114
+ handler .postDelayed (new RendererThread () , delayMillis );
115
+ } catch (Exception e ) {
116
+ jmeSurfaceViewLogger .log (Level .WARNING , e .getMessage ());
117
+ showErrorDialog (e , e .getMessage ());
118
+ if (onExceptionThrown != null ) {
119
+ onExceptionThrown .onExceptionThrown (e );
118
120
}
119
121
}
120
122
}
121
123
124
+ /**
125
+ * A state change observer to the current Activity life cycle.
126
+ * @param source the life cycle source, aka the observable object.
127
+ * @param event the fired event by the observable object, which is dispatched and sent to the observers.
128
+ */
129
+ @ Override
130
+ public void onStateChanged (@ NonNull LifecycleOwner source , @ NonNull Lifecycle .Event event ) {
131
+ switch (event ){
132
+ case ON_DESTROY :
133
+ destroy ();
134
+ break ;
135
+ case ON_PAUSE :
136
+ loseFocus ();
137
+ break ;
138
+ case ON_RESUME :
139
+ gainFocus ();
140
+ break ;
141
+ }
142
+ }
143
+
122
144
/**
123
145
* Custom thread that delays the appearance of the display of jme game on the screen for the sake of initial frame pacing & splash screens.
124
146
*/
@@ -132,36 +154,40 @@ private class RendererThread implements Runnable {
132
154
public void run () {
133
155
/*jme Renderer joins the UIThread at that point*/
134
156
JmeSurfaceView .this .addView (glSurfaceView );
157
+ //register this Ui Component as an observer to the context of jmeSurfaceView only if this context is a LifeCycleOwner
158
+ if (getContext () instanceof LifecycleOwner ) {
159
+ ((LifecycleOwner ) getContext ()).getLifecycle ().addObserver (JmeSurfaceView .this );
160
+ }
135
161
jmeSurfaceViewLogger .log (Level .CONFIG , "JmeSurfaceView's joined the UI thread......." );
136
162
}
137
163
}
138
164
139
165
@ Override
140
166
public void initialize () {
141
- if (legacyApplication != null ) {
142
- legacyApplication .initialize ();
143
- /*log for display*/
144
- jmeSurfaceViewLogger .log (Level .INFO , "JmeGame started in GLThread Asynchronously......." );
167
+ if (legacyApplication == null ) {
168
+ return ;
145
169
}
170
+ legacyApplication .initialize ();
171
+ /*log for display*/
172
+ jmeSurfaceViewLogger .log (Level .INFO , "JmeGame started in GLThread Asynchronously......." );
146
173
}
147
174
148
175
@ Override
149
176
public void reshape (int width , int height ) {
150
- if (legacyApplication ! = null ) {
151
- legacyApplication . reshape ( width , height ) ;
177
+ if (legacyApplication = = null ) {
178
+ return ;
152
179
}
180
+ legacyApplication .reshape (width , height );
153
181
}
154
182
155
183
@ Override
156
184
public void update () {
157
- if (legacyApplication == null ) {
185
+ if (legacyApplication == null || glSurfaceView == null ) {
158
186
return ;
159
187
}
160
- if (glSurfaceView != null ) {
161
- legacyApplication .update ();
162
- }
188
+ legacyApplication .update ();
163
189
int timeToPlay = synthesizedTime .addAndGet (1 );
164
- if (timeToPlay == (delayMillis > 100 ? (delayMillis - TOLERANCE_TIMER ) : delayMillis )) {
190
+ if (timeToPlay == (delayMillis > 100 ? (delayMillis - TOLERANCE_TIMER ) : delayMillis )) {
165
191
((Activity )getContext ()).runOnUiThread (() -> {
166
192
jmeSurfaceViewLogger .log (Level .INFO ,"SplashScreen Dismissed , User Delay completed with 0 errors......." );
167
193
if (onRendererCompleted != null ) {
@@ -173,51 +199,56 @@ public void update() {
173
199
174
200
@ Override
175
201
public void requestClose (boolean esc ) {
176
- if (legacyApplication ! = null ) {
177
- legacyApplication . requestClose ( esc ) ;
202
+ if (legacyApplication = = null ) {
203
+ return ;
178
204
}
205
+ legacyApplication .requestClose (esc );
179
206
}
180
207
181
208
@ Override
182
209
public void gainFocus () {
183
- if (legacyApplication != null ) {
184
- /*resume the audio*/
185
- AudioRenderer audioRenderer = legacyApplication .getAudioRenderer ();
186
- if (audioRenderer != null ) {
187
- audioRenderer .resumeAll ();
188
- }
189
- /*resume the sensors (aka joysticks)*/
190
- if (legacyApplication .getContext () != null ) {
191
- JoyInput joyInput = legacyApplication .getContext ().getJoyInput ();
192
- if (joyInput != null ) {
193
- if (joyInput instanceof AndroidSensorJoyInput ) {
194
- AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput ) joyInput ;
195
- androidJoyInput .resumeSensors ();
196
- }
210
+ if (legacyApplication == null || glSurfaceView == null ) {
211
+ return ;
212
+ }
213
+ glSurfaceView .onResume ();
214
+ /*resume the audio*/
215
+ AudioRenderer audioRenderer = legacyApplication .getAudioRenderer ();
216
+ if (audioRenderer != null ) {
217
+ audioRenderer .resumeAll ();
218
+ }
219
+ /*resume the sensors (aka joysticks)*/
220
+ if (legacyApplication .getContext () != null ) {
221
+ JoyInput joyInput = legacyApplication .getContext ().getJoyInput ();
222
+ if (joyInput != null ) {
223
+ if (joyInput instanceof AndroidSensorJoyInput ) {
224
+ AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput ) joyInput ;
225
+ androidJoyInput .resumeSensors ();
197
226
}
198
- legacyApplication .gainFocus ();
199
227
}
228
+ legacyApplication .gainFocus ();
200
229
}
201
230
setGLThreadPaused (false );
202
231
}
203
232
204
233
@ Override
205
234
public void loseFocus () {
206
- if (legacyApplication != null ) {
207
- /*pause the audio*/
208
- legacyApplication .loseFocus ();
209
- AudioRenderer audioRenderer = legacyApplication .getAudioRenderer ();
210
- if (audioRenderer != null ) {
211
- audioRenderer .pauseAll ();
212
- }
213
- /*pause the sensors (aka joysticks)*/
214
- if (legacyApplication .getContext () != null ) {
215
- JoyInput joyInput = legacyApplication .getContext ().getJoyInput ();
216
- if (joyInput != null ) {
217
- if (joyInput instanceof AndroidSensorJoyInput ) {
218
- AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput ) joyInput ;
219
- androidJoyInput .pauseSensors ();
220
- }
235
+ if (legacyApplication == null || glSurfaceView == null ) {
236
+ return ;
237
+ }
238
+ glSurfaceView .onPause ();
239
+ /*pause the audio*/
240
+ legacyApplication .loseFocus ();
241
+ AudioRenderer audioRenderer = legacyApplication .getAudioRenderer ();
242
+ if (audioRenderer != null ) {
243
+ audioRenderer .pauseAll ();
244
+ }
245
+ /*pause the sensors (aka joysticks)*/
246
+ if (legacyApplication .getContext () != null ) {
247
+ JoyInput joyInput = legacyApplication .getContext ().getJoyInput ();
248
+ if (joyInput != null ) {
249
+ if (joyInput instanceof AndroidSensorJoyInput ) {
250
+ AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput ) joyInput ;
251
+ androidJoyInput .pauseSensors ();
221
252
}
222
253
}
223
254
}
@@ -236,8 +267,7 @@ public void handleError(String errorMsg , Throwable throwable) {
236
267
@ Override
237
268
public void destroy () {
238
269
if (legacyApplication != null ) {
239
- legacyApplication .stop (isGLThreadPaused ());
240
- legacyApplication .destroy ();
270
+ legacyApplication .stop (!isGLThreadPaused ());
241
271
}
242
272
}
243
273
@@ -247,6 +277,9 @@ public void destroy() {
247
277
* @param message the string message.
248
278
*/
249
279
protected void showErrorDialog (Throwable throwable , String message ) {
280
+ if (!isShowErrorDialog ()){
281
+ return ;
282
+ }
250
283
((Activity )getContext ()).runOnUiThread (() -> {
251
284
AlertDialog alertDialog = new AlertDialog .Builder (getContext ()).create ();
252
285
alertDialog .setTitle (new StringBuffer (String .valueOf (throwable )));
@@ -258,6 +291,14 @@ protected void showErrorDialog(Throwable throwable , String message) {
258
291
});
259
292
}
260
293
294
+ public void setShowErrorDialog (boolean showErrorDialog ) {
295
+ this .showErrorDialog = showErrorDialog ;
296
+ }
297
+
298
+ public boolean isShowErrorDialog () {
299
+ return showErrorDialog ;
300
+ }
301
+
261
302
@ Override
262
303
public void onClick (DialogInterface dialog , int which ) {
263
304
switch (which ) {
0 commit comments