1
0
Fork 0
menu/menu.py

176 lines
6.2 KiB
Python
Raw Normal View History

2024-09-08 11:43:21 +00:00
#!/usr/bin/env python3
2024-09-08 14:44:47 +00:00
import math
import sys
from time import sleep
from threading import Thread
from queue import Queue, Empty
2024-09-08 11:43:21 +00:00
from abc import abstractmethod
2024-09-08 14:44:47 +00:00
from typing import Tuple, Callable, TypedDict, List
2024-09-08 11:43:21 +00:00
import pygame
2024-09-08 14:44:47 +00:00
import pygame.freetype
class ButtonDef(TypedDict):
text: str
2024-09-08 14:50:18 +00:00
cbk: Callable[[], None]
2024-09-08 11:43:21 +00:00
class IBoard:
@abstractmethod
def draw(self, screen: pygame.Surface):
raise NotImplementedError()
@abstractmethod
def handle_event(self, event: pygame.event.Event):
raise NotImplementedError()
class Button:
2024-09-08 14:44:47 +00:00
def __init__(self, position: pygame.Rect, text: str, cbk: Callable[[], None]):
2024-09-08 11:43:21 +00:00
self.position: pygame.Rect
self.set_position(position)
2024-09-08 14:44:47 +00:00
self.text = text
2024-09-08 11:43:21 +00:00
self.cbk = cbk
2024-09-08 14:44:47 +00:00
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)
2024-09-08 11:43:21 +00:00
def set_position(self, position: pygame.Rect):
self.position = position
def draw(self, screen: pygame.Surface):
pygame.draw.rect(screen, "black", self.position, 0)
2024-09-08 14:44:47 +00:00
self.font.render_to(screen, self.text_pos, self.text, fgcolor="pink")
2024-09-08 11:43:21 +00:00
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")
2024-09-08 11:43:21 +00:00
class MenuBoard(IBoard):
2024-09-08 14:44:47 +00:00
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']}",
2024-09-08 14:50:18 +00:00
d['cbk']), buttons))
2024-09-08 14:44:47 +00:00
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)
2024-09-08 11:43:21 +00:00
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:
2024-09-08 14:44:47 +00:00
def __init__(self, btns: int):
2024-09-08 11:43:21 +00:00
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))))
2024-09-08 14:50:18 +00:00
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
2024-09-08 15:20:47 +00:00
def thr_fun():
def end_thr():
self.board = previous_board
2024-09-08 15:20:47 +00:00
sleep(5)
self.task_q.put(end_thr)
2024-09-08 15:20:47 +00:00
process_thr = Thread(target=thr_fun)
process_thr.start()
self.board = MessageBoard(self.get_screen_rect(), f"Wait: {text}")
return impl
2024-09-08 11:43:21 +00:00
def loop(self):
self.running = True
while self.running:
try:
self.task_q.get_nowait()()
except Empty:
pass
2024-09-08 11:43:21 +00:00
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()
2024-09-08 14:50:18 +00:00
def quit(self):
self.board = MessageBoard(self.get_screen_rect(), "Exiting...")
2024-09-08 14:50:18 +00:00
self.running = False
2024-09-08 11:43:21 +00:00
if __name__ == '__main__':
2024-09-08 14:44:47 +00:00
app = App(int(sys.argv[1]))
2024-09-08 11:43:21 +00:00
app.loop()