1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
105 self.engine.input.addKeyListener(self, priority = True)
106 self.engine.input.enableKeyRepeat()
107
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
168
171
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):
207
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
220
226
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):
268
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
279
285
288
289 - def run(self, ticks):
290 self.time += ticks / 50.0
291
292 - def render(self, visibility, topMost):
316
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
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
358
361
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
380 for i, item in enumerate(self.items):
381 if isinstance(item, Song.LibraryInfo):
382 self.loadItemLabel(i)
383 self.updateSelection()
384
388
397
399 if isinstance(self.selectedItem, Song.SongInfo):
400 return self.selectedItem.songName
401
404
406
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
418 self.selectedItem = self.items[self.selectedIndex]
419 self.songCountdown = 1024
420 self.loadItemLabel(self.selectedIndex)
421
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
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
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
506
508 song = self.getSelectedSong()
509 if not song:
510 return
511
512 if self.songLoader:
513 self.songLoader.cancel()
514
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
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
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
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
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
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
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
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
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
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
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
783
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
797 self.accepted = True
798 self.engine.view.popLayer(self)
799
801 if not self.menu:
802 self.accepted = True
803 self.engine.view.popLayer(self)
804
807
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
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
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
855 return cb
856
858 self.selectedItem = item
859 accepted = True
860 self.engine.view.popLayer(self.menu)
861 self.engine.view.popLayer(self)
862
864 self.accepted = True
865 self.engine.view.popLayer(self)
866
868 self.accepted = True
869 self.engine.view.popLayer(self)
870
873
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
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
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
920
924
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):
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
971 """Keyboard configuration testing layer."""
972 - def __init__(self, engine, prompt = ""):
979
982
985
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
999
1000 - def run(self, 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
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
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
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
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