Gestion explicite de KeyboardInterrupt pour être sûr de quitter le jeu sur un ^C.
[minwii.git] / src / minwii / widgets / instrumentselector.py
1 # -*- coding: utf-8 -*-
2 """
3 Écran de sélection de l'instrument
4
5 $Id$
6 $URL$
7 """
8 import os.path
9 import pygame
10 from minwii.eventutils import event_handler, EventDispatcher, EventHandlerMixin
11 from minwii.config import FRAMERATE
12 from minwii.config import INSTRUMENTS
13 from minwii.globals import BACKGROUND_LAYER
14 from minwii.globals import FOREGROUND_LAYER
15 from minwii.globals import CURSOR_LAYER
16 from minwii.globals import hls_to_rgba_8bits
17 from cursors import WarpingCursor
18
19
20 class InstrumentSelector(pygame.sprite.LayeredDirty, EventHandlerMixin) :
21
22 rows = 3
23 cols = 3
24 instruments = INSTRUMENTS
25
26 def __init__(self) :
27 super(InstrumentSelector, self).__init__()
28 #self._initRects()
29 self._initTiles()
30 self._initCursor()
31 self._inflatedTile = None
32 self.selectedInstrument = None
33
34 def _initTiles(self) :
35 screen = pygame.display.get_surface()
36 tileWidth = int(round(float(screen.get_width()) / self.cols))
37 tileHeight = int(round(float(screen.get_height()) / self.rows))
38
39 self.tiles = []
40 instrus = list(self.instruments[:])
41 for y in range(self.cols) :
42 for x in range(self.rows) :
43 upperLeftCorner = (x * tileWidth, y * tileHeight)
44 rect = pygame.Rect(upperLeftCorner, (tileWidth, tileHeight))
45 # !!! s'il y avait plus de 3x3 tuiles !!!, il faudrait alors
46 # changer le tuple (x,y) qui concerne le point d'application de l'homotétie.
47 # Cf. InstrumentTile.inflate
48 tile = InstrumentTile(instrus.pop(0), self, rect, (x,y))
49 self.add(tile, layer=BACKGROUND_LAYER)
50 self.tiles.append(tile)
51
52 def _initCursor(self) :
53 self.cursor = WarpingCursor(blinkMode=True)
54 self.add(self.cursor, layer=CURSOR_LAYER)
55
56
57 def run(self):
58 self._running = True
59 clock = pygame.time.Clock()
60 pygame.display.flip()
61 pygame.mouse.set_visible(False)
62 while self._running :
63 try :
64 EventDispatcher.dispatchEvents()
65 dirty = self.draw(pygame.display.get_surface())
66 pygame.display.update(dirty)
67 clock.tick(FRAMERATE)
68 except KeyboardInterrupt :
69 self.stop()
70 raise
71
72 def stop(self) :
73 self._running = False
74 pygame.mouse.set_visible(True)
75 self.cursor._stopBlink()
76
77 @event_handler(pygame.KEYDOWN)
78 def handleKeyDown(self, event) :
79 if event.key in (pygame.K_q, pygame.K_ESCAPE) or \
80 event.unicode == u'q' :
81 self.stop()
82
83 @event_handler(pygame.MOUSEMOTION)
84 def onMouseMove(self, event) :
85 for tile in reversed(self.sprites()[:-1]) :
86 if tile.rect.collidepoint(*event.pos) :
87 self.raiseTileOver(tile)
88 break
89
90 def raiseTileOver(self, tile) :
91 if not tile.inflated :
92 self.change_layer(tile, FOREGROUND_LAYER)
93 tile.inflate(tile.coords)
94
95 if self._inflatedTile :
96 self._inflatedTile.deflate()
97 self.change_layer(self._inflatedTile, BACKGROUND_LAYER)
98
99 self._inflatedTile = tile
100
101 @event_handler(pygame.MOUSEBUTTONDOWN)
102 def selectInstrument(self, event) :
103 for tile in reversed(self.sprites()[:-1]) :
104 if tile.rect.collidepoint(*event.pos) :
105 self.selectedInstrument = tile.instrumentDescription
106 self.stop()
107 break
108
109
110
111 class InstrumentTile(pygame.sprite.DirtySprite) :
112
113 @staticmethod
114 def _get_instrument_image(name) :
115 imagePath = os.path.abspath(__file__).split(os.path.sep)[:-1]
116 imagePath.extend(['data', 'instruments'])
117 name, ext = os.path.splitext(name)
118 imagePath.append('%s%s' % (name, ext or '.jpg'))
119 return os.path.sep.join(imagePath)
120
121 BORDER = 10
122 INFLATE_ZOOM = 0.4
123
124 def __init__(self, instrumentDescription, group, rect, coords) :
125 pygame.sprite.DirtySprite.__init__(self, group)
126 self.inflated = False
127 self.instrumentDescription = instrumentDescription
128 self.rect = rect
129 self._baseRect = rect.copy()
130 self.coords = coords
131 imagePath = InstrumentTile._get_instrument_image(instrumentDescription['name'])
132 self._img = pygame.image.load(imagePath)
133 self.update()
134
135
136 def update(self) :
137 innerWidth, innerHeight = [l-self.BORDER*2 for l in self.rect.size]
138 innerSize = innerWidth, innerHeight
139
140 border = pygame.Surface(self.rect.size)
141 border.fill((0xdd,0xdd,0xdd,255))
142
143 bg = pygame.Surface(innerSize)
144 bg.fill((255,255,255,255))
145 bgRect = pygame.Rect((self.BORDER, self.BORDER), innerSize)
146
147 img = self._img
148 iWidth, iHeight = img.get_size()
149 imgRatio = float(iWidth) / iHeight
150
151 # adapts dimensions
152 iw = innerWidth
153 ih = int(round(innerWidth / imgRatio))
154
155 if ih > innerHeight:
156 ih = innerHeight
157 iw = int(round(innerHeight * imgRatio))
158
159 imgPosition = ((innerWidth - iw) / 2, (innerHeight - ih) / 2)
160 imgRect = pygame.Rect(imgPosition, (iw, ih))
161 img = pygame.transform.smoothscale(img, (iw, ih))
162
163 bg.blit(img, imgRect)
164 border.blit(bg, bgRect)
165 self.image = border
166
167
168 def inflate(self, refPoint) :
169 self.inflated = True
170 keep = {}
171 for name in REF_POINTS[refPoint] :
172 keep[name] = getattr(self.rect, name)
173
174 self.rect.inflate_ip(*[l*self.INFLATE_ZOOM for l in self.rect.size])
175
176 for k, v in keep.items() :
177 setattr(self.rect, k, v)
178
179 self.update()
180 self.dirty = 1
181
182
183 def deflate(self) :
184 self.inflated = False
185 self.rect = self._baseRect.copy()
186 self.update()
187 self.dirty = 1
188
189
190
191 REF_POINTS = {
192 (0, 0) : ['top', 'left'],
193 (1, 0) : ['top'],
194 (2, 0) : ['top', 'right'],
195
196 (0, 1) : ['left'],
197 (1, 1) : [],
198 (2, 1) : ['right'],
199
200 (0, 2) : ['bottom', 'left'],
201 (1, 2) : ['bottom'],
202 (2, 2) : ['bottom', 'right']
203 }