1
0
Fork 0
menu/menu.py

176 lines
6.2 KiB
Python

#!/usr/bin/env python3
import math
import sys
from time import sleep
from threading import Thread
from queue import Queue, Empty
from abc import abstractmethod
from typing import Tuple, Callable, TypedDict, List
import pygame
import pygame.freetype
class ButtonDef(TypedDict):
text: str
cbk: Callable[[], None]
class IBoard:
@abstractmethod
def draw(self, screen: pygame.Surface):
raise NotImplementedError()
@abstractmethod
def handle_event(self, event: pygame.event.Event):
raise NotImplementedError()
class Button:
def __init__(self, position: pygame.Rect, text: str, cbk: Callable[[], None]):
self.position: pygame.Rect
self.set_position(position)
self.text = text
self.cbk = cbk
self.font = pygame.freetype.SysFont(name="Sans", size=20)
text_rect = self.font.get_rect(self.text)
self.text_pos = pygame.Rect((self.position.left + (self.position.width - text_rect.width)/2),
(self.position.top + (self.position.height - text_rect.height)/2),
text_rect.width, text_rect.height)
def set_position(self, position: pygame.Rect):
self.position = position
def draw(self, screen: pygame.Surface):
pygame.draw.rect(screen, "black", self.position, 0)
self.font.render_to(screen, self.text_pos, self.text, fgcolor="pink")
def pos_is_inside(self, pos: Tuple) -> bool:
return (self.position.left < pos[0] and (self.position.left + self.position.width) > pos[0]) and \
(self.position.top < pos[1] and (self.position.top + self.position.height) > pos[1])
def handle_event(self, event: pygame.event.Event):
if event.type == pygame.MOUSEBUTTONUP:
if event.button == 1 and self.pos_is_inside(event.pos):
self.cbk()
class MessageBoard(IBoard):
def __init__(self, screen_rect: pygame.Rect, text: str):
self.text = text
self.font = pygame.freetype.SysFont(name="Sans", size=30)
text_rect = self.font.get_rect(self.text)
self.text_pos = pygame.Rect((screen_rect.left + (screen_rect.width - text_rect.width)/2),
(screen_rect.top + (screen_rect.height - text_rect.height)/2),
text_rect.width, text_rect.height)
def handle_event(self, _):
pass
def draw(self, screen: pygame.Surface):
screen.fill("purple")
self.font.render_to(screen, self.text_pos, self.text, fgcolor="black")
class MenuBoard(IBoard):
BUTTON_MARGINS = 10
def __init__(self, screen_rect: pygame.Rect, buttons: List[ButtonDef]):
self.rows: int
self.cols: int
if len(buttons) == 2:
# special case which doesn't seem to fit into the sqrt
self.rows = 1
self.cols = 2
else:
self.rows = math.ceil(math.sqrt(len(buttons)))
self.cols = math.ceil(math.sqrt(len(buttons)))
button_positions = self.generate_button_positions(screen_rect)
self.buttons = list(map(lambda d: Button(next(button_positions),
f"Button {d['text']}",
d['cbk']), buttons))
def generate_button_positions(self, screen_rect: pygame.Rect):
current_button_row = 0
current_button_col = 0
button_width = math.floor((screen_rect.width - (self.cols + 1) * self.BUTTON_MARGINS) / self.cols)
button_height = math.floor((screen_rect.height - (self.rows + 1) * self.BUTTON_MARGINS) / self.rows)
while (current_button_row * current_button_col) < (self.cols * self.rows):
top = self.BUTTON_MARGINS + (current_button_row * (button_height + self.BUTTON_MARGINS))
left = self.BUTTON_MARGINS + (current_button_col * (button_width + self.BUTTON_MARGINS))
current_button_col += 1
if current_button_col >= self.cols:
current_button_col = 0
current_button_row += 1
yield pygame.Rect(left, top, button_width, button_height)
def handle_event(self, event: pygame.event.Event):
for b in self.buttons:
b.handle_event(event)
def draw(self, screen: pygame.Surface):
screen.fill("purple")
for b in self.buttons:
b.draw(screen)
class App:
def __init__(self, btns: int):
pygame.init()
info = pygame.display.Info()
self.screen = pygame.display.set_mode((info.current_w, info.current_h), flags=pygame.FULLSCREEN)
self.clock = pygame.time.Clock()
self.running = False
buttons = list(map(lambda t: ButtonDef(text=f"{t}", cbk=self.button_press_handler(str(t))), list(range(btns))))
buttons.append(ButtonDef(text="Exit", cbk=self.quit))
self.board: IBoard = MenuBoard(self.get_screen_rect(), buttons)
self.task_q = Queue()
def get_screen_rect(self) -> pygame.Rect:
return pygame.Rect(0, 0, self.screen.get_width(), self.screen.get_height())
def button_press_handler(self, text: str) -> Callable[[], None]:
def impl():
previous_board = self.board
def thr_fun():
def end_thr():
self.board = previous_board
sleep(5)
self.task_q.put(end_thr)
process_thr = Thread(target=thr_fun)
process_thr.start()
self.board = MessageBoard(self.get_screen_rect(), f"Wait: {text}")
return impl
def loop(self):
self.running = True
while self.running:
try:
self.task_q.get_nowait()()
except Empty:
pass
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
self.board.handle_event(event)
self.board.draw(self.screen)
pygame.display.flip()
self.clock.tick(30)
pygame.quit()
def quit(self):
self.board = MessageBoard(self.get_screen_rect(), "Exiting...")
self.running = False
if __name__ == '__main__':
app = App(int(sys.argv[1]))
app.loop()