Module Dialogs
[hide private]
[frames] | no frames]

Source Code for Module Dialogs

   1  ##################################################################### 
   2  # -*- coding: iso-8859-1 -*-                                        # 
   3  #                                                                   # 
   4  # Frets on Fire                                                     # 
   5  # Copyright (C) 2006 Sami Kyöstilä                                  # 
   6  #                                                                   # 
   7  # This program is free software; you can redistribute it and/or     # 
   8  # modify it under the terms of the GNU General Public License       # 
   9  # as published by the Free Software Foundation; either version 2    # 
  10  # of the License, or (at your option) any later version.            # 
  11  #                                                                   # 
  12  # This program is distributed in the hope that it will be useful,   # 
  13  # but WITHOUT ANY WARRANTY; without even the implied warranty of    # 
  14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     # 
  15  # GNU General Public License for more details.                      # 
  16  #                                                                   # 
  17  # You should have received a copy of the GNU General Public License # 
  18  # along with this program; if not, write to the Free Software       # 
  19  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,        # 
  20  # MA  02110-1301, USA.                                              # 
  21  ##################################################################### 
  22   
  23  """A bunch of dialog functions for interacting with the user.""" 
  24   
  25  import pygame 
  26  from OpenGL.GL import * 
  27  from OpenGL.GLU import * 
  28  import math 
  29  import os 
  30  import fnmatch 
  31   
  32  from View import Layer, BackgroundLayer 
  33  from Input import KeyListener 
  34  from Camera import Camera 
  35  from Mesh import Mesh 
  36  from Menu import Menu 
  37  from Language import _ 
  38  from Texture import Texture 
  39  import Theme 
  40  import Log 
  41  import Song 
  42  import Data 
  43  import Player 
  44  import Guitar 
  45   
46 -def wrapText(font, pos, text, rightMargin = 0.9, scale = 0.002, visibility = 0.0):
47 """ 48 Wrap a piece of text inside given margins. 49 50 @param pos: (x, y) tuple, x defines the left margin 51 @param text: Text to wrap 52 @param rightMargin: Right margin 53 @param scale: Text scale 54 @param visibility: Visibility factor [0..1], 0 is fully visible 55 """ 56 x, y = pos 57 space = font.getStringSize(" ", scale = scale)[0] 58 59 for n, word in enumerate(text.split(" ")): 60 w, h = font.getStringSize(word, scale = scale) 61 if x + w > rightMargin or word == "\n": 62 x = pos[0] 63 y += h 64 if word == "\n": 65 continue 66 glPushMatrix() 67 glRotate(visibility * (n + 1) * -45, 0, 0, 1) 68 font.render(word, (x, y + visibility * n), scale = scale) 69 glPopMatrix() 70 x += w + space 71 return (x - space, y)
72
73 -def fadeScreen(v):
74 """ 75 Fade the screen to a dark color to make whatever is on top easier to read. 76 77 @param v: Visibility factor [0..1], 0 is fully visible 78 """ 79 glEnable(GL_BLEND) 80 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 81 glEnable(GL_COLOR_MATERIAL) 82 83 glBegin(GL_TRIANGLE_STRIP) 84 glColor4f(0, 0, 0, .3 - v * .3) 85 glVertex2f(0, 0) 86 glColor4f(0, 0, 0, .3 - v * .3) 87 glVertex2f(1, 0) 88 glColor4f(0, 0, 0, .9 - v * .9) 89 glVertex2f(0, 1) 90 glColor4f(0, 0, 0, .9 - v * .9) 91 glVertex2f(1, 1) 92 glEnd()
93 94
95 -class GetText(Layer, KeyListener):
96 """Text input layer."""
97 - def __init__(self, engine, prompt = "", text = ""):
98 self.text = text 99 self.prompt = prompt 100 self.engine = engine 101 self.time = 0 102 self.accepted = False
103
104 - def shown(self):
105 self.engine.input.addKeyListener(self, priority = True) 106 self.engine.input.enableKeyRepeat()
107
108 - def hidden(self):
109 self.engine.input.removeKeyListener(self) 110 self.engine.input.disableKeyRepeat()
111
112 - def keyPressed(self, key, unicode):
113 self.time = 0 114 c = self.engine.input.controls.getMapping(key) 115 if (c in [Player.KEY1] or key == pygame.K_RETURN) and not self.accepted: 116 self.engine.view.popLayer(self) 117 self.accepted = True 118 elif c in [Player.CANCEL, Player.KEY2] and not self.accepted: 119 self.text = None 120 self.engine.view.popLayer(self) 121 self.accepted = True 122 elif key == pygame.K_BACKSPACE and not self.accepted: 123 self.text = self.text[:-1] 124 elif unicode and ord(unicode) > 31 and not self.accepted: 125 self.text += unicode 126 return True
127
128 - def run(self, ticks):
129 self.time += ticks / 50.0
130
131 - def render(self, visibility, topMost):
132 self.engine.view.setOrthogonalProjection(normalize = True) 133 font = self.engine.data.font 134 135 try: 136 v = (1 - visibility) ** 2 137 138 fadeScreen(v) 139 Theme.setBaseColor(1 - v) 140 141 if (self.time % 10) < 5 and visibility > .9: 142 cursor = "|" 143 else: 144 cursor = "" 145 146 pos = wrapText(font, (.1, .33 - v), self.prompt) 147 148 Theme.setSelectedColor(1 - v) 149 150 if self.text is not None: 151 pos = wrapText(font, (.1, (pos[1] + v) + .08 + v / 4), self.text) 152 font.render(cursor, pos) 153 154 finally: 155 self.engine.view.resetProjection()
156
157 -class GetKey(Layer, KeyListener):
158 """Key choosing layer."""
159 - def __init__(self, engine, prompt = "", key = None):
160 self.key = key 161 self.prompt = prompt 162 self.engine = engine 163 self.time = 0 164 self.accepted = False
165
166 - def shown(self):
167 self.engine.input.addKeyListener(self, priority = True)
168
169 - def hidden(self):
170 self.engine.input.removeKeyListener(self)
171
172 - def keyPressed(self, key, unicode):
173 c = self.engine.input.controls.getMapping(key) 174 if c in [Player.CANCEL, Player.KEY2] and not self.accepted: 175 self.key = None 176 self.engine.view.popLayer(self) 177 self.accepted = True 178 elif not self.accepted: 179 self.key = key 180 self.engine.view.popLayer(self) 181 self.accepted = True 182 return True
183
184 - def run(self, ticks):
185 self.time += ticks / 50.0
186
187 - def render(self, visibility, topMost):
188 self.engine.view.setOrthogonalProjection(normalize = True) 189 font = self.engine.data.font 190 191 try: 192 v = (1 - visibility) ** 2 193 194 fadeScreen(v) 195 Theme.setBaseColor(1 - v) 196 197 pos = wrapText(font, (.1, .33 - v), self.prompt) 198 199 Theme.setSelectedColor(1 - v) 200 201 if self.key is not None: 202 text = pygame.key.name(self.key).capitalize() 203 pos = wrapText(font, (.1, (pos[1] + v) + .08 + v / 4), text) 204 205 finally: 206 self.engine.view.resetProjection()
207
208 -class LoadingScreen(Layer, KeyListener):
209 """Loading screen layer."""
210 - def __init__(self, engine, condition, text, allowCancel = False):
211 self.engine = engine 212 self.text = text 213 self.condition = condition 214 self.ready = False 215 self.allowCancel = allowCancel 216 self.time = 0.0
217
218 - def shown(self):
219 self.engine.input.addKeyListener(self, priority = True)
220
221 - def keyPressed(self, key, unicode):
222 c = self.engine.input.controls.getMapping(key) 223 if self.allowCancel and c == Player.CANCEL: 224 self.engine.view.popLayer(self) 225 return True
226
227 - def hidden(self):
228 self.engine.boostBackgroundThreads(False) 229 self.engine.input.removeKeyListener(self)
230
231 - def run(self, ticks):
232 self.time += ticks / 50.0 233 if not self.ready and self.condition(): 234 self.engine.view.popLayer(self) 235 self.ready = True
236
237 - def render(self, visibility, topMost):
238 self.engine.view.setOrthogonalProjection(normalize = True) 239 font = self.engine.data.font 240 241 if not font: 242 return 243 244 if visibility > 0.9: 245 self.engine.boostBackgroundThreads(True) 246 else: 247 self.engine.boostBackgroundThreads(False) 248 249 try: 250 v = (1 - visibility) ** 2 251 fadeScreen(v) 252 253 w, h = self.engine.view.geometry[2:4] 254 self.engine.data.loadingImage.transform.reset() 255 self.engine.data.loadingImage.transform.translate(w / 2, (1.0 - v * .25) * h / 2) 256 self.engine.data.loadingImage.transform.scale(1, -1) 257 self.engine.data.loadingImage.draw(color = (1, 1, 1, visibility)) 258 259 Theme.setBaseColor(1 - v) 260 w, h = font.getStringSize(self.text) 261 x = .5 - w / 2 262 y = .6 - h / 2 + v * .5 263 264 font.render(self.text, (x, y)) 265 266 finally: 267 self.engine.view.resetProjection()
268
269 -class MessageScreen(Layer, KeyListener):
270 """Message screen layer."""
271 - def __init__(self, engine, text, prompt = _("<OK>")):
272 self.engine = engine 273 self.text = text 274 self.time = 0.0 275 self.prompt = prompt
276
277 - def shown(self):
278 self.engine.input.addKeyListener(self, priority = True)
279
280 - def keyPressed(self, key, unicode):
281 c = self.engine.input.controls.getMapping(key) 282 if c in [Player.KEY1, Player.KEY2, Player.CANCEL] or key == pygame.K_RETURN: 283 self.engine.view.popLayer(self) 284 return True
285
286 - def hidden(self):
287 self.engine.input.removeKeyListener(self)
288
289 - def run(self, ticks):
290 self.time += ticks / 50.0
291
292 - def render(self, visibility, topMost):
293 self.engine.view.setOrthogonalProjection(normalize = True) 294 font = self.engine.data.font 295 296 if not font: 297 return 298 299 try: 300 v = (1 - visibility) ** 2 301 fadeScreen(v) 302 303 x = .1 304 y = .3 + v * 2 305 Theme.setBaseColor(1 - v) 306 pos = wrapText(font, (x, y), self.text, visibility = v) 307 308 w, h = font.getStringSize(self.prompt, scale = 0.001) 309 x = .5 - w / 2 310 y = pos[1] + 3 * h + v * 2 311 Theme.setSelectedColor(1 - v) 312 font.render(self.prompt, (x, y), scale = 0.001) 313 314 finally: 315 self.engine.view.resetProjection()
316
317 -class SongChooser(Layer, KeyListener):
318 """Song choosing layer."""
319 - def __init__(self, engine, prompt = "", selectedSong = None, selectedLibrary = None):
320 self.prompt = prompt 321 self.engine = engine 322 self.time = 0 323 self.accepted = False 324 self.selectedIndex = 0 325 self.camera = Camera() 326 self.cassetteHeight = .8 327 self.cassetteWidth = 4.0 328 self.libraryHeight = 1.2 329 self.libraryWidth = 4.0 330 self.itemAngles = None 331 self.itemLabels = None 332 self.selectedOffset = 0.0 333 self.cameraOffset = 0.0 334 self.selectedItem = None 335 self.song = None 336 self.songCountdown = 1024 337 self.songLoader = None 338 self.initialItem = selectedSong 339 self.library = selectedLibrary 340 self.searchText = "" 341 342 # Use the default library if this one doesn't exist 343 if not self.library or not os.path.isdir(self.engine.resource.fileName(self.library)): 344 self.library = Song.DEFAULT_LIBRARY 345 346 self.loadCollection() 347 self.engine.resource.load(self, "cassette", lambda: Mesh(self.engine.resource.fileName("cassette.dae")), synch = True) 348 self.engine.resource.load(self, "label", lambda: Mesh(self.engine.resource.fileName("label.dae")), synch = True) 349 self.engine.resource.load(self, "libraryMesh", lambda: Mesh(self.engine.resource.fileName("library.dae")), synch = True) 350 self.engine.resource.load(self, "libraryLabel", lambda: Mesh(self.engine.resource.fileName("library_label.dae")), synch = True) 351 352 self.engine.loadSvgDrawing(self, "background", "cassette.svg")
353
354 - def loadCollection(self):
355 self.loaded = False 356 self.engine.resource.load(self, "libraries", lambda: Song.getAvailableLibraries(self.engine, self.library), onLoad = self.libraryListLoaded) 357 showLoadingScreen(self.engine, lambda: self.loaded, text = _("Browsing Collection..."))
358
359 - def libraryListLoaded(self, libraries):
360 self.engine.resource.load(self, "songs", lambda: Song.getAvailableSongs(self.engine, self.library), onLoad = self.songListLoaded)
361
362 - def songListLoaded(self, songs):
363 if self.songLoader: 364 self.songLoader.cancel() 365 self.selectedIndex = 0 366 self.items = self.libraries + self.songs 367 self.itemAngles = [0.0] * len(self.items) 368 self.itemLabels = [None] * len(self.items) 369 self.loaded = True 370 self.searchText = "" 371 if self.initialItem is not None: 372 for i, item in enumerate(self.items): 373 if isinstance(item, Song.SongInfo) and self.initialItem == item.songName: 374 self.selectedIndex = i 375 break 376 elif isinstance(item, Song.LibraryInfo) and self.initialItem == item.libraryName: 377 self.selectedIndex = i 378 break 379 # Load labels for libraries right away 380 for i, item in enumerate(self.items): 381 if isinstance(item, Song.LibraryInfo): 382 self.loadItemLabel(i) 383 self.updateSelection()
384
385 - def shown(self):
386 self.engine.input.addKeyListener(self, priority = True) 387 self.engine.input.enableKeyRepeat()
388
389 - def hidden(self):
390 if self.songLoader: 391 self.songLoader.cancel() 392 if self.song: 393 self.song.fadeout(1000) 394 self.song = None 395 self.engine.input.removeKeyListener(self) 396 self.engine.input.disableKeyRepeat()
397
398 - def getSelectedSong(self):
399 if isinstance(self.selectedItem, Song.SongInfo): 400 return self.selectedItem.songName
401
402 - def getSelectedLibrary(self):
403 return self.library
404
405 - def loadItemLabel(self, i):
406 # Load the item label if it isn't yet loaded 407 item = self.items[i] 408 if self.itemLabels[i] is None: 409 if isinstance(item, Song.SongInfo): 410 label = self.engine.resource.fileName(self.library, item.songName, "label.png") 411 else: 412 assert isinstance(item, Song.LibraryInfo) 413 label = self.engine.resource.fileName(item.libraryName, "label.png") 414 if os.path.exists(label): 415 self.itemLabels[i] = Texture(label)
416
417 - def updateSelection(self):
418 self.selectedItem = self.items[self.selectedIndex] 419 self.songCountdown = 1024 420 self.loadItemLabel(self.selectedIndex)
421
422 - def keyPressed(self, key, unicode):
423 if not self.items or self.accepted: 424 return 425 426 c = self.engine.input.controls.getMapping(key) 427 if c in [Player.KEY1] or key == pygame.K_RETURN: 428 if self.matchesSearch(self.selectedItem): 429 if isinstance(self.selectedItem, Song.LibraryInfo): 430 self.library = self.selectedItem.libraryName 431 self.initialItem = None 432 self.loadCollection() 433 else: 434 self.engine.view.popLayer(self) 435 self.accepted = True 436 if not self.song: 437 self.engine.data.acceptSound.play() 438 elif c in [Player.CANCEL, Player.KEY2]: 439 if self.library != Song.DEFAULT_LIBRARY: 440 self.initialItem = self.library 441 self.library = os.path.dirname(self.library) 442 self.loadCollection() 443 else: 444 self.selectedItem = None 445 self.engine.view.popLayer(self) 446 self.accepted = True 447 if not self.song: 448 self.engine.data.cancelSound.play() 449 elif c in [Player.UP, Player.ACTION1]: 450 if self.matchesSearch(self.items[self.selectedIndex]): 451 while 1: 452 self.selectedIndex = (self.selectedIndex - 1) % len(self.items) 453 if self.matchesSearch(self.items[self.selectedIndex]): 454 break 455 self.updateSelection() 456 if not self.song: 457 self.engine.data.selectSound.play() 458 elif c in [Player.DOWN, Player.ACTION2]: 459 if self.matchesSearch(self.items[self.selectedIndex]): 460 while 1: 461 self.selectedIndex = (self.selectedIndex + 1) % len(self.items) 462 if self.matchesSearch(self.items[self.selectedIndex]): 463 break 464 self.updateSelection() 465 if not self.song: 466 self.engine.data.selectSound.play() 467 elif key == pygame.K_BACKSPACE and not self.accepted: 468 self.searchText = self.searchText[:-1] 469 elif unicode and ord(unicode) > 31 and not self.accepted: 470 self.searchText += unicode 471 self.doSearch() 472 return True
473
474 - def matchesSearch(self, item):
475 if not self.searchText: 476 return True 477 if isinstance(item, Song.SongInfo): 478 if self.searchText.lower() in item.name.lower() or self.searchText.lower() in item.artist.lower(): 479 return True 480 elif isinstance(item, Song.LibraryInfo): 481 if self.searchText.lower() in item.name.lower(): 482 return True 483 return False
484
485 - def doSearch(self):
486 if not self.searchText: 487 return 488 489 for i, item in enumerate(self.items): 490 if self.matchesSearch(item): 491 self.selectedIndex = i 492 self.updateSelection() 493 break
494
495 - def songLoaded(self, song):
496 self.songLoader = None 497 498 if self.song: 499 self.song.stop() 500 501 song.setGuitarVolume(self.engine.config.get("audio", "guitarvol")) 502 song.setBackgroundVolume(self.engine.config.get("audio", "songvol")) 503 song.setRhythmVolume(self.engine.config.get("audio", "rhythmvol")) 504 song.play() 505 self.song = song
506
507 - def playSelectedSong(self):
508 song = self.getSelectedSong() 509 if not song: 510 return 511 512 if self.songLoader: 513 self.songLoader.cancel() 514 # Don't start a new song loader until the previous one is finished 515 if self.songLoader.isAlive(): 516 self.songCountdown = 256 517 return 518 519 if self.song: 520 self.song.fadeout(1000) 521 522 self.songLoader = self.engine.resource.load(self, None, lambda: Song.loadSong(self.engine, song, playbackOnly = True, library = self.library), 523 onLoad = self.songLoaded)
524
525 - def run(self, ticks):
526 self.time += ticks / 50.0 527 528 if self.songCountdown > 0: 529 self.songCountdown -= ticks 530 if self.songCountdown <= 0: 531 self.playSelectedSong() 532 533 d = self.cameraOffset - self.selectedOffset 534 self.cameraOffset -= d * ticks / 192.0 535 536 for i in range(len(self.itemAngles)): 537 if i == self.selectedIndex: 538 self.itemAngles[i] = min(90, self.itemAngles[i] + ticks / 2.0) 539 else: 540 self.itemAngles[i] = max(0, self.itemAngles[i] - ticks / 2.0)
541
542 - def renderCassette(self, color, label):
543 if not self.cassette: 544 return 545 546 if color: 547 glColor3f(*color) 548 549 glEnable(GL_COLOR_MATERIAL) 550 self.cassette.render("Mesh_001") 551 glColor3f(.1, .1, .1) 552 self.cassette.render("Mesh") 553 554 # Draw the label if there is one 555 if label is not None: 556 glEnable(GL_TEXTURE_2D) 557 label.bind() 558 glColor3f(1, 1, 1) 559 glMatrixMode(GL_TEXTURE) 560 glScalef(1, -1, 1) 561 glMatrixMode(GL_MODELVIEW) 562 self.label.render("Mesh_001") 563 glMatrixMode(GL_TEXTURE) 564 glLoadIdentity() 565 glMatrixMode(GL_MODELVIEW) 566 glDisable(GL_TEXTURE_2D)
567
568 - def renderLibrary(self, color, label):
569 if not self.libraryMesh: 570 return 571 572 if color: 573 glColor3f(*color) 574 575 glEnable(GL_NORMALIZE) 576 glEnable(GL_COLOR_MATERIAL) 577 self.libraryMesh.render("Mesh_001") 578 glColor3f(.1, .1, .1) 579 self.libraryMesh.render("Mesh") 580 581 # Draw the label if there is one 582 if label is not None: 583 glEnable(GL_TEXTURE_2D) 584 label.bind() 585 glColor3f(1, 1, 1) 586 glMatrixMode(GL_TEXTURE) 587 glScalef(1, -1, 1) 588 glMatrixMode(GL_MODELVIEW) 589 self.libraryLabel.render() 590 glMatrixMode(GL_TEXTURE) 591 glLoadIdentity() 592 glMatrixMode(GL_MODELVIEW) 593 glDisable(GL_TEXTURE_2D) 594 glDisable(GL_NORMALIZE)
595
596 - def render(self, visibility, topMost):
597 v = (1 - visibility) ** 2 598 599 # render the background 600 t = self.time / 100 601 w, h, = self.engine.view.geometry[2:4] 602 r = .5 603 self.background.transform.reset() 604 self.background.transform.translate(v * 2 * w + w / 2 + math.sin(t / 2) * w / 2 * r, h / 2 + math.cos(t) * h / 2 * r) 605 self.background.transform.rotate(-t) 606 self.background.transform.scale(math.sin(t / 8) + 2, math.sin(t / 8) + 2) 607 self.background.draw() 608 609 # render the item list 610 try: 611 glMatrixMode(GL_PROJECTION) 612 glPushMatrix() 613 glLoadIdentity() 614 gluPerspective(60, self.engine.view.aspectRatio, 0.1, 1000) 615 glMatrixMode(GL_MODELVIEW) 616 glLoadIdentity() 617 618 glEnable(GL_DEPTH_TEST) 619 glDisable(GL_CULL_FACE) 620 glDepthMask(1) 621 622 offset = 10 * (v ** 2) 623 self.camera.origin = (-10 + offset, -self.cameraOffset, 4 + offset) 624 self.camera.target = ( 0 + offset, -self.cameraOffset, 2.5 + offset) 625 self.camera.apply() 626 627 y = 0.0 628 for i, item in enumerate(self.items): 629 if not self.matchesSearch(item): 630 continue 631 632 c = math.sin(self.itemAngles[i] * math.pi / 180) 633 634 if isinstance(item, Song.SongInfo): 635 h = c * self.cassetteWidth + (1 - c) * self.cassetteHeight 636 else: 637 h = c * self.libraryWidth + (1 - c) * self.libraryHeight 638 639 d = (y + h * .5 + self.camera.origin[1]) / (4 * (self.camera.target[2] - self.camera.origin[2])) 640 641 if i == self.selectedIndex: 642 self.selectedOffset = y + h / 2 643 Theme.setSelectedColor() 644 else: 645 Theme.setBaseColor() 646 647 glTranslatef(0, -h / 2, 0) 648 649 glPushMatrix() 650 if abs(d) < 1.2: 651 if isinstance(item, Song.SongInfo): 652 glRotate(self.itemAngles[i], 0, 0, 1) 653 self.renderCassette(item.cassetteColor, self.itemLabels[i]) 654 elif isinstance(item, Song.LibraryInfo): 655 glRotate(-self.itemAngles[i], 0, 0, 1) 656 if i == self.selectedIndex: 657 glRotate(self.time * 4, 1, 0, 0) 658 self.renderLibrary(item.color, self.itemLabels[i]) 659 glPopMatrix() 660 661 glTranslatef(0, -h / 2, 0) 662 y += h 663 glDisable(GL_DEPTH_TEST) 664 glDisable(GL_CULL_FACE) 665 glDepthMask(0) 666 667 finally: 668 glMatrixMode(GL_PROJECTION) 669 glPopMatrix() 670 glMatrixMode(GL_MODELVIEW) 671 672 # render the song info 673 self.engine.view.setOrthogonalProjection(normalize = True) 674 font = self.engine.data.font 675 676 try: 677 glEnable(GL_BLEND) 678 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 679 glEnable(GL_COLOR_MATERIAL) 680 Theme.setBaseColor(1 - v) 681 682 if self.searchText: 683 text = _("Filter: %s") % (self.searchText) + "|" 684 if not self.matchesSearch(self.items[self.selectedIndex]): 685 text += " (%s)" % _("Not found") 686 font.render(text, (.05, .7 + v), scale = 0.001) 687 elif self.songLoader: 688 font.render(_("Loading Preview..."), (.05, .7 + v), scale = 0.001) 689 690 x = .6 691 y = .15 692 font.render(self.prompt, (x, .05 - v)) 693 694 Theme.setSelectedColor(1 - v) 695 696 item = self.items[self.selectedIndex] 697 698 if self.matchesSearch(item): 699 angle = self.itemAngles[self.selectedIndex] 700 f = ((90.0 - angle) / 90.0) ** 2 701 pos = wrapText(font, (x, y), item.name, visibility = f, scale = 0.0016) 702 703 if isinstance(item, Song.SongInfo): 704 Theme.setBaseColor(1 - v) 705 wrapText(font, (x, pos[1] + font.getHeight() * 0.0016), item.artist, visibility = f, scale = 0.0016) 706 707 Theme.setSelectedColor(1 - v) 708 scale = 0.0011 709 w, h = font.getStringSize(self.prompt, scale = scale) 710 x = .6 711 y = .5 + f / 2.0 712 if len(item.difficulties) > 3: 713 y = .42 + f / 2.0 714 715 for d in item.difficulties: 716 scores = item.getHighscores(d) 717 if scores: 718 score, stars, name = scores[0] 719 else: 720 score, stars, name = "---", 0, "---" 721 Theme.setBaseColor(1 - v) 722 font.render(unicode(d), (x, y), scale = scale) 723 font.render(unicode(Data.STAR2 * stars + Data.STAR1 * (5 - stars)), (x, y + h), scale = scale * .9) 724 Theme.setSelectedColor(1 - v) 725 font.render(unicode(score), (x + .15, y), scale = scale) 726 font.render(name, (x + .15, y + h), scale = scale) 727 y += 2 * h + f / 4.0 728 elif isinstance(item, Song.LibraryInfo): 729 Theme.setBaseColor(1 - v) 730 if item.songCount == 1: 731 songCount = _("One song in this library") 732 else: 733 songCount = _("%d songs in this library") % item.songCount 734 wrapText(font, (x, pos[1] + 3 * font.getHeight() * 0.0016), songCount, visibility = f, scale = 0.0016) 735 finally: 736 self.engine.view.resetProjection()
737
738 -class FileChooser(BackgroundLayer, KeyListener):
739 """File choosing layer."""
740 - def __init__(self, engine, masks, path, prompt = ""):
741 self.masks = masks 742 self.path = path 743 self.prompt = prompt 744 self.engine = engine 745 self.accepted = False 746 self.selectedFile = None 747 self.time = 0.0 748 self.menu = None 749 750 self.engine.loadSvgDrawing(self, "background", "editor.svg")
751
752 - def _getFileCallback(self, fileName):
753 return lambda: self.chooseFile(fileName)
754
755 - def _getFileText(self, fileName):
756 f = os.path.join(self.path, fileName) 757 if fileName == "..": 758 return _("[Parent Folder]") 759 if os.path.isdir(f): 760 return _("%s [Folder]") % fileName 761 return fileName
762
763 - def getFiles(self):
764 files = [".."] 765 for fn in os.listdir(self.path): 766 if fn.startswith("."): continue 767 f = os.path.join(self.path, fn) 768 for mask in self.masks: 769 if fnmatch.fnmatch(fn, mask): 770 break 771 else: 772 if not os.path.isdir(f): 773 continue 774 files.append(fn) 775 files.sort() 776 return files
777
778 - def updateFiles(self):
779 if self.menu: 780 self.engine.view.popLayer(self.menu) 781 self.menu = Menu(self.engine, choices = [(self._getFileText(f), self._getFileCallback(f)) for f in self.getFiles()], onClose = self.close, onCancel = self.cancel) 782 self.engine.view.pushLayer(self.menu)
783
784 - def chooseFile(self, fileName):
785 path = os.path.abspath(os.path.join(self.path, fileName)) 786 if os.path.isdir(path): 787 self.path = path 788 self.updateFiles() 789 return 790 self.selectedFile = path 791 accepted = True 792 self.engine.view.popLayer(self.menu) 793 self.engine.view.popLayer(self) 794 self.menu = None
795
796 - def cancel(self):
797 self.accepted = True 798 self.engine.view.popLayer(self)
799
800 - def close(self):
801 if not self.menu: 802 self.accepted = True 803 self.engine.view.popLayer(self)
804
805 - def shown(self):
806 self.updateFiles()
807
808 - def getSelectedFile(self):
809 return self.selectedFile
810
811 - def run(self, ticks):
812 self.time += ticks / 50.0
813
814 - def render(self, visibility, topMost):
815 v = (1 - visibility) ** 2 816 817 # render the background 818 t = self.time / 100 819 w, h, = self.engine.view.geometry[2:4] 820 r = .5 821 self.background.transform.reset() 822 self.background.transform.translate(v * 2 * w + w / 2 + math.sin(t / 2) * w / 2 * r, h / 2 + math.cos(t) * h / 2 * r) 823 self.background.transform.rotate(-t) 824 self.background.transform.scale(math.sin(t / 8) + 2, math.sin(t / 8) + 2) 825 self.background.draw() 826 827 self.engine.view.setOrthogonalProjection(normalize = True) 828 font = self.engine.data.font 829 830 try: 831 glEnable(GL_BLEND) 832 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 833 glEnable(GL_COLOR_MATERIAL) 834 Theme.setBaseColor(1 - v) 835 wrapText(font, (.1, .05 - v), self.prompt) 836 finally: 837 self.engine.view.resetProjection()
838
839 -class ItemChooser(BackgroundLayer, KeyListener):
840 """Item menu layer."""
841 - def __init__(self, engine, items, selected = None, prompt = ""):
842 self.prompt = prompt 843 self.engine = engine 844 self.accepted = False 845 self.selectedItem = None 846 self.time = 0.0 847 self.menu = Menu(self.engine, choices = [(c, self._callbackForItem(c)) for c in items], onClose = self.close, onCancel = self.cancel) 848 if selected and selected in items: 849 self.menu.selectItem(items.index(selected)) 850 self.engine.loadSvgDrawing(self, "background", "editor.svg")
851
852 - def _callbackForItem(self, item):
853 def cb(): 854 self.chooseItem(item)
855 return cb
856
857 - def chooseItem(self, item):
858 self.selectedItem = item 859 accepted = True 860 self.engine.view.popLayer(self.menu) 861 self.engine.view.popLayer(self)
862
863 - def cancel(self):
864 self.accepted = True 865 self.engine.view.popLayer(self)
866
867 - def close(self):
868 self.accepted = True 869 self.engine.view.popLayer(self)
870
871 - def shown(self):
872 self.engine.view.pushLayer(self.menu)
873
874 - def getSelectedItem(self):
875 return self.selectedItem
876
877 - def run(self, ticks):
878 self.time += ticks / 50.0
879
880 - def render(self, visibility, topMost):
881 v = (1 - visibility) ** 2 882 883 # render the background 884 t = self.time / 100 885 w, h, = self.engine.view.geometry[2:4] 886 r = .5 887 self.background.transform.reset() 888 self.background.transform.translate(v * 2 * w + w / 2 + math.sin(t / 2) * w / 2 * r, h / 2 + math.cos(t) * h / 2 * r) 889 self.background.transform.rotate(-t) 890 self.background.transform.scale(math.sin(t / 8) + 2, math.sin(t / 8) + 2) 891 self.background.draw() 892 893 self.engine.view.setOrthogonalProjection(normalize = True) 894 font = self.engine.data.font 895 896 try: 897 glEnable(GL_BLEND) 898 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 899 glEnable(GL_COLOR_MATERIAL) 900 Theme.setBaseColor(1 - v) 901 wrapText(font, (.1, .05 - v), self.prompt) 902 finally: 903 self.engine.view.resetProjection()
904 905
906 -class BpmEstimator(Layer, KeyListener):
907 """Beats per minute value estimation layer."""
908 - def __init__(self, engine, song, prompt = ""):
909 self.prompt = prompt 910 self.engine = engine 911 self.song = song 912 self.accepted = False 913 self.bpm = None 914 self.time = 0.0 915 self.beats = []
916
917 - def shown(self):
918 self.engine.input.addKeyListener(self, priority = True) 919 self.song.play()
920
921 - def hidden(self):
922 self.engine.input.removeKeyListener(self) 923 self.song.fadeout(1000)
924
925 - def keyPressed(self, key, unicode):
926 if self.accepted: 927 return True 928 929 c = self.engine.input.controls.getMapping(key) 930 if key == pygame.K_SPACE: 931 self.beats.append(self.time) 932 if len(self.beats) > 12: 933 diffs = [self.beats[i + 1] - self.beats[i] for i in range(len(self.beats) - 1)] 934 self.bpm = 60000.0 / (sum(diffs) / float(len(diffs))) 935 self.beats = self.beats[-12:] 936 elif c in [Player.CANCEL, Player.KEY2]: 937 self.engine.view.popLayer(self) 938 self.accepted = True 939 self.bpm = None 940 elif c in [Player.KEY1] or key == pygame.K_RETURN: 941 self.engine.view.popLayer(self) 942 self.accepted = True 943 944 return True
945
946 - def run(self, ticks):
947 self.time += ticks
948
949 - def render(self, visibility, topMost):
950 v = (1 - visibility) ** 2 951 952 self.engine.view.setOrthogonalProjection(normalize = True) 953 font = self.engine.data.font 954 955 fadeScreen(v) 956 957 try: 958 glEnable(GL_BLEND) 959 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 960 glEnable(GL_COLOR_MATERIAL) 961 Theme.setBaseColor(1 - v) 962 wrapText(font, (.1, .2 - v), self.prompt) 963 964 if self.bpm is not None: 965 Theme.setSelectedColor(1 - v) 966 wrapText(font, (.1, .5 + v), _("%.2f beats per minute") % (self.bpm)) 967 finally: 968 self.engine.view.resetProjection()
969
970 -class KeyTester(Layer, KeyListener):
971 """Keyboard configuration testing layer."""
972 - def __init__(self, engine, prompt = ""):
973 self.prompt = prompt 974 self.engine = engine 975 self.accepted = False 976 self.time = 0.0 977 self.controls = Player.Controls() 978 self.fretColors = Theme.fretColors
979
980 - def shown(self):
981 self.engine.input.addKeyListener(self, priority = True)
982
983 - def hidden(self):
984 self.engine.input.removeKeyListener(self)
985
986 - def keyPressed(self, key, unicode):
987 if self.accepted: 988 return True 989 990 self.controls.keyPressed(key) 991 c = self.engine.input.controls.getMapping(key) 992 if c in [Player.CANCEL]: 993 self.engine.view.popLayer(self) 994 self.accepted = True 995 return True
996
997 - def keyReleased(self, key):
998 self.controls.keyReleased(key)
999
1000 - def run(self, ticks):
1001 self.time += ticks
1002
1003 - def render(self, visibility, topMost):
1004 v = (1 - visibility) ** 2 1005 1006 self.engine.view.setOrthogonalProjection(normalize = True) 1007 font = self.engine.data.font 1008 1009 fadeScreen(v) 1010 1011 try: 1012 glEnable(GL_BLEND) 1013 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) 1014 glEnable(GL_COLOR_MATERIAL) 1015 Theme.setBaseColor(1 - v) 1016 wrapText(font, (.1, .2 - v), self.prompt) 1017 1018 for n, c in enumerate(Guitar.KEYS): 1019 if self.controls.getState(c): 1020 glColor3f(*self.fretColors[n]) 1021 else: 1022 glColor3f(.4, .4, .4) 1023 font.render("#%d" % (n + 1), (.5 - .15 * (2 - n), .4 + v)) 1024 1025 if self.controls.getState(Player.ACTION1) or \ 1026 self.controls.getState(Player.ACTION2): 1027 Theme.setSelectedColor(1 - v) 1028 else: 1029 glColor3f(.4, .4, .4) 1030 font.render(_("Pick!"), (.45, .5 + v)) 1031 1032 finally: 1033 self.engine.view.resetProjection()
1034
1035 -def _runDialog(engine, dialog):
1036 """Run a dialog in a sub event loop until it is finished.""" 1037 if not engine.running: 1038 return 1039 1040 engine.view.pushLayer(dialog) 1041 1042 while engine.running and dialog in engine.view.layers: 1043 engine.run()
1044
1045 -def getText(engine, prompt, text = ""):
1046 """ 1047 Get a string of text from the user. 1048 1049 @param engine: Game engine 1050 @param prompt: Prompt shown to the user 1051 @param text: Default text 1052 """ 1053 d = GetText(engine, prompt, text) 1054 _runDialog(engine, d) 1055 return d.text
1056
1057 -def getKey(engine, prompt, key = None):
1058 """ 1059 Ask the user to choose a key. 1060 1061 @param engine: Game engine 1062 @param prompt: Prompt shown to the user 1063 @param key: Default key 1064 """ 1065 d = GetKey(engine, prompt, key) 1066 _runDialog(engine, d) 1067 return d.key
1068
1069 -def chooseSong(engine, prompt = _("Choose a Song"), selectedSong = None, selectedLibrary = None):
1070 """ 1071 Ask the user to select a song. 1072 1073 @param engine: Game engine 1074 @param prompt: Prompt shown to the user 1075 @param selectedSong: Name of song to select initially 1076 @param selectedLibrary: Name of the library where to search for the songs or None for the default library 1077 1078 @returns a (library, song) pair 1079 """ 1080 d = SongChooser(engine, prompt, selectedLibrary = selectedLibrary, selectedSong = selectedSong) 1081 _runDialog(engine, d) 1082 return (d.getSelectedLibrary(), d.getSelectedSong())
1083
1084 -def chooseFile(engine, masks = ["*.*"], path = ".", prompt = _("Choose a File")):
1085 """ 1086 Ask the user to select a file. 1087 1088 @param engine: Game engine 1089 @param masks: List of glob masks for files that are acceptable 1090 @param path: Initial path 1091 @param prompt: Prompt shown to the user 1092 """ 1093 d = FileChooser(engine, masks, path, prompt) 1094 _runDialog(engine, d) 1095 return d.getSelectedFile()
1096
1097 -def chooseItem(engine, items, prompt, selected = None):
1098 """ 1099 Ask the user to one item from a list. 1100 1101 @param engine: Game engine 1102 @param items: List of items 1103 @param prompt: Prompt shown to the user 1104 @param selected: Item selected by default 1105 """ 1106 d = ItemChooser(engine, items, prompt = prompt, selected = selected) 1107 _runDialog(engine, d) 1108 return d.getSelectedItem()
1109
1110 -def testKeys(engine, prompt = _("Play with the keys and press Escape when you're done.")):
1111 """ 1112 Have the user test the current keyboard configuration. 1113 1114 @param engine: Game engine 1115 @param prompt: Prompt shown to the user 1116 """ 1117 d = KeyTester(engine, prompt = prompt) 1118 _runDialog(engine, d)
1119
1120 -def showLoadingScreen(engine, condition, text = _("Loading..."), allowCancel = False):
1121 """ 1122 Show a loading screen until a condition is met. 1123 1124 @param engine: Game engine 1125 @param condition: A function that will be polled until it returns a true value 1126 @param text: Text shown to the user 1127 @type allowCancel: bool 1128 @param allowCancel: Can the loading be canceled 1129 @return: True if the condition was met, Fales if the loading was canceled. 1130 """ 1131 1132 # poll the condition first for some time 1133 n = 0 1134 while n < 32: 1135 n += 1 1136 if condition(): 1137 return True 1138 engine.run() 1139 1140 d = LoadingScreen(engine, condition, text, allowCancel) 1141 _runDialog(engine, d) 1142 return d.ready
1143
1144 -def showMessage(engine, text):
1145 """ 1146 Show a message to the user. 1147 1148 @param engine: Game engine 1149 @param text: Message text 1150 """ 1151 Log.notice("%s" % text) 1152 d = MessageScreen(engine, text) 1153 _runDialog(engine, d)
1154
1155 -def estimateBpm(engine, song, prompt):
1156 """ 1157 Ask the user to estimate the beats per minute value of a song. 1158 1159 @param engine: Game engine 1160 @param song: Song instance 1161 @param prompt: Prompt shown to the user 1162 """ 1163 d = BpmEstimator(engine, song, prompt) 1164 _runDialog(engine, d) 1165 return d.bpm
1166