1
0
Fork 0

Compare commits

..

2 Commits

Author SHA1 Message Date
Michał Rudowicz 8fafd58b15 Add "empty" buttons 2024-09-11 22:23:59 +02:00
Michał Rudowicz b8b2cb61f4 Add ability to hide the mouse cursor 2024-09-11 22:05:26 +02:00
1 changed files with 31 additions and 11 deletions

42
menu.py
View File

@ -19,12 +19,14 @@ from screensavers import ClockScreensaver
class ButtonDef(TypedDict): class ButtonDef(TypedDict):
text: str text: str
cbk: Callable[[], None] cbk: Callable[[], None]
is_empty: bool
class ActionType(Enum): class ActionType(Enum):
MSG = auto() # Shows a message for about 5 seconds MSG = auto() # Shows a message for about 5 seconds
GET = auto() # Performs a HTTP GET GET = auto() # Performs a HTTP GET
QUIT = auto() # Quits an application QUIT = auto() # Quits an application
EMPTY = auto() # Shows nothing instead of a button
class Action(TypedDict): class Action(TypedDict):
@ -56,7 +58,7 @@ class MultilineText:
current_top += rect.top + self.VMARGIN current_top += rect.top + self.VMARGIN
class Button: class Button(IBoard):
PRESS_OFFSET = 2 PRESS_OFFSET = 2
HOVER_ANIMATION_STEPS = 5 HOVER_ANIMATION_STEPS = 5
@ -108,7 +110,7 @@ class Button:
self.text_pos.width, self.text_pos.height) self.text_pos.width, self.text_pos.height)
return self.text_pos return self.text_pos
def draw(self, screen: pygame.Surface): def draw(self, screen: pygame.Surface, _):
pygame.draw.rect(screen, self.get_bg_color(), self.get_position(), 0) pygame.draw.rect(screen, self.get_bg_color(), self.get_position(), 0)
self.label.render_to(screen, self.get_text_pos(), self.theme["btn_text_color"]) self.label.render_to(screen, self.get_text_pos(), self.theme["btn_text_color"])
@ -147,13 +149,22 @@ class MenuBoard(IBoard):
BUTTON_MARGINS = 10 BUTTON_MARGINS = 10
def __init__(self, screen_rect: pygame.Rect, buttons: List[ButtonDef], theme: Dict): def __init__(self, screen_rect: pygame.Rect, buttons: List[ButtonDef], theme: Dict):
def generate_buttons(button_positions_generator):
def impl(data: ButtonDef) -> Optional[IBoard]:
if data['is_empty']:
next(button_positions)
return None
else:
return Button(next(button_positions), data['text'], theme, data['cbk'])
return impl
self.rows = math.floor(math.sqrt(len(buttons))) self.rows = math.floor(math.sqrt(len(buttons)))
self.cols = math.ceil(math.sqrt(len(buttons))) self.cols = math.ceil(math.sqrt(len(buttons)))
if (self.rows * self.cols) < len(buttons): if (self.rows * self.cols) < len(buttons):
# extra row if buttons don't fit # extra row if buttons don't fit
self.rows += 1 self.rows += 1
button_positions = self.generate_button_positions(screen_rect) button_positions = self.generate_button_positions(screen_rect)
self.buttons = list(map(lambda d: Button(next(button_positions), d['text'], theme, d['cbk']), buttons)) self.buttons = list(filter(lambda x: x is not None, map(generate_buttons(button_positions), buttons)))
self.theme = theme self.theme = theme
def generate_button_positions(self, screen_rect: pygame.Rect): def generate_button_positions(self, screen_rect: pygame.Rect):
@ -178,7 +189,7 @@ class MenuBoard(IBoard):
if force_redraw or any(map(Button.needs_redrawing, self.buttons)): if force_redraw or any(map(Button.needs_redrawing, self.buttons)):
screen.fill(self.theme["menu_bg_color"]) screen.fill(self.theme["menu_bg_color"])
for b in self.buttons: for b in self.buttons:
b.draw(screen) b.draw(screen, force_redraw)
class App: class App:
@ -186,7 +197,8 @@ class App:
SHOW_REQUEST_MESSAGE_FOR_AT_LEAST_S = 5 SHOW_REQUEST_MESSAGE_FOR_AT_LEAST_S = 5
IGNORE_EVENTS_FOR_TICKS_AFTER_SCREENSAVER = 5 IGNORE_EVENTS_FOR_TICKS_AFTER_SCREENSAVER = 5
def __init__(self, actions: List[Action], theme: Dict, fullscreen: bool, screensaver_delay: int): def __init__(self, actions: List[Action], theme: Dict, fullscreen: bool, screensaver_delay: int,
hide_cursor: bool):
pygame.init() pygame.init()
info = pygame.display.Info() info = pygame.display.Info()
flags = 0 flags = 0
@ -196,11 +208,16 @@ class App:
self.clock = pygame.time.Clock() self.clock = pygame.time.Clock()
self.running = False self.running = False
self.theme = theme self.theme = theme
buttons = list(map(lambda u: ButtonDef(text=u['label'], cbk=self.button_press_handler(u)), actions)) buttons = list(map(lambda u: ButtonDef(text=u['label'],
cbk=self.button_press_handler(u),
is_empty=(u['action_type'] == ActionType.EMPTY)),
actions))
self.board: IBoard = MenuBoard(self.get_screen_rect(), buttons, theme) self.board: IBoard = MenuBoard(self.get_screen_rect(), buttons, theme)
self.task_q = Queue() self.task_q = Queue()
self.screensaver = ClockScreensaver() self.screensaver = ClockScreensaver()
self.TICKS_UNTIL_SCREENSAVER = screensaver_delay * self.FPS self.TICKS_UNTIL_SCREENSAVER = screensaver_delay * self.FPS
if hide_cursor:
pygame.mouse.set_visible(False)
def get_screen_rect(self) -> pygame.Rect: def get_screen_rect(self) -> pygame.Rect:
return pygame.Rect(0, 0, self.screen.get_width(), self.screen.get_height()) return pygame.Rect(0, 0, self.screen.get_width(), self.screen.get_height())
@ -258,6 +275,8 @@ class App:
return self.get_handler(action) return self.get_handler(action)
elif action['action_type'] == ActionType.QUIT: elif action['action_type'] == ActionType.QUIT:
return self.quit return self.quit
elif action['action_type'] == ActionType.EMPTY:
return lambda: print("Tried to do an action on EMPTY - shouldn't happen")
raise NotImplementedError(action['action_type']) raise NotImplementedError(action['action_type'])
def loop(self): def loop(self):
@ -304,7 +323,7 @@ class App:
def get_url_defs(config_data: dict) -> List[Action]: def get_url_defs(config_data: dict) -> List[Action]:
url_defs = [] url_defs = []
for d in data['actions']: for d in data['actions']:
url_defs.append(Action(label=d['label'], url_defs.append(Action(label=d.get('label'),
action_type=ActionType[d['action_type']], action_type=ActionType[d['action_type']],
action_param=d.get('action_param'), action_param=d.get('action_param'),
action_message=str(d.get('action_message')))) action_message=str(d.get('action_message'))))
@ -317,6 +336,7 @@ if __name__ == '__main__':
parser.add_argument("--no-fullscreen", help="Fullscreen", action='store_true') parser.add_argument("--no-fullscreen", help="Fullscreen", action='store_true')
parser.add_argument("--screensaver-delay", help="Screensaver delay, in seconds. Default=30", parser.add_argument("--screensaver-delay", help="Screensaver delay, in seconds. Default=30",
type=int, default=30) type=int, default=30)
parser.add_argument("--no-cursor", help="Hide the mouse cursor", action='store_true')
args = parser.parse_args() args = parser.parse_args()
url_defs: List[Action] = [] url_defs: List[Action] = []
@ -325,5 +345,5 @@ if __name__ == '__main__':
url_defs = get_url_defs(data) url_defs = get_url_defs(data)
app = App(url_defs, theme=data["theme"], fullscreen=not args.no_fullscreen, app = App(url_defs, theme=data["theme"], fullscreen=not args.no_fullscreen,
screensaver_delay=args.screensaver_delay) screensaver_delay=args.screensaver_delay, hide_cursor=args.no_cursor)
app.loop() app.loop()