##################################################################### # -*- 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. # ##################################################################### import pygame from OpenGL.GL import * import math from View import Layer from Input import KeyListener import Data import Theme import Dialogs import Player class Choice: def __init__(self, text, callback, values = None, valueIndex = 0): self.text = unicode(text) self.callback = callback self.values = values self.valueIndex = valueIndex if self.text.endswith(" >"): self.text = text[:-2] self.isSubMenu = True else: self.isSubMenu = isinstance(self.callback, Menu) or isinstance(self.callback, list) def trigger(self, engine = None): if engine and isinstance(self.callback, list): nextMenu = Menu(engine, self.callback) elif engine and isinstance(self.callback, Menu): nextMenu = self.callback elif self.values: nextMenu = self.callback(self.values[self.valueIndex]) else: nextMenu = self.callback() if isinstance(nextMenu, Menu): engine.view.pushLayer(nextMenu) def selectNextValue(self): if self.values: self.valueIndex = (self.valueIndex + 1) % len(self.values) self.trigger() def selectPreviousValue(self): if self.values: self.valueIndex = (self.valueIndex - 1) % len(self.values) self.trigger() def getText(self, selected): if not self.values: if self.isSubMenu: return "%s >" % self.text return self.text if selected: return "%s: %s%s%s" % (self.text, Data.LEFT, self.values[self.valueIndex], Data.RIGHT) else: return "%s: %s" % (self.text, self.values[self.valueIndex]) class Menu(Layer, KeyListener): def __init__(self, engine, choices, onClose = None, onCancel = None, pos = (.2, .66 - .35), viewSize = 6, fadeScreen = False): self.engine = engine self.choices = [] self.currentIndex = 0 self.time = 0 self.onClose = onClose self.onCancel = onCancel self.viewOffset = 0 self.pos = pos self.viewSize = viewSize self.fadeScreen = fadeScreen for c in choices: try: text, callback = c if isinstance(text, tuple): c = Choice(text[0], callback, values = text[2], valueIndex = text[1]) else: c = Choice(text, callback) except TypeError: pass self.choices.append(c) def selectItem(self, index): self.currentIndex = index def shown(self): self.engine.input.addKeyListener(self) self.engine.input.enableKeyRepeat() def hidden(self): self.engine.input.removeKeyListener(self) self.engine.input.disableKeyRepeat() if self.onClose: self.onClose() def updateSelection(self): if self.currentIndex > self.viewOffset + self.viewSize - 1: self.viewOffset = self.currentIndex - self.viewSize + 1 if self.currentIndex < self.viewOffset: self.viewOffset = self.currentIndex def keyPressed(self, key, unicode): self.time = 0 choice = self.choices[self.currentIndex] c = self.engine.input.controls.getMapping(key) if c in [Player.KEY1] or key == pygame.K_RETURN: choice.trigger(self.engine) self.engine.data.acceptSound.play() elif c in [Player.CANCEL, Player.KEY2]: if self.onCancel: self.onCancel() self.engine.view.popLayer(self) self.engine.input.removeKeyListener(self) self.engine.data.cancelSound.play() elif c in [Player.DOWN, Player.ACTION2]: self.currentIndex = (self.currentIndex + 1) % len(self.choices) self.updateSelection() self.engine.data.selectSound.play() elif c in [Player.UP, Player.ACTION1]: self.currentIndex = (self.currentIndex - 1) % len(self.choices) self.updateSelection() self.engine.data.selectSound.play() elif c in [Player.RIGHT, Player.KEY4]: choice.selectNextValue() elif c in [Player.LEFT, Player.KEY3]: choice.selectPreviousValue() return True def run(self, ticks): self.time += ticks / 50.0 def renderTriangle(self, up = (0, 1), s = .2): left = (-up[1], up[0]) glBegin(GL_TRIANGLES) glVertex2f( up[0] * s, up[1] * s) glVertex2f((-up[0] + left[0]) * s, (-up[1] + left[1]) * s) glVertex2f((-up[0] - left[0]) * s, (-up[1] - left[1]) * s) glEnd() def render(self, visibility, topMost): if not visibility: return self.engine.view.setOrthogonalProjection(normalize = True) try: v = (1 - visibility) ** 2 font = self.engine.data.font if self.fadeScreen: Dialogs.fadeScreen(v) glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glEnable(GL_COLOR_MATERIAL) n = len(self.choices) x, y = self.pos w, h = font.getStringSize("_") for i, choice in enumerate(self.choices[self.viewOffset:self.viewOffset + self.viewSize]): text = choice.getText(i + self.viewOffset == self.currentIndex) glPushMatrix() glRotate(v * 45, 0, 0, 1) # Draw arrows if scrolling is needed to see all items if i == 0 and self.viewOffset > 0: Theme.setBaseColor((1 - v) * max(.1, 1 - (1.0 / self.viewOffset) / 3)) glPushMatrix() glTranslatef(x - v / 4 - w * 2, y + h / 2, 0) self.renderTriangle(up = (0, -1), s = .015) glPopMatrix() elif i == self.viewSize - 1 and self.viewOffset + self.viewSize < n: Theme.setBaseColor((1 - v) * max(.1, 1 - (1.0 / (n - self.viewOffset - self.viewSize)) / 3)) glPushMatrix() glTranslatef(x - v / 4 - w * 2, y + h / 2, 0) self.renderTriangle(up = (0, 1), s = .015) glPopMatrix() if i + self.viewOffset == self.currentIndex: a = (math.sin(self.time) * .15 + .75) * (1 - v * 2) Theme.setSelectedColor(a) a *= -.005 glTranslatef(a, a, a) else: Theme.setBaseColor(1 - v) font.render(text, (x - v / 4, y)) v *= 2 y += h glPopMatrix() finally: self.engine.view.resetProjection()