Vega strike Python Modules doc  0.5.1
Documentation of the " Modules " folder of Vega strike
 All Data Structures Namespaces Files Functions Variables
GUI.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 """ """ # dummy docstring - do not remove
3 
4 """
5 
6 VegaStrik GUI abstraction layer
7 
8 This framework provides a set of classes that allow easy construction
9 of complex user interfaces based on the rather primitive base API
10 of the engine.
11 
12 The framework operates with a GUIRoot singleton, responsible of all
13 general bookkeeping an a portal for accessing most operations, a
14 set of GUIRoom objects contained within this singleton (only one
15 base can be active at any moment in the engine, so this GUIRoot
16 singleton needs not handle multiple concurrent bases), and several
17 GUIElement objects within each room.
18 
19 Hot spots, sprite locations and screen coordinates in general are
20 specified in a rather complex way through GUIRect. The complexity
21 is there for several reasons.
22 
23  * Vega Strike's base API has several coordinate systems for various
24  applications, where hot spots use one convention, sprites another,
25  etc... GUIRect provides a unified coordinate system which is far
26  easier to work with.
27  * Several uses of this framework involve careful placement of elements
28  overlaid on top of fixed-resolution bitmaps. When this is the case,
29  normalized coordinates tend to be a poor choice, being a lot easier
30  to work instead with integral pixel coordinates. GUIRect provides
31  an interface to specify coordinates in such a way, providing a
32  reference resolution (the underlying bitmap's resolution), and thus
33  resulting in pixel-perfect placement.
34  * Bases have configurable margins in VS, but some applications of the
35  base interface (ie: as a cutscene player) are better off disregarding
36  those margins. GUIRect thus provides all the math necessary to leave
37  those margins (by default), or to disregard them (only explicitly)
38  as easily as possible.
39  * Sometimes you'll want non-power-of-two bitmaps, but you won't want
40  to store them with npot dimensions because of compatibility reasons.
41  At those times, there's a handy specialization of GUIRect,
42  GUINPOTRect, which does the required math to properly render those
43  images by just specifying their actual dimensions. GUIRect is thus
44  a convenient interface for several alternatve (perhaps user-defined)
45  coordinate systems.
46 
47 See the module for details on each class, following is an overview of
48 the framework's usage:
49 
50 1. Initialization
51 =================
52 
53 The first step to take is the initialization of the framwork and the
54 GUIRoot singleton.
55 
56  import GUI
57  GUI.GUIInit()
58 
59 You can call it just like that (which will take screen dimensions,
60 margins and aspect ratio from the engine), or you can specify
61 those explicitly (see GUIInit's signature and inline documentation)
62 
63 2. Creating rooms
64 =================
65 
66 The second first thing to do, is create rooms. The first room you
67 create will be the initial room, so be attentive and be sure to
68 create them in the right order.
69 
70 GUIRoom **does not** create the underlying room. Since creation order
71 matters that much, room creation is the one thing that is usually
72 done by hand, using the raw base API. But GUIRoom does *administer* it.
73 
74  # Create rooms (intro, menu)
75  # Their order matter, here "preintro" is the initial room
76  room_preintro = Base.Room ('XXXPreIntro')
77  room_intro = Base.Room ('XXXIntro')
78  room_menu = Base.Room ('XXXMain_Menu')
79 
80  # Create GUIRoom wrappers
81  # Their order doesn't matter
82  guiroom_menu = GUI.GUIRoom(room_menu)
83  guiroom_intro = GUI.GUIRoom(room_intro)
84  guiroom_preintro = GUI.GUIRoom(room_preintro)
85 
86 Pay attention to the room's title, the "XXX" prefix makes the title
87 invisible, while omitting the prefix will render the room's title
88 on the status bar (usually the bottom edge).
89 
90 Sometimes you want this, sometimes you don't.
91 
92 2.1. Special rooms
93 ==================
94 
95 In the example above, we have to rooms, intro and preintro.
96 
97 Preintro would be a short movie or animation clip to show when the
98 game launches (e.g: "Vega Strike" logo). Intro would be the story's
99 intro cutscene to display when starting a new campaign.
100 
101 It is clear that rendering a complex cutscene with such basic elements
102 at our disposal (as given by the Base API) would be... cumbersome. So
103 instead of doing a CGI clip, we go for a theora movie.
104 
105 How would we play it?
106 
107 We could add a movie stream element in guiroom_preintro, but there's
108 already a specialized room type that handles such a common task (and
109 lets the user skip the movie, an hides the mouse, all the detail
110 usually forgotten at first glance).
111 
112 So scratch the above GUI.GUIRoom initialization for intro/preintro,
113 and better use the followin:
114 
115  # Set up preintro room
116  class PreIntroRoom(GUI.GUIMovieRoom):
117  def onSkip(self, button, params):
118  GUI.GUIMovieRoom.onSkip(self, button, params)
119  Base.SetDJEnabled(1)
120 
121  preintroroom = PreIntroRoom(room_preintro,
122  ( 'preintro.ogv',
123  GUI.GUIRect(0, 0, 1, 1, "normalized")),
124  guiroom)
125  preintroroom.setAspectRatio(16.0/9.0)
126  Base.SetDJEnabled(0)
127 
128  # Set up intro room
129  class IntroRoom(PreIntroRoom):
130  def onSkip(self, button, params):
131  PreIntroRoom.onSkip(self, button, params)
132  DoStartNewGame(self, params)
133 
134  introroom = IntroRoom(room_intro,
135  ( 'intro.ogv',
136  GUI.GUIRect(0, 0, 1, 1, "normalized")),
137  guiroom_menu)
138  introroom.setAspectRatio(16.0/9.0)
139  Base.SetDJEnabled(0)
140 
141 Here we have a lot of new stuff.
142 
143 First, we don't use the plain GUIMovieRoom, we create a derived
144 class to add behavior to the onSkip event. That's called "decorating"
145 the class. In the case of the preintro, we have to enable the
146 music DJ after the movie clip has finished, so we can hear music
147 in the main menu. In the case of the intro room, after the intro
148 is done playing we must... er... actually start the campaign.
149 
150 Then we create such a room. The first argumen tells it which
151 room number to bind with (room_preintro). The second one is
152 a "sprite definition" (a tuple) containing the movie's path
153 and the GUIRect where we want the movie - we give it fullscreen
154 with (0,0) for top-left an (1,1) for width-height (in normalized
155 screen coodinates). The third and last argument is the next room.
156 Since movie clips are usually transitions, GUIMovieRoom accepts
157 a "next room" and it will automatically switch to that room
158 when the movie's done playing.
159 
160 So the initial room will be the preintro room, the preintro will
161 start playing immediately, and when done "onSkip" will be called,
162 which will call the base implementation (that switches to the next
163 room) and then re-enable the music DJ which we disabled with SetDJEnabled(0).
164 
165 Lets leave the intro room as an excercise to the reader ;-)
166 
167 But notice the aspect ratio stuff. Movies have an aspect ratio,
168 and GUIMovieRoom knows about that, and will adjust the location we've
169 given it so that aspect ratio is preserved. Ain't it cool?
170 
171 3. Adding elements
172 ==================
173 
174 We have a menu, so we need stuff on it. We have tons of prebuilt
175 elements, or widgets, at our disposal.
176 
177 All elements are constructed by giving their constructors both
178 a room and an element id. The element will automatically register
179 with the given room and from then on you can find the element by
180 its id if you need to.
181 
182 Most elements take more parameters, but those two are universal.
183 
184 3.1. Adding pictures
185 ====================
186 
187 We want pretty menues. We want a background. Which is to say,
188 a static image on the screen. Straigforward task:
189 
190  # Create background
191  GUI.GUIStaticImage(guiroom, 'background',
192  ( 'interfaces/main_menu/menu.spr',
193  GUI.GUIRect(0, 0, 1024, 768, "pixel", (1024,768)) ))
194 
195 GUIStaticImage takes, again, a sprite efinition. This time, we use
196 a "pixel" coordinate system, giving it (0,0) top-left and (1024x768)
197 dimensions to our sprite. The last (1024,768) tuple is the "reference"
198 dimensions, which is the "virtual screen resolution".
199 
200 So (0,0,1024,768,"pixel",(1024,768))
201 is equivalent to (0,0,1,1,"normalized").
202 
203 We could have used that. But when coordinates have to match a
204 specific background image to look right, this "pixel" coordinate system
205 is preferred, since it always aligns right.
206 
207 3.2. Adding text
208 ================
209 
210 Now we want some text in the credits room (which we didn't show).
211 
212  text_loc = GUI.GUIRect(408,8,300,50,"pixel",(1024,768))
213  GUI.GUIStaticText(credits_guiroom, 'mytitle', credits_title, text_loc, GUI.GUIColor.white())
214 
215 Now you can see the usefulness of GUIRect and its "pixel" mode.
216 We give the text line a very precise location (so that it matches the room's background).
217 
218 Text elements can be given a color, we use white, but we could have specified an RGB value or even
219 an RGBA (with transparency) one. We can even specify a font size (not sure it works right all the time,
220 but it eventually should be fixed) and a background color (again, potentially with transparency).
221 
222 Don't think for a moment this maps directly to Base.TextBox, the base API is rather picky (and buggy),
223 and GUIStaticText handles all the tricks needed to get this flexibility.
224 
225 3.3. Linking rooms
226 ==================
227 
228 Ok, we have a main menu, we have an "intro" room that ends up starting a campaign.
229 
230 Now we need a "new game" button. Right?
231 
232  # New game
233  sprite_loc = GUI.GUIRect(48,224,128,32,"pixel",(1280,1024))
234  sprite = {
235  '*':None,
236  'down' : ( 'interfaces/main_menu/new_button_pressed.spr', sprite_loc ) }
237  btn = GUI.GUIButton(guiroom, 'XXXNew Game','New_Game',sprite,sprite_loc,'enabled',StartNewGame)
238 
239 Ok, buttons are more complex, aren't they?
240 
241 Lets start with the sprite. It's gone from a simple tuple, to a dictionary. Why?
242 
243 Well, buttons change states. They can be neutral, disabled, hot (mouse over), down (pressed).
244 The given mapping here maps those states to a sprite definition (like we've seen with GUIStaticText),
245 with the special state "*", which applies to any state not explicitly given. If the sprite definition
246 assigned to a state is None, then the button is transparent there.
247 
248 Why would we wan it transparent? Well, in our case, the background picture already has the button
249 pre-rendered in. No need to render anything on top. When the button is pressed, we want to overlay
250 a highlighted version of the button, so we specify that sprite for 'down'.
251 
252 Confusingly enough, GUIButton takes the button's "tooltip" or "title" before its element ID.
253 Again, the "XXX" prefix makes the tooltip invisible (it's still required though by the engine).
254 it also takes the initial state ('enabled'), and a callable, an action to be performed when clicked.
255 
256  # Base music
257  plist_menu=VS.musicAddList('maintitle.m3u')
258 
259  def DoStartNewGame(self,params):
260  ShowProgress.activateProgressScreen('loading',3)
261  VS.loadGame(VS.getNewGameSaveName())
262  enterMainMenu(self,params)
263 
264  def StartNewGame(self,params):
265  Base.SetCurRoom(introroom.getIndex())
266  Base.SetDJEnabled(0)
267 
268  def enterMainMenu(self,params):
269  global plist_menu
270  VS.musicPlayList(plist_menu)
271 
272 The callable must take two arguments, "self", which will be the element clicked, and "params", which
273 will have other info, like which button was used and exact mouse coordinates, modifier keys and whatnot.
274 
275 See, when the button is pressed, StartNewGame sends the user to the intro and disables the music DJ
276 (we only want the movie's music playing). Then when the movie is done playing, remember, DoStartNewGame
277 will do some stuff, among which is loading the new campaign.
278 
279 There's also a very straightforward way of linking rooms with GUIRoomButton(from,to,title,id,...):
280 
281  sprite_loc = GUI.GUIRect(48,510,92,32,"pixel",(1280,1024))
282  sprite = {
283  '*':None,
284  'down' : ( 'interfaces/main_menu/credits_button_pressed.spr', sprite_loc ) }
285  GUI.GUIRoomButton(guiroom, credits_guiroom, 'XXXShow Credits','Show_Credits',sprite,sprite_loc,clickHandler=enterCredits)
286 
287 See how you can even do extra stuff in a "clickHandler"
288 
289  plist_credits=VS.musicAddList('maincredits.m3u')
290 
291  def enterCredits(self,params):
292  global plist_credits
293  VS.musicPlayList(plist_credits)
294 
295 
296 4. Advanced widgets
297 ===================
298 
299 There are even more advanced widgets than images an push buttons.
300 
301 There's GUICheckButton s, which toggle between "checked" and "unchecked"
302 states when clicked, and its cousin the GUIRadioButton, which when checked
303 also uncheck all other radio buttons within their "radio group".
304 
305 There's a GUISimpleListPicker, used for instance to build the
306 game loading interface in the main menu, and a lifesaver in many
307 situations. You manipulate its content by adding/removing
308 GUISimpleListPicker.listitem instances to its "items" attribute,
309 as easy as that.
310 
311 If you want scrolling buttons for the list picker. you must code
312 them yourself (using GUIButton), but it's certainly not difficult
313 since you can easily manipulate the list with its
314 viewMove and pageMove member functions.
315 
316 """
317 
318 import Base
319 import VS
320 from XGUIDebug import *
321 
322 GUIRootSingleton = None
323 _doWhiteHack = 0
324 _GUITraceLevel = TRACE_VERBOSE
325 
326 """----------------------------------------------------------------"""
327 """ """
328 """ GUIRoot - root management interface for the GUI framework. """
329 """ Also acts as singleton "glue" since python snippets can't """
330 """ hold any kind of state (that's a To-Do) """
331 """ """
332 """----------------------------------------------------------------"""
333 
334 class GUIRoot:
335  """global GUI setup"""
336  def __init__(self,screenX=None,screenY=None,marginX=None,marginY=None,aspect=None):
337  self.deregisterAllObjects()
338  self.deregisterAllRooms()
339  if not screenX or not screenY:
340  # Get screen dimensions from config
341  screenX = int(VS.vsConfig("graphics","base_max_width",
342  VS.vsConfig("graphics","x_resolution","0")))
343  screenY = int(VS.vsConfig("graphics","base_max_height",
344  VS.vsConfig("graphics","y_resolution","0")))
345  aspect = float(VS.vsConfig("graphics","aspect","0"))
346  self.setScreenDimensions(screenX,screenY)
347  self.aspect = aspect or (screenX * 1.0 / screenY)
348  if (marginX == None):
349  marginX = 0.00
350  if (marginY == None):
351  marginY = 0.00
352  self.setScreenMargins(marginX,marginY)
353  self.needRedraw = {}
354  self.modalElement = None
355  self.keyTarget = None
356  Base.GlobalKeyPython('#\nfrom GUI import GUIRootSingleton\nGUIRootSingleton.keyEvent()\n')
357 
358  def setScreenDimensions(self,screenX,screenY):
359  self.screenX=screenX
360  self.screenY=screenY
361  self.broadcastMessage('changedScreenDimensions', {'screenX':screenX,'screenY':screenY} )
362 
364  return (self.screenX,self.screenY)
365 
366  def setScreenMargins(self,marginX,marginY):
367  self.marginX=marginX
368  self.marginY=marginY
369  self.broadcastMessage('changedScreenMargins', {'marginX':marginX,'marginY':marginY} )
370 
372  return self.aspect
373 
374  def setScreenAspectRatio(self, aspect):
375  self.aspect = aspect
376 
377  def getScreenMargins(self):
378  return (self.marginX,self.marginY)
379 
380  def dispatchMessage(self,id,message,params):
381  trace(_GUITraceLevel, "::: calling GUI.GUIRoot.dispatchMessage(%s, %s, %s) :::" %(id,message,params))
382  if id in self.objects:
383  self.objects[id][1].onMessage(message,params)
384  else:
385  trace(_GUITraceLevel + 1, 'WARNING! - gui.py - GUIRoot::dispatchMessage(): Object id "' + str(id) + '" not found\n')
386 
387  def broadcastMessage(self,message,params):
388  trace(_GUITraceLevel, "::: calling GUI.GUIRoot.broadcastMessage(%s,%s)" %(message,params) )
389  for i in self.objects.keys():
390  self.objects[i][1].onMessage(message,params)
391 
392  def broadcastRoomMessage(self,roomindex,message,params):
393  trace(_GUITraceLevel, "::: calling GUI.GUIRoot.broadcastRoomMessage(%s,%s,%s)" %(roomindex,message,params) )
394  for i in self.objects.keys():
395  if self.objects[i][0]==roomindex:
396  self.objects[i][1].onMessage(message,params)
397 
398  def registerObject(self,room,object):
399  id = self.nextId
400  self.objects[id] = (room,object)
401  self.nextId = self.nextId + 1
402  return id
403 
404  def keyEvent(self):
405  eventdata = Base.GetEventData();
406  if self.keyTarget is not None:
407  eventTarget = self.keyTarget
408  else:
409  eventTarget = self.rooms.get(Base.GetCurRoom())
410  if eventTarget is not None:
411  if eventdata['type'] == 'keyup' and hasattr(eventTarget,'keyUp'):
412  eventTarget.keyUp(eventdata['key'])
413  if eventdata['type'] == 'keydown' and hasattr(eventTarget,'keyDown'):
414  eventTarget.keyDown(eventdata['key'])
415 
416  def registerRoom(self,room):
417  self.rooms[room.getIndex()] = room
418 
419  def deregisterObject(self,id):
420  self.objects.erase(id)
421 
423  self.objects = {}
424  self.nextId = 0
425 
427  self.rooms = {}
428 
429  def redrawIfNeeded(self):
430  trace(_GUITraceLevel, "::: calling GUI.GUIRoot.redrawIfNeeded()" )
431  for i in self.rooms:
432  self.rooms[i].redrawIfNeeded()
433 
434  def getRoomById(self,id):
435  return self.rooms.get(id,None)
436 
437 
438 def GUIInit(screenX=None,screenY=None,marginX=None,marginY=None,**kwargs):
439  """ GUIInit sets up the GUIRootSingleton variable, which is used to store the state """
440  global GUIRootSingleton
441  GUIRootSingleton = GUIRoot(screenX,screenY,marginX,marginY,**kwargs)
442 
443 
444 """----------------------------------------------------------------"""
445 """ """
446 """ GUIRect - defines rectangles on the screen. """
447 """ By popular demand, those can be defined in a number of """
448 """ modes: pixel, normalized (and variantes). """
449 """ """
450 """----------------------------------------------------------------"""
451 
452 class GUIRect:
453  """A rectangle on the screen """
454  """ref: may be (room), (screenx,screeny) or (screenx,screeny,marginx,marginy) """
455  """ (first takes the dimensions from the room, the next from the tuple itself"""
456  """ just the dimensions but margins and stuff from the root, and the last """
457  """ takes everything from the tuple) """
458  def __init__(self,x,y,wid,hei,mode="pixel",ref=None):
459  self.x = x
460  self.y = y
461  self.w = wid
462  self.h = hei
463  self.ref = ref
464  self.mode = mode
465 
466  def __repr__(self):
467  return repr((self.x,self.y,self.w,self.h,self.mode,self.ref))
468  def __str__(self):
469  return str((self.x,self.y,self.w,self.h,self.mode,self.ref))
470 
471  def getNormalXYWH(self):
472  """ returns (x,y,w,h) - x/y is top-left - in Base coordinates """
473 
474  """ Allow two kinds of screen dimension overrides, for design convenience """
475  """ a) (screenx,screeny) - margins set as per root settings """
476  """ b) (screenx,screeny,marginx,marginy) - set both dimensions and margin manually """
477  if not self.ref or (type(self.ref)!=tuple) or ((len(self.ref)!=1)and(len(self.ref)!=2)and(len(self.ref)!=4)):
478  (screenX,screenY) = GUIRootSingleton.getScreenDimensions()
479  (marginX,marginY) = GUIRootSingleton.getScreenMargins()
480  else:
481  if len(self.ref)==4:
482  (screenX,screenY,marginX,marginY) = self.ref
483  elif len(self.ref)==2:
484  (screenX,screenY) = self.ref
485  (marginX,marginY) = GUIRootSingleton.getScreenMargins()
486  else:
487  (screenX,screenY) = self.ref[0].getScreenDimensions()
488  (marginX,marginY) = self.ref[0].getScreenMargins()
489  if (self.mode=="pixel"):
490  """ pixel coordinates relative to current screen settings
491  to translate (0,0) (screenX,screenY) to (-1,1) (1,-1)
492  x = 2*xi/screenX - 1
493  y = 1 - 2*yi/screenY
494  """
495  return ( (2.0 * self.x / screenX - 1.0)*(1.0-marginX) , \
496  (-2.0 * self.y / screenY + 1.0)*(1.0-marginY) , \
497  (2.0 * self.w / screenX * (1.0-marginX)) , \
498  (2.0 * self.h / screenY * (1.0-marginY)) \
499  )
500  elif (self.mode=='normalized_biased_scaled'):
501  """ direct coordinates: top-left = (-1,+1), bottom-right = (+1,-1) - margins WILL NOT be applied """
502  return (self.x,self.y,self.w,self.h)
503  elif (self.mode=='normalized_biased'):
504  """ direct coordinates: top-left = (-1,+1), bottom-right = (+1,-1) - margins WILL be applied """
505  return (self.x*(1.0-marginX),self.y*(1.0-marginY),self.w*(1.0-marginX),self.h*(1.0-marginY))
506  elif (self.mode=='normalized_scaled'):
507  """ normalized coordinates: top-left = (0,0), bottom-right = (1,1) - margins WILL NOT be applied """
508  return ((2.0*self.x-1.0),(-2.0*self.y+1.0),2.0*self.w,2.0*self.h)
509  elif (self.mode=='normalized'):
510  """ normalized coordinates: top-left = (0,0), bottom-right = (1,1) - margins WILL be applied """
511  return ((2.0*self.x-1.0)*(1.0-marginX),(-2.0*self.y+1.0)*(1.0-marginY),2.0*self.w*(1.0-marginX),2.0*self.h*(1.0-marginY))
512  else:
513  trace(_GUITraceLevel, "WARNING! - gui.py - GUIRect::getNormalizedCoords(): unknown coordinate mode\n")
514 
515  def getNormalCenter(self):
516  aux = self.getNormalXYWH()
517  return ( aux[0]+aux[2]/2, aux[1]-aux[3]/2 )
518 
519  def getNormalTL(self):
520  """ returns TOP-LEFT coordinate pair """
521  aux = self.getNormalXYWH()
522  return ( aux[0], aux[1] )
523 
524  def getNormalBR(self):
525  """ returns BOTTOM-RIGHT coordinate pair """
526  aux = self.getNormalXYWH()
527  return ( aux[0]+aux[2], aux[1]-aux[3] )
528 
529  def getNormalWH(self):
530  """ returns WIDTH-HEIGHT dimensions """
531  aux = self.getNormalXYWH()
532  return ( aux[2], aux[3] )
533 
534  #
535  # The Base module uses inconsistent coordinates.
536  # For the Link/Comp/Python methods, it is the bottom left.
537  # For the Texture methods, it is the center point.
538  # For the TextBox method, it is the top left?
539  #
540  def getHotRect(self):
541  """ (BOTTOM, LEFT, WIDTH, HEIGHT) as needed by Base hotspots """
542  aux = self.getNormalXYWH()
543  return ( aux[0], aux[1]-aux[3], aux[2], aux[3] )
544 
545  def getSpriteRect(self):
546  """ (CenterX, CenterY, WIDTH, -HEIGHT) as needed by Base sprites """
547  aux = self.getNormalXYWH()
548  return ( aux[0]+aux[2]/2, aux[1]-aux[3]/2, aux[2], -aux[3] )
549 
550  def getTextRect(self):
551  """ (TOP, LEFT, WIDTH, HEIGHT) as needed by Base textboxes """
552  return self.getNormalXYWH()
553 
554 
555 """----------------------------------------------------------------"""
556 """ """
557 """ GUINPOTRect - defines rectangles on the screen. """
558 """ Adjusts a rectangle so that a Power-Of-Two texture """
559 """ with Non-Power-Of-Two contents (top/left aligned) """
560 """ displays its contents in the specified area. """
561 """ """
562 """----------------------------------------------------------------"""
563 
565  def __init__(self,x,y,wid,hei,npotw,npoth,mode="pixel"):
566  """ Adjust """
567  potw = 1
568  poth = 1
569  while potw < npotw:
570  potw = potw * 2
571  while poth < npoth:
572  poth = poth * 2
573 
574  """ Store original parameters """
575  self.potw = potw
576  self.poth = poth
577  self.npotw = npotw
578  self.npoth = npoth
579 
580  """ Initialized base class """
581  GUIRect.__init__(self,x,y,wid*potw/npotw,hei*poth/npoth,mode)
582 
583  def __repr__(self):
584  return GUIRect.__repr__(self)
585 
586  def __str__(self):
587  return GUIRect.__str__(self)
588 
589 
590 """----------------------------------------------------------------"""
591 """ """
592 """ GUIColor - an RGBA color representation """
593 """ values range from 0.0 to 1.0. """
594 """ """
595 """----------------------------------------------------------------"""
596 class GUIColor:
597  def __init__(self,r,g,b,a=1.0):
598  self.r = r
599  self.g = g
600  self.b = b
601  self.a = a
602 
603  def __repr__(self):
604  return "RGB(%f,%f,%f,%f)" % (self.r,self.g,self.b,self.a)
605 
606  def __str__(self):
607  return "RGB(%.2f,%.2f,%.2f,%.2f)" % (self.r,self.g,self.b,self.a)
608 
609  def getRGB(self):
610  # create a tuple
611  t = (self.r, self.g, self.b)
612  return (t)
613 
614  def getAlpha(self):
615  # create a tuple
616  return (self.a)
617 
618  @staticmethod
619  def white():
620  return GUIColor(1.0,1.0,1.0)
621 
622  @staticmethod
623  def black():
624  return GUIColor(0.0,0.0,0.0)
625 
626  @staticmethod
627  def clear():
628  return GUIColor(0.0,0.0,0.0,0.0)
629 
630 
631 
632 """----------------------------------------------------------------"""
633 """ """
634 """ GUIRoom - interface for rooms. Lets you define the layout of """
635 """ a room in high-level terms, and implements serialization """
636 """ of room layouts. """
637 """ """
638 """----------------------------------------------------------------"""
639 
640 class GUIRoom:
641  def __init__(self,index):
642  self.index = index
643  self.needRedraw = 0
644  self.needPreserveZ = 0
645  self.screenDimensions = None
646  self.screenMargins = None
647  # add this GUIRoom to GUIRootSingleton
648  GUIRootSingleton.registerRoom(self)
649 
650  def __repr__(self):
651  return "<room %r>" % self.index
652 
653  def __str__(self):
654  return "Room %s" % self.index
655 
656  def getIndex(self):
657  return self.index
658 
659  def redrawIfNeeded(self):
660  if self.needRedraw != 0:
661  self.redraw()
662 
663  def redraw(self):
664  if self.needPreserveZ != 0:
665  """ Sadly, this mess is required to preserve z-order - some day we'll have a proper redraw in place """
666  GUIRootSingleton.broadcastRoomMessage(self.getIndex(),'undraw',None)
667  GUIRootSingleton.broadcastRoomMessage(self.getIndex(),'draw',None)
668  else:
669  GUIRootSingleton.broadcastRoomMessage(self.getIndex(),'redraw',None)
670  self.needRedraw = 0
671  self.needPreserveZ = 0
672 
673  def notifyNeedRedraw(self,preserveZ=1):
674  self.needRedraw = 1
675  if preserveZ != 0:
676  self.needPreserveZ = 1
677 
679  global GUIRootSingleton
680  if self.screenDimensions:
681  return self.screenDimensions
682  else:
683  return GUIRootSingleton.getScreenDimensions()
684 
685  def getScreenMargins(self):
686  global GUIRootSingleton
687  if self.screenDimensions:
688  return self.screenMargins
689  else:
690  return GUIRootSingleton.getScreenMargins()
691 
692  def setScreenDimensions(self,dimensions):
693  global GUIRootSingleton
694  self.screenDimensions = dimensions
695  dims = self.getScreenDimensions()
696  GUIRootSingleton.broadcastRoomMessage(self.index,'changedScreenDimensions', {'screenX':dims[0],'screenY':dims[1]} )
697 
698  def setScreenMargins(self,margins):
699  global GUIRootSingleton
700  self.screenMargins = margins
701  mars = self.getScreenMargins()
702  GUIRootSingleton.broadcastMessage(self.index,'changedScreenMargins', {'marginX':mars[0],'marginY':mars[1]} )
703 
704 
705 """----------------------------------------------------------------"""
706 """ """
707 """ GUIElement - it's the base class for all GUI elements """
708 """ Implements standard methods and message handling """
709 """ """
710 """----------------------------------------------------------------"""
711 
712 
714  def __init__(self,room,owner=None):
715  self.room = room
716  self.owner = owner
717  self.visible = 1
719  self.id = GUIRootSingleton.registerObject(room.getIndex(),self)
720 
721  def __str__(self):
722  return "GUIElement: room = %s, visible = %s, redrawPreservesZ = %s, id = %s" %(self.room, self.visible, self.redrawPreservesZ, self.id)
723 
724  def __repr__(self):
725  # this needs work
726  # would like it to say <GUI.GUIElementSubclass a = 1, b= 2, ...> for any subclass of GUIElement
727  r = "<%s: " %(type(self)) #, self.room, self.visible, self.redrawPreservesZ, self.id)
728  for key in dir(self):
729  try:
730  value = getattr(self, key)
731  if callable(value):
732  value = ''
733  except:
734  value = ''
735  if value != '':
736  r = r + "%s = %s, " %(key, value)
737  r = r + ">"
738  return r
739 
740  def show(self):
741  self.visible=1
742  self.notifyNeedRedraw()
743 
744  def hide(self):
745  """ Usually does not need a full redraw """
746  self.visible=0
747  self.undraw()
748 
749  def notifyNeedRedraw(self,preserveZ=1):
750  self.room.notifyNeedRedraw(preserveZ)
751 
752  def draw(self):
753  """ Intentionally blank """
754 
755  def undraw(self):
756  """ Intentionally blank """
757 
758  def redraw(self):
759  """ override to preserve Z-order if possible """
760  """ (if so, mark self.redrawPreservesZ) """
761  self.undraw()
762  self.draw()
763 
764  def onMessage(self,message,params):
765  """Standard message dispatch"""
766  if (message=='click'):
767  self.onClick(params)
768  elif (message=='show'):
769  self.onShow(params)
770  elif (message=='hide'):
771  self.onHide(params)
772  elif (message=='draw'):
773  self.onDraw(params)
774  elif (message=='undraw'):
775  self.onUndraw(params)
776  elif (message=='redraw'):
777  self.onRedraw(params)
778  else:
779  return False
780  return True
781 
782 
783  def onClick(self,params):
784  """ Intentionally blank """
785 
786  def onShow(self,params):
787  self.show()
788 
789  def onHide(self,params):
790  self.hide()
791 
792  def onDraw(self,params):
793  if self.visible:
794  self.draw()
795 
796  def onUndraw(self,params):
797  if self.visible:
798  self.undraw()
799 
800  def onRedraw(self,params):
801  """ WARNING! does not necessarily preserve z-order, so only issue if you don't care about z-order """
802  if self.visible:
803  self.redraw()
804 
805  def focus(self,dofocus):
806  if dofocus:
807  GUIRootSingleton.keyTarget=self
808  elif GUIRootSingleton.keyTarget==self:
809  GUIRootSingleton.keyTarget=None
810 
811  def setModal(self,modal):
812  if modal:
813  GUIRootSingleton.modalElement=self
814  GUIRootSingleton.broadcastMessage('disable',{})
815  if 'enable' in dir(self):
816  self.enable()
817  GUIRootSingleton.broadcastRoomMessage('redraw',{})
818  elif GUIRootSingleton.modalElement == self:
819  GUIRootSingleton.modalElement=None
820  GUIRootSingleton.broadcastMessage('enable',{})
821 
822  def isInteractive(self):
823  return GUIRootSingleton.modalElement==self or GUIRootSingleton.modalElement==None
824 
825 
826 """----------------------------------------------------------------"""
827 """ """
828 """ GUIGroup - allows grouping of elements. Notice that GUIGroup """
829 """ will relay messages to its attached elements, and will """
830 """ keep them alive (by holding references to them) """
831 """ """
832 """----------------------------------------------------------------"""
833 
834 
836  def __init__(self,room,**kwargs):
837  GUIElement.__init__(self,room,**kwargs)
838  self.children = []
839 
840  def show(self):
841  for i in self.children:
842  i.show()
843  GUIElement.show(self)
844 
845  def hide(self):
846  for i in self.children:
847  i.hide()
848  GUIElement.hide(self)
849 
850  def draw(self):
851  """ Intentionally blank """
852 
853  def undraw(self):
854  """ Intentionally blank """
855 
856  def redraw(self):
857  """ Intentionally blank """
858 
859  def onMessage(self,message,params):
860  for i in self.children:
861  i.onMessage(message,params)
862 
863 
864 
865 
866 """----------------------------------------------------------------"""
867 """ """
868 """ GUIStaticImage - a non-interactive image """
869 """ """
870 """----------------------------------------------------------------"""
871 
873  def __init__(self,room,index,sprite,**kwarg):
874  """ Sprite must be a tuple of the form: """
875  """ ( path , location ) """
876  """ with 'location' being a GUIRect """
877  """ NOTE: It is legal to set sprite to None """
878  """ This allows subclassing to create """
879  """ non-static elements """
880 
881  GUIElement.__init__(self,room,**kwarg)
882 
883  self.sprite=sprite
884  self.index=index
885  self.spritestate=0
887 
888  def spriteIsValid(self):
889  return ( self.sprite
890  and type(self.sprite) is tuple
891  and len(self.sprite)>=2
892  and self.sprite[0] is not None
893  and self.sprite[1] is not None )
894 
895  def draw(self):
896  """ Creates the element """
897 # if (self.visible == 0):
898 # print "::: GUIStaticImage draw called when self.visible == 0"
899  if (self.visible == 1) and (self.spritestate==0) and self.spriteIsValid():
900  (x,y,w,h) = self.sprite[1].getSpriteRect()
901  Base.Texture(self.room.getIndex(),self.index,self.sprite[0],x,y)
902  Base.SetTextureSize(self.room.getIndex(),self.index,w,h) # override spr file data... it's hideously unmantainable...
903  self.spritestate=1
904 
905  def undraw(self):
906  """ Hides the element """
907  if self.spritestate==1:
908  Base.EraseObj(self.room.getIndex(),self.index)
909  self.spritestate=0
910 
911  def redraw(self):
912  """ Sets a new image """
913  if self.spritestate!=1:
914  self.draw()
915  elif self.spriteIsValid():
916  (x,y,w,h) = self.sprite[1].getSpriteRect()
917  Base.SetTexture(self.room.getIndex(),self.index,self.sprite[0]);
918  Base.SetTexturePos(self.room.getIndex(),self.index,x,y);
919  Base.SetTextureSize(self.room.getIndex(),self.index,w,h);
920  else:
921  # Avoid calling subclass implementations
922  GUIStaticImage.undraw(self)
923 
924  def setSprite(self,newsprite):
925  if self.sprite != newsprite:
926  self.sprite = newsprite
927  self.notifyNeedRedraw()
928 
929 
930 
931 """----------------------------------------------------------------"""
932 """ """
933 """ GUIStaticText - a non-interactive text box """
934 """ """
935 """----------------------------------------------------------------"""
936 
938  def __init__(self,room,index,text,location,color,fontsize=1.0,bgcolor=None,**kwarg):
939  GUIElement.__init__(self,room,**kwarg)
940 
941  self.index=index
942  self.textstate = 0
943  self.location = location
944  self.color = color
945  if (bgcolor != None):
946  self.bgcolor = bgcolor
947  else:
948  self.bgcolor = GUIColor.clear()
949  self.fontsize = fontsize
950  self.text = text
951 
952  def draw(self):
953  """ Creates the element """
954  if (self.textstate==0):
955  (x,y,w,h) = self.location.getTextRect()
956  # the dimensions for Base.TextBox are all screwed up. the (width height multiplier) value is actually (x2, y2, unused)
957  # and the text is always the same size, regardless of how the height or multiplier values get set
958  Base.TextBox(self.room.getIndex(), str(self.index), str(self.text), x, y, (x + w, y - h, self.fontsize), self.bgcolor.getRGB(), self.bgcolor.getAlpha(), self.color.getRGB())
959  if _doWhiteHack != 0:
960  """ ugly hack, needed to counter a stupid bug """
961  Base.TextBox(self.room.getIndex(),str(self.index)+"_white_hack","", -100.0, -100.0, (0.01, 0.01, 1), (0,0,0), 0, GUIColor.white().getRGB())
962  self.textstate=1
963 
964  def undraw(self):
965  """ Hides the element """
966  if self.textstate==1:
967  Base.EraseObj(self.room.getIndex(),str(self.index))
968  if _doWhiteHack != 0:
969  Base.EraseObj(self.room.getIndex(),self.index+"_white_hack")
970  self.textstate=0
971 
972  def setText(self,newtext):
973  self.text = newtext
974  if self.textstate==1:
975  Base.SetTextBoxText(self.room.getIndex(),str(self.index),str(self.text))
976 
977  def setColor(self,newcolor):
978  self.color = newcolor
979  self.notifyNeedRedraw()
980 
981  def getText(self):
982  return self.text
983 
984  def getColor(self):
985  return self.color
986 
987 
988 """----------------------------------------------------------------"""
989 """ """
990 """ GUILineEdit - an interactive text box """
991 """ """
992 """----------------------------------------------------------------"""
993 
994 #todo: add optional frame
995 
997 
998  def focus_text(self,button,params):
999  print 'focusing',self.index
1000  self.focus(True)
1001  def __init__(self,action,room,index,text,location,color,fontsize=1.0,bgcolor=None,focusbutton=None,**kwarg):
1002  GUIGroup.__init__(self,room,**kwarg)
1003  self.text = GUIStaticText(room,index,' '+text+'-',location,color,fontsize,bgcolor,**kwarg)
1004  self.children.append(self.text)
1005  if focusbutton:
1006  self.focusbutton = GUIButton(room,"XXXFocus Element",str(index)+'focus',{'*':None},location,
1007  clickHandler=self.focus_text)
1008  self.children.append(self.focusbutton)
1009  (x,y,w,h) = location.getNormalXYWH()
1010  (uw,uh) = GUIRect(0,0,10,10).getNormalWH()
1011  self.index=index
1012  Base.TextBox(room.getIndex(), str(self.index)+'line1', '---', x, y, (x+w, y+uh, 1),
1013  color.getRGB(), color.getAlpha(), color.getRGB())
1014  Base.TextBox(room.getIndex(), str(self.index)+'line2', '!', x, y, (x-uw, y+h, 1),
1015  color.getRGB(), color.getAlpha(), color.getRGB())
1016  Base.TextBox(room.getIndex(), str(self.index)+'line3', '---', x, y-h, (x+w, y+uh, 1),
1017  color.getRGB(), color.getAlpha(), color.getRGB())
1018  Base.TextBox(room.getIndex(), str(self.index)+'line4', '!', x+w, y, (x+uw, y+h, 1),
1019  color.getRGB(), color.getAlpha(), color.getRGB())
1020  if _doWhiteHack != 0:
1021  """ ugly hack, needed to counter a stupid bug """
1022  Base.TextBox(self.room.getIndex(),str(self.index)+"_white_hack","", -100.0, -100.0, (0.01, 0.01, 1), (0,0,0), 0, GUIColor.white().getRGB())
1023  self.action=action
1024  self.draw()
1025  self.canceled = False
1026 
1027  def getText(self):
1028  return self.text.getText()[1:-1]
1029 
1030  def undraw(self):
1031  Base.EraseObj(self.room.getIndex(),str(self.index)+"line1")
1032  Base.EraseObj(self.room.getIndex(),str(self.index)+"line2")
1033  Base.EraseObj(self.room.getIndex(),str(self.index)+"line3")
1034  Base.EraseObj(self.room.getIndex(),str(self.index)+"line4")
1035  if _doWhiteHack != 0:
1036  Base.EraseObj(self.room.getIndex(),self.index+"_white_hack")
1037  GUIGroup.undraw(self)
1038 
1039  def keyDown(self,key):
1040  print "got key: %i" % key
1041  if key == 13 or key == 10: #should be some kind of return
1042  self.action(self)
1043  elif key == 27: #escape is always 27, isn't it?
1044  self.canceled = True
1045  self.action(self)
1046  elif key == 127 or key == 8: #avoid specifying the platform by treating del and backspace alike
1047  self.text.setText(' ' + self.getText()[:-1] + '-')
1048  elif key<127 and key>0:
1049  try:
1050  self.text.setText(' '+self.getText() + ('%c' % key) + '-');
1051  except:
1052  print "Character value too high "+str(key)
1053  #self.notifyNeedRedraw()
1054 
1055 """------------------------------------------------------------------"""
1056 """ """
1057 """ GUITextInputDialog - a little dialog in which you can enter text """
1058 """ """
1059 """------------------------------------------------------------------"""
1060 
1061 
1063  def __init__(self,room,index,location,text,action,color,**kwargs):
1064  GUIGroup.__init__(self,room,kwargs)
1065  self.children.append(GUILineEdit(self.editcallback,room,index,text,location,color))
1066 
1067 
1068 
1069 
1071  def __init__(self,room,linkdesc,index,hotspot,**kwarg):
1072  self.linkdesc=linkdesc
1073  self.index=index
1074  self.hotspot=hotspot
1075  self.linkstate=0
1076  GUIElement.__init__(self,room,**kwarg)
1077 
1078  def draw(self):
1079  if self.visible and self.linkstate==0:
1080  (x,y,w,h) = self.hotspot.getHotRect()
1081  Base.Python(self.room.getIndex(),self.index,x,y,w,h,self.linkdesc,'#',True)
1082  self.linkstate=1
1083 
1084  def undraw(self):
1085  if self.linkstate==1:
1086  self.linkstate=0
1087  Base.EraseLink(self.room.getIndex(),self.index)
1088 
1089  def redraw(self):
1090  self.undraw()
1091  self.draw()
1092 
1093 
1094 """----------------------------------------------------------------"""
1095 """ """
1096 """ GUIButton - a button you can click on. """
1097 """ """
1098 """----------------------------------------------------------------"""
1099 
1101  def __init__(self,room,linkdesc,index,spritefiles,hotspot,initialstate='enabled',clickHandler=None,textcolor=GUIColor.white(),textbgcolor=None,textfontsize=1.0,**kwarg):
1102  """ Initializes the button (but does not draw it; use drawobjs()) """
1103  """ spritefiles: a dictionary, mapping states to sprites """
1104  """ 'enabled' : normal, default, enabled state """
1105  """ 'disabled': disabled (grayed) state """
1106  """ 'hot' : hot state (mouse over) """
1107  """ 'down' : down state (mouse clicking) """
1108  """ '*' : fallback state """
1109  """ Each state in spriteifiles must be a tuple of the form: """
1110  """ ( path , location ) """
1111  """ or ( path, location, text [,textattrs] ) """
1112  """ ( ) - empty, for "no change" """
1113  """ with 'location' being a GUIRect """
1114  """ and 'text' being an optional overlaid text element """
1115 
1116  self.sprites=spritefiles
1117  self.hotspot=hotspot
1118  self.linkdesc=linkdesc
1119  self.linkstate=0
1120  self.state=initialstate
1121  self.group=index
1122  self.enabled=(initialstate!='disabled')
1123  self.clickHandler = clickHandler
1124  self.textOverlay = None
1125  self.textcolor = textcolor
1126  self.textbgcolor = textbgcolor
1127  self.textfontsize = textfontsize
1128 
1129  """ Init base class """
1130  GUIStaticImage.__init__(self,room,index,self._getStateSprite(self.state),**kwarg)
1131  self.textOverlay = GUIStaticText(self.room,self.index+"__text_overlay","",
1132  self.hotspot,self.textcolor,self.textfontsize,self.textbgcolor)
1133  self.textOverlay.hide()
1134 
1135  self.pythonstr = \
1136  "# <-- this disables precompiled python objects\n" \
1137  +"from GUI import GUIRootSingleton\n" \
1138  +"evData = Base.GetEventData()\n" \
1139  +"typeToMessage = {'click':'click','up':'up','down':'down','move':'move','enter':'enter','leave':'leave'}\n" \
1140  +"if ('type' in evData) and (evData['type'] in typeToMessage):\n" \
1141  +"\tGUIRootSingleton.dispatchMessage("+str(self.id)+",typeToMessage[evData['type']],evData)\n" \
1142  +"\tGUIRootSingleton.redrawIfNeeded()\n"
1143 
1144  def _getStateSprite(self,state):
1145  if self.sprites:
1146  if (state in self.sprites):
1147  sprite = self.sprites[state]
1148  elif ('*' in self.sprites):
1149  sprite = self.sprites['*']
1150  else:
1151  sprite = None
1152  # this is frequently ok (ie, when a button is just a region on the screen, not a separate sprite)
1153  trace(_GUITraceLevel + 2, "WARNING! - gui.py - GUIButton::_getStateSprite(): can't map sprite, %s not in %s\n" % (state,self.sprites))
1154  if sprite and ( (type(sprite)!=tuple) or (len(sprite)<2) ):
1155  if sprite:
1156  trace(_GUITraceLevel, "WARNING! - gui.py - GUIButton::_getStateSprite(): type error in sprite map\n")
1157  sprite = None
1158  else:
1159  sprite = None
1160  return sprite
1161 
1162  def onMouseUp(self,params):
1163  if self.getState()=='down':
1164  self.setNeutralState()
1165 
1166  def onMouseDown(self,params):
1167  if self.isEnabled() and self.hasState('down'):
1168  self.setState('down')
1169 
1170  def onMouseEnter(self,params):
1171  if self.isEnabled() and self.hasState('hot'):
1172  self.setState('hot')
1173 
1174  def onMouseLeave(self,params):
1175  if self.getState()=='hot' or self.getState()=='down':
1176  self.setNeutralState()
1177 
1178  def onMouseMove(self,params):
1179  """ Intentionally blank """
1180 
1181  def onClick(self,params):
1182  if self.clickHandler:
1183  self.clickHandler(self,params)
1184  GUIStaticImage.onClick(self,params)
1185 
1186  def draw(self):
1187  """ Creates the button """
1188  if (self.linkstate==0) and (self.visible==1) and self.enabled:
1189  # getHotRect returns the BOTTOM-left x,y needed by Base.Python
1190  (x,y,w,h) = self.hotspot.getHotRect()
1191  Base.Python(self.room.getIndex(),self.index,x,y,w,h,self.linkdesc,self.pythonstr,True)
1192  Base.SetLinkEventMask(self.room.getIndex(),self.index,'cduel')
1193  self.linkstate=1
1194  self.setState(self.state)
1195  GUIStaticImage.draw(self)
1196  if self.textOverlay.visible:
1197  self.textOverlay.draw()
1198 
1199  def setNeutralState(self):
1200  if self.isEnabled():
1201  self.setState('enabled')
1202  else:
1203  self.setState('disabled')
1204 
1205  def undraw(self):
1206  """Hides the button"""
1207  if self.linkstate==1:
1208  Base.EraseLink(self.room.getIndex(),self.index)
1209  self.linkstate=0
1210  GUIStaticImage.undraw(self)
1211  self.textOverlay.undraw()
1212 
1213  def hide(self):
1214  GUIStaticImage.hide(self)
1215  self.textOverlay.hide()
1216 
1217  def show(self):
1218  GUIStaticImage.show(self)
1219  self.textOverlay.show()
1220 
1221  def redraw(self):
1222  """ Creates the button """
1223  if (self.linkstate==1) and (self.visible==1) and self.enabled:
1224  (x,y,w,h) = self.hotspot.getHotRect()
1225  Base.SetLinkArea(self.room.getIndex(),self.index,x,y,w,h)
1226  Base.SetLinkText(self.room.getIndex(),self.index,self.linkdesc)
1227  Base.SetLinkPython(self.room.getIndex(),self.index,self.pythonstr)
1228  self.linkstate=1
1229  self.setState(self.state)
1230  GUIStaticImage.redraw(self)
1231  if self.textOverlay.visible:
1232  self.textOverlay.redraw()
1233  else:
1234  self.undraw()
1235  self.draw()
1236 
1237  def setCaption(self,caption,attrs=None):
1238  if caption is not None:
1239  if not self.textOverlay.visible:
1240  self.textOverlay.show()
1241  if attrs is None:
1242  self.textOverlay.setText(str(caption))
1243  else:
1244  self.textOverlay.text = str(caption)
1245  self.textOverlay.color = attrs.get('color',self.textOverlay.color)
1246  self.textOverlay.bgcolor = attrs.get('bgcolor',self.textOverlay.bgcolor)
1247  self.textOverlay.fontsize = attrs.get('fontsize',self.textOverlay.fontsize)
1248  self.textOverlay.notifyNeedRedraw()
1249  else:
1250  self.textOverlay.hide()
1251 
1252 
1253  def setState(self,newstate):
1254  self.state = newstate
1255  spr = self._getStateSprite(self.state)
1256  self.setSprite(spr)
1257  if spr and len(spr)>2:
1258  if len(spr)>3:
1259  self.setCaption(spr[2],spr[3])
1260  else:
1261  self.setCaption(spr[2])
1262  elif spr:
1263  self.setCaption(None)
1264 
1265  def getState(self):
1266  return self.state
1267 
1268  def hasState(self,state):
1269  return self.sprites and (state in self.sprites)
1270 
1271  def isEnabled(self):
1272  return self.enabled
1273 
1274  def enable(self):
1275  self.enabled=True
1276  self.setNeutralState()
1277 
1278  def disable(self):
1279  self.enabled=False
1280  self.setNeutralState()
1281 
1282  def setEnable(self,state):
1283  if state:
1284  self.enable()
1285  else:
1286  self.disable()
1287 
1288  def getGroup(self):
1289  return self.group
1290 
1291  def setGroup(self,group):
1292  self.group = group
1293 
1294  def onMessage(self,message,params):
1295  # Button-specific actions
1296  if (message=='enable'):
1297  if (not ('group' in params) or (self.getGroup() == params['group'])) and (not ('exclude' in params) or (self.id != params['exclude'])):
1298  self.enable()
1299  elif (message=='disable'):
1300  if (not ('group' in params) or (self.getGroup() == params['group'])) and (not ('exclude' in params) or (self.id != params['exclude'])):
1301  self.disable()
1302  # Button-specific mouse events
1303  elif self.isInteractive():
1304  if (message=='move'):
1305  self.onMouseMove(params)
1306  elif (message=='up'):
1307  self.onMouseUp(params)
1308  elif (message=='down'):
1309  self.onMouseDown(params)
1310  elif (message=='enter'):
1311  self.onMouseEnter(params)
1312  elif (message=='leave'):
1313  self.onMouseLeave(params)
1314  # Fallback
1315  else:
1316  GUIStaticImage.onMessage(self,message,params)
1317 
1318 
1319 
1320 """----------------------------------------------------------------"""
1321 """ """
1322 """ GUICompButton - a button you can click on that takes you """
1323 """ to the original computer interface """
1324 """ """
1325 """----------------------------------------------------------------"""
1326 
1328  def __init__(self,room,modes,*parg,**kwarg):
1329  self.compmodes = modes
1330 
1331  """ Init base class """
1332  GUIButton.__init__(self,room,*parg,**kwarg)
1333 
1334  def draw(self):
1335  """ Creates the button """
1336  if (self.linkstate==0) and (self.visible==1) and self.enabled:
1337  # getHotRect returns the BOTTOM-left x,y needed by Base.Python
1338  (x,y,w,h) = self.hotspot.getHotRect()
1339  # possible bug: not set to frontmost.
1340  Base.CompPython(self.room.getIndex(),self.index,self.pythonstr,x,y,w,h,self.linkdesc,self.compmodes)
1341  Base.SetLinkEventMask(self.room.getIndex(),self.index,'cduel')
1342  self.linkstate=1
1343  self.setState(self.state)
1344  GUIStaticImage.draw(self)
1345  if self.textOverlay.visible:
1346  self.textOverlay.draw()
1347 
1348 """----------------------------------------------------------------"""
1349 """ """
1350 """ GUIRoomButton - a button you can click on that takes you """
1351 """ to another room """
1352 """ """
1353 """----------------------------------------------------------------"""
1354 
1356  def __init__(self,room,targetroom,*parg,**kwarg):
1357  self.target = targetroom
1358 
1359  """ Init base class """
1360  GUIButton.__init__(self,room,*parg,**kwarg)
1361 
1362  def onClick(self,params):
1363  Base.SetCurRoom(self.target.getIndex())
1364  GUIButton.onClick(self,params)
1365 
1366 
1367 """----------------------------------------------------------------"""
1368 """ """
1369 """ GUICheckButton - a button you can click on, which toggles """
1370 """ between on/off states. """
1371 """ """
1372 """----------------------------------------------------------------"""
1373 
1375  def __init__(self,room,linkdesc,index,spritefiles,hotspot,**kwarg):
1376  """ Initializes the button (but does not draw it; use drawobjs()) """
1377  """ spritefiles: a dictionary, mapping states to sprites """
1378  """ 'checked' : normal, enabled and checked state """
1379  """ 'unchecked' : normal, enabled and unchecked state """
1380  """ 'disabled' : disabled (grayed) state """
1381  """ 'hot' : hot state (mouse over) """
1382  """ 'down' : down state (mouse clicking) """
1383  """ '*' : fallback state """
1384  """ Each state in spriteifiles must be a tuple of the form: """
1385  """ ( path , location ) """
1386  """ ( ) - empty, for "no change" """
1387  """ with 'location' being a GUIRect """
1388 
1389  GUIButton.__init__(self,room,linkdesc,index,spritefiles,hotspot,'unchecked',**kwarg)
1390  self.checked = 0
1391 
1392  def setNeutralState(self):
1393  if self.isEnabled():
1394  if self.isChecked():
1395  self.setState('checked')
1396  else:
1397  self.setState('unchecked')
1398  else:
1399  self.setState('disabled')
1400 
1401  def isChecked(self):
1402  return self.checked != 0
1403 
1404  def setChecked(self,check=1):
1405  if self.checked != check:
1406  self.checked = check
1407  self.onChange(None)
1408  if self.isEnabled():
1409  self.enable()
1410 
1411  def check(self):
1412  self.setChecked(1)
1413 
1414  def uncheck(self):
1415  self.setChecked(0)
1416 
1417  def toggleChecked(self):
1418  if not self.isChecked():
1419  self.setChecked(1)
1420  else:
1421  self.setChecked(0)
1422 
1423  def onClick(self,params):
1424  self.toggleChecked()
1425 
1426  def onChange(self,params):
1427  """ Intentionally blank """
1428 
1429  def onMessage(self,message,params):
1430  """ Intercept group reset """
1431  if ( (message=='setcheck' or message=='check' or message=='uncheck') \
1432  and (not ('group' in params) or (self.group == params['group'])) \
1433  and (not ('exclude' in params) or (self.id != params['exclude'])) \
1434  and (not ('index' in params) or (self.index == params['index'])) ):
1435  if (message=='setcheck'):
1436  if ('state' in params):
1437  self.setChecked(params['state'])
1438  elif (message=='check'):
1439  self.check()
1440  elif (message=='uncheck'):
1441  self.uncheck()
1442  else:
1443  GUIButton.onMessage(self,message,params)
1444 
1445 
1446 
1447 """----------------------------------------------------------------"""
1448 """ """
1449 """ GUIRadioButton - a button you can click on, which toggles """
1450 """ between on/off states. Only one button on a group will """
1451 """ be allowed to be in the checked state. """
1452 """ """
1453 """----------------------------------------------------------------"""
1454 
1456  def __init__(self,room,linkdesc,index,spritefiles,hotspot,radiogroup,value=None,onChange=(lambda group,newval,caller:None),**kwarg):
1457  """ Initializes the button (but does not draw it; use drawobjs()) """
1458  """ spritefiles: a dictionary, mapping states to sprites """
1459  """ 'checked' : normal, enabled and checked state """
1460  """ 'unchecked' : normal, enabled and unchecked state """
1461  """ 'disabled' : disabled (grayed) state """
1462  """ 'hot' : hot state (mouse over) """
1463  """ 'down' : down state (mouse clicking) """
1464  """ '*' : fallback state """
1465  """ Each state in spriteifiles must be a tuple of the form: """
1466  """ ( path , location ) """
1467  """ ( ) - empty, for "no change" """
1468  """ with 'location' being a GUIRect """
1469  """ NOTE FOR SUBLCASSERS: """
1470  """ onChange() may and will be issued for ANY changed element """
1471  """ that means that it would be wise to only perform an action """
1472  """ if self.isChecked() - also, be sure to call the base """
1473  """ implementation, or the radio button won't work properly. """
1474 
1475 
1476  GUICheckButton.__init__(self,room,linkdesc,index,spritefiles,hotspot,**kwarg)
1477  self.setGroup(radiogroup)
1478  self.uncheck()
1479  self.onChange_fn = onChange
1480  self.value = value
1481 
1482  def onChange(self,params):
1483  if self.isChecked():
1484  GUIRootSingleton.broadcastRoomMessage(self.room.getIndex(),'uncheck', { 'group':self.group,'exclude':self.id } )
1485  self.onChange_fn(self.group,self.value,self)
1486 
1487  def onClick(self,params):
1488  # radio buttons can't be unchecked; another button in the group has to be clicked instead
1489  if (not self.isChecked()):
1490  GUICheckButton.onClick(self,params)
1491 
1492  def groupUncheck(self):
1493  GUIRadioButton.staticGroupUncheck(self.room,self.getGroup())
1494 
1495  @staticmethod
1496  def staticGroupUncheck(room,group):
1497  GUIRootSingleton.broadcastRoomMessage(room.getIndex(),'uncheck', { 'group':group } )
1498 
1499 
1500 """----------------------------------------------------------------"""
1501 """ """
1502 """ GUISimpleListPicker - a simple (featureless) list picker """
1503 """ """
1504 """----------------------------------------------------------------"""
1506  class managedlist(list):
1507  def __init__(self,iterable=[],onChange=(lambda me,kind,key:None),owner=None):
1508  self.onChange = onChange
1509  self.owner = owner
1510  self[:] = iterable
1511  def __setitem__(self,key,value):
1512  self.onChange(self,'set',key)
1513  return super(type(self),self).__setitem__(key,value)
1514  def __delitem__(self,key):
1515  self.onChange(self,'del',key)
1516  return super(type(self),self).__delitem__(key)
1517  def __setslice__(self,i,j,sequence):
1518  self.onChange(self,'setslice',(i,j,sequence))
1519  return super(type(self),self).__setslice__(i,j,sequence)
1520  def __delslice__(self,i,j):
1521  self.onChange(self,'delslice',(i,j))
1522  return super(type(self),self).__delslice__(i,j)
1523  return setattr(super(type(self),self),name,value)
1524  def append(self,item):
1525  self.onChange(self,'append',None)
1526  return super(type(self),self).append(item)
1527  def remove(self,item):
1528  del self[self.index(item)]
1529  def extend(self,iterable):
1530  self[len(self):] = iterable
1531  def insert(self,i,value):
1532  self[i:i] = [value]
1533 
1534  class listitem:
1535  def __init__(self,string,data):
1536  self.string = string
1537  self.data = data
1538  def __repr__(self):
1539  return "[%r,%r]" % (self.string,self.data)
1540  def __str__(self):
1541  return self.string
1542 
1543 
1544  def __init__(self,room,linkdesc,index,hotspot,textcolor=GUIColor.white(),textbgcolor=GUIColor.clear(),textfontsize=1.0,selectedcolor=GUIColor.white(),selectedbgcolor=GUIColor.black(),**kwargs):
1545  GUIElement.__init__(self,room,**kwargs)
1546  self.linkdesc = linkdesc
1547  self.index = index
1548  self.hotspot = hotspot
1549  self.__dict__['items'] = GUISimpleListPicker.managedlist(onChange=self._notifyListChange,owner=self)
1550  self.selection = None
1551  self.firstVisible = 0
1552  self._listitems = []
1553  self.textcolor = textcolor
1554  self.textbgcolor = textbgcolor
1555  self.selectedcolor = selectedcolor
1556  self.selectedbgcolor = selectedbgcolor
1557  self.textfontsize = textfontsize
1558  self.selectedattrs = dict(color=selectedcolor,bgcolor=selectedbgcolor,fontsize=textfontsize)
1559  self.unselectedattrs = dict(color=textcolor,bgcolor=textbgcolor,fontsize=textfontsize)
1560  self.createListItems()
1561 
1562  def __setattr__(self,name,value):
1563  if name == 'items':
1564  # Preserves type
1565  self.items[:] = value
1566  else:
1567  if name in ['textcolor','textbgcolor','textfontsize']:
1568  self.notifyNeedRedraw(0)
1569  self.__dict__[name] = value
1570 
1571  @staticmethod
1572  def _notifyListChange(lst,kind,key):
1573  lst.owner.notifyNeedRedraw(0)
1574 
1575  @staticmethod
1576  def _notifySelectionChange(group,newval,caller):
1577  print "New selection: %s" % newval
1578  caller.owner.selection = newval + caller.owner.firstVisible
1579 
1580  def _radiogroup(self):
1581  return self.index+"_slp_rg"
1582 
1583  def _recheck(self):
1584  if self.selection is not None:
1585  visindex = self.selection - self.firstVisible
1586  if visindex < 0 or visindex >= len(self._listitems):
1587  self._listitems[0].groupUncheck()
1588  else:
1589  self._listitems[visindex].check()
1590 
1591  def destroyListItems(self):
1592  for l in self._listitems:
1593  l.undraw()
1594  self._listitems = []
1595 
1596  def createListItems(self):
1597  self.destroyListItems()
1598 
1599  theight = Base.GetTextHeight('|',tuple([self.textfontsize]*3))*1.15 # Need a small margin to avoid text clipping
1600  spr = { 'checked':(None,None,''), 'unchecked':(None,None,'') }
1601  if theight<=0:
1602  return
1603  nlines = int(self.hotspot.getTextRect()[3]/theight)
1604  if nlines<=0:
1605  return
1606  hotx,hoty,hotw,hoth = self.hotspot.getNormalXYWH()
1607  theight = hoth / nlines
1608  for i in range(nlines):
1609  hot = GUIRect(hotx,hoty-i*hoth/float(nlines),hotw,theight,'normalized_biased_scaled')
1610  self._listitems.append( GUIRadioButton(self.room,self.linkdesc,"%s[%s]" % (self.index,i),spr,hot,self._radiogroup(),i,onChange=self._notifySelectionChange,owner=self) )
1611  i += 1
1612 
1613  def _visItemText(self,i):
1614  if i+self.firstVisible < len(self.items):
1615  txt = str(self.items[i+self.firstVisible])
1616  else:
1617  txt = ""
1618  return txt
1619 
1620  def _updateListItemText(self):
1621  for i in range(len(self._listitems)):
1622  txt = self._visItemText(i)
1623  self._listitems[i].sprites = {
1624  'checked':(None,None,txt,self.selectedattrs),
1625  'unchecked':(None,None,txt,self.unselectedattrs),
1626  'disabled':(None,None) }
1627  self._listitems[i].setEnable((self.firstVisible + i) < len(self.items))
1628  self._listitems[i].notifyNeedRedraw()
1629 
1630  def draw(self):
1631  self._updateListItemText()
1632  for item in self._listitems:
1633  item.draw()
1634  def undraw(self):
1635  for item in self._listitems:
1636  item.undraw()
1637  def redraw(self):
1638  if len(self._listitems) <= 0:
1639  self.createListItems()
1640  self._updateListItemText()
1641  for it in self._listitems:
1642  it.redraw()
1643  def show(self):
1644  GUIElement.show(self)
1645  for item in self._listitems:
1646  item.show()
1647  def hide(self):
1648  GUIElement.hide(self)
1649  for item in self._listitems:
1650  item.hide()
1651 
1652  def pageMove(self,nPages):
1653  self.viewMove(nPages*len(self._listitems))
1654  self._recheck()
1655 
1656  def viewMove(self,lines):
1657  self.firstVisible = max(0,min(len(self.items)-1-len(self._listitems)/2,self.firstVisible + lines))
1658  self.notifyNeedRedraw()
1659  self._recheck()
1660 
1661 
1662 
1663 """----------------------------------------------------------------"""
1664 """ """
1665 """ GUIVideoTexture - a non-interactive video texture """
1666 """ (no audio) """
1667 """ """
1668 """----------------------------------------------------------------"""
1669 
1671  def __init__(self,room,index,sprite,**kwarg):
1672  """ Sprite must be a tuple of the form: """
1673  """ ( path , location ) """
1674  """ with 'location' being a GUIRect """
1675  """ NOTE: It is legal to set sprite to None """
1676  """ This allows subclassing to create """
1677  """ non-static elements """
1678 
1679  GUIStaticImage.__init__(self,room,index,sprite,**kwarg)
1680 
1681  # it does not for this subclass
1683 
1684  def draw(self):
1685  """ Creates the element """
1686  if (self.visible == 1) and (self.spritestate==0) and self.spriteIsValid():
1687  (x,y,w,h) = self.sprite[1].getSpriteRect()
1688  Base.Video(self.room.getIndex(),self.index,self.sprite[0],"",x,y)
1689  Base.SetTextureSize(self.room.getIndex(),self.index,w,h) # override spr file data... it's hideously unmantainable...
1690  self.spritestate=1
1691 
1692  def redraw(self):
1693  """ Sets a new image """
1694  if self.spritestate==1:
1695  self.undraw()
1696  self.draw()
1697 
1698 
1699 
1700 """----------------------------------------------------------------"""
1701 """ """
1702 """ GUIVideoStream - a non-interactive video stream """
1703 """ (with audio) """
1704 """ """
1705 """----------------------------------------------------------------"""
1706 
1708  def __init__(self,room,index,sprite,**kwarg):
1709  """ Sprite must be a tuple of the form: """
1710  """ ( path , location ) """
1711  """ with 'location' being a GUIRect """
1712  """ NOTE: It is legal to set sprite to None """
1713  """ This allows subclassing to create """
1714  """ non-static elements """
1715  self.eosHandler = kwarg.pop('eosHandler',None)
1716  self.startHandler = kwarg.pop('startHandler',None)
1717  self.stopHandler = kwarg.pop('stopHandler',None)
1718 
1719  GUIStaticImage.__init__(self,room,index,sprite,**kwarg)
1720 
1721  # it does not for this subclass
1723 
1724  self.nextRoom = None
1725  self.aspect = None
1726 
1727  def draw(self):
1728  """ Creates the element """
1729  if (self.visible == 1) and (self.spritestate==0) and self.spriteIsValid():
1730  pythoncallback = lambda id,event : (
1731  "# <-- this disables precompiled python objects\n"
1732  +"from GUI import GUIRootSingleton\n"
1733  +"GUIRootSingleton.dispatchMessage(%r,%r,None)\n"
1734  +"GUIRootSingleton.redrawIfNeeded()\n" ) % (id,event)
1735 
1736  (x,y,w,h) = self.sprite[1].getSpriteRect()
1737  self.spritestate = Base.VideoStream(self.room.getIndex(),self.index,self.sprite[0],x,y,w,h)
1738  if self.spritestate is None:
1739  self.spritestate = 1
1740  if self.spritestate:
1741  Base.SetVideoCallback(self.room.getIndex(),self.index,pythoncallback(self.id, "eos"))
1742  self.setAspectRatio(self.aspect)
1743  Base.RunScript(self.room.getIndex(),self.index+"PLAY",pythoncallback(self.id, "play"),0.0)
1744  else:
1745  # Movies are optional, so don't break - skip (immediate EOS) instead
1746  # Enqueue it to happen as soon as the movie screen is shown
1747  Base.RunScript(self.room.getIndex(),self.index+"EOS",pythoncallback(self.id, "eos"),0.0)
1748 
1749  def undraw(self):
1750  """ Hides the element """
1751  self.stopPlaying()
1752  GUIStaticImage.undraw(self)
1753 
1754  def redraw(self):
1755  """ Sets a new image """
1756  if self.spritestate==1:
1757  self.undraw()
1758  self.draw()
1759 
1760  def stopPlaying(self):
1761  if self.spritestate==1:
1762  Base.StopVideo(self.room.getIndex(), self.index)
1763  if self.stopHandler:
1764  self.stopHandler(self)
1765 
1766  def startPlaying(self):
1767  if self.spritestate==1:
1768  Base.PlayVideo(self.room.getIndex(), self.index)
1769  if self.startHandler:
1770  self.startHandler(self)
1771 
1772  def setNextRoom(self, nextRoom):
1773  self.nextRoom = nextRoom
1774 
1775  def setAspectRatio(self, aspect):
1776  self.aspect = aspect
1777  if self.spritestate:
1778  (x,y,w,h) = self.sprite[1].getSpriteRect()
1779  screenAspect = GUIRootSingleton.getScreenAspectRatio()
1780 
1781  xf = yf = 1.0
1782  if aspect is not None:
1783  if screenAspect < aspect:
1784  # horizontal bars needed
1785  yf = screenAspect / aspect
1786  elif aspect < screenAspect:
1787  # vertical bars needed
1788  xf = aspect / screenAspect
1789 
1790  Base.SetTextureSize(self.room.getIndex(), self.index, w * xf, h * yf)
1791 
1792  def onMessage(self, message, params):
1793  if not GUIStaticImage.onMessage(self, message, params):
1794  if message == 'eos':
1795  self.onEOS(params)
1796  elif message == 'play':
1797  self.startPlaying()
1798  elif message == 'stop':
1799  self.stopPlaying()
1800  else:
1801  return False
1802  return True
1803  else:
1804  return True
1805 
1806  def onEOS(self, params):
1807  """
1808  Callback for End-Of-Stream.
1809  """
1810  if self.eosHandler:
1811  self.eosHandler(self, params)
1812 
1813 
1814 """----------------------------------------------------------------"""
1815 """ """
1816 """ GUIMovieRoom - implements a cutscene movie as a room with just """
1817 """ the movie playing, it will transition to another room """
1818 """ when the movie ends, or a "skip" key is pressed, or """
1819 """ the mouse clicked anywhere on the screen """
1820 """ """
1821 """----------------------------------------------------------------"""
1822 
1824  def __init__(self, index, moviesprite, nextroom):
1825  GUIRoom.__init__(self, index)
1826 
1827  sx, sw = GUIRootSingleton.getScreenDimensions()
1828  self.aspect = sw * 1.0 / sx
1829 
1830  self.video = GUIVideoStream(self, "movie", moviesprite,
1831  eosHandler=self.onSkip,
1832  startHandler=self.onStart,
1833  stopHandler=self.onStop)
1834  self.video.setNextRoom(nextroom)
1835  self.video.setAspectRatio(self.aspect)
1836 
1837  self.skipzone = GUIRoomButton(self, nextroom,
1838  "XXXskip", "skipmovie",
1839  {'*':None},
1840  GUIRect(0,0,1,1,"normalized"),
1841  clickHandler = self.onSkip )
1842 
1843  def __repr__(self):
1844  return "<movie room %r - %r>" % (self.index, self.moviepath)
1845 
1846  def __str__(self):
1847  return "MovieRoom %s %s" % (self.index, self.moviepath)
1848 
1849  def keyDown(self,key):
1850  if key in (13, 10, 27): # return, return, escape
1851  self.skipzone.onClick({})
1852 
1853  def onSkip(self, button, params):
1854  self.video.stopPlaying()
1855  if self.video.nextRoom:
1856  Base.SetCurRoom(self.video.nextRoom.getIndex())
1857 
1858  def onStart(self, video):
1859  pass
1860 
1861  def onStop(self, video):
1862  pass
1863 
1864  def setAspectRatio(self, ratio):
1865  self.aspect = ratio
1866  self.video.setAspectRatio(self.aspect)