#!/usr/bin/python import sys, os, UserList, random, math import pygame, pygame.font from pygame.locals import * pygame.init() TRANSITION = USEREVENT + 2 class Wraparound(UserList.UserList): "A list which wraps around, so asking for an index higher than len() works" def __getitem__(self, idx): return self.data[idx % len(self.data)] class Menu(object): def __init__(self, main_renderer): self.main_renderer = main_renderer def render(self): print "render a menu", self.__class__.__name__ def handle_key(self, key): pass def handle_transition(self, event): pass def startup(self): pass def calculate_transition(self, start, end, stepcount): diff = end - start parts = [start + ((diff/stepcount) * i) for i in range(stepcount)] parts.append(end) return parts def start_transition(self, start, end, stepcount, attribute, callback=None, *args): event = pygame.event.Event(TRANSITION, { "steps": self.calculate_transition(start, end, stepcount), "attribute": attribute, "recipient": self, "callback": callback, "callback_args": args }) pygame.event.post(event) def handle_transition(self, event): steps = event.steps next_step = steps.pop(0) self.set_dirty() setattr(event.recipient, event.attribute, next_step) self.render() if steps: # repost event newevent = pygame.event.Event(TRANSITION, { "steps": steps, "attribute": event.attribute, "recipient": event.recipient, "callback": event.callback, "callback_args": event.callback_args }) pygame.event.post(newevent) else: # we are finished; call the callback if event.callback: event.callback(*event.callback_args) class MediaMenu(Menu): def __init__(self, main_renderer, media): super(MediaMenu, self).__init__(main_renderer) self.media = Wraparound(media) self.y = 0 self.height = self.main_renderer.screen.get_height() - self.y self.width = main_renderer.screen.get_width() self.surface = pygame.surface.Surface((self.width, self.height), flags=SRCALPHA) self.selected = 0 self.iter = [0] for i in range(1,10): self.iter.append(i) self.iter.append(-i) self.height = self.main_renderer.screen.get_height() - self.y grey_gradient_orig = pygame.image.load("images/grey-gradient.png") self.grey_gradient = pygame.surface.Surface((self.width, self.height), flags=SRCALPHA) self.grey_gradient.blit(grey_gradient_orig, (0,0)) del(grey_gradient_orig) self.grey_gradient.set_alpha(30) self.titlefont = pygame.font.SysFont("aeon title", 32) self.bodyfont = pygame.font.SysFont("aeon list", 18) self.spacing = 30 self.non_highlight_offset = (self.highlighted_size[1] - self.non_highlighted_size[1]) / 2 def render(self): self.surface.fill((200,200,200,0)) # full transparent self.surface.blit(self.grey_gradient, (0,0)) left_position = self.width / 2 right_position = self.width / 2 for locn_offset in self.iter: locn = self.selected + locn_offset item = self.media[locn] #print "-----------------" #print item #for x in item: # if hasattr(x, "url"): # print "item to play", x # else: # print "album", x # if str(x) != "All songs": # for y in x: # print " ",y,y.url #print "-----------------" if not hasattr(item, "surface") or item.surface is None: self.set_item_surfaces(item) if locn_offset == 0: surf = item.surface halfwidth = surf.get_width() / 2 self.surface.blit(surf, (left_position - halfwidth, 0)) self.surface.blit(item.text_surface, ((self.width / 2) - (item.text_surface.get_width() / 2), surf.get_height() + 30)) left_position = left_position - halfwidth - self.spacing right_position = right_position + halfwidth + self.spacing elif locn_offset > 0: if locn >= len(self.media): continue if right_position > self.width: continue surf = item.small_surface self.surface.blit(surf, (right_position, self.non_highlight_offset)) right_position += surf.get_width() + self.spacing elif locn_offset < 0: if locn < 0: continue if left_position < 0: continue surf = item.small_surface self.surface.blit(surf, (left_position - surf.get_width(), self.non_highlight_offset)) left_position -= surf.get_width() + self.spacing # render to main screen self.main_renderer.screen.blit(self.surface, (0, self.y)) self.set_dirty() def set_item_art_surfaces(self, item): if item.art is None: item.surface = pygame.transform.scale(random.choice([self.surface_noart,self.surface_noart2,self.surface_noart3]), self.highlighted_size) else: art = pygame.image.load(item.art) item.surface = pygame.transform.scale(art, self.highlighted_size) CIRCLE_CORNERS = [12, 9, 8, 6, 5, 4, 3, 3, 2, 1, 1, 1] for i in range(len(CIRCLE_CORNERS)): w, h = self.highlighted_size # top left pygame.draw.line(item.surface, (255,255,255,0), (i,0), (i,CIRCLE_CORNERS[i])) # top right pygame.draw.line(item.surface, (255,255,255,0), (w-i,0), (w-i,CIRCLE_CORNERS[i])) # bottom left pygame.draw.line(item.surface, (255,255,255,0), (i,h), (i,h-CIRCLE_CORNERS[i])) # bottom right pygame.draw.line(item.surface, (255,255,255,0), (w-i,h), (w-i,h-CIRCLE_CORNERS[i])) item.small_surface = pygame.transform.scale(item.surface, self.non_highlighted_size) item.surface.convert() item.small_surface.convert() def startup(self): self.start_transition(self.main_renderer.screen.get_height(), 140, 10, "y") def set_item_surfaces(self, item): self.set_item_art_surfaces(item) self.set_item_text_surfaces(item) def handle_key(self, key): changed = False if key == K_LEFT: if self.selected == 0: return self.selected = (self.selected - 1) % len(self.media) changed = True elif key == K_RIGHT: if self.selected == len(self.media) - 1: return self.selected = (self.selected + 1) % len(self.media) changed = True elif key == K_ESCAPE: self.start_transition(self.y, self.main_renderer.screen.get_height(), 10, "y") self.main_renderer.focus(self.main_renderer.mainmenu) if changed: self.render() def set_dirty(self): self.main_renderer.dirty.append((0, self.y, self.width, self.height)) class ArtistsMenu(MediaMenu): def __init__(self, main_renderer, media): self.y = 140 self.surface_noart = pygame.image.load("images/album-noart.png") self.surface_noart2 = pygame.image.load("images/album-noart2.png") self.surface_noart3 = pygame.image.load("images/album-noart3.png") self.highlighted_size = (200,200) self.non_highlighted_size = (140, 140) self.spacing = 30 super(ArtistsMenu, self).__init__(main_renderer, media) def set_item_text_surfaces(self, item): texts = [] texts.append((self.titlefont, str(item))) for album in item: if hasattr(album, "url"): continue # playall, shuffleall if str(album) == "All songs": continue texts.append((self.bodyfont, "album: %s" % album)) for song in album: if song.url.startswith("playall:"): continue # playall, shuffleall texts.append((self.bodyfont, " song: %s" % song)) text_surface_height = 0 text_surface_width = 0 for font, text in texts: w, h = font.size(text) if w > text_surface_width: text_surface_width = w text_surface_height += h item.text_surface = pygame.surface.Surface((text_surface_width, text_surface_height), flags=SRCALPHA) item.text_surface.fill((200,200,200,0)) ypos = 0 for font, text in texts: surf = font.render(text, True, (0, 0, 40)) item.text_surface.blit(surf, (0,ypos)) ypos += surf.get_height() class AlbumsMenu(MediaMenu): def __init__(self, main_renderer, media): self.y = 140 self.surface_noart = pygame.image.load("images/album-noart.png") self.surface_noart2 = pygame.image.load("images/album-noart2.png") self.surface_noart3 = pygame.image.load("images/album-noart3.png") self.highlighted_size = (200,200) self.non_highlighted_size = (140, 140) self.spacing = 30 super(AlbumsMenu, self).__init__(main_renderer, media) class PlaylistsMenu(MediaMenu): def __init__(self, main_renderer, media): self.y = 140 self.surface_noart = pygame.image.load("images/album-noart.png") self.surface_noart2 = pygame.image.load("images/album-noart2.png") self.surface_noart3 = pygame.image.load("images/album-noart3.png") self.highlighted_size = (200,200) self.non_highlighted_size = (140, 140) self.spacing = 30 super(PlaylistsMenu, self).__init__(main_renderer, media) class MoviesMenu(MediaMenu): def __init__(self, main_renderer, media): self.y = 140 self.surface_noart = pygame.image.load("images/video-noart.jpg") self.surface_noart2 = pygame.image.load("images/video-noart.jpg") self.surface_noart3 = pygame.image.load("images/video-noart.jpg") self.highlighted_size = (160,240) self.non_highlighted_size = (100, 150) self.spacing = 30 super(MoviesMenu, self).__init__(main_renderer, media) def set_item_text_surfaces(self, item): item.text_surface = self.titlefont.render(str(item), True, (0, 0, 40)) class MainMenu(Menu): def __init__(self, main_renderer): super(MainMenu, self).__init__(main_renderer) self.y = main_renderer.screen.get_height() / 2 self.width = main_renderer.screen.get_width() self.height = 100 self.surface = pygame.surface.Surface((self.width, self.height)) # create background bg_texture = pygame.image.load(os.path.join('images', 'stone.jpg')) self.bg = self.surface.copy() self.bg.blit(bg_texture, (0,0)) del(bg_texture) self.textitems = Wraparound(["MOVIES", "ARTISTS", "ALBUMS", "PLAYLISTS"]) self.selected = 0 self.iter = [0] for i in range(1,10): self.iter.append(i) self.iter.append(-i) # Calculate correct font size fontsize = 10 height = 0 while height < self.height + 35: self.font = pygame.font.SysFont("aeon main", fontsize) fontsize += 1 height = self.font.size("MOVIES")[1] self.font_vert_pos = (self.height - self.font.size("MOVIES")[1]) / 2 self.spacing = 80 self.word_offset = 0 self.opacity = 255 def render(self): # background texture self.surface.blit(self.bg, (0,0)) # lines pygame.draw.line(self.surface, (255, 255, 255), (0,0), (self.width, 0), 1) pygame.draw.line(self.surface, (255, 255, 255), (0,self.height-1), (self.width, self.height-1), 1) # words left_position = self.width / 2 + self.word_offset right_position = self.width / 2 + self.word_offset for locn_offset in self.iter: locn = self.selected + locn_offset if locn_offset == 0: # first (currently selected) word wordsurf = self.font.render(self.textitems[locn], True, (255, 255, 255)) halfwidth = wordsurf.get_width() / 2 self.surface.blit(wordsurf, (left_position - halfwidth, self.font_vert_pos)) left_position = left_position - halfwidth - self.spacing right_position = right_position + halfwidth + self.spacing elif locn_offset > 0: if right_position > self.width: continue wordsurf = self.font.render(self.textitems[locn], True, (130, 130, 130)) self.surface.blit(wordsurf, (right_position, self.font_vert_pos)) right_position += wordsurf.get_width() + self.spacing elif locn_offset < 0: if left_position < 0: continue wordsurf = self.font.render(self.textitems[locn], True, (130, 130, 130)) self.surface.blit(wordsurf, (left_position - wordsurf.get_width(), self.font_vert_pos)) left_position -= wordsurf.get_width() + self.spacing # render to main screen self.surface.set_alpha(self.opacity) self.main_renderer.screen.blit(self.surface, (0, self.y)) self.set_dirty() def set_dirty(self): self.main_renderer.dirty.append((0, self.y, self.width, self.height)) def word_scroll_transition(self, direction): # distance to travel is: # half current word width + spacing + half next word width current_word_width = self.font.size(self.textitems[self.selected])[0] next_word_width = self.font.size(self.textitems[self.selected + direction])[0] transition_distance = (current_word_width / 2) + self.spacing + (next_word_width / 2) transition_distance = transition_distance * direction self.start_transition(0, transition_distance, 20, "word_offset", self.set_selected, self.selected - direction) def set_selected(self, new_selected): self.selected = new_selected self.word_offset = 0 self.render() def handle_key(self, key): if key == K_RIGHT or key == K_LEFT: if key == K_LEFT: self.word_scroll_transition(1) if key == K_RIGHT: self.word_scroll_transition(-1) elif key == K_RETURN: # we can only get an enter keypress when we're the main # menu, so ENTER here must mean that we are, and we're # choosing the currently selected item self.start_transition(self.y, 20, 10, "y") self.start_transition(255, 80, 10, "opacity") # and hand off control to the next thing if self.textitems[self.selected] == "MOVIES": self.main_renderer.focus(self.main_renderer.moviesmenu) elif self.textitems[self.selected] == "ARTISTS": self.main_renderer.focus(self.main_renderer.artistsmenu) elif self.textitems[self.selected] == "ALBUMS": self.main_renderer.focus(self.main_renderer.albumsmenu) elif self.textitems[self.selected] == "PLAYLISTS": self.main_renderer.focus(self.main_renderer.playlistsmenu) elif key == K_ESCAPE: pygame.event.post(pygame.event.Event(QUIT)) def startup(self): if self.y == 20: # at the top, so scroll back down again self.start_transition(self.y, self.main_renderer.screen.get_height() / 2, 10, "y") self.start_transition(80, 255, 10, "opacity") else: # actual startup of whole program, so just display self.render() class PygameRenderer(object): def __init__(self, media): self.media = media videos = [x for x in media if str(x) == "Videos"][0] music = [x for x in media if str(x) == "Music"][0] byartist = [x for x in music if str(x) == "By artist"][0] #self.screen = pygame.display.set_mode((0,0), pygame.FULLSCREEN) self.screen = pygame.display.set_mode((800, 600)) self.background = self.get_background() pygame.key.set_repeat(300, 1) self.clock = pygame.time.Clock() self.mainmenu = MainMenu(self) self.artistsmenu = ArtistsMenu(self, byartist) self.albumsmenu = AlbumsMenu(self, byartist) self.moviesmenu = MoviesMenu(self, videos) self.playlistsmenu = PlaylistsMenu(self, byartist) self.dirty = [] def get_background(self): backgrounds = os.listdir("images/backgrounds") bg = random.choice(backgrounds) bg_surf_orig = pygame.image.load(os.path.join("images/backgrounds", bg)) bg_surf = pygame.transform.scale(bg_surf_orig, self.screen.get_size()) del(bg_surf_orig) nbg_surf = bg_surf.convert() del(bg_surf) return nbg_surf def focus(self, menu): self.focused = menu self.focused.startup() def main(self): self.screen.blit(self.background, (0,0)) self.dirty.append((0,0,self.screen.get_width(), self.screen.get_height())) self.focus(self.mainmenu) self.mainloop() def mainloop(self): while 1: self.clock.tick(100) pygame.display.update(self.dirty) self.screen.blit(self.background, (0,0)) self.dirty = [] for event in pygame.event.get(): if event.type == KEYDOWN: if event.key == K_q: pygame.event.post(pygame.event.Event(QUIT)) else: self.focused.handle_key(event.key) elif event.type == TRANSITION: event.recipient.handle_transition(event) elif event.type == pygame.QUIT: sys.exit()