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