5
|
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 import pyglet
|
|
39 from grease.mode import *
|
|
40
|
|
41 class PygletManager(BaseManager):
|
|
42 """Mode manager abstract base class using pyglet.
|
|
43
|
|
44 The mode manager keeps a stack of modes where a single mode
|
|
45 is active at one time. As modes are pushed on and popped from
|
|
46 the stack, the mode at the top is always active. The current
|
|
47 active mode receives events from the manager's event dispatcher.
|
|
48 """
|
|
49
|
|
50 event_dispatcher = None
|
|
51 """:class:`pyglet.event.EventDispatcher` object that the
|
|
52 active mode receive events from.
|
|
53 """
|
|
54
|
|
55 def activate_mode(self, mode):
|
|
56 """Perform actions to activate a node
|
|
57
|
|
58 :param mode: The :class: 'Mode' object to activate
|
|
59 """
|
|
60 BaseManager.activate_mode(self, mode)
|
|
61 self.event_dispatcher.push_handlers(mode)
|
|
62
|
|
63 def deactivate_mode(self, mode):
|
|
64 """Perform actions to deactivate a node
|
|
65
|
|
66 :param mode: The :class: 'Mode' object to deactivate
|
|
67 """
|
|
68 BaseManager.deactivate_mode(self, mode)
|
|
69 self.event_dispatcher.remove_handlers(mode)
|
|
70
|
|
71 class Manager(PygletManager):
|
|
72 """A basic mode manager that wraps a single
|
|
73 :class:`pyglet.event.EventDispatcher` object for use by its modes.
|
|
74 """
|
|
75
|
|
76 def __init__(self, event_dispatcher):
|
|
77 self.modes = []
|
|
78 self.event_dispatcher = event_dispatcher
|
|
79
|
|
80
|
|
81 class ManagerWindow(PygletManager, pyglet.window.Window):
|
|
82 """An integrated mode manager and pyglet window for convenience.
|
|
83 The window is the event dispatcher used by modes pushed to
|
|
84 this manager.
|
|
85
|
|
86 Constructor arguments are identical to :class:`pyglet.window.Window`
|
|
87 """
|
|
88
|
|
89 def __init__(self, *args, **kw):
|
|
90 super(ManagerWindow, self).__init__(*args, **kw)
|
|
91 self.modes = []
|
|
92 self.event_dispatcher = self
|
|
93
|
|
94 def on_key_press(self, symbol, modifiers):
|
|
95 """Default :meth:`on_key_press handler`, pops the current mode on ``ESC``"""
|
|
96 if symbol == pyglet.window.key.ESCAPE:
|
|
97 self.pop_mode()
|
|
98
|
|
99 def on_last_mode_pop(self, mode):
|
|
100 """Hook executed when the last mode is popped from the manager.
|
|
101 When the last mode is popped from a window, an :meth:`on_close` event
|
|
102 is dispatched.
|
|
103
|
|
104 :param mode: The :class:`Mode` object just popped from the manager
|
|
105 """
|
|
106 self.dispatch_event('on_close')
|
|
107
|
|
108
|
|
109 class Mode(BaseMode):
|
|
110 """Application mode abstract base class using pyglet
|
|
111
|
|
112 Subclasses must implement the :meth:`step` method
|
|
113
|
|
114 :param step_rate: The rate of :meth:`step()` calls per second.
|
|
115
|
|
116 :param master_clock: The :class:`pyglet.clock.Clock` interface used
|
|
117 as the master clock that ticks the world's clock. This
|
|
118 defaults to the main pyglet clock.
|
|
119 """
|
|
120 clock = None
|
|
121 """The :class:`pyglet.clock.Clock` instance used as this mode's clock.
|
|
122 You should use this clock to schedule tasks for this mode, so they
|
|
123 properly respect when the mode is active or inactive
|
|
124
|
|
125 Example::
|
|
126
|
|
127 my_mode.clock.schedule_once(my_cool_function, 4)
|
|
128 """
|
|
129
|
|
130 def __init__(self, step_rate=60, master_clock=pyglet.clock,
|
|
131 clock_factory=pyglet.clock.Clock):
|
|
132 BaseMode.__init__(self)
|
|
133 self.step_rate = step_rate
|
|
134 self.time = 0.0
|
|
135 self.master_clock = master_clock
|
|
136 self.clock = clock_factory(time_function=lambda: self.time)
|
|
137 self.clock.schedule_interval(self.step, 1.0 / step_rate)
|
|
138
|
|
139 def on_activate(self):
|
|
140 """Being called when the Mode is activated"""
|
|
141 self.master_clock.schedule(self.tick)
|
|
142
|
|
143 def on_deactivate(self):
|
|
144 """Being called when the Mode is deactivated"""
|
|
145 self.master_clock.unschedule(self.tick)
|
|
146
|
|
147 def tick(self, dt):
|
|
148 """Tick the mode's clock.
|
|
149
|
|
150 :param dt: The time delta since the last tick
|
|
151 :type dt: float
|
|
152 """
|
|
153 self.time += dt
|
|
154 self.clock.tick(poll=False)
|
|
155
|
|
156 @abc.abstractmethod
|
|
157 def step(self, dt):
|
|
158 """Execute a timestep for this mode. Must be defined by subclasses.
|
|
159
|
|
160 :param dt: The time delta since the last time step
|
|
161 :type dt: float
|
|
162 """
|
|
163
|
|
164 class Multi(BaseMulti, Mode):
|
|
165 """A mode with multiple submodes. One submode is active at one time.
|
|
166 Submodes can be switched to directly or switched in sequence. If
|
|
167 the Multi is active, then one submode is always active.
|
|
168
|
|
169 Multis are useful when modes can switch in an order other than
|
|
170 a LIFO stack, such as in "hotseat" multiplayer games, a
|
|
171 "wizard" style ui, or a sequence of slides.
|
|
172
|
|
173 Note unlike a normal :class:`Mode`, a :class:`Multi` doesn't have it's own
|
|
174 :attr:`clock` and :attr:`step_rate`. The active submode's are used
|
|
175 instead.
|
|
176 """
|
|
177
|
|
178 def __init__(self, submodes):
|
|
179 BaseMulti.__init__(self, submodes)
|
|
180 self.time = 0.0
|
|
181
|
|
182
|
|
183 def _set_active_submode(self, submode):
|
|
184 BaseMulti._set_active_submode(self, submode)
|
|
185 self.master_clock = submode.master_clock
|
|
186 self.clock = submode.clock
|
|
187
|
|
188 def clear_subnode(self):
|
|
189 """Clear any subnmode data"""
|
|
190 BaseMulti.clear_subnode(self)
|
|
191 self.master_clock = None
|
|
192 self.clock = None
|
|
193
|
|
194 def tick(self, dt):
|
|
195 """Tick the active submode's clock.
|
|
196
|
|
197 :param dt: The time delta since the last tick
|
|
198 :type dt: float
|
|
199 """
|
|
200 self.time += dt
|
|
201 if self.active_submode is not None:
|
|
202 self.active_submode.clock.tick(poll=False)
|
|
203
|