327 lines
12 KiB
Python
327 lines
12 KiB
Python
|
|
import pygame
|
||
|
|
from constants import *
|
||
|
|
from enum import Enum
|
||
|
|
|
||
|
|
class ButtonTypes(Enum):
|
||
|
|
PLAY = "play"
|
||
|
|
RESUME = "resume"
|
||
|
|
MENU = "menu"
|
||
|
|
DELETE = "delete"
|
||
|
|
QUIT = "quit"
|
||
|
|
RETRY = "retry"
|
||
|
|
FW = "fw"
|
||
|
|
BW = "bw"
|
||
|
|
GO = "go"
|
||
|
|
|
||
|
|
class MenuType(Enum):
|
||
|
|
PAUSE = 0
|
||
|
|
COMPLETED = 1
|
||
|
|
START = 2
|
||
|
|
SELECT = 3
|
||
|
|
|
||
|
|
|
||
|
|
# Button class that can store a value
|
||
|
|
class Button:
|
||
|
|
def __init__(self, x, y, value, type=ButtonTypes.PLAY):
|
||
|
|
# value is a utility field that can store something useful for the use of the button
|
||
|
|
self.x = x
|
||
|
|
self.y = y
|
||
|
|
self.value = value
|
||
|
|
|
||
|
|
self.original_image, self.dark_image = self.load_image(type)
|
||
|
|
self.image = self.original_image.convert_alpha()
|
||
|
|
|
||
|
|
# Collide rect measures based on the type
|
||
|
|
if type == ButtonTypes.FW or type == ButtonTypes.BW:
|
||
|
|
width = 105
|
||
|
|
height = 82
|
||
|
|
elif type == ButtonTypes.GO:
|
||
|
|
width = 83
|
||
|
|
height = 63
|
||
|
|
else:
|
||
|
|
width = 232
|
||
|
|
height = 82
|
||
|
|
|
||
|
|
self.rect = pygame.Rect(
|
||
|
|
x,
|
||
|
|
y,
|
||
|
|
int(width * SCALE_FACTOR),
|
||
|
|
int(height * SCALE_FACTOR)
|
||
|
|
)
|
||
|
|
|
||
|
|
self.image_rect = self.image.get_rect(
|
||
|
|
center=self.rect.center
|
||
|
|
)
|
||
|
|
|
||
|
|
def load_image(self, type):
|
||
|
|
if type == ButtonTypes.MENU:
|
||
|
|
image = pygame.transform.scale_by(pygame.image.load('assets/menu_btn.png'), SCALE_FACTOR)
|
||
|
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/menu_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
elif type == ButtonTypes.QUIT:
|
||
|
|
image = pygame.transform.scale_by(pygame.image.load('assets/quit_btn.png'), SCALE_FACTOR)
|
||
|
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/quit_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
elif type == ButtonTypes.DELETE:
|
||
|
|
image = pygame.transform.scale_by(pygame.image.load('assets/delete_btn.png'), SCALE_FACTOR)
|
||
|
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/delete_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
elif type == ButtonTypes.RESUME:
|
||
|
|
image = pygame.transform.scale_by(pygame.image.load('assets/resume_btn.png'), SCALE_FACTOR)
|
||
|
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/resume_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
elif type == ButtonTypes.PLAY:
|
||
|
|
image = pygame.transform.scale_by(pygame.image.load('assets/play_btn.png'), SCALE_FACTOR)
|
||
|
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/play_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
elif type == ButtonTypes.RETRY:
|
||
|
|
image = pygame.transform.scale_by(pygame.image.load('assets/retry_btn.png'), SCALE_FACTOR)
|
||
|
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/retry_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
elif type == ButtonTypes.FW:
|
||
|
|
image = pygame.transform.scale_by(pygame.image.load('assets/fw_btn.png'), SCALE_FACTOR)
|
||
|
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/fw_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
elif type == ButtonTypes.BW:
|
||
|
|
image = pygame.transform.scale_by(pygame.image.load('assets/bw_btn.png'), SCALE_FACTOR)
|
||
|
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/bw_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
elif type == ButtonTypes.GO:
|
||
|
|
image = pygame.transform.scale_by(pygame.image.load('assets/go_btn.png'), SCALE_FACTOR)
|
||
|
|
dark_image = pygame.transform.scale_by(pygame.image.load('assets/go_dark_btn.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
|
||
|
|
return image, dark_image
|
||
|
|
|
||
|
|
def draw(self, screen, mouse, font):
|
||
|
|
#text_surface = text_outline_perfect(font, self.text, WHITE, DARK)
|
||
|
|
#text_rect = text_surface.get_rect(center=self.rect.center)
|
||
|
|
# shadow_rect = sahdow_surface.get_rect(center=self.rect.center)
|
||
|
|
|
||
|
|
if self.rect.collidepoint(mouse):
|
||
|
|
screen.blit(self.dark_image, self.image_rect)
|
||
|
|
else:
|
||
|
|
screen.blit(self.image, self.image_rect)
|
||
|
|
|
||
|
|
#pygame.draw.rect(screen, (255, 0, 255), self.rect, 2)
|
||
|
|
#screen.blit(text_surface, text_rect)
|
||
|
|
|
||
|
|
def clicked(self, mouse):
|
||
|
|
return self.rect.collidepoint(mouse)
|
||
|
|
|
||
|
|
# Easy, text area
|
||
|
|
class TextArea:
|
||
|
|
def __init__(self, x, y, text, color):
|
||
|
|
self.text = text
|
||
|
|
self.rect = pygame.Rect(x, y, 0, 0)
|
||
|
|
self.color = color
|
||
|
|
def draw(self, screen, font):
|
||
|
|
text_surface = font.render(self.text, True, self.color)
|
||
|
|
self.rect = text_surface.get_rect(center=self.rect.center)
|
||
|
|
screen.blit(text_surface, self.rect)
|
||
|
|
|
||
|
|
class EntryState(Enum):
|
||
|
|
UNLOCKED = 0
|
||
|
|
LOCKED = 1
|
||
|
|
|
||
|
|
class SelectEntry:
|
||
|
|
def __init__(self, x, y, text, value, state):
|
||
|
|
self.x = x
|
||
|
|
self.y = y
|
||
|
|
self.text = text
|
||
|
|
self.btn = Button(0, 0, value, ButtonTypes.GO)
|
||
|
|
self.area = TextArea(0, 0, text, DARK)
|
||
|
|
self.icon = self.load_icon(state)
|
||
|
|
self.icon_rect = self.icon.get_rect()
|
||
|
|
self.value = value
|
||
|
|
|
||
|
|
# Calculate full width
|
||
|
|
#self.width = self.btn.rect.width + self.area.rect.width + self.icon_rect.width
|
||
|
|
self.width = 500
|
||
|
|
self.height = self.btn.rect.height
|
||
|
|
self.rect = pygame.Rect(x, y, self.width, self.height)
|
||
|
|
|
||
|
|
def clicked(self, mouse):
|
||
|
|
return self.btn.clicked(mouse)
|
||
|
|
|
||
|
|
def load_icon(self, state):
|
||
|
|
if state == EntryState.LOCKED:
|
||
|
|
return pygame.image.load("assets/locked.png").convert_alpha()
|
||
|
|
elif state == EntryState.UNLOCKED:
|
||
|
|
return pygame.image.load("assets/unlocked.png").convert_alpha()
|
||
|
|
|
||
|
|
def draw(self, screen, mouse, font):
|
||
|
|
|
||
|
|
self.btn.rect.y = self.rect.y
|
||
|
|
self.btn.image_rect.y = self.rect.y
|
||
|
|
self.area.rect.y = self.rect.y
|
||
|
|
self.icon_rect.y = self.rect.y
|
||
|
|
|
||
|
|
x = self.rect.x + 20
|
||
|
|
self.btn.rect.x = x
|
||
|
|
self.btn.image_rect.x = x
|
||
|
|
x += self.btn.rect.width*2
|
||
|
|
|
||
|
|
self.area.rect.x = x
|
||
|
|
|
||
|
|
self.icon_rect.x = 800
|
||
|
|
|
||
|
|
self.btn.draw(screen, mouse, font)
|
||
|
|
self.area.draw(screen, font)
|
||
|
|
screen.blit(self.icon, self.icon_rect)
|
||
|
|
|
||
|
|
|
||
|
|
# Single page menu class
|
||
|
|
class MenuPage:
|
||
|
|
def __init__(self, type):
|
||
|
|
self.top_text_area = None
|
||
|
|
self.bottom_text_area = None
|
||
|
|
self.buttons = []
|
||
|
|
self.entries = []
|
||
|
|
self.bg_image = self.load_card(type)
|
||
|
|
|
||
|
|
def load_card(self, type):
|
||
|
|
if type == MenuType.PAUSE:
|
||
|
|
return pygame.transform.scale_by(pygame.image.load('assets/paused_card.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
elif type == MenuType.COMPLETED:
|
||
|
|
return pygame.transform.scale_by(pygame.image.load('assets/completed_card.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
elif type == MenuType.START:
|
||
|
|
return pygame.transform.scale_by(pygame.image.load('assets/menu_card.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
elif type == MenuType.SELECT:
|
||
|
|
return pygame.transform.scale_by(pygame.image.load('assets/select_card.png').convert_alpha(), SCALE_FACTOR)
|
||
|
|
|
||
|
|
# Button methods
|
||
|
|
def add_button(self, value, type):
|
||
|
|
self.buttons.append(Button(0, 0, value, type))
|
||
|
|
def add_entry(self, text, value, state):
|
||
|
|
self.entries.append(SelectEntry(0, 0, text, value, state))
|
||
|
|
|
||
|
|
def add_top_text(self, text):
|
||
|
|
self.top_text_area = TextArea(0, 0, text, DARK)
|
||
|
|
def add_bottom_text(self, text):
|
||
|
|
self.bottom_text_area = TextArea(0, 0, text, DARK)
|
||
|
|
def draw(self, screen, mouse, btn_font, text_font):
|
||
|
|
center_x, center_y = screen.get_rect().center
|
||
|
|
|
||
|
|
# Draw the card image
|
||
|
|
screen.blit(self.bg_image, (center_x-self.bg_image.get_width()/2, center_y-self.bg_image.get_height()/2))
|
||
|
|
|
||
|
|
spacing = BTN_HEIGHT * 0.5
|
||
|
|
elements = []
|
||
|
|
|
||
|
|
if self.top_text_area is not None:
|
||
|
|
elements.append(self.top_text_area)
|
||
|
|
|
||
|
|
elements.extend(self.buttons)
|
||
|
|
elements.extend(self.entries)
|
||
|
|
|
||
|
|
if self.bottom_text_area is not None:
|
||
|
|
elements.append(self.bottom_text_area)
|
||
|
|
|
||
|
|
# Calculate the dimensions
|
||
|
|
heights = []
|
||
|
|
for element in elements:
|
||
|
|
if isinstance(element, TextArea):
|
||
|
|
text_surface = text_font.render(element.text, True, WHITE)
|
||
|
|
heights.append(text_surface.get_height())
|
||
|
|
elif isinstance(element, Button):
|
||
|
|
heights.append(element.image.get_height()/2)
|
||
|
|
else:
|
||
|
|
heights.append(element.height)
|
||
|
|
|
||
|
|
self.total_height = sum(heights) + spacing * (len(elements) - 1)
|
||
|
|
y = center_y - self.total_height / 2
|
||
|
|
|
||
|
|
for element, height in zip(elements, heights):
|
||
|
|
element.rect.centerx = center_x
|
||
|
|
element.rect.y = y
|
||
|
|
|
||
|
|
if isinstance(element, Button):
|
||
|
|
element.image_rect.center = element.rect.center
|
||
|
|
element.draw(screen, mouse, btn_font)
|
||
|
|
elif isinstance(element, TextArea):
|
||
|
|
element.draw(screen, text_font)
|
||
|
|
else:
|
||
|
|
element.draw(screen, mouse, text_font)
|
||
|
|
|
||
|
|
y += height + spacing
|
||
|
|
|
||
|
|
def handle_event(self, event, mouse):
|
||
|
|
if event.type == pygame.QUIT:
|
||
|
|
return "quit_game"
|
||
|
|
if event.type == pygame.MOUSEBUTTONDOWN:
|
||
|
|
for button in self.buttons:
|
||
|
|
if button.clicked(mouse):
|
||
|
|
return button.value
|
||
|
|
for entry in self.entries:
|
||
|
|
if entry.clicked(mouse):
|
||
|
|
return entry.value
|
||
|
|
|
||
|
|
return None
|
||
|
|
|
||
|
|
# Multi-page menu, automatically creates new page 3 buttons per page
|
||
|
|
class Menu:
|
||
|
|
def __init__(self, menu_type):
|
||
|
|
self.bottom_buttons = [Button(0, 0, None, ButtonTypes.BW), Button(0, 0, "menu", ButtonTypes.MENU), Button(0, 0, None, ButtonTypes.FW)]
|
||
|
|
|
||
|
|
self.pages = []
|
||
|
|
self.menu_type = menu_type
|
||
|
|
# Creating the first page
|
||
|
|
self.pages.append(MenuPage(self.menu_type))
|
||
|
|
self.current_page_i = 0
|
||
|
|
|
||
|
|
|
||
|
|
# For now the paged menu can only have pages with just buttons, no top or bottom text
|
||
|
|
def add_button(self, value, type):
|
||
|
|
current_page = self.pages[-1]
|
||
|
|
if len(current_page.buttons) >= MAX_BTN:
|
||
|
|
current_page = MenuPage(self.menu_type)
|
||
|
|
self.pages.append(current_page)
|
||
|
|
|
||
|
|
current_page.add_button(value, type)
|
||
|
|
|
||
|
|
def add_entry(self, text, value, state):
|
||
|
|
current_page = self.pages[-1]
|
||
|
|
if len(current_page.entries) >= MAX_BTN:
|
||
|
|
current_page = MenuPage(self.menu_type)
|
||
|
|
self.pages.append(current_page)
|
||
|
|
|
||
|
|
current_page.add_entry(text, value, state)
|
||
|
|
|
||
|
|
def draw(self, screen, mouse, btn_font, text_font):
|
||
|
|
center_x, center_y = screen.get_rect().center
|
||
|
|
spacing = BTN_HEIGHT * 0.5
|
||
|
|
|
||
|
|
self.pages[self.current_page_i].draw(screen, mouse, btn_font, text_font)
|
||
|
|
|
||
|
|
# bottom y, OK
|
||
|
|
bottom_y = center_y + self.pages[self.current_page_i].total_height / 2 + spacing
|
||
|
|
|
||
|
|
# Calculate total width
|
||
|
|
group_width = 0
|
||
|
|
for button in self.bottom_buttons:
|
||
|
|
group_width += button.rect.width
|
||
|
|
|
||
|
|
x = center_x - group_width / 2.5
|
||
|
|
for button in self.bottom_buttons:
|
||
|
|
button.rect.centerx = x
|
||
|
|
button.rect.y = bottom_y
|
||
|
|
button.image_rect.center = button.rect.center
|
||
|
|
x += group_width / 2.5
|
||
|
|
|
||
|
|
for button in self.bottom_buttons:
|
||
|
|
button.draw(screen, mouse, btn_font)
|
||
|
|
|
||
|
|
def handle_event(self, event, mouse):
|
||
|
|
if event.type == pygame.QUIT:
|
||
|
|
return "quit_game"
|
||
|
|
|
||
|
|
# Maybe in the future the index can wrap around
|
||
|
|
if event.type == pygame.MOUSEBUTTONDOWN:
|
||
|
|
if self.bottom_buttons[0].clicked(mouse):
|
||
|
|
if self.current_page_i > 0:
|
||
|
|
self.current_page_i -= 1
|
||
|
|
return None
|
||
|
|
|
||
|
|
if self.bottom_buttons[1].clicked(mouse):
|
||
|
|
return self.bottom_buttons[1].value
|
||
|
|
|
||
|
|
if self.bottom_buttons[2].clicked(mouse):
|
||
|
|
if self.current_page_i < len(self.pages) - 1:
|
||
|
|
self.current_page_i += 1
|
||
|
|
return None
|
||
|
|
|
||
|
|
return self.pages[self.current_page_i].handle_event(event, mouse)
|
||
|
|
|
||
|
|
return None
|