mirror of https://git.sr.ht/~michalr/menu
Compare commits
2 Commits
1dd3e4b22d
...
8fafd58b15
Author | SHA1 | Date |
---|---|---|
Michał Rudowicz | 8fafd58b15 | |
Michał Rudowicz | b8b2cb61f4 |
36
menu.py
36
menu.py
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue