Esempio n. 1
0
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!')