Skip to content

Commit 53994eb

Browse files
authored
Create CompositeAppState.java (#2193)
A composite app state which auto manages child app states.
1 parent a2c7b5a commit 53994eb

File tree

1 file changed

+236
-0
lines changed

1 file changed

+236
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*
2+
*
3+
* Copyright (c) 2014-2024 jMonkeyEngine
4+
* Copied with Paul Speed's permission from: https://github.com/Simsilica/SiO2
5+
* All rights reserved.
6+
*
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions
9+
* are met:
10+
*
11+
* 1. Redistributions of source code must retain the above copyright
12+
* notice, this list of conditions and the following disclaimer.
13+
*
14+
* 2. Redistributions in binary form must reproduce the above copyright
15+
* notice, this list of conditions and the following disclaimer in
16+
* the documentation and/or other materials provided with the
17+
* distribution.
18+
*
19+
* 3. Neither the name of the copyright holder nor the names of its
20+
* contributors may be used to endorse or promote products derived
21+
* from this software without specific prior written permission.
22+
*
23+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26+
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27+
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
28+
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31+
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32+
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
34+
* OF THE POSSIBILITY OF SUCH DAMAGE.
35+
*/
36+
37+
package com.jme3.app.state;
38+
39+
import com.jme3.app.Application;
40+
import com.jme3.util.SafeArrayList;
41+
42+
/**
43+
* An AppState that manages a set of child app states, making sure
44+
* they are attached/detached and optional enabled/disabled with the
45+
* parent state.
46+
*
47+
* @author Paul Speed
48+
*/
49+
public class CompositeAppState extends BaseAppState {
50+
51+
private final SafeArrayList<AppStateEntry> states = new SafeArrayList<>(AppStateEntry.class);
52+
private boolean childrenEnabled;
53+
54+
/**
55+
* Since we manage attachmend/detachment possibly before
56+
* initialization, we need to keep track of the stateManager we
57+
* were given in stateAttached() in case we have to attach another
58+
* child prior to initialization (but after we're attached).
59+
* It's possible that we should actually be waiting for initialize
60+
* to add these but I feel like there was some reason I did it this
61+
* way originally. Past-me did not leave any clues.
62+
*/
63+
private AppStateManager stateManager;
64+
private boolean attached;
65+
66+
public CompositeAppState( AppState... states ) {
67+
for( AppState a : states ) {
68+
this.states.add(new AppStateEntry(a, false));
69+
}
70+
}
71+
72+
private int indexOf( AppState state ) {
73+
for( int i = 0; i < states.size(); i++ ) {
74+
AppStateEntry e = states.get(i);
75+
if( e.state == state ) {
76+
return i;
77+
}
78+
}
79+
return -1;
80+
}
81+
82+
private AppStateEntry entry( AppState state ) {
83+
for( AppStateEntry e : states.getArray() ) {
84+
if( e.state == state ) {
85+
return e;
86+
}
87+
}
88+
return null;
89+
}
90+
91+
protected <T extends AppState> T addChild( T state ) {
92+
return addChild(state, false);
93+
}
94+
95+
protected <T extends AppState> T addChild( T state, boolean overrideEnable ) {
96+
if( indexOf(state) >= 0 ) {
97+
return state;
98+
}
99+
states.add(new AppStateEntry(state, overrideEnable));
100+
if( attached ) {
101+
stateManager.attach(state);
102+
}
103+
return state;
104+
}
105+
106+
protected void removeChild( AppState state ) {
107+
int index = indexOf(state);
108+
if( index < 0 ) {
109+
return;
110+
}
111+
states.remove(index);
112+
if( attached ) {
113+
stateManager.detach(state);
114+
}
115+
}
116+
117+
protected <T extends AppState> T getChild( Class<T> stateType ) {
118+
for( AppStateEntry e : states.getArray() ) {
119+
if( stateType.isInstance(e.state) ) {
120+
return stateType.cast(e.state);
121+
}
122+
}
123+
return null;
124+
}
125+
126+
protected void clearChildren() {
127+
for( AppStateEntry e : states.getArray() ) {
128+
removeChild(e.state);
129+
}
130+
}
131+
132+
@Override
133+
public void stateAttached( AppStateManager stateManager ) {
134+
this.stateManager = stateManager;
135+
for( AppStateEntry e : states.getArray() ) {
136+
stateManager.attach(e.state);
137+
}
138+
this.attached = true;
139+
}
140+
141+
@Override
142+
public void stateDetached( AppStateManager stateManager ) {
143+
// Reverse order
144+
for( int i = states.size() - 1; i >= 0; i-- ) {
145+
stateManager.detach(states.get(i).state);
146+
}
147+
this.attached = false;
148+
this.stateManager = null;
149+
}
150+
151+
protected void setChildrenEnabled( boolean b ) {
152+
if( childrenEnabled == b ) {
153+
return;
154+
}
155+
childrenEnabled = b;
156+
for( AppStateEntry e : states.getArray() ) {
157+
e.setEnabled(b);
158+
}
159+
}
160+
161+
/**
162+
* Overrides the automatic synching of a child's enabled state.
163+
* When override is true, a child will remember its old state when
164+
* the parent's enabled state is false so that when the parent is
165+
* re-enabled the child can resume its previous enabled state. This
166+
* is useful for the cases where a child may want to be disabled
167+
* independent of the parent... and then not automatically become
168+
* enabled just because the parent does.
169+
* Currently, the parent's disabled state always disables the children,
170+
* too. Override is about remembering the child's state before that
171+
* happened and restoring it when the 'family' is enabled again as a whole.
172+
*/
173+
public void setOverrideEnabled( AppState state, boolean override ) {
174+
AppStateEntry e = entry(state);
175+
if( e == null ) {
176+
throw new IllegalArgumentException("State not managed:" + state);
177+
}
178+
if( override ) {
179+
e.override = true;
180+
} else {
181+
e.override = false;
182+
e.state.setEnabled(isEnabled());
183+
}
184+
}
185+
186+
@Override
187+
protected void initialize(Application app) {
188+
}
189+
190+
@Override
191+
protected void cleanup(Application app) {
192+
}
193+
194+
@Override
195+
protected void onEnable() {
196+
setChildrenEnabled(true);
197+
}
198+
199+
@Override
200+
protected void onDisable() {
201+
setChildrenEnabled(false);
202+
}
203+
204+
private class AppStateEntry {
205+
AppState state;
206+
boolean enabled;
207+
boolean override;
208+
209+
public AppStateEntry( AppState state, boolean overrideEnable ) {
210+
this.state = state;
211+
this.override = overrideEnable;
212+
this.enabled = state.isEnabled();
213+
}
214+
215+
public void setEnabled( boolean b ) {
216+
217+
if( override ) {
218+
if( b ) {
219+
// Set it to whatever its enabled state
220+
// was before going disabled last time.
221+
state.setEnabled(enabled);
222+
} else {
223+
// We are going to set enabled to false
224+
// but keep track of what it was before we did
225+
// that
226+
this.enabled = state.isEnabled();
227+
state.setEnabled(false);
228+
}
229+
} else {
230+
// Just synch it always
231+
state.setEnabled(b);
232+
}
233+
}
234+
}
235+
}
236+

0 commit comments

Comments
 (0)