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