6 VegaStrik GUI abstraction layer
8 This framework provides a set of classes that allow easy construction
9 of complex user interfaces based on the rather primitive base API
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.
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.
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
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)
47 See the module for details on each class, following is an overview of
48 the framework's usage:
53 The first step to take is the initialization of the framwork and the
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)
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.
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.
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')
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)
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).
90 Sometimes you want this, sometimes you don't.
95 In the example above, we have to rooms, intro and preintro.
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.
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.
105 How would we play it?
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).
112 So scratch the above GUI.GUIRoom initialization for intro/preintro,
113 and better use the followin:
115 # Set up preintro room
116 class PreIntroRoom(GUI.GUIMovieRoom):
117 def onSkip(self, button, params):
118 GUI.GUIMovieRoom.onSkip(self, button, params)
121 preintroroom = PreIntroRoom(room_preintro,
123 GUI.GUIRect(0, 0, 1, 1, "normalized")),
125 preintroroom.setAspectRatio(16.0/9.0)
129 class IntroRoom(PreIntroRoom):
130 def onSkip(self, button, params):
131 PreIntroRoom.onSkip(self, button, params)
132 DoStartNewGame(self, params)
134 introroom = IntroRoom(room_intro,
136 GUI.GUIRect(0, 0, 1, 1, "normalized")),
138 introroom.setAspectRatio(16.0/9.0)
141 Here we have a lot of new stuff.
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.
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.
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).
165 Lets leave the intro room as an excercise to the reader ;-)
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?
174 We have a menu, so we need stuff on it. We have tons of prebuilt
175 elements, or widgets, at our disposal.
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.
182 Most elements take more parameters, but those two are universal.
187 We want pretty menues. We want a background. Which is to say,
188 a static image on the screen. Straigforward task:
191 GUI.GUIStaticImage(guiroom, 'background',
192 ( 'interfaces/main_menu/menu.spr',
193 GUI.GUIRect(0, 0, 1024, 768, "pixel", (1024,768)) ))
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".
200 So (0,0,1024,768,"pixel",(1024,768))
201 is equivalent to (0,0,1,1,"normalized").
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.
210 Now we want some text in the credits room (which we didn't show).
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())
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).
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).
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.
228 Ok, we have a main menu, we have an "intro" room that ends up starting a campaign.
230 Now we need a "new game" button. Right?
233 sprite_loc = GUI.GUIRect(48,224,128,32,"pixel",(1280,1024))
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)
239 Ok, buttons are more complex, aren't they?
241 Lets start with the sprite. It's gone from a simple tuple, to a dictionary. Why?
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.
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'.
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.
257 plist_menu=VS.musicAddList('maintitle.m3u')
259 def DoStartNewGame(self,params):
260 ShowProgress.activateProgressScreen('loading',3)
261 VS.loadGame(VS.getNewGameSaveName())
262 enterMainMenu(self,params)
264 def StartNewGame(self,params):
265 Base.SetCurRoom(introroom.getIndex())
268 def enterMainMenu(self,params):
270 VS.musicPlayList(plist_menu)
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.
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.
279 There's also a very straightforward way of linking rooms with GUIRoomButton(from,to,title,id,...):
281 sprite_loc = GUI.GUIRect(48,510,92,32,"pixel",(1280,1024))
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)
287 See how you can even do extra stuff in a "clickHandler"
289 plist_credits=VS.musicAddList('maincredits.m3u')
291 def enterCredits(self,params):
293 VS.musicPlayList(plist_credits)
299 There are even more advanced widgets than images an push buttons.
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".
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,
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.
320 from XGUIDebug
import *
322 GUIRootSingleton =
None
324 _GUITraceLevel = TRACE_VERBOSE
326 """----------------------------------------------------------------"""
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) """
332 """----------------------------------------------------------------"""
335 """global GUI setup"""
336 def __init__(self,screenX=None,screenY=None,marginX=None,marginY=None,aspect=None):
339 if not screenX
or not screenY:
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"))
347 self.
aspect = aspect
or (screenX * 1.0 / screenY)
348 if (marginX ==
None):
350 if (marginY ==
None):
356 Base.GlobalKeyPython(
'#\nfrom GUI import GUIRootSingleton\nGUIRootSingleton.keyEvent()\n')
361 self.
broadcastMessage(
'changedScreenDimensions', {
'screenX':screenX,
'screenY':screenY} )
369 self.
broadcastMessage(
'changedScreenMargins', {
'marginX':marginX,
'marginY':marginY} )
381 trace(_GUITraceLevel,
"::: calling GUI.GUIRoot.dispatchMessage(%s, %s, %s) :::" %(id,message,params))
383 self.
objects[id][1].onMessage(message,params)
385 trace(_GUITraceLevel + 1,
'WARNING! - gui.py - GUIRoot::dispatchMessage(): Object id "' +
str(id) +
'" not found\n')
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)
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)
400 self.
objects[id] = (room,object)
405 eventdata = Base.GetEventData();
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'])
417 self.
rooms[room.getIndex()] = room
420 self.objects.erase(id)
430 trace(_GUITraceLevel,
"::: calling GUI.GUIRoot.redrawIfNeeded()" )
435 return self.rooms.get(id,
None)
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)
444 """----------------------------------------------------------------"""
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). """
450 """----------------------------------------------------------------"""
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):
472 """ returns (x,y,w,h) - x/y is top-left - in Base coordinates """
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()
482 (screenX,screenY,marginX,marginY) = self.
ref
483 elif len(self.
ref)==2:
484 (screenX,screenY) = self.
ref
485 (marginX,marginY) = GUIRootSingleton.getScreenMargins()
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)
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)) \
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))
513 trace(_GUITraceLevel,
"WARNING! - gui.py - GUIRect::getNormalizedCoords(): unknown coordinate mode\n")
517 return ( aux[0]+aux[2]/2, aux[1]-aux[3]/2 )
520 """ returns TOP-LEFT coordinate pair """
522 return ( aux[0], aux[1] )
525 """ returns BOTTOM-RIGHT coordinate pair """
527 return ( aux[0]+aux[2], aux[1]-aux[3] )
530 """ returns WIDTH-HEIGHT dimensions """
532 return ( aux[2], aux[3] )
541 """ (BOTTOM, LEFT, WIDTH, HEIGHT) as needed by Base hotspots """
543 return ( aux[0], aux[1]-aux[3], aux[2], aux[3] )
546 """ (CenterX, CenterY, WIDTH, -HEIGHT) as needed by Base sprites """
548 return ( aux[0]+aux[2]/2, aux[1]-aux[3]/2, aux[2], -aux[3] )
551 """ (TOP, LEFT, WIDTH, HEIGHT) as needed by Base textboxes """
555 """----------------------------------------------------------------"""
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. """
562 """----------------------------------------------------------------"""
565 def __init__(self,x,y,wid,hei,npotw,npoth,mode="pixel"):
574 """ Store original parameters """
580 """ Initialized base class """
581 GUIRect.__init__(self,x,y,wid*potw/npotw,hei*poth/npoth,mode)
584 return GUIRect.__repr__(self)
587 return GUIRect.__str__(self)
590 """----------------------------------------------------------------"""
592 """ GUIColor - an RGBA color representation """
593 """ values range from 0.0 to 1.0. """
595 """----------------------------------------------------------------"""
604 return "RGB(%f,%f,%f,%f)" % (self.
r,self.
g,self.
b,self.
a)
607 return "RGB(%.2f,%.2f,%.2f,%.2f)" % (self.
r,self.
g,self.
b,self.
a)
611 t = (self.
r, self.
g, self.
b)
632 """----------------------------------------------------------------"""
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. """
638 """----------------------------------------------------------------"""
648 GUIRootSingleton.registerRoom(self)
651 return "<room %r>" % self.
index
654 return "Room %s" % self.
index
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)
669 GUIRootSingleton.broadcastRoomMessage(self.
getIndex(),
'redraw',
None)
679 global GUIRootSingleton
683 return GUIRootSingleton.getScreenDimensions()
686 global GUIRootSingleton
690 return GUIRootSingleton.getScreenMargins()
693 global GUIRootSingleton
696 GUIRootSingleton.broadcastRoomMessage(self.
index,
'changedScreenDimensions', {
'screenX':dims[0],
'screenY':dims[1]} )
699 global GUIRootSingleton
702 GUIRootSingleton.broadcastMessage(self.
index,
'changedScreenMargins', {
'marginX':mars[0],
'marginY':mars[1]} )
705 """----------------------------------------------------------------"""
707 """ GUIElement - it's the base class for all GUI elements """
708 """ Implements standard methods and message handling """
710 """----------------------------------------------------------------"""
719 self.
id = GUIRootSingleton.registerObject(room.getIndex(),self)
727 r =
"<%s: " %(type(self))
728 for key
in dir(self):
730 value = getattr(self, key)
736 r = r +
"%s = %s, " %(key, value)
745 """ Usually does not need a full redraw """
750 self.room.notifyNeedRedraw(preserveZ)
753 """ Intentionally blank """
756 """ Intentionally blank """
759 """ override to preserve Z-order if possible """
760 """ (if so, mark self.redrawPreservesZ) """
765 """Standard message dispatch"""
766 if (message==
'click'):
768 elif (message==
'show'):
770 elif (message==
'hide'):
772 elif (message==
'draw'):
774 elif (message==
'undraw'):
776 elif (message==
'redraw'):
784 """ Intentionally blank """
801 """ WARNING! does not necessarily preserve z-order, so only issue if you don't care about z-order """
807 GUIRootSingleton.keyTarget=self
808 elif GUIRootSingleton.keyTarget==self:
809 GUIRootSingleton.keyTarget=
None
813 GUIRootSingleton.modalElement=self
814 GUIRootSingleton.broadcastMessage(
'disable',{})
815 if 'enable' in dir(self):
817 GUIRootSingleton.broadcastRoomMessage(
'redraw',{})
818 elif GUIRootSingleton.modalElement == self:
819 GUIRootSingleton.modalElement=
None
820 GUIRootSingleton.broadcastMessage(
'enable',{})
823 return GUIRootSingleton.modalElement==self
or GUIRootSingleton.modalElement==
None
826 """----------------------------------------------------------------"""
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) """
832 """----------------------------------------------------------------"""
837 GUIElement.__init__(self,room,**kwargs)
843 GUIElement.show(self)
848 GUIElement.hide(self)
851 """ Intentionally blank """
854 """ Intentionally blank """
857 """ Intentionally blank """
860 for i
in self.children:
861 i.onMessage(message,params)
866 """----------------------------------------------------------------"""
868 """ GUIStaticImage - a non-interactive image """
870 """----------------------------------------------------------------"""
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 """
881 GUIElement.__init__(self,room,**kwarg)
890 and type(self.
sprite)
is tuple
892 and self.
sprite[0]
is not None
893 and self.
sprite[1]
is not None )
896 """ Creates the element """
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)
906 """ Hides the element """
908 Base.EraseObj(self.room.getIndex(),self.
index)
912 """ Sets a new image """
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);
922 GUIStaticImage.undraw(self)
925 if self.
sprite != newsprite:
931 """----------------------------------------------------------------"""
933 """ GUIStaticText - a non-interactive text box """
935 """----------------------------------------------------------------"""
938 def __init__(self,room,index,text,location,color,fontsize=1.0,bgcolor=None,**kwarg):
939 GUIElement.__init__(self,room,**kwarg)
945 if (bgcolor !=
None):
948 self.
bgcolor = GUIColor.clear()
953 """ Creates the element """
955 (x,y,w,h) = self.location.getTextRect()
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())
965 """ Hides the element """
967 Base.EraseObj(self.room.getIndex(),
str(self.
index))
968 if _doWhiteHack != 0:
969 Base.EraseObj(self.room.getIndex(),self.
index+
"_white_hack")
975 Base.SetTextBoxText(self.room.getIndex(),
str(self.
index),
str(self.
text))
978 self.
color = newcolor
988 """----------------------------------------------------------------"""
990 """ GUILineEdit - an interactive text box """
992 """----------------------------------------------------------------"""
999 print 'focusing',self.
index
1001 def __init__(self,action,room,index,text,location,color,fontsize=1.0,bgcolor=None,focusbutton=None,**kwarg):
1002 GUIGroup.__init__(self,room,**kwarg)
1004 self.children.append(self.
text)
1009 (x,y,w,h) = location.getNormalXYWH()
1010 (uw,uh) =
GUIRect(0,0,10,10).getNormalWH()
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())
1028 return self.text.getText()[1:-1]
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)
1040 print "got key: %i" % key
1041 if key == 13
or key == 10:
1046 elif key == 127
or key == 8:
1047 self.text.setText(
' ' + self.
getText()[:-1] +
'-')
1048 elif key<127
and key>0:
1050 self.text.setText(
' '+self.
getText() + (
'%c' % key) +
'-');
1052 print "Character value too high "+
str(key)
1055 """------------------------------------------------------------------"""
1057 """ GUITextInputDialog - a little dialog in which you can enter text """
1059 """------------------------------------------------------------------"""
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))
1071 def __init__(self,room,linkdesc,index,hotspot,**kwarg):
1076 GUIElement.__init__(self,room,**kwarg)
1080 (x,y,w,h) = self.hotspot.getHotRect()
1081 Base.Python(self.room.getIndex(),self.
index,x,y,w,h,self.
linkdesc,
'#',
True)
1087 Base.EraseLink(self.room.getIndex(),self.
index)
1094 """----------------------------------------------------------------"""
1096 """ GUIButton - a button you can click on. """
1098 """----------------------------------------------------------------"""
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 """
1129 """ Init base class """
1133 self.textOverlay.hide()
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"
1144 def _getStateSprite(self,state):
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) ):
1156 trace(_GUITraceLevel,
"WARNING! - gui.py - GUIButton::_getStateSprite(): type error in sprite map\n")
1179 """ Intentionally blank """
1182 if self.clickHandler:
1183 self.clickHandler(self,params)
1184 GUIStaticImage.onClick(self,params)
1187 """ Creates the button """
1190 (x,y,w,h) = self.hotspot.getHotRect()
1192 Base.SetLinkEventMask(self.room.getIndex(),self.
index,
'cduel')
1195 GUIStaticImage.draw(self)
1196 if self.textOverlay.visible:
1197 self.textOverlay.draw()
1206 """Hides the button"""
1208 Base.EraseLink(self.room.getIndex(),self.
index)
1210 GUIStaticImage.undraw(self)
1211 self.textOverlay.undraw()
1214 GUIStaticImage.hide(self)
1215 self.textOverlay.hide()
1218 GUIStaticImage.show(self)
1219 self.textOverlay.show()
1222 """ Creates the button """
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)
1230 GUIStaticImage.redraw(self)
1231 if self.textOverlay.visible:
1232 self.textOverlay.redraw()
1238 if caption
is not None:
1239 if not self.textOverlay.visible:
1240 self.textOverlay.show()
1242 self.textOverlay.setText(
str(caption))
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()
1250 self.textOverlay.hide()
1254 self.
state = newstate
1257 if spr
and len(spr)>2:
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'])):
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'])):
1304 if (message==
'move'):
1306 elif (message==
'up'):
1308 elif (message==
'down'):
1310 elif (message==
'enter'):
1312 elif (message==
'leave'):
1316 GUIStaticImage.onMessage(self,message,params)
1320 """----------------------------------------------------------------"""
1322 """ GUICompButton - a button you can click on that takes you """
1323 """ to the original computer interface """
1325 """----------------------------------------------------------------"""
1331 """ Init base class """
1332 GUIButton.__init__(self,room,*parg,**kwarg)
1335 """ Creates the button """
1338 (x,y,w,h) = self.hotspot.getHotRect()
1341 Base.SetLinkEventMask(self.room.getIndex(),self.
index,
'cduel')
1344 GUIStaticImage.draw(self)
1345 if self.textOverlay.visible:
1346 self.textOverlay.draw()
1348 """----------------------------------------------------------------"""
1350 """ GUIRoomButton - a button you can click on that takes you """
1351 """ to another room """
1353 """----------------------------------------------------------------"""
1359 """ Init base class """
1360 GUIButton.__init__(self,room,*parg,**kwarg)
1363 Base.SetCurRoom(self.target.getIndex())
1364 GUIButton.onClick(self,params)
1367 """----------------------------------------------------------------"""
1369 """ GUICheckButton - a button you can click on, which toggles """
1370 """ between on/off states. """
1372 """----------------------------------------------------------------"""
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 """
1389 GUIButton.__init__(self,room,linkdesc,index,spritefiles,hotspot,
'unchecked',**kwarg)
1427 """ Intentionally blank """
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):
1438 elif (message==
'check'):
1440 elif (message==
'uncheck'):
1443 GUIButton.onMessage(self,message,params)
1447 """----------------------------------------------------------------"""
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. """
1453 """----------------------------------------------------------------"""
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. """
1476 GUICheckButton.__init__(self,room,linkdesc,index,spritefiles,hotspot,**kwarg)
1484 GUIRootSingleton.broadcastRoomMessage(self.room.getIndex(),
'uncheck', {
'group':self.
group,
'exclude':self.
id } )
1490 GUICheckButton.onClick(self,params)
1493 GUIRadioButton.staticGroupUncheck(self.
room,self.
getGroup())
1497 GUIRootSingleton.broadcastRoomMessage(room.getIndex(),
'uncheck', {
'group':group } )
1500 """----------------------------------------------------------------"""
1502 """ GUISimpleListPicker - a simple (featureless) list picker """
1504 """----------------------------------------------------------------"""
1507 def __init__(self,iterable=[],onChange=(
lambda me,kind,key:
None),owner=
None):
1513 return super(type(self),self).
__setitem__(key,value)
1518 self.
onChange(self,
'setslice',(i,j,sequence))
1519 return super(type(self),self).
__setslice__(i,j,sequence)
1521 self.
onChange(self,
'delslice',(i,j))
1523 return setattr(super(type(self),self),name,value)
1526 return super(type(self),self).
append(item)
1528 del self[self.index(item)]
1530 self[len(self):] = iterable
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)
1558 self.
selectedattrs = dict(color=selectedcolor,bgcolor=selectedbgcolor,fontsize=textfontsize)
1565 self.items[:] = value
1567 if name
in [
'textcolor',
'textbgcolor',
'textfontsize']:
1569 self.__dict__[name] = value
1572 def _notifyListChange(lst,kind,key):
1573 lst.owner.notifyNeedRedraw(0)
1576 def _notifySelectionChange(group,newval,caller):
1577 print "New selection: %s" % newval
1578 caller.owner.selection = newval + caller.owner.firstVisible
1580 def _radiogroup(self):
1581 return self.
index+
"_slp_rg"
1586 if visindex < 0
or visindex >= len(self.
_listitems):
1600 spr = {
'checked':(
None,
None,
''),
'unchecked':(
None,
None,
'') }
1603 nlines = int(self.hotspot.getTextRect()[3]/theight)
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')
1613 def _visItemText(self,i):
1620 def _updateListItemText(self):
1626 'disabled':(
None,
None) }
1644 GUIElement.show(self)
1648 GUIElement.hide(self)
1663 """----------------------------------------------------------------"""
1665 """ GUIVideoTexture - a non-interactive video texture """
1668 """----------------------------------------------------------------"""
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 """
1679 GUIStaticImage.__init__(self,room,index,sprite,**kwarg)
1685 """ Creates the element """
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)
1693 """ Sets a new image """
1700 """----------------------------------------------------------------"""
1702 """ GUIVideoStream - a non-interactive video stream """
1703 """ (with audio) """
1705 """----------------------------------------------------------------"""
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 """
1719 GUIStaticImage.__init__(self,room,index,sprite,**kwarg)
1728 """ Creates the element """
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)
1736 (x,y,w,h) = self.
sprite[1].getSpriteRect()
1741 Base.SetVideoCallback(self.room.getIndex(),self.
index,pythoncallback(self.
id,
"eos"))
1743 Base.RunScript(self.room.getIndex(),self.
index+
"PLAY",pythoncallback(self.
id,
"play"),0.0)
1747 Base.RunScript(self.room.getIndex(),self.
index+
"EOS",pythoncallback(self.
id,
"eos"),0.0)
1750 """ Hides the element """
1752 GUIStaticImage.undraw(self)
1755 """ Sets a new image """
1762 Base.StopVideo(self.room.getIndex(), self.
index)
1768 Base.PlayVideo(self.room.getIndex(), self.
index)
1778 (x,y,w,h) = self.
sprite[1].getSpriteRect()
1779 screenAspect = GUIRootSingleton.getScreenAspectRatio()
1782 if aspect
is not None:
1783 if screenAspect < aspect:
1785 yf = screenAspect / aspect
1786 elif aspect < screenAspect:
1788 xf = aspect / screenAspect
1790 Base.SetTextureSize(self.room.getIndex(), self.
index, w * xf, h * yf)
1793 if not GUIStaticImage.onMessage(self, message, params):
1794 if message ==
'eos':
1796 elif message ==
'play':
1798 elif message ==
'stop':
1808 Callback for End-Of-Stream.
1814 """----------------------------------------------------------------"""
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 """
1821 """----------------------------------------------------------------"""
1825 GUIRoom.__init__(self, index)
1827 sx, sw = GUIRootSingleton.getScreenDimensions()
1834 self.video.setNextRoom(nextroom)
1835 self.video.setAspectRatio(self.
aspect)
1838 "XXXskip",
"skipmovie",
1840 GUIRect(0,0,1,1,
"normalized"),
1841 clickHandler = self.
onSkip )
1844 return "<movie room %r - %r>" % (self.
index, self.moviepath)
1847 return "MovieRoom %s %s" % (self.
index, self.moviepath)
1850 if key
in (13, 10, 27):
1851 self.skipzone.onClick({})
1854 self.video.stopPlaying()
1855 if self.video.nextRoom:
1856 Base.SetCurRoom(self.video.nextRoom.getIndex())
1866 self.video.setAspectRatio(self.aspect)