Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #####################################################################
- # -*- coding: iso-8859-1 -*- #
- # #
- # Frets on Fire #
- # Copyright (C) 2006 Sami Kyöstilä #
- # #
- # This program is free software; you can redistribute it and/or #
- # modify it under the terms of the GNU General Public License #
- # as published by the Free Software Foundation; either version 2 #
- # of the License, or (at your option) any later version. #
- # #
- # This program is distributed in the hope that it will be useful, #
- # but WITHOUT ANY WARRANTY; without even the implied warranty of #
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
- # GNU General Public License for more details. #
- # #
- # You should have received a copy of the GNU General Public License #
- # along with this program; if not, write to the Free Software #
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, #
- # MA 02110-1301, USA. #
- #####################################################################
- """A bunch of dialog functions for interacting with the user."""
- import pygame
- from OpenGL.GL import *
- from OpenGL.GLU import *
- import math
- import os
- import fnmatch
- from View import Layer, BackgroundLayer
- from Input import KeyListener
- from Camera import Camera
- from Mesh import Mesh
- from Menu import Menu
- from Language import _
- from Texture import Texture
- import Theme
- import Log
- import Song
- import Data
- import Player
- import Guitar
- def wrapText(font, pos, text, rightMargin = 0.9, scale = 0.002, visibility = 0.0, hide = 0, hidestring = ""):
- """
- Wrap a piece of text inside given margins.
- @param pos: (x, y) tuple, x defines the left margin
- @param text: Text to wrap
- @param rightMargin: Right margin
- @param scale: Text scale
- @param visibility: Visibility factor [0..1], 0 is fully visible
- @param hide: Hide text instead of line wrap
- """
- x, y = pos
- space = font.getStringSize(" ", scale = scale)[0]
- hidew, hideh = font.getStringSize(hidestring, scale = scale)
- rightMargin = rightMargin - hidew
- for n, word in enumerate(text.split(" ")):
- w, h = font.getStringSize(word, scale = scale)
- if x + w > rightMargin and hide:
- word = hidestring
- if (x + w > rightMargin and not hide) or word == "\n":
- x = pos[0]
- y += h
- if word == "\n":
- continue
- glPushMatrix()
- glRotate(visibility * (n + 1) * -45, 0, 0, 1)
- font.render(word, (x, y + visibility * n), scale = scale)
- glPopMatrix()
- if x + w > rightMargin and hide:
- x += hidew + space
- break
- x += w + space
- return (x - space, y)
- def fadeScreen(v):
- """
- Fade the screen to a dark color to make whatever is on top easier to read.
- @param v: Visibility factor [0..1], 0 is fully visible
- """
- glEnable(GL_BLEND)
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
- glEnable(GL_COLOR_MATERIAL)
- glBegin(GL_TRIANGLE_STRIP)
- glColor4f(0, 0, 0, .3 - v * .3)
- glVertex2f(0, 0)
- glColor4f(0, 0, 0, .3 - v * .3)
- glVertex2f(1, 0)
- glColor4f(0, 0, 0, .9 - v * .9)
- glVertex2f(0, 1)
- glColor4f(0, 0, 0, .9 - v * .9)
- glVertex2f(1, 1)
- glEnd()
- class GetText(Layer, KeyListener):
- """Text input layer."""
- def __init__(self, engine, prompt = "", text = ""):
- self.text = text
- self.prompt = prompt
- self.engine = engine
- self.time = 0
- self.accepted = False
- def shown(self):
- self.engine.input.addKeyListener(self, priority = True)
- self.engine.input.enableKeyRepeat()
- def hidden(self):
- self.engine.input.removeKeyListener(self)
- self.engine.input.disableKeyRepeat()
- def keyPressed(self, key, unicode):
- self.time = 0
- c = self.engine.input.controls.getMapping(key)
- if (c in [Player.KEY1] or key == pygame.K_RETURN) and not self.accepted:
- self.engine.view.popLayer(self)
- self.accepted = True
- elif c in [Player.CANCEL, Player.KEY2] and not self.accepted:
- self.text = None
- self.engine.view.popLayer(self)
- self.accepted = True
- elif key == pygame.K_BACKSPACE and not self.accepted:
- self.text = self.text[:-1]
- elif unicode and ord(unicode) > 31 and not self.accepted:
- self.text += str(unicode)
- return True
- def run(self, ticks):
- self.time += ticks / 50.0
- def render(self, visibility, topMost):
- self.engine.view.setOrthogonalProjection(normalize = True)
- font = self.engine.data.font
- try:
- v = (1 - visibility) ** 2
- fadeScreen(v)
- Theme.setBaseColor(1 - v)
- if (self.time % 10) < 5 and visibility > .9:
- cursor = "|"
- else:
- cursor = ""
- pos = wrapText(font, (.1, .33 - v), self.prompt)
- Theme.setSelectedColor(1 - v)
- if self.text is not None:
- pos = wrapText(font, (.1, (pos[1] + v) + .08 + v / 4), self.text)
- font.render(cursor, pos)
- finally:
- self.engine.view.resetProjection()
- class GetKey(Layer, KeyListener):
- """Key choosing layer."""
- def __init__(self, engine, prompt = "", key = None):
- self.key = key
- self.prompt = prompt
- self.engine = engine
- self.time = 0
- self.accepted = False
- def shown(self):
- self.engine.input.addKeyListener(self, priority = True)
- def hidden(self):
- self.engine.input.removeKeyListener(self)
- def keyPressed(self, key, unicode):
- c = self.engine.input.controls.getMapping(key)
- if c in [Player.CANCEL, Player.KEY2] and not self.accepted:
- self.key = None
- self.engine.view.popLayer(self)
- self.accepted = True
- elif not self.accepted:
- self.key = key
- self.engine.view.popLayer(self)
- self.accepted = True
- return True
- def run(self, ticks):
- self.time += ticks / 50.0
- def render(self, visibility, topMost):
- self.engine.view.setOrthogonalProjection(normalize = True)
- font = self.engine.data.font
- try:
- v = (1 - visibility) ** 2
- fadeScreen(v)
- Theme.setBaseColor(1 - v)
- pos = wrapText(font, (.1, .33 - v), self.prompt)
- Theme.setSelectedColor(1 - v)
- if self.key is not None:
- text = pygame.key.name(self.key).capitalize()
- pos = wrapText(font, (.1, (pos[1] + v) + .08 + v / 4), text)
- finally:
- self.engine.view.resetProjection()
- class LoadingScreen(Layer, KeyListener):
- """Loading screen layer."""
- def __init__(self, engine, condition, text, allowCancel = False):
- self.engine = engine
- self.text = text
- self.condition = condition
- self.ready = False
- self.allowCancel = allowCancel
- self.time = 0.0
- def shown(self):
- self.engine.input.addKeyListener(self, priority = True)
- def keyPressed(self, key, unicode):
- c = self.engine.input.controls.getMapping(key)
- if self.allowCancel and c == Player.CANCEL:
- self.engine.view.popLayer(self)
- return True
- def hidden(self):
- self.engine.boostBackgroundThreads(False)
- self.engine.input.removeKeyListener(self)
- def run(self, ticks):
- self.time += ticks / 50.0
- if not self.ready and self.condition():
- self.engine.view.popLayer(self)
- self.ready = True
- def render(self, visibility, topMost):
- self.engine.view.setOrthogonalProjection(normalize = True)
- font = self.engine.data.font
- if not font:
- return
- if visibility > 0.9:
- self.engine.boostBackgroundThreads(True)
- else:
- self.engine.boostBackgroundThreads(False)
- try:
- v = (1 - visibility) ** 2
- fadeScreen(v)
- w, h = self.engine.view.geometry[2:4]
- self.engine.data.loadingImage.transform.reset()
- self.engine.data.loadingImage.transform.translate(w / 2, (1.0 - v * .25) * h / 2)
- self.engine.data.loadingImage.transform.scale(1, -1)
- self.engine.data.loadingImage.draw(color = (1, 1, 1, visibility))
- Theme.setBaseColor(1 - v)
- w, h = font.getStringSize(self.text)
- x = .5 - w / 2
- y = .6 - h / 2 + v * .5
- font.render(self.text, (x, y))
- finally:
- self.engine.view.resetProjection()
- class MessageScreen(Layer, KeyListener):
- """Message screen layer."""
- def __init__(self, engine, text, prompt = _("<OK>")):
- self.engine = engine
- self.text = text
- self.time = 0.0
- self.prompt = prompt
- def shown(self):
- self.engine.input.addKeyListener(self, priority = True)
- def keyPressed(self, key, unicode):
- c = self.engine.input.controls.getMapping(key)
- if c in [Player.KEY1, Player.KEY2, Player.CANCEL] or key == pygame.K_RETURN:
- self.engine.view.popLayer(self)
- return True
- def hidden(self):
- self.engine.input.removeKeyListener(self)
- def run(self, ticks):
- self.time += ticks / 50.0
- def render(self, visibility, topMost):
- self.engine.view.setOrthogonalProjection(normalize = True)
- font = self.engine.data.font
- if not font:
- return
- try:
- v = (1 - visibility) ** 2
- fadeScreen(v)
- x = .1
- y = .3 + v * 2
- Theme.setBaseColor(1 - v)
- pos = wrapText(font, (x, y), self.text, visibility = v)
- w, h = font.getStringSize(self.prompt, scale = 0.001)
- x = .5 - w / 2
- y = pos[1] + 3 * h + v * 2
- Theme.setSelectedColor(1 - v)
- font.render(self.prompt, (x, y), scale = 0.001)
- finally:
- self.engine.view.resetProjection()
- class SongChooser(Layer, KeyListener):
- """Song choosing layer."""
- def __init__(self, engine, prompt = "", selectedSong = None, selectedLibrary = None):
- self.prompt = prompt
- self.engine = engine
- self.time = 0
- self.accepted = False
- self.selectedIndex = 0
- self.camera = Camera()
- self.cassetteHeight = .8
- self.cassetteWidth = 4.0
- self.libraryHeight = 1.2
- self.libraryWidth = 4.0
- self.itemAngles = None
- self.itemLabels = None
- self.selectedOffset = 0.0
- self.cameraOffset = 0.0
- self.selectedItem = None
- self.song = None
- self.songCountdown = 1024
- self.songLoader = None
- self.initialItem = selectedSong
- self.library = selectedLibrary
- self.searchText = ""
- self.playSongName = ""
- self.cassetteShow = not self.engine.config.get("game", "compactlist")
- self.autoPreview = self.engine.config.get("game", "autopreview")
- self.artistSort = self.engine.config.get("game", "artistsort")
- # Use the default library if this one doesn't exist
- if not self.library or not os.path.isdir(self.engine.resource.fileName(self.library)):
- self.library = Song.DEFAULT_LIBRARY
- self.loadCollection()
- self.engine.resource.load(self, "cassette", lambda: Mesh(self.engine.resource.fileName("cassette.dae")), synch = True)
- self.engine.resource.load(self, "label", lambda: Mesh(self.engine.resource.fileName("label.dae")), synch = True)
- self.engine.resource.load(self, "libraryMesh", lambda: Mesh(self.engine.resource.fileName("library.dae")), synch = True)
- self.engine.resource.load(self, "libraryLabel", lambda: Mesh(self.engine.resource.fileName("library_label.dae")), synch = True)
- self.engine.loadSvgDrawing(self, "background", "cassette.svg")
- def loadCollection(self):
- self.loaded = False
- self.engine.resource.load(self, "libraries", lambda: Song.getAvailableLibraries(self.engine, self.library), onLoad = self.libraryListLoaded)
- showLoadingScreen(self.engine, lambda: self.loaded, text = _("Browsing Collection..."))
- def libraryListLoaded(self, libraries):
- self.engine.resource.load(self, "songs", lambda: Song.getAvailableSongs(self.engine, self.library), onLoad = self.songListLoaded)
- def songListLoaded(self, songs):
- if self.songLoader:
- self.songLoader.cancel()
- self.selectedIndex = 0
- self.items = self.libraries + self.songs
- self.itemAngles = [0.0] * len(self.items)
- self.itemLabels = [None] * len(self.items)
- self.loaded = True
- self.searchText = ""
- if self.initialItem is not None:
- for i, item in enumerate(self.items):
- if isinstance(item, Song.SongInfo) and self.initialItem == item.songName:
- self.selectedIndex = i
- break
- elif isinstance(item, Song.LibraryInfo) and self.initialItem == item.libraryName:
- self.selectedIndex = i
- break
- # Load labels for libraries right away
- for i, item in enumerate(self.items):
- if isinstance(item, Song.LibraryInfo):
- self.loadItemLabel(i)
- self.updateSelection()
- def shown(self):
- self.engine.input.addKeyListener(self, priority = True)
- self.engine.input.enableKeyRepeat()
- def hidden(self):
- if self.songLoader:
- self.songLoader.cancel()
- if self.song:
- self.song.fadeout(1000)
- self.song = None
- self.engine.input.removeKeyListener(self)
- self.engine.input.disableKeyRepeat()
- def getSelectedSong(self):
- if isinstance(self.selectedItem, Song.SongInfo):
- return self.selectedItem.songName
- def getSelectedLibrary(self):
- return self.library
- def loadItemLabel(self, i):
- # Load the item label if it isn't yet loaded
- item = self.items[i]
- if self.itemLabels[i] is None:
- if isinstance(item, Song.SongInfo):
- label = self.engine.resource.fileName(self.library, item.songName, "label.png")
- else:
- assert isinstance(item, Song.LibraryInfo)
- label = self.engine.resource.fileName(item.libraryName, "label.png")
- if os.path.exists(label):
- self.itemLabels[i] = Texture(label)
- def updateSelection(self):
- self.selectedItem = self.items[self.selectedIndex]
- self.songCountdown = 1024
- self.loadItemLabel(self.selectedIndex)
- def keyPressed(self, key, unicode):
- if not self.items or self.accepted:
- return
- c = self.engine.input.controls.getMapping(key)
- if c in [Player.KEY1] or key == pygame.K_RETURN:
- if self.matchesSearch(self.selectedItem):
- if isinstance(self.selectedItem, Song.LibraryInfo):
- self.library = self.selectedItem.libraryName
- self.initialItem = None
- self.loadCollection()
- else:
- self.engine.view.popLayer(self)
- self.accepted = True
- if not self.song and self.autoPreview:
- self.engine.data.acceptSound.play()
- elif c in [Player.CANCEL, Player.KEY2]:
- if self.library != Song.DEFAULT_LIBRARY:
- self.initialItem = self.library
- self.library = os.path.dirname(self.library)
- self.loadCollection()
- else:
- self.selectedItem = None
- self.engine.view.popLayer(self)
- self.accepted = True
- if not self.song:
- self.engine.data.cancelSound.play()
- elif c in [Player.UP, Player.ACTION1]:
- if self.matchesSearch(self.items[self.selectedIndex]):
- while 1:
- self.selectedIndex = (self.selectedIndex - 1) % len(self.items)
- if self.matchesSearch(self.items[self.selectedIndex]):
- break
- self.updateSelection()
- if not self.song and self.autoPreview:
- self.engine.data.selectSound.play()
- elif c in [Player.DOWN, Player.ACTION2]:
- if self.matchesSearch(self.items[self.selectedIndex]):
- while 1:
- self.selectedIndex = (self.selectedIndex + 1) % len(self.items)
- if self.matchesSearch(self.items[self.selectedIndex]):
- break
- self.updateSelection()
- if not self.song and self.autoPreview:
- self.engine.data.selectSound.play()
- elif key == pygame.K_PAGEUP:
- if self.matchesSearch(self.items[self.selectedIndex]):
- while 1:
- self.selectedIndex = (self.selectedIndex - 10) % len(self.items)
- if self.matchesSearch(self.items[self.selectedIndex]):
- break
- self.updateSelection()
- if not self.song and self.autoPreview:
- self.engine.data.selectSound.play()
- elif key == pygame.K_PAGEDOWN:
- if self.matchesSearch(self.items[self.selectedIndex]):
- while 1:
- self.selectedIndex = (self.selectedIndex + 10) % len(self.items)
- if self.matchesSearch(self.items[self.selectedIndex]):
- break
- self.updateSelection()
- if not self.song and self.autoPreview:
- self.engine.data.selectSound.play()
- elif key == pygame.K_BACKSPACE and not self.accepted:
- self.searchText = self.searchText[:-1]
- self.doSearch()
- elif key == pygame.K_SPACE:
- if self.playSongName == self.getSelectedSong():
- self.playSongName = ""
- self.song.fadeout(1000)
- else:
- self.playSelectedSong(forceplay=1)
- elif key == pygame.K_HOME:
- self.artistSort = (self.artistSort + 1) % 2
- if self.artistSort:
- self.items.sort(key=lambda l: (l.artist.lower() if isinstance(l, Song.SongInfo) else '0'))
- else:
- self.items.sort(key=lambda l: (l.name.lower()))
- elif key == pygame.K_TAB:
- self.cassetteShow = not self.cassetteShow
- elif unicode and ord(unicode) > 31 and not self.accepted:
- self.searchText += str(unicode)
- self.doSearch()
- return True
- def matchesSearch(self, item):
- if not self.searchText:
- return True
- if isinstance(item, Song.SongInfo):
- if self.searchText.lower() in item.name.lower() or self.searchText.lower() in item.artist.lower():
- return True
- elif isinstance(item, Song.LibraryInfo):
- if self.searchText.lower() in item.name.lower():
- return True
- return False
- def doSearch(self):
- if not self.searchText:
- return
- for i, item in enumerate(self.items):
- if self.matchesSearch(item):
- self.selectedIndex = i
- self.updateSelection()
- break
- def songLoaded(self, song):
- self.songLoader = None
- if self.song:
- self.song.stop()
- song.setGuitarVolume(self.engine.config.get("audio", "guitarvol"))
- song.setBackgroundVolume(self.engine.config.get("audio", "songvol"))
- song.setRhythmVolume(self.engine.config.get("audio", "rhythmvol"))
- song.play()
- self.song = song
- def playSelectedSong(self, forceplay = 0):
- song = self.getSelectedSong()
- if not song or (not self.autoPreview and not forceplay):
- return
- if self.songLoader:
- self.songLoader.cancel()
- # Don't start a new song loader until the previous one is finished
- if self.songLoader.isAlive():
- self.songCountdown = 256
- return
- if self.song:
- self.playSongName = ""
- self.song.fadeout(1000)
- self.song = None
- self.songLoader = self.engine.resource.load(self, None, lambda: Song.loadSong(self.engine, song, playbackOnly = True, library = self.library),
- onLoad = self.songLoaded)
- self.playSongName = self.getSelectedSong()
- def run(self, ticks):
- self.time += ticks / 50.0
- if self.songCountdown > 0:
- self.songCountdown -= ticks
- if self.songCountdown <= 0:
- self.playSelectedSong()
- d = self.cameraOffset - self.selectedOffset
- self.cameraOffset -= d * ticks / 192.0
- for i in range(len(self.itemAngles)):
- if i == self.selectedIndex:
- self.itemAngles[i] = min(90, self.itemAngles[i] + ticks / 2.0)
- else:
- self.itemAngles[i] = max(0, self.itemAngles[i] - ticks / 2.0)
- def renderCassette(self, color, label):
- if not self.cassette:
- return
- if color:
- glColor3f(*color)
- glEnable(GL_COLOR_MATERIAL)
- self.cassette.render("Mesh_001")
- glColor3f(.1, .1, .1)
- self.cassette.render("Mesh")
- # Draw the label if there is one
- if label is not None:
- glEnable(GL_TEXTURE_2D)
- label.bind()
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
- glColor3f(1, 1, 1)
- glMatrixMode(GL_TEXTURE)
- glScalef(1, -1, 1)
- glMatrixMode(GL_MODELVIEW)
- self.label.render("Mesh_001")
- glMatrixMode(GL_TEXTURE)
- glLoadIdentity()
- glMatrixMode(GL_MODELVIEW)
- glDisable(GL_TEXTURE_2D)
- def renderLibrary(self, color, label):
- if not self.libraryMesh:
- return
- if color:
- glColor3f(*color)
- glEnable(GL_NORMALIZE)
- glEnable(GL_COLOR_MATERIAL)
- self.libraryMesh.render("Mesh_001")
- glColor3f(.1, .1, .1)
- self.libraryMesh.render("Mesh")
- # Draw the label if there is one
- if label is not None:
- glEnable(GL_TEXTURE_2D)
- label.bind()
- glColor3f(1, 1, 1)
- glMatrixMode(GL_TEXTURE)
- glScalef(1, -1, 1)
- glMatrixMode(GL_MODELVIEW)
- self.libraryLabel.render()
- glMatrixMode(GL_TEXTURE)
- glLoadIdentity()
- glMatrixMode(GL_MODELVIEW)
- glDisable(GL_TEXTURE_2D)
- glDisable(GL_NORMALIZE)
- def render(self, visibility, topMost):
- v = (1 - visibility) ** 2
- # render the background
- t = self.time / 100
- w, h, = self.engine.view.geometry[2:4]
- r = .5
- self.background.transform.reset()
- self.background.transform.translate(v * 2 * w + w / 2 + math.sin(t / 2) * w / 2 * r, h / 2 + math.cos(t) * h / 2 * r)
- self.background.transform.rotate(-t)
- self.background.transform.scale(math.sin(t / 8) + 2, math.sin(t / 8) + 2)
- self.background.draw()
- x = .6
- y = .15
- if self.cassetteShow:
- # render the item list
- try:
- glMatrixMode(GL_PROJECTION)
- glPushMatrix()
- glLoadIdentity()
- gluPerspective(60, self.engine.view.aspectRatio, 0.1, 1000)
- glMatrixMode(GL_MODELVIEW)
- glLoadIdentity()
- glEnable(GL_DEPTH_TEST)
- glDisable(GL_CULL_FACE)
- glDepthMask(1)
- offset = 10 * (v ** 2)
- self.camera.origin = (-10 + offset, -self.cameraOffset, 4 + offset)
- self.camera.target = ( 0 + offset, -self.cameraOffset, 2.5 + offset)
- self.camera.apply()
- y = 0.0
- for i, item in enumerate(self.items):
- if not self.matchesSearch(item):
- continue
- c = math.sin(self.itemAngles[i] * math.pi / 180)
- if isinstance(item, Song.SongInfo):
- h = c * self.cassetteWidth + (1 - c) * self.cassetteHeight
- else:
- h = c * self.libraryWidth + (1 - c) * self.libraryHeight
- d = (y + h * .5 + self.camera.origin[1]) / (4 * (self.camera.target[2] - self.camera.origin[2]))
- if i == self.selectedIndex:
- self.selectedOffset = y + h / 2
- Theme.setSelectedColor()
- else:
- Theme.setBaseColor()
- glTranslatef(0, -h / 2, 0)
- glPushMatrix()
- if abs(d) < 1.2:
- if isinstance(item, Song.SongInfo):
- glRotate(self.itemAngles[i], 0, 0, 1)
- self.renderCassette(item.cassetteColor, self.itemLabels[i])
- elif isinstance(item, Song.LibraryInfo):
- glRotate(-self.itemAngles[i], 0, 0, 1)
- if i == self.selectedIndex:
- glRotate(self.time * 4, 1, 0, 0)
- self.renderLibrary(item.color, self.itemLabels[i])
- glPopMatrix()
- glTranslatef(0, -h / 2, 0)
- y += h
- glDisable(GL_DEPTH_TEST)
- glDisable(GL_CULL_FACE)
- glDepthMask(0)
- finally:
- glMatrixMode(GL_PROJECTION)
- glPopMatrix()
- glMatrixMode(GL_MODELVIEW)
- else:
- self.engine.view.setOrthogonalProjection(normalize = True)
- font = self.engine.data.font
- try:
- glEnable(GL_BLEND)
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
- glEnable(GL_COLOR_MATERIAL)
- n = (0, 0)
- glBegin(GL_QUADS)
- glColor4f(0,0,0, .2)
- glVertex2f(.04, .02)
- glVertex2f(.04, .652)
- glVertex2f(.58, .652)
- glVertex2f(.58, .02)
- glEnd()
- glBegin(GL_LINE_LOOP)
- Theme.setBaseColor(1 - v)
- glVertex2f(.04, .02)
- glVertex2f(.04, .652)
- glVertex2f(.58, .652)
- glVertex2f(.58, .02)
- glEnd()
- length = 0
- select = 0
- it = 0
- for i, item in enumerate(self.items):
- if not self.matchesSearch(item):
- continue
- if isinstance(item, Song.SongInfo) or isinstance(item, Song.LibraryInfo):
- length+=1
- if self.selectedIndex == i:
- select = length
- Theme.setSelectedColor(1 - v)
- scale = 0.0008
- for i, item in enumerate(self.items):
- if not self.matchesSearch(item):
- continue
- if isinstance(item, Song.SongInfo) or isinstance(item, Song.LibraryInfo):
- it+=1
- if it >= (select - 5) or it >= (select + 11):
- if self.selectedIndex == i:
- glBegin(GL_QUADS)
- glColor4f(1,1,1, .1)
- else:
- glBegin(GL_QUADS)
- if it % 2 == 0:
- glColor4f(0,0,0, .3)
- else:
- glColor4f(0,0,0, .5)
- glVertex2f(.045, n[1] + font.getHeight() * scale)
- glVertex2f(.045, n[1] + 3*font.getHeight() * scale)
- glVertex2f(.575, n[1] + 3*font.getHeight() * scale)
- glVertex2f(.575, n[1] + font.getHeight() * scale)
- glEnd()
- Theme.setSelectedColor(1 - v)
- if self.artistSort:
- n = wrapText(font, (.05, n[1] + font.getHeight() * scale), item.artist if isinstance(item, Song.SongInfo) else _("Songs library"), 0.57, visibility = 0.0, scale = scale, hide = 1, hidestring = "...")
- Theme.setBaseColor(1 - v)
- n = wrapText(font, (.07, n[1] + font.getHeight() * scale), item.name, 0.57, visibility = 0.0, scale = scale, hide = 1, hidestring = "..." )
- else:
- n = wrapText(font, (.05, n[1] + font.getHeight() * scale), item.name, 0.57, visibility = 0.0, scale = scale, hide = 1, hidestring = "...")
- Theme.setBaseColor(1 - v)
- n = wrapText(font, (.07, n[1] + font.getHeight() * scale), item.artist if isinstance(item, Song.SongInfo) else _("Songs library"), 0.57, visibility = 0.0, scale = scale, hide = 1, hidestring = "..." )
- if ((n[1] + 2*font.getHeight() * scale) >= .65):
- break
- # draw the scrollbar
- perc = float(select - 1)/float(length - 1) if length > 1 else 0
- glBegin(GL_QUADS)
- Theme.setBaseColor(1 - v)
- glVertex2f(.575, .02 + .59*perc)
- Theme.setBaseColor((1 - v) * .3)
- glVertex2f(.575, .02 + .59*perc + .04)
- Theme.setBaseColor(1 - v)
- glColor4f(1,1,1,.8)
- Theme.setBaseColor((1 - v) * .3)
- glVertex2f(.586, .02 + .59*perc + .04)
- glVertex2f(.586, .02 + .59*perc)
- glEnd()
- finally:
- self.engine.view.resetProjection()
- self.engine.view.setOrthogonalProjection(normalize = True)
- font = self.engine.data.font
- # render the song info
- try:
- glEnable(GL_BLEND)
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
- glEnable(GL_COLOR_MATERIAL)
- Theme.setBaseColor(1 - v)
- if self.searchText:
- text = _("Filter: %s") % (self.searchText) + "|"
- if not self.matchesSearch(self.items[self.selectedIndex]):
- text += " (%s)" % _("Not found")
- font.render(text, (.05, .7 + v), scale = 0.001)
- elif self.songLoader:
- font.render(_("Loading Preview..."), (.05, .7 + v), scale = 0.001)
- elif not self.autoPreview:
- font.render(_("Press Space to Preview"), (.05, .7 + v), scale = 0.001)
- x = .6
- y = .15
- font.render(self.prompt, (x, .05 - v))
- Theme.setSelectedColor(1 - v)
- item = self.items[self.selectedIndex]
- if self.matchesSearch(item):
- angle = self.itemAngles[self.selectedIndex]
- f = ((90.0 - angle) / 90.0) ** 2
- pos = wrapText(font, (x, y), item.name, visibility = f, scale = 0.0016)
- if isinstance(item, Song.SongInfo):
- Theme.setBaseColor(1 - v)
- wrapText(font, (x, pos[1] + font.getHeight() * 0.0016), item.artist, visibility = f, scale = 0.0016)
- Theme.setSelectedColor(1 - v)
- scale = 0.0011
- w, h = font.getStringSize(self.prompt, scale = scale)
- x = .6
- y = .5 + f / 2.0
- if len(item.difficulties) > 3:
- y = .42 + f / 2.0
- for d in item.difficulties:
- scores = item.getHighscores(d)
- if scores:
- score, stars, name = scores[0]
- else:
- score, stars, name = "---", 0, "---"
- Theme.setBaseColor(1 - v)
- font.render(unicode(d), (x, y), scale = scale)
- font.render(unicode(Data.STAR2 * stars + Data.STAR1 * (5 - stars)), (x, y + h), scale = scale * .9)
- Theme.setSelectedColor(1 - v)
- font.render(unicode(score), (x + .15, y), scale = scale)
- font.render(name, (x + .15, y + h), scale = scale)
- y += 2 * h + f / 4.0
- elif isinstance(item, Song.LibraryInfo):
- Theme.setBaseColor(1 - v)
- if item.songCount == 1:
- songCount = _("One song in this library")
- else:
- songCount = _("%d songs in this library") % item.songCount
- wrapText(font, (x, pos[1] + 3 * font.getHeight() * 0.0016), songCount, visibility = f, scale = 0.0016)
- finally:
- self.engine.view.resetProjection()
- class FileChooser(BackgroundLayer, KeyListener):
- """File choosing layer."""
- def __init__(self, engine, masks, path, prompt = ""):
- self.masks = masks
- self.path = path
- self.prompt = prompt
- self.engine = engine
- self.accepted = False
- self.selectedFile = None
- self.time = 0.0
- self.menu = None
- self.engine.loadSvgDrawing(self, "background", "editor.svg")
- def _getFileCallback(self, fileName):
- return lambda: self.chooseFile(fileName)
- def _getFileText(self, fileName):
- f = os.path.join(self.path, fileName)
- if fileName == "..":
- return _("[Parent Folder]")
- if os.path.isdir(f):
- return _("%s [Folder]") % fileName
- return fileName
- def getFiles(self):
- files = [".."]
- for fn in os.listdir(self.path):
- if fn.startswith("."): continue
- f = os.path.join(self.path, fn)
- for mask in self.masks:
- if fnmatch.fnmatch(fn, mask):
- break
- else:
- if not os.path.isdir(f):
- continue
- files.append(fn)
- files.sort()
- return files
- def updateFiles(self):
- if self.menu:
- self.engine.view.popLayer(self.menu)
- self.menu = Menu(self.engine, choices = [(self._getFileText(f), self._getFileCallback(f)) for f in self.getFiles()], onClose = self.close, onCancel = self.cancel)
- self.engine.view.pushLayer(self.menu)
- def chooseFile(self, fileName):
- path = os.path.abspath(os.path.join(self.path, fileName))
- if os.path.isdir(path):
- self.path = path
- self.updateFiles()
- return
- self.selectedFile = path
- accepted = True
- self.engine.view.popLayer(self.menu)
- self.engine.view.popLayer(self)
- self.menu = None
- def cancel(self):
- self.accepted = True
- self.engine.view.popLayer(self)
- def close(self):
- if not self.menu:
- self.accepted = True
- self.engine.view.popLayer(self)
- def shown(self):
- self.updateFiles()
- def getSelectedFile(self):
- return self.selectedFile
- def run(self, ticks):
- self.time += ticks / 50.0
- def render(self, visibility, topMost):
- v = (1 - visibility) ** 2
- # render the background
- t = self.time / 100
- w, h, = self.engine.view.geometry[2:4]
- r = .5
- self.background.transform.reset()
- self.background.transform.translate(v * 2 * w + w / 2 + math.sin(t / 2) * w / 2 * r, h / 2 + math.cos(t) * h / 2 * r)
- self.background.transform.rotate(-t)
- self.background.transform.scale(math.sin(t / 8) + 2, math.sin(t / 8) + 2)
- self.background.draw()
- self.engine.view.setOrthogonalProjection(normalize = True)
- font = self.engine.data.font
- try:
- glEnable(GL_BLEND)
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
- glEnable(GL_COLOR_MATERIAL)
- Theme.setBaseColor(1 - v)
- wrapText(font, (.1, .05 - v), self.prompt)
- finally:
- self.engine.view.resetProjection()
- class ItemChooser(BackgroundLayer, KeyListener):
- """Item menu layer."""
- def __init__(self, engine, items, selected = None, prompt = ""):
- self.prompt = prompt
- self.engine = engine
- self.accepted = False
- self.selectedItem = None
- self.time = 0.0
- self.menu = Menu(self.engine, choices = [(c, self._callbackForItem(c)) for c in items], onClose = self.close, onCancel = self.cancel)
- if selected and selected in items:
- self.menu.selectItem(items.index(selected))
- self.engine.loadSvgDrawing(self, "background", "editor.svg")
- def _callbackForItem(self, item):
- def cb():
- self.chooseItem(item)
- return cb
- def chooseItem(self, item):
- self.selectedItem = item
- accepted = True
- self.engine.view.popLayer(self.menu)
- self.engine.view.popLayer(self)
- def cancel(self):
- self.accepted = True
- self.engine.view.popLayer(self)
- def close(self):
- self.accepted = True
- self.engine.view.popLayer(self)
- def shown(self):
- self.engine.view.pushLayer(self.menu)
- def getSelectedItem(self):
- return self.selectedItem
- def run(self, ticks):
- self.time += ticks / 50.0
- def render(self, visibility, topMost):
- v = (1 - visibility) ** 2
- # render the background
- t = self.time / 100
- w, h, = self.engine.view.geometry[2:4]
- r = .5
- self.background.transform.reset()
- self.background.transform.translate(v * 2 * w + w / 2 + math.sin(t / 2) * w / 2 * r, h / 2 + math.cos(t) * h / 2 * r)
- self.background.transform.rotate(-t)
- self.background.transform.scale(math.sin(t / 8) + 2, math.sin(t / 8) + 2)
- self.background.draw()
- self.engine.view.setOrthogonalProjection(normalize = True)
- font = self.engine.data.font
- try:
- glEnable(GL_BLEND)
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
- glEnable(GL_COLOR_MATERIAL)
- Theme.setBaseColor(1 - v)
- wrapText(font, (.1, .05 - v), self.prompt)
- finally:
- self.engine.view.resetProjection()
- class BpmEstimator(Layer, KeyListener):
- """Beats per minute value estimation layer."""
- def __init__(self, engine, song, prompt = ""):
- self.prompt = prompt
- self.engine = engine
- self.song = song
- self.accepted = False
- self.bpm = None
- self.time = 0.0
- self.beats = []
- def shown(self):
- self.engine.input.addKeyListener(self, priority = True)
- self.song.play()
- def hidden(self):
- self.engine.input.removeKeyListener(self)
- self.song.fadeout(1000)
- def keyPressed(self, key, unicode):
- if self.accepted:
- return True
- c = self.engine.input.controls.getMapping(key)
- if key == pygame.K_SPACE:
- self.beats.append(self.time)
- if len(self.beats) > 12:
- diffs = [self.beats[i + 1] - self.beats[i] for i in range(len(self.beats) - 1)]
- self.bpm = 60000.0 / (sum(diffs) / float(len(diffs)))
- self.beats = self.beats[-12:]
- elif c in [Player.CANCEL, Player.KEY2]:
- self.engine.view.popLayer(self)
- self.accepted = True
- self.bpm = None
- elif c in [Player.KEY1] or key == pygame.K_RETURN:
- self.engine.view.popLayer(self)
- self.accepted = True
- return True
- def run(self, ticks):
- self.time += ticks
- def render(self, visibility, topMost):
- v = (1 - visibility) ** 2
- self.engine.view.setOrthogonalProjection(normalize = True)
- font = self.engine.data.font
- fadeScreen(v)
- try:
- glEnable(GL_BLEND)
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
- glEnable(GL_COLOR_MATERIAL)
- Theme.setBaseColor(1 - v)
- wrapText(font, (.1, .2 - v), self.prompt)
- if self.bpm is not None:
- Theme.setSelectedColor(1 - v)
- wrapText(font, (.1, .5 + v), _("%.2f beats per minute") % (self.bpm))
- finally:
- self.engine.view.resetProjection()
- class KeyTester(Layer, KeyListener):
- """Keyboard configuration testing layer."""
- def __init__(self, engine, prompt = ""):
- self.prompt = prompt
- self.engine = engine
- self.accepted = False
- self.time = 0.0
- self.controls = Player.Controls()
- self.fretColors = Theme.fretColors
- def shown(self):
- self.engine.input.addKeyListener(self, priority = True)
- def hidden(self):
- self.engine.input.removeKeyListener(self)
- def keyPressed(self, key, unicode):
- if self.accepted:
- return True
- self.controls.keyPressed(key)
- c = self.engine.input.controls.getMapping(key)
- if c in [Player.CANCEL]:
- self.engine.view.popLayer(self)
- self.accepted = True
- return True
- def keyReleased(self, key):
- self.controls.keyReleased(key)
- def run(self, ticks):
- self.time += ticks
- def render(self, visibility, topMost):
- v = (1 - visibility) ** 2
- self.engine.view.setOrthogonalProjection(normalize = True)
- font = self.engine.data.font
- fadeScreen(v)
- try:
- glEnable(GL_BLEND)
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
- glEnable(GL_COLOR_MATERIAL)
- Theme.setBaseColor(1 - v)
- wrapText(font, (.1, .2 - v), self.prompt)
- for n, c in enumerate(Guitar.KEYS):
- if self.controls.getState(c):
- glColor3f(*self.fretColors[n])
- else:
- glColor3f(.4, .4, .4)
- font.render("#%d" % (n + 1), (.5 - .15 * (2 - n), .4 + v))
- if self.controls.getState(Player.ACTION1) or \
- self.controls.getState(Player.ACTION2):
- Theme.setSelectedColor(1 - v)
- else:
- glColor3f(.4, .4, .4)
- font.render(_("Pick!"), (.45, .5 + v))
- finally:
- self.engine.view.resetProjection()
- def _runDialog(engine, dialog):
- """Run a dialog in a sub event loop until it is finished."""
- if not engine.running:
- return
- engine.view.pushLayer(dialog)
- while engine.running and dialog in engine.view.layers:
- engine.run()
- def getText(engine, prompt, text = ""):
- """
- Get a string of text from the user.
- @param engine: Game engine
- @param prompt: Prompt shown to the user
- @param text: Default text
- """
- d = GetText(engine, prompt, text)
- _runDialog(engine, d)
- return d.text
- def getKey(engine, prompt, key = None):
- """
- Ask the user to choose a key.
- @param engine: Game engine
- @param prompt: Prompt shown to the user
- @param key: Default key
- """
- d = GetKey(engine, prompt, key)
- _runDialog(engine, d)
- return d.key
- def chooseSong(engine, prompt = _("Choose a Song"), selectedSong = None, selectedLibrary = None):
- """
- Ask the user to select a song.
- @param engine: Game engine
- @param prompt: Prompt shown to the user
- @param selectedSong: Name of song to select initially
- @param selectedLibrary: Name of the library where to search for the songs or None for the default library
- @returns a (library, song) pair
- """
- d = SongChooser(engine, prompt, selectedLibrary = selectedLibrary, selectedSong = selectedSong)
- _runDialog(engine, d)
- return (d.getSelectedLibrary(), d.getSelectedSong())
- def chooseFile(engine, masks = ["*.*"], path = ".", prompt = _("Choose a File")):
- """
- Ask the user to select a file.
- @param engine: Game engine
- @param masks: List of glob masks for files that are acceptable
- @param path: Initial path
- @param prompt: Prompt shown to the user
- """
- d = FileChooser(engine, masks, path, prompt)
- _runDialog(engine, d)
- return d.getSelectedFile()
- def chooseItem(engine, items, prompt, selected = None):
- """
- Ask the user to one item from a list.
- @param engine: Game engine
- @param items: List of items
- @param prompt: Prompt shown to the user
- @param selected: Item selected by default
- """
- d = ItemChooser(engine, items, prompt = prompt, selected = selected)
- _runDialog(engine, d)
- return d.getSelectedItem()
- def testKeys(engine, prompt = _("Play with the keys and press Escape when you're done.")):
- """
- Have the user test the current keyboard configuration.
- @param engine: Game engine
- @param prompt: Prompt shown to the user
- """
- d = KeyTester(engine, prompt = prompt)
- _runDialog(engine, d)
- def showLoadingScreen(engine, condition, text = _("Loading..."), allowCancel = False):
- """
- Show a loading screen until a condition is met.
- @param engine: Game engine
- @param condition: A function that will be polled until it returns a true value
- @param text: Text shown to the user
- @type allowCancel: bool
- @param allowCancel: Can the loading be canceled
- @return: True if the condition was met, Fales if the loading was canceled.
- """
- # poll the condition first for some time
- n = 0
- while n < 32:
- n += 1
- if condition():
- return True
- engine.run()
- d = LoadingScreen(engine, condition, text, allowCancel)
- _runDialog(engine, d)
- return d.ready
- def showMessage(engine, text):
- """
- Show a message to the user.
- @param engine: Game engine
- @param text: Message text
- """
- Log.notice("%s" % text)
- d = MessageScreen(engine, text)
- _runDialog(engine, d)
- def estimateBpm(engine, song, prompt):
- """
- Ask the user to estimate the beats per minute value of a song.
- @param engine: Game engine
- @param song: Song instance
- @param prompt: Prompt shown to the user
- """
- d = BpmEstimator(engine, song, prompt)
- _runDialog(engine, d)
- return d.bpm
Add Comment
Please, Sign In to add comment