class KezMenu(object): def __init__(self, game_opts, *menu_opts): # create a programmer friendly mapping stucture of the menu options self.options = [{'label': x[0], 'callable': x[1]} for x in menu_opts] self.game_opts = game_opts # set the default menu dimensions (these dimensions # are calculated depending on the font and its size) self.width = 0 self.height = 0 # set up the default coordinates of the menu self.x = 0 self.y = 0 # the topleft corner of the screen self.screen_topleft_offset = (0, 0) # set the default focused option self.option = 0 # set the default previous focused option self.option_previous = self.option # set the default normal color of the menu font self.normal_color = pygame.Color('black') # set the default focused color of the menu font self.focus_color = pygame.Color('red') # default is to enable support of menu image on focused options self.image_enabled = True # default is to enable support mouse on menu self.mouse_enabled = True # set mouse focusing at unknown by default self.mouse_focus = None self._font = None # set a default font and its size (also fix size) self.font = pygame.font.Font(None, FONT_SIZE) self._fix_size() # set the default sound to play when an option is focused self.focus_sound = load_sound( constants.FILES['sounds']['menu']['share']['focus'][0]) # set the default sound to play when an option is entered self.select_sound = load_sound( constants.FILES['sounds']['menu']['share']['sel'][0]) # set the default graphic to display before the option label self.focus_graphic = ResourceManager().getImage( constants.FILES['graphics']['menu']['share']['focus'][0]) def _fix_size(self): '''Fix the menu size (called when the font is changed).''' self.height = 0 # for each option in the menu (set menu's width, height) for o in self.options: text = o['label'] font = o['font'] ren = font.render(text, True, self.normal_color) # find the maximum label width and set the menu's width if ren.get_width() > self.width: self.width = ren.get_width() # add all labels' heights and set the menu's height self.height += font.get_height() def draw(self, surface): '''Blit the menu to a surface.''' offset = 0 i = 0 ol, ot = self.screen_topleft_offset first = self.options and self.options[0] last = self.options and self.options[-1] for o in self.options: indent = o.get('padding_col', 0) # padding above the line if o != first and o.get('padding_line', 0): offset += o['padding_line'] font = o.get('font', self._font) # if there is a highlight color use it if i == self.option and self.focus_color: clr = self.focus_color else: clr = self.normal_color # get the label of the option text = o['label'] # get the size of the first letter letter_width, letter_height = font.size(text[0]) # render the options's label ren = font.render(text, True, clr) # draw line in correct coordinates in # order to underline the first letter surface.lock() pygame.draw.line(ren, clr, (0, letter_height - 1), (letter_width, letter_height - 1)) surface.unlock() if ren.get_width() > self.width: self.width = ren.get_width() o['label_rect'] = pygame.Rect( (ol + self.x + indent, ot + self.y + offset), (ren.get_width(), ren.get_height())) surface.blit(ren, (self.x + indent, self.y + offset)) # divide the difference between the font and graphic height calc_space = abs( (font.get_height() - self.focus_graphic.get_height()) / 2.0) # print the menu image on focused option (only if it's enabled) if i == self.option and self.image_enabled: surface.blit(self.focus_graphic, (abs(self.x - FOCUS_IMAGE_SPACE - self.focus_graphic.get_width()), self.y + calc_space + offset)) offset += font.get_height() # padding below the line if o != last and o.get('padding_line', 0): offset += o['padding_line'] i += 1 def update(self, events): for e in events: if e.type == pygame.KEYDOWN: # change menu option if e.key == pygame.K_DOWN: self.option += 1 elif e.key == pygame.K_UP: self.option -= 1 # play a sound when a menu option: # is focused if e.key in (pygame.K_UP, pygame.K_DOWN): if self.game_opts.sound: play_sound(self.focus_sound, FOCUS_SOUND_VOL) # is activated elif e.key in (pygame.K_RETURN, pygame.K_SPACE): if self.game_opts.sound: play_sound(self.select_sound, FOCUS_SOUND_VOL) self.options[self.option]['callable']() # mouse controls (if enabled) if self.mouse_enabled: if e.type == pygame.MOUSEMOTION: # keep in mind the previous option self.option_previous = self.option # check the mouse's positions to # know if move focus on a option self._check_mouse_pos_for_focus() # play a sound only when we change between options if self.game_opts.sound: if self.option != self.option_previous: play_sound(self.focus_sound, FOCUS_SOUND_VOL) # if there are any mouse buttons pressed down elif e.type == pygame.MOUSEBUTTONDOWN: # check the mouse's positions to # know if move focus on a option self._check_mouse_pos_for_focus() # get mouse button events lb, _, _ = pygame.mouse.get_pressed() # play when left button is pressed on focused option if lb and self.mouse_focus: if self.game_opts.sound: play_sound(self.select_sound, FOCUS_SOUND_VOL) self.options[self.option]['callable']() # menu options limits (cyclic logic) option_len = len(self.options) - 1 if self.option > option_len: self.option = 0 elif self.option < 0: self.option = option_len def _check_mouse_pos_for_focus(self): i = 0 # get mouse position at the moment mouse_pos = pygame.mouse.get_pos() # for each option in the menu for o in self.options: # get its label rectangle rect = o.get('label_rect') if rect: # if an option is focused, update that option and # then inform the caller that the mouse is focused if rect.collidepoint(mouse_pos): self.option = i self.mouse_focus = True break i += 1 else: # there was no mouse focus on menu options self.mouse_focus = False def toggle_image(self): self.image_enabled = not self.image_enabled def set_position(self, x, y): self.position = (x, y) def _set_position(self, position): self.x, self.y = position def set_font(self, font): self.font = font def _set_font(self, font): self._font = font for o in self.options: o['font'] = font self._fix_size() def set_highlight_color(self, color): self.focus_color = color def set_normal_color(self, color): self.normal_color = color def center_at(self, x, y): self.x = x - (self.width / 2.0) self.y = y - (self.height / 2.0) # this code does not belong to any function position = property(lambda self: (self.x, self.y), _set_position, doc="""The menu position inside the container.""") font = property(lambda self: self._font, _set_font, doc="""Font used by the menu.""")
class Menu(State): def __init__(self, game_opts): State.__init__(self, constants.SCENES['menu']) self.game_opts = game_opts self.screen = pygame.display.get_surface() self.menu_settings_running = None self.menu_main_running = True pygame.key.set_repeat(MENU_KEY_DEL, MENU_KEY_INT) self.menu_main_bg = ResourceManager().getImage( constants.FILES['graphics']['menu']['main']['bg'][0]) self.menu_settings_bg = ResourceManager().getImage( constants.FILES['graphics']['menu']['share']['bg'][0]) self.menu_box_bg = ResourceManager().getImage( constants.FILES['graphics']['menu']['settings']['box'][0]) self.window_frame = ResourceManager().getImage( constants.FILES['graphics']['menu']['share']['frame'][0]) self.mouse_cursor = ResourceManager().getImage( constants.FILES['graphics']['menu']['share']['cursor'][0]) self.select_option_snd = sound_mixer.load_sound( constants.FILES['sounds']['menu']['share']['sel'][0]) # create the main menu - string, callback function self.menu_main = KezMenu(self.game_opts, ['Play' , self._play_option], ['Settings' , self._settings_option], ['Credits' , self._credits_option], ['Quit' , self._quit_option]) self.menu_main.set_position(MENU_MAIN_POS_X, MENU_MAIN_POS_Y) self.menu_main.set_font(graphics.load_font( constants.FILES['fonts']['menu']['share'][0], MAIN_FONT_SIZE)) self.menu_main.set_highlight_color(MAIN_FOCUS_COLOR) # create the settings menu - string, callback function self.menu_settings = KezMenu(self.game_opts, ['Fullscreen' , self._toggle_fullscreen_option], ['Sounds' , self._toggle_sounds_option], ['Music' , self._toggle_music_option], ['Back' , self._back_option]) # disable the menu graphic for focused options self.menu_settings.toggle_image() self.menu_settings.set_font(graphics.load_font( constants.FILES['fonts']['menu']['share'][0], MENU_FONT_SIZE)) self.menu_settings.center_at(constants.SCREEN_WIDTH / 2.0, constants.SCREEN_HEIGHT / 2.0) self.menu_settings.set_highlight_color(SETTINGS_FOCUS_COLOR) self.sprites = pygame.sprite.LayeredUpdates() sprites_number = len(constants.FILES['graphics']['menu']['share']['anim']) sprite_area = self.screen.get_rect() sprite_limiter = LimiterFactory().getInstance('Default') for i in range(sprites_number): sprite = MenuSprite(constants.FILES['graphics']['menu']['share']['anim'][i], (sprite_area.center), i, MAX_ALPHA, False, SPRITE_SPEED, sprite_area, 'Random') sprite.limiter = sprite_limiter self.sprites.add(sprite) self.clock = pygame.time.Clock() def do_actions(self): while self.menu_main_running: bg = self.screen.blit(self.menu_main_bg, (0, 0)) time_passed_seconds = get_time_sec(self.clock.tick(MENU_CLOCK_TICK)) self.sprites.update(time_passed_seconds) self.sprites.draw(self.screen) self.menu_main.draw(self.screen) graphics.handle_mouse_cursor(self.mouse_cursor, self.screen) fr = self.screen.blit(self.window_frame, (0, 0)) need_update = (bg, fr) pygame.display.update(need_update) events = pygame.event.get() self.menu_main.update(events) for e in events: if e.type == pygame.QUIT: self._quit_option() elif e.type == pygame.KEYDOWN: if self.game_opts.sound: if e.key in (pygame.K_p, pygame.K_s, pygame.K_c): sound_mixer.play_sound( self.select_option_snd, SOUND_VOL) if e.key in (pygame.K_ESCAPE, pygame.K_q): self._quit_option() elif e.key == pygame.K_p: self._play_option() elif e.key == pygame.K_s: self._settings_option() elif e.key == pygame.K_c: self._credits_option() # TODO eliminate this duplicate code def _settings_option(self): '''Entry point for main menu's settings option.''' # each time we enter in settings # sub menu, set the flag to true self.menu_settings_running = True for s in self.sprites: s.alpha = SPRITE_ALPHA while self.menu_settings_running: bg = self.screen.blit(self.menu_settings_bg, (0, 0)) self.menu_main.draw(self.screen) time_passed_seconds = get_time_sec(self.clock.tick(MENU_CLOCK_TICK)) self.sprites.update(time_passed_seconds) self.sprites.draw(self.screen) menu_bg = self.screen.blit(self.menu_box_bg, ( (constants.SCREEN_WIDTH - self.menu_box_bg.get_width()) / 2.0, (constants.SCREEN_HEIGHT - self.menu_box_bg.get_height()) / 2.0)) self.menu_settings.draw(self.screen) graphics.handle_mouse_cursor(self.mouse_cursor, self.screen) fr = self.screen.blit(self.window_frame, (0, 0)) need_update = (bg, menu_bg, fr) pygame.display.update(need_update) events = pygame.event.get() self.menu_settings.update(events) for e in events: if e.type == pygame.QUIT: self._back_option() self._quit_option() elif e.type == pygame.KEYDOWN: if self.game_opts.sound: if e.key in (pygame.K_f, pygame.K_s, pygame.K_m, pygame.K_b, pygame.K_ESCAPE): sound_mixer.play_sound( self.select_option_snd, SOUND_VOL) if e.key in (pygame.K_ESCAPE, pygame.K_b): self._back_option() elif e.key == pygame.K_f: self._toggle_fullscreen_option() elif e.key == pygame.K_s: self._toggle_sounds_option() elif e.key == pygame.K_m: self._toggle_music_option() for s in self.sprites: s.alpha = MAX_ALPHA def _play_option(self): if self.game_opts.verbose: print('Start a new game.') self.menu_main_running = False def check_conditions(self): if not self.menu_main_running: return constants.SCENES['level_one'] return None ## entry point for main menu's credits option # # @param self the object pointer def _credits_option(self): fullname = file_path( constants.FILES['texts']['menu']['credits']['text'][0], constants.TEXTS_DIR) if os.access(fullname, os.F_OK) and os.access(fullname, os.R_OK): if self.game_opts.verbose: print('Go to the credits screen.') c = Credits(self.screen, self.game_opts, self.window_frame, self.menu_settings_bg, self.select_option_snd, fullname) if not c.run(): # quit if the close button is # pressed (inside the credits) self._quit_option() else: path = os.path.basename(__file__) print("{0}: couldn't read text file: {1}".format(path, fullname)) raise SystemExit def _quit_option(self): if self.game_opts.verbose: print('Exit the game!') # perform safe exit safe_exit() def _toggle_fullscreen_option(self): self.game_opts.fullscreen = not self.game_opts.fullscreen if self.game_opts.verbose: print('Toggle fullscreen!') mouse_position = pygame.mouse.get_pos() pygame.display.set_mode( (constants.SCREEN_WIDTH, constants.SCREEN_HEIGHT), pygame.FULLSCREEN if self.game_opts.fullscreen else 0) pygame.mouse.set_pos(mouse_position) def _toggle_sounds_option(self): self.game_opts.sound = not self.game_opts.sound if self.game_opts.verbose: print('Toggle sounds!') def _toggle_music_option(self): if self.game_opts.verbose: print('Toggle music!') self.game_opts.music = not self.game_opts.music if self.game_opts.music: pygame.mixer.music.unpause() else: pygame.mixer.music.pause() def _back_option(self): self.menu_settings_running = False if self.game_opts.verbose: print('Go back to main menu!')