1 """
2 Sprites representing parts of a slide that the user can click.
3 """
4 import pyzzle, media, Text, Slide
5 import pygame
6 import os
7 from pygame.rect import Rect
8 from pygame.sprite import Sprite
9 from pygame.font import *
10 from pygame.locals import *
11 import standard
12 from RelativeRect import RelativeRect
13 from DB import Table,Row
16 """Pygame Sprites representing parts of a slide
17 that the user can click."""
18 __metaclass__=Table
19
20 cursorDefault ='fwd.png'
21 """The value of the cursor attribute when no other is specified"""
22
23 @staticmethod
25 row=Row(cells)
26 parent =Slide.Slide[row.parent] if row.parent else None
27 link =Slide.Slide[row.link] if row.link else None
28 hotspot=Hotspot(parent, link,
29 rectRel=RelativeRect((row.left, row.top, row.width, row.height)),
30 cursor=row.cursor,
31 text=row.text,
32 layer=row.layer,
33 id=row.id,
34 soundfile=row.sound,
35 delay=row.delay,
36 zip=row.zip)
37 if any((row.dragleft,row.dragtop,
38 row.dragwidth,row.dragheight)):
39 hotspot.drag=RelativeRect((row.dragleft,row.dragtop,
40 row.dragwidth,row.dragheight))
41 if parent:
42 parent.add(hotspot)
43 if row.transition:
44 hotspot.onTransition=getattr(standard, row.transition)
45 return hotspot
47 cells= \
48 {'id':self.id,
49 'parent':self.parent.id if self.parent else None,
50 'link' :self.link.id if self.link else None,
51 'cursor':self.cursor,
52 'sound' :self.soundfile,
53 'delay' :self.delay,
54 'layer' :self._layer,
55 'text' :self.text,
56 'zip' :self.zip}
57 if 'lambda' not in self.onTransition.__name__:
58 cells['transition']=self.onTransition.__name__
59 if self._rect:
60 self.rectRel=RelativeRect(self._rect, self.parent.rect)
61 for attr in 'left','top','width','height':
62 cells[attr]=getattr(self.rectRel,attr)
63 if self.drag:
64 for attr in 'left','top','width','height':
65 cells['drag'+attr]=getattr(self.drag,attr)
66 return cells
67 - def __init__(self, parent, link, id=None,
68 rectRel=None, layer=0.0,
69 cursor='', delay=.1, zip=False,
70 soundfile=None, text=None, drag=None,
71 _template=False,
72
73 onClick=lambda self: Hotspot.transition(self),
74 onHighlight=lambda self:None,
75 onTransition=standard._noTransition):
76 """
77 Creates a new Hotspot.
78 @param id: A unique identifier for the Hotspot. Used to refer to the Hotspot
79 in scripts and the database
80 @param parent: Panel in which the Hotspot resides.
81 Note: This will not add the slide to the panel. You still need to add it
82 using Slide.enter() or one of the transition functions
83 @param link: Slide the Hotspot will transition to when clicked
84 @type rectRel: RelativeRect
85 @param rectRel: The relative coordinates representing the area
86 that can be clicked. Coordinates are relative to the size of parent.
87 @param layer: The layer of the Hotspot. Larger numbers represent upper layers.
88 When the player clicks on an area of the Slide where two Sprites overlap,
89 the sprite with the topmost layer is the only one that's activated.
90 @param cursor: The name of the cursor file that is displayed
91 when no hotspots are highlighted. The default is defined by
92 Panel.cursorDefault. None displays no cursor.
93 @param delay: The time that should be taken to transition from parent to link
94 when the Hotspot is clicked.
95 @param zip: Whether this is a zip hotspot
96 (disabled unless zip mode is on and the link has been visited).
97 @param soundfile: The name of the sound file played when the user clicks
98 the hotspot.
99 @param text: Text that is displayed under the cursor when the user highlights
100 the Hotspot.
101 @type drag: RelativeRect
102 @param drag: The relative coordinates of the area the user
103 must drag to activate the hotspot.
104 @param onClick: The function that is called when the Hotspot is clicked.
105 If the drag parameter is specified, this function will not be
106 called until the user has dragged the Hotspot to the correct location.
107 @param onHighlight: The function that is called when the Hotspot is clicked.
108 @param onTransition: The transition function used to transition from
109 parent to link when the Hotspot is clicked.
110 """
111 if id and not _template: Hotspot.rows[id]=self
112 Sprite.__init__(self)
113 self.id=id
114 self.parent=parent
115 self._link=link
116 self._setLink(link)
117 self.rectRel=rectRel
118 self._rect=None
119 self.cursor=Hotspot.cursorDefault if cursor == '' else cursor
120 self.delay=delay
121 self._layer=layer
122 self.soundfile=soundfile
123 self.drag=drag
124 self.used=False
125 self.text=text
126 self.zip=zip
127
128 self._template=_template
129 self._enabled=True
130
131 self.onClick=onClick
132 self.onHighlight=onHighlight
133 self.onTransition=onTransition
134
136 """The portion of the screen the user may click to
137 activate the Hotspot."""
138 if self.rectRel:
139 self._rect=self.rectRel.absolute(self.parent.rect)
140 return self._rect
142 rect=property(_getRect, _setRect)
143
145 """The Panel the user will transition to
146 when the Hotspot is activated"""
147 return self._link
149 if hasattr(self._link, 'links'): self._link.links.remove(self)
150 self._link=slide
151 if hasattr(self._link, 'links'): self._link.links.add(self)
152 link=property(_getLink, _setLink)
153
155 if not self._enabled:return False
156 if not self.zip: return True
157 return pyzzle.zip and self.link and self.link.visited
160 enabled=property(_getEnabled,_setEnabled)
161
162 - def draw(self,screen):
169 """Called when the user hovers over the Hotspot.
170 Draws cursor and text to the screen, where present,
171 and calls onHighlight"""
172 if self.text or (self._link and pyzzle.design):
173 text=Text.Text(self._link.id if pyzzle.design else self.text)
174 text._getImage()
175 textrect=text.rect
176 textrect.topleft=pyzzle.cursor.rect.bottomright
177 textrect.clamp_ip(pyzzle.screen.get_rect())
178 text.draw(pyzzle.screen)
179 self.onHighlight(self)
180 return self.cursor
181
182 - def click(self, design=False):
183 """Called when the user clicks the Hotspot.
184 Plays soundfile and calls onClick.
185 If drag is specified, the user must drag the cursor
186 to the drag area in order to call onClick.
187 @param design: Used only for design mode
188 """
189 if design:
190 self.design()
191 return
192 if pyzzle.design and not self.link:
193 return
194
195 if self.soundfile:
196 media.sounds.load(self.soundfile).play()
197
198 activate=not self.drag
199 if self.drag:
200 rect=self.drag.absolute(self.parent.rect)
201 pos=pyzzle.drag()
202 activate=rect.collidepoint(pos)
203
204 if activate:
205 self.onClick(self)
206 self.used=True
208 """Commands the hotspot to transition the user to the link.
209 onClick must explicitly call this to transition the user.
210 This makes it very easy to script certain behavior,
211 such as for locks."""
212 self.onTransition(self.parent, self._link, self.delay)
213
214
216 selected=pyzzle.dragRect(color=(255,0,255)) if drag else Rect(0,0,0,0)
217 if selected.width>10 and selected.height>10:
218
219 hotspotname=pyzzle.promptText("Enter hotspot name: ",
220 self.parent.id+self.id if self.id else '')
221 if hotspotname and hotspotname not in Hotspot.rows:
222 cursorfile=pyzzle.promptText("Enter cursor file: ", Hotspot.cursorDefault)
223 if cursorfile:
224 hotspot=Hotspot(parent=self.parent, link=None,
225 cursor=cursorfile, id=hotspotname)
226 hotspot.onTransition=standard.transition
227 hotspot.rect=selected
228 self.parent.add(hotspot)
229 hotspot.design(drag=False)
230 elif pygame.key.get_mods() & KMOD_CTRL:
231
232 if self._template:
233 self._link=None
234 else:
235 self.kill()
236 else:
237
238 slidename=pyzzle.promptText('Enter slide name:')
239 if slidename:
240 slide=None
241 if slidename in Slide.Slide.rows:
242 slide=Slide.Slide[slidename]
243 else:
244 slidefile=pyzzle.promptText('Enter slide file:', slidename+'.jpg')
245 if slidefile:
246 slide=Slide.Slide(slidename, slidefile, self.parent.stage)
247 slide._refs={}
248 for ref in 'forward', 'up', 'down', 'right', 'left':
249 slide._refs[ref]=None
250 slide._loadRefs()
251 slide.insert()
252 if slide:
253 self._link=slide
254 self.click()
255