Mercurial > parpg-source
comparison grease/mode.py @ 5:bc88f7d5ca8b
Added base files for grease
author | KarstenBock@gmx.net |
---|---|
date | Tue, 12 Jul 2011 10:16:48 +0200 |
parents | |
children | ce33d344e202 |
comparison
equal
deleted
inserted
replaced
4:bf1dd9c24a7e | 5:bc88f7d5ca8b |
---|---|
1 ############################################################################# | |
2 # | |
3 # Copyright (c) 2010 by Casey Duncan and contributors | |
4 # All Rights Reserved. | |
5 # | |
6 # This software is subject to the provisions of the MIT License | |
7 # A copy of the license should accompany this distribution. | |
8 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
9 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
10 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
11 # | |
12 ############################################################################# | |
13 """ | |
14 Modes manage the state and transition between different application modes. | |
15 Typically such modes are presented as different screens that the user can | |
16 navigate between, similar to the way a browser navigates web pages. Individual | |
17 modes may be things like: | |
18 | |
19 - Title screen | |
20 - Options dialog | |
21 - About screen | |
22 - In-progress game | |
23 - Inventory interface | |
24 | |
25 The modal framework provides a simple mechanism to ensure that modes are | |
26 activated and deactivated properly. An activated mode is running and receives | |
27 events. A deactivated mode is paused and does not receive events. | |
28 | |
29 Modes may be managed as a *last-in-first-out* stack, or as a list, or ring | |
30 of modes in sequence, or some combination of all. | |
31 | |
32 For example usage see: :ref:`the mode section of the tutorial <tut-mode-section>`. | |
33 """ | |
34 | |
35 __version__ = '$Id$' | |
36 | |
37 import abc | |
38 | |
39 | |
40 class BaseManager(object): | |
41 """Mode manager abstract base class. | |
42 | |
43 The mode manager keeps a stack of modes where a single mode | |
44 is active at one time. As modes are pushed on and popped from | |
45 the stack, the mode at the top is always active. The current | |
46 active mode receives events from the manager's event dispatcher. | |
47 """ | |
48 | |
49 modes = () | |
50 """The mode stack sequence. The last mode in the stack is | |
51 the current active mode. Read-only. | |
52 """ | |
53 | |
54 @property | |
55 def current_mode(self): | |
56 """The current active mode or ``None``. Read-only""" | |
57 try: | |
58 return self.modes[-1] | |
59 except IndexError: | |
60 return None | |
61 | |
62 def on_last_mode_pop(self, mode): | |
63 """Hook executed when the last mode is popped from the manager. | |
64 Implementing this method is optional for subclasses. | |
65 | |
66 :param mode: The :class:`Mode` object just popped from the manager | |
67 """ | |
68 | |
69 def activate_mode(self, mode): | |
70 """Perform actions to activate a node | |
71 | |
72 :param mode: The :class: 'Mode' object to activate | |
73 """ | |
74 mode.activate(self) | |
75 | |
76 def deactivate_mode(self, mode): | |
77 """Perform actions to deactivate a node | |
78 | |
79 :param mode: The :class: 'Mode' object to deactivate | |
80 """ | |
81 mode.deactivate(self) | |
82 | |
83 | |
84 def push_mode(self, mode): | |
85 """Push a mode to the top of the mode stack and make it active | |
86 | |
87 :param mode: The :class:`Mode` object to make active | |
88 """ | |
89 current = self.current_mode | |
90 if current is not None: | |
91 self.deactivate_mode(current) | |
92 self.modes.append(mode) | |
93 self.activate_mode(mode) | |
94 | |
95 def pop_mode(self): | |
96 """Pop the current mode off the top of the stack and deactivate it. | |
97 The mode now at the top of the stack, if any is then activated. | |
98 | |
99 :param mode: The :class:`Mode` object popped from the stack | |
100 """ | |
101 mode = self.modes.pop() | |
102 mode.deactivate(self) | |
103 self.event_dispatcher.remove_handlers(mode) | |
104 current = self.current_mode | |
105 if current is not None: | |
106 self.activate_mode(current) | |
107 else: | |
108 self.on_last_mode_pop(mode) | |
109 return mode | |
110 | |
111 def swap_modes(self, mode): | |
112 """Exchange the specified mode with the mode at the top of the stack. | |
113 This is similar to popping the current mode and pushing the specified | |
114 one, but without activating the previous mode on the stack or | |
115 executing :meth:`on_last_mode_pop()` if there is no previous mode. | |
116 | |
117 :param mode: The :class:`Mode` object that was deactivated and replaced. | |
118 """ | |
119 old_mode = self.modes.pop() | |
120 self.deactivate_mode(old_mode) | |
121 self.modes.append(mode) | |
122 self.activate_mode(mode) | |
123 return old_mode | |
124 | |
125 def remove_mode(self, mode): | |
126 """Remove the specified mode. If the mode is at the top of the stack, | |
127 this is equivilent to :meth:`pop_mode()`. If not, no other modes | |
128 are affected. If the mode is not in the manager, do nothing. | |
129 | |
130 :param mode: The :class:`Mode` object to remove from the manager. | |
131 """ | |
132 if self.current_mode is mode: | |
133 self.pop_mode() | |
134 else: | |
135 try: | |
136 self.modes.remove(mode) | |
137 except ValueError: | |
138 pass | |
139 | |
140 class BaseMode(object): | |
141 """Application mode very abstract base class | |
142 """ | |
143 __metaclass__ = abc.ABCMeta | |
144 | |
145 manager = None | |
146 """The :class:`BaseManager` that manages this mode""" | |
147 | |
148 def __init__(self): | |
149 self.active = False | |
150 | |
151 def on_activate(self): | |
152 """Being called when the Mode is activated""" | |
153 pass | |
154 | |
155 def activate(self, mode_manager): | |
156 """Activate the mode for the given mode manager, if the mode is already active, | |
157 do nothing | |
158 | |
159 The default implementation schedules time steps at :attr:`step_rate` per | |
160 second, sets the :attr:`manager` and sets the :attr:`active` flag to True. | |
161 """ | |
162 if not self.active: | |
163 self.on_activate() | |
164 self.manager = mode_manager | |
165 self.active = True | |
166 | |
167 def on_deactivate(self): | |
168 """Being called when the Mode is deactivated""" | |
169 pass | |
170 | |
171 def deactivate(self, mode_manager): | |
172 """Deactivate the mode, if the mode is not active, do nothing | |
173 | |
174 The default implementation unschedules time steps for the mode and | |
175 sets the :attr:`active` flag to False. | |
176 """ | |
177 self.on_deactivate() | |
178 self.active = False | |
179 | |
180 | |
181 class BaseMulti(BaseMode): | |
182 """A mode with multiple submodes. One submode is active at one time. | |
183 Submodes can be switched to directly or switched in sequence. If | |
184 the Multi is active, then one submode is always active. | |
185 | |
186 Multis are useful when modes can switch in an order other than | |
187 a LIFO stack, such as in "hotseat" multiplayer games, a | |
188 "wizard" style ui, or a sequence of slides. | |
189 | |
190 Note unlike a normal :class:`Mode`, a :class:`Multi` doesn't have it's own | |
191 :attr:`clock` and :attr:`step_rate`. The active submode's are used | |
192 instead. | |
193 """ | |
194 active_submode = None | |
195 """The currently active submode""" | |
196 | |
197 def __init__(self, *submodes): | |
198 # We do not invoke the superclass __init__ intentionally | |
199 self.active = False | |
200 self.submodes = list(submodes) | |
201 | |
202 def add_submode(self, mode, before=None, index=None): | |
203 """Add the submode, but do not make it active. | |
204 | |
205 :param mode: The :class:`Mode` object to add. | |
206 | |
207 :param before: The existing mode to insert the mode before. | |
208 If the mode specified is not a submode, raise | |
209 ValueError. | |
210 | |
211 :param index: The place to insert the mode in the mode list. | |
212 Only one of ``before`` or ``index`` may be specified. | |
213 | |
214 If neither ``before`` or ``index`` are specified, the | |
215 mode is appended to the end of the list. | |
216 """ | |
217 assert before is None or index is None, ( | |
218 "Cannot specify both 'before' and 'index' arguments") | |
219 if before is not None: | |
220 index = self.submodes.index(mode) | |
221 if index is not None: | |
222 self.submodes.insert(index, mode) | |
223 else: | |
224 self.submodes.append(mode) | |
225 | |
226 def remove_submode(self, mode=None): | |
227 """Remove the submode. | |
228 | |
229 :param mode: The submode to remove, if omitted the active submode | |
230 is removed. If the mode is not present, do nothing. If the | |
231 mode is active, it is deactivated, and the next mode, if any | |
232 is activated. If the last mode is removed, the :class:`Multi` | |
233 is removed from its manager. | |
234 """ | |
235 # TODO handle multiple instances of the same subnode | |
236 if mode is None: | |
237 mode = self.active_submode | |
238 elif mode not in self.submodes: | |
239 return | |
240 next_mode = self.activate_next() | |
241 self.submodes.remove(mode) | |
242 if next_mode is mode: | |
243 if self.manager is not None: | |
244 self.manager.remove_mode(self) | |
245 self._deactivate_submode() | |
246 | |
247 def activate_subnode(self, mode, before=None, index=None): | |
248 """Activate the specified mode, adding it as a subnode | |
249 if it is not already. If the mode is already the active | |
250 submode, do nothing. | |
251 | |
252 :param mode: The mode to activate, and add as necesary. | |
253 | |
254 :param before: The existing mode to insert the mode before | |
255 if it is not already a submode. If the mode specified is not | |
256 a submode, raise ValueError. | |
257 | |
258 :param index: The place to insert the mode in the mode list | |
259 if it is not already a submode. Only one of ``before`` or | |
260 ``index`` may be specified. | |
261 | |
262 If the mode is already a submode, the ``before`` and ``index`` | |
263 arguments are ignored. | |
264 """ | |
265 if mode not in self.submodes: | |
266 self.add_submode(mode, before, index) | |
267 if self.active_submode is not mode: | |
268 self._activate_submode(mode) | |
269 | |
270 def activate_next(self, loop=True): | |
271 """Activate the submode after the current submode in order. If there | |
272 is no current submode, the first submode is activated. | |
273 | |
274 Note if there is only one submode, it's active, and `loop` is True | |
275 (the default), then this method does nothing and the subnode remains | |
276 active. | |
277 | |
278 :param loop: When :meth:`activate_next` is called | |
279 when the last submode is active, a True value for ``loop`` will | |
280 cause the first submode to be activated. Otherwise the | |
281 :class:`Multi` is removed from its manager. | |
282 :type loop: bool | |
283 | |
284 :return: | |
285 The submode that was activated or None if there is no | |
286 other submode to activate. | |
287 """ | |
288 assert self.submodes, "No submode to activate" | |
289 next_mode = None | |
290 if self.active_submode is None: | |
291 next_mode = self.submodes[0] | |
292 else: | |
293 last_mode = self.active_submode | |
294 index = self.submodes.index(last_mode) + 1 | |
295 if index < len(self.submodes): | |
296 next_mode = self.submodes[index] | |
297 elif loop: | |
298 next_mode = self.submodes[0] | |
299 self._activate_submode(next_mode) | |
300 return next_mode | |
301 | |
302 def activate_previous(self, loop=True): | |
303 """Activate the submode before the current submode in order. If there | |
304 is no current submode, the last submode is activated. | |
305 | |
306 Note if there is only one submode, it's active, and `loop` is True | |
307 (the default), then this method does nothing and the subnode remains | |
308 active. | |
309 | |
310 :param loop: When :meth:`activate_previous` is called | |
311 when the first submode is active, a True value for ``loop`` will | |
312 cause the last submode to be activated. Otherwise the | |
313 :class:`Multi` is removed from its manager. | |
314 :type loop: bool | |
315 | |
316 :return: | |
317 The submode that was activated or None if there is no | |
318 other submode to activate. | |
319 """ | |
320 assert self.submodes, "No submode to activate" | |
321 prev_mode = None | |
322 if self.active_submode is None: | |
323 prev_mode = self.submodes[-1] | |
324 else: | |
325 last_mode = self.active_submode | |
326 index = self.submodes.index(last_mode) - 1 | |
327 if loop or index >= 0: | |
328 prev_mode = self.submodes[index] | |
329 self._activate_submode(prev_mode) | |
330 return prev_mode | |
331 | |
332 def _set_active_submode(self, submode): | |
333 self.active_submode = submode | |
334 self.step_rate = submode.step_rate | |
335 | |
336 def _activate_submode(self, submode): | |
337 """Activate a submode deactivating any current submode. If the Multi | |
338 itself is active, this happens immediately, otherwise the actual | |
339 activation is deferred until the Multi is activated. If the submode | |
340 is None, the Mulitmode is removed from its manager. | |
341 | |
342 If submode is already the active submode, do nothing. | |
343 """ | |
344 if self.active_submode is submode: | |
345 return | |
346 assert submode in self.submodes, "Unknown submode" | |
347 self._deactivate_submode() | |
348 self._set_active_submode(submode) | |
349 if submode is not None: | |
350 if self.active: | |
351 self.manager.activate_mode(submode) | |
352 else: | |
353 if self.manager is not None: | |
354 self.manager.remove_mode(self) | |
355 | |
356 def clear_subnode(self): | |
357 """Clear any subnmode data""" | |
358 self.active_submode = None | |
359 self.step_rate = None | |
360 | |
361 def _deactivate_submode(self, clear_subnode=True): | |
362 """Deactivate the current submode, if any. if `clear_subnode` is | |
363 True, `active_submode` is always None when this method returns | |
364 """ | |
365 if self.active_submode is not None: | |
366 if self.active: | |
367 self.manager.deactivate_mode(self.active_submode) | |
368 if clear_subnode: | |
369 self.clear_subnode() | |
370 | |
371 def activate(self, mode_manager): | |
372 """Activate the :class:`Multi` for the specified manager. The | |
373 previously active submode of the :class:`Multi` is activated. If there | |
374 is no previously active submode, then the first submode is made active. | |
375 A :class:`Multi` with no submodes cannot be activated | |
376 """ | |
377 assert self.submodes, "No submode to activate" | |
378 self.manager = mode_manager | |
379 if self.active_submode is None: | |
380 self._set_active_submode(self.submodes[0]) | |
381 else: | |
382 self._set_active_submode(self.active_submode) | |
383 self.manager.activate_mode(self.active_submode) | |
384 super(BaseMulti, self).activate(mode_manager) | |
385 | |
386 def deactivate(self, mode_manager): | |
387 """Deactivate the :class:`Multi` for the specified manager. | |
388 The `active_submode`, if any, is deactivated. | |
389 """ | |
390 self._deactivate_submode(clear_subnode=False) | |
391 super(BaseMulti, self).deactivate(mode_manager) | |
392 |