Skip to content

Commit

Permalink
Merge pull request #14 from emfcamp/hugh/ui-menu
Browse files Browse the repository at this point in the history
Add menu widget
  • Loading branch information
MatthewWilkes committed May 6, 2024
2 parents d97da14 + 1f38518 commit a24f09f
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 0 deletions.
115 changes: 115 additions & 0 deletions modules/app_components/menu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from typing import Any, Callable, Literal, Union

from app import App
from events.input import BUTTON_TYPES, ButtonDownEvent
from system.eventbus import eventbus


def ease_out_quart(x):
return 1 - pow(1 - x, 4)


class Menu:
def __init__(
self,
app: App,
menu_items: list[str] = [],
position=0,
select_handler: Union[Callable[[str], Any], None] = None,
back_handler: Union[Callable, None] = None,
speed_ms=300,
item_font_size=20,
item_line_height=30,
focused_item_font_size=40,
focused_item_margin=20,
):
self.app = app
self.menu_items = menu_items
self.position = position
self.select_handler = select_handler
self.back_handler = back_handler
self.speed_ms = speed_ms
self.item_font_size = item_font_size
self.item_line_height = item_line_height
self.focused_item_font_size = focused_item_font_size
self.focused_item_margin = focused_item_margin

self.animation_time_ms = 0
# self.is_animating: Literal["up", "down", "none"] = "none"
self.is_animating: Literal["up", "down", "none"] = "up"

eventbus.on(ButtonDownEvent, self._handle_buttondown, app)

def _cleanup(self):
eventbus.remove(ButtonDownEvent, self._handle_buttondown, self.app)

def _handle_buttondown(self, event: ButtonDownEvent):
if BUTTON_TYPES["UP"] in event.button:
self.up_handler()
if BUTTON_TYPES["DOWN"] in event.button:
self.down_handler()
if BUTTON_TYPES["CANCEL"] in event.button:
if self.back_handler is not None:
self.back_handler()
if BUTTON_TYPES["CONFIRM"] in event.button:
if self.select_handler is not None:
self.select_handler(
self.menu_items[self.position % len(self.menu_items)]
)

def up_handler(self):
self.is_animating = "up"
self.animation_time_ms = 0
self.position = (self.position - 1) % len(self.menu_items)

def down_handler(self):
self.is_animating = "down"
self.animation_time_ms = 0
self.position = (self.position + 1) % len(self.menu_items)

def draw(self, ctx):
animation_progress = ease_out_quart(self.animation_time_ms / self.speed_ms)
animation_direction = 1 if self.is_animating == "up" else -1

# Current menu item
ctx.font_size = self.item_font_size + animation_progress * (
self.focused_item_font_size - self.item_font_size
)

ctx.text_align = ctx.CENTER
ctx.text_baseline = ctx.MIDDLE

ctx.rgb(1, 1, 1)
ctx.move_to(
0, animation_direction * -30 + animation_progress * animation_direction * 30
).text(self.menu_items[self.position % len(self.menu_items)])

# Previous menu items
ctx.font_size = 20
for i in range(1, 4):
if (self.position - i) >= 0:
ctx.move_to(
0,
-self.focused_item_margin
+ -i * self.item_line_height
- animation_direction * 30
+ animation_progress * animation_direction * 30,
).text(self.menu_items[self.position - i])

# Next menu items
for i in range(1, 4):
if (self.position + i) < len(self.menu_items):
ctx.move_to(
0,
self.focused_item_margin
+ i * self.item_line_height
- animation_direction * 30
+ animation_progress * animation_direction * 30,
).text(self.menu_items[self.position + i])

def update(self, delta):
if self.is_animating != "none":
self.animation_time_ms += delta
if self.animation_time_ms > self.speed_ms:
self.is_animating = "none"
self.animation_time_ms = self.speed_ms
77 changes: 77 additions & 0 deletions modules/apps/menu_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from typing import Literal

from app import App
from app_components.menu import Menu

main_menu_items = ["numbers", "letters", "words"]

numbers_menu_items = ["one", "two", "three", "four", "five"]

letters_menu_items = ["a", "b", "c", "d", "e"]

words_menu_items = ["emfcamp", "bodgeham-on-wye", "hackers", "hexpansions", "tildagon"]


class MenuDemo(App):
def __init__(self):
self.current_menu = "main"
self.menu = Menu(
self,
main_menu_items,
select_handler=self.select_handler,
back_handler=self.back_handler,
)

def select_handler(self, item):
if item in ["numbers", "letters", "words", "main"]:
self.set_menu(item)
else:
# flash or spin the word or something
pass

def set_menu(self, menu_name: Literal["main", "numbers", "letters", "words"]):
self.menu._cleanup()
self.current_menu = menu_name
if menu_name == "main":
self.menu = Menu(
self,
main_menu_items,
select_handler=self.select_handler,
back_handler=self.back_handler,
)
elif menu_name == "numbers":
self.menu = Menu(
self,
numbers_menu_items,
select_handler=self.select_handler,
back_handler=self.back_handler,
)
elif menu_name == "letters":
self.menu = Menu(
self,
letters_menu_items,
select_handler=self.select_handler,
back_handler=self.back_handler,
)
elif menu_name == "words":
self.menu = Menu(
self,
words_menu_items,
select_handler=self.select_handler,
back_handler=self.back_handler,
)

def back_handler(self):
if self.current_menu == "main":
return
self.set_menu("main")

def draw_background(self, ctx):
ctx.gray(0).rectangle(-120, -120, 240, 240).fill()

def draw(self, ctx):
self.draw_background(ctx)
self.menu.draw(ctx)

def update(self, delta):
self.menu.update(delta)

0 comments on commit a24f09f

Please sign in to comment.