Mercurial > parpg-source
comparison bGrease/mode.py @ 41:ff3e395abf91
Renamed grease to bGrease (Basic Grease) to get rid of conflicts with an already installed grease.
author | KarstenBock@gmx.net |
---|---|
date | Mon, 05 Sep 2011 15:00:34 +0200 |
parents | grease/mode.py@ce33d344e202 |
children | a6bbb732b27b |
comparison
equal
deleted
inserted
replaced
40:2e3ab06a2f47 | 41:ff3e395abf91 |
---|---|
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 current = self.current_mode | |
104 if current is not None: | |
105 self.activate_mode(current) | |
106 else: | |
107 self.on_last_mode_pop(mode) | |
108 return mode | |
109 | |
110 def swap_modes(self, mode): | |
111 """Exchange the specified mode with the mode at the top of the stack. | |
112 This is similar to popping the current mode and pushing the specified | |
113 one, but without activating the previous mode on the stack or | |
114 executing :meth:`on_last_mode_pop()` if there is no previous mode. | |
115 | |
116 :param mode: The :class:`Mode` object that was deactivated and replaced. | |
117 """ | |
118 old_mode = self.modes.pop() | |
119 self.deactivate_mode(old_mode) | |
120 self.modes.append(mode) | |
121 self.activate_mode(mode) | |
122 return old_mode | |
123 | |
124 def remove_mode(self, mode): | |
125 """Remove the specified mode. If the mode is at the top of the stack, | |
126 this is equivilent to :meth:`pop_mode()`. If not, no other modes | |
127 are affected. If the mode is not in the manager, do nothing. | |
128 | |
129 :param mode: The :class:`Mode` object to remove from the manager. | |
130 """ | |
131 if self.current_mode is mode: | |
132 self.pop_mode() | |
133 else: | |
134 try: | |
135 self.modes.remove(mode) | |
136 except ValueError: | |
137 pass | |
138 | |
139 class BaseMode(object): | |
140 """Application mode very abstract base class | |
141 """ | |
142 __metaclass__ = abc.ABCMeta | |
143 | |
144 manager = None | |
145 """The :class:`BaseManager` that manages this mode""" | |
146 | |
147 def __init__(self): | |
148 self.active = False | |
149 | |
150 def on_activate(self): | |
151 """Being called when the Mode is activated""" | |
152 pass | |
153 | |
154 def activate(self, mode_manager): | |
155 """Activate the mode for the given mode manager, if the mode is already active, | |
156 do nothing | |
157 | |
158 The default implementation schedules time steps at :attr:`step_rate` per | |
159 second, sets the :attr:`manager` and sets the :attr:`active` flag to True. | |
160 """ | |
161 if not self.active: | |
162 self.on_activate() | |
163 self.manager = mode_manager | |
164 self.active = True | |
165 | |
166 def on_deactivate(self): | |
167 """Being called when the Mode is deactivated""" | |
168 pass | |
169 | |
170 def deactivate(self, mode_manager): | |
171 """Deactivate the mode, if the mode is not active, do nothing | |
172 | |
173 The default implementation unschedules time steps for the mode and | |
174 sets the :attr:`active` flag to False. | |
175 """ | |
176 self.on_deactivate() | |
177 self.active = False | |
178 | |
179 | |
180 class BaseMulti(BaseMode): | |
181 """A mode with multiple submodes. One submode is active at one time. | |
182 Submodes can be switched to directly or switched in sequence. If | |
183 the Multi is active, then one submode is always active. | |
184 | |
185 Multis are useful when modes can switch in an order other than | |
186 a LIFO stack, such as in "hotseat" multiplayer games, a | |
187 "wizard" style ui, or a sequence of slides. | |
188 | |
189 Note unlike a normal :class:`Mode`, a :class:`Multi` doesn't have it's own | |
190 :attr:`clock` and :attr:`step_rate`. The active submode's are used | |
191 instead. | |
192 """ | |
193 active_submode = None | |
194 """The currently active submode""" | |
195 | |
196 def __init__(self, *submodes): | |
197 # We do not invoke the superclass __init__ intentionally | |
198 self.active = False | |
199 self.submodes = list(submodes) | |
200 | |
201 def add_submode(self, mode, before=None, index=None): | |
202 """Add the submode, but do not make it active. | |
203 | |
204 :param mode: The :class:`Mode` object to add. | |
205 | |
206 :param before: The existing mode to insert the mode before. | |
207 If the mode specified is not a submode, raise | |
208 ValueError. | |
209 | |
210 :param index: The place to insert the mode in the mode list. | |
211 Only one of ``before`` or ``index`` may be specified. | |
212 | |
213 If neither ``before`` or ``index`` are specified, the | |
214 mode is appended to the end of the list. | |
215 """ | |
216 assert before is None or index is None, ( | |
217 "Cannot specify both 'before' and 'index' arguments") | |
218 if before is not None: | |
219 index = self.submodes.index(mode) | |
220 if index is not None: | |
221 self.submodes.insert(index, mode) | |
222 else: | |
223 self.submodes.append(mode) | |
224 | |
225 def remove_submode(self, mode=None): | |
226 """Remove the submode. | |
227 | |
228 :param mode: The submode to remove, if omitted the active submode | |
229 is removed. If the mode is not present, do nothing. If the | |
230 mode is active, it is deactivated, and the next mode, if any | |
231 is activated. If the last mode is removed, the :class:`Multi` | |
232 is removed from its manager. | |
233 """ | |
234 # TODO handle multiple instances of the same subnode | |
235 if mode is None: | |
236 mode = self.active_submode | |
237 elif mode not in self.submodes: | |
238 return | |
239 next_mode = self.activate_next() | |
240 self.submodes.remove(mode) | |
241 if next_mode is mode: | |
242 if self.manager is not None: | |
243 self.manager.remove_mode(self) | |
244 self._deactivate_submode() | |
245 | |
246 def activate_subnode(self, mode, before=None, index=None): | |
247 """Activate the specified mode, adding it as a subnode | |
248 if it is not already. If the mode is already the active | |
249 submode, do nothing. | |
250 | |
251 :param mode: The mode to activate, and add as necesary. | |
252 | |
253 :param before: The existing mode to insert the mode before | |
254 if it is not already a submode. If the mode specified is not | |
255 a submode, raise ValueError. | |
256 | |
257 :param index: The place to insert the mode in the mode list | |
258 if it is not already a submode. Only one of ``before`` or | |
259 ``index`` may be specified. | |
260 | |
261 If the mode is already a submode, the ``before`` and ``index`` | |
262 arguments are ignored. | |
263 """ | |
264 if mode not in self.submodes: | |
265 self.add_submode(mode, before, index) | |
266 if self.active_submode is not mode: | |
267 self._activate_submode(mode) | |
268 | |
269 def activate_next(self, loop=True): | |
270 """Activate the submode after the current submode in order. If there | |
271 is no current submode, the first submode is activated. | |
272 | |
273 Note if there is only one submode, it's active, and `loop` is True | |
274 (the default), then this method does nothing and the subnode remains | |
275 active. | |
276 | |
277 :param loop: When :meth:`activate_next` is called | |
278 when the last submode is active, a True value for ``loop`` will | |
279 cause the first submode to be activated. Otherwise the | |
280 :class:`Multi` is removed from its manager. | |
281 :type loop: bool | |
282 | |
283 :return: | |
284 The submode that was activated or None if there is no | |
285 other submode to activate. | |
286 """ | |
287 assert self.submodes, "No submode to activate" | |
288 next_mode = None | |
289 if self.active_submode is None: | |
290 next_mode = self.submodes[0] | |
291 else: | |
292 last_mode = self.active_submode | |
293 index = self.submodes.index(last_mode) + 1 | |
294 if index < len(self.submodes): | |
295 next_mode = self.submodes[index] | |
296 elif loop: | |
297 next_mode = self.submodes[0] | |
298 self._activate_submode(next_mode) | |
299 return next_mode | |
300 | |
301 def activate_previous(self, loop=True): | |
302 """Activate the submode before the current submode in order. If there | |
303 is no current submode, the last submode is activated. | |
304 | |
305 Note if there is only one submode, it's active, and `loop` is True | |
306 (the default), then this method does nothing and the subnode remains | |
307 active. | |
308 | |
309 :param loop: When :meth:`activate_previous` is called | |
310 when the first submode is active, a True value for ``loop`` will | |
311 cause the last submode to be activated. Otherwise the | |
312 :class:`Multi` is removed from its manager. | |
313 :type loop: bool | |
314 | |
315 :return: | |
316 The submode that was activated or None if there is no | |
317 other submode to activate. | |
318 """ | |
319 assert self.submodes, "No submode to activate" | |
320 prev_mode = None | |
321 if self.active_submode is None: | |
322 prev_mode = self.submodes[-1] | |
323 else: | |
324 last_mode = self.active_submode | |
325 index = self.submodes.index(last_mode) - 1 | |
326 if loop or index >= 0: | |
327 prev_mode = self.submodes[index] | |
328 self._activate_submode(prev_mode) | |
329 return prev_mode | |
330 | |
331 def _set_active_submode(self, submode): | |
332 self.active_submode = submode | |
333 self.step_rate = submode.step_rate | |
334 | |
335 def _activate_submode(self, submode): | |
336 """Activate a submode deactivating any current submode. If the Multi | |
337 itself is active, this happens immediately, otherwise the actual | |
338 activation is deferred until the Multi is activated. If the submode | |
339 is None, the Mulitmode is removed from its manager. | |
340 | |
341 If submode is already the active submode, do nothing. | |
342 """ | |
343 if self.active_submode is submode: | |
344 return | |
345 assert submode in self.submodes, "Unknown submode" | |
346 self._deactivate_submode() | |
347 self._set_active_submode(submode) | |
348 if submode is not None: | |
349 if self.active: | |
350 self.manager.activate_mode(submode) | |
351 else: | |
352 if self.manager is not None: | |
353 self.manager.remove_mode(self) | |
354 | |
355 def clear_subnode(self): | |
356 """Clear any subnmode data""" | |
357 self.active_submode = None | |
358 self.step_rate = None | |
359 | |
360 def _deactivate_submode(self, clear_subnode=True): | |
361 """Deactivate the current submode, if any. if `clear_subnode` is | |
362 True, `active_submode` is always None when this method returns | |
363 """ | |
364 if self.active_submode is not None: | |
365 if self.active: | |
366 self.manager.deactivate_mode(self.active_submode) | |
367 if clear_subnode: | |
368 self.clear_subnode() | |
369 | |
370 def activate(self, mode_manager): | |
371 """Activate the :class:`Multi` for the specified manager. The | |
372 previously active submode of the :class:`Multi` is activated. If there | |
373 is no previously active submode, then the first submode is made active. | |
374 A :class:`Multi` with no submodes cannot be activated | |
375 """ | |
376 assert self.submodes, "No submode to activate" | |
377 self.manager = mode_manager | |
378 if self.active_submode is None: | |
379 self._set_active_submode(self.submodes[0]) | |
380 else: | |
381 self._set_active_submode(self.active_submode) | |
382 self.manager.activate_mode(self.active_submode) | |
383 super(BaseMulti, self).activate(mode_manager) | |
384 | |
385 def deactivate(self, mode_manager): | |
386 """Deactivate the :class:`Multi` for the specified manager. | |
387 The `active_submode`, if any, is deactivated. | |
388 """ | |
389 self._deactivate_submode(clear_subnode=False) | |
390 super(BaseMulti, self).deactivate(mode_manager) | |
391 |