From 2bbe5ae0e8784567252c778fd2a599e6061e871e Mon Sep 17 00:00:00 2001 From: xGlc Date: Sun, 10 May 2026 13:03:24 +0200 Subject: [PATCH] feat: menu demo --- display_program.py | 123 ++++++++++++++++++++++++++------------------- encoder.py | 10 ++++ main.py | 4 +- menu.py | 76 ++++++++++++++++++++++++++++ 4 files changed, 158 insertions(+), 55 deletions(-) create mode 100644 encoder.py create mode 100644 menu.py diff --git a/display_program.py b/display_program.py index eecaa52..f11e1a8 100644 --- a/display_program.py +++ b/display_program.py @@ -5,6 +5,7 @@ from time import sleep, time_ns from machine import SPI, Pin +from encoder import Encoder from ili9341 import Display, color565 colors = { @@ -18,6 +19,12 @@ colors = { "NAVY": (0, 0, 128), } +menu = [ + "Option 1", + "Option 2", + "Option 3", +] + def display(): bg = Pin(33, Pin.OUT) @@ -26,70 +33,80 @@ def display(): bg.on() - while True: + encoder_a = Pin(2, Pin.IN, Pin.PULL_UP) + encoder_b = Pin(4, Pin.IN, Pin.PULL_UP) + + def draw_debug_text(text: str, y: int): display.draw_text8x8( 10, - 10, - "ILI9341 Display Test", + y, + text, color565(255, 255, 255), color565(0, 0, 0), - 270, ) - display.draw_text8x8( - 10, - 280, - "PPAP", - color565(255, 255, 255), - color565(0, 0, 0), - 270, - ) - sleep(1) - # Calculate valid hlines parameters for display clear method - valid_hlines = [] - for i in range(1, display.height): - if display.height % i == 0: - valid_hlines.append(i) - # Ensure only 13 entries, truncate or repeat the last one - valid_hlines = valid_hlines[:8] - if len(valid_hlines) < 8: - valid_hlines += [valid_hlines[-1]] * (8 - len(valid_hlines)) - # Ensure only 13 entries, truncate or repeat the last one - valid_hlines = valid_hlines[:8] - if len(valid_hlines) < 8: - valid_hlines += [valid_hlines[-1]] * (8 - len(valid_hlines)) - print("Clearing to black...") - start = time_ns() - display.clear() - end = time_ns() - print(f"Display cleared in {end - start} ms.") - sleep(2) + last_registered: int = time_ns() - print("Clearing to white...") - start = time_ns() - display.clear(color565(255, 255, 255)) - end = time_ns() - print(f"Display cleared in {end - start} ms.") - sleep(2) + menu_index = 0 - for hlines, (color, rgb) in zip(valid_hlines, colors.items()): + def handle_button_pres(dir: str): + nonlocal last_registered + nonlocal menu_index + + if (time_ns() - last_registered) < 100000000: + return + + last_registered = time_ns() + + if dir == "Left": + menu_index = (menu_index - 1) % 3 + elif dir == "Right": + menu_index = (menu_index + 1) % 3 + else: + return + + refresh_display() + + encoder = Encoder( + encoder_a, + encoder_b, + on_left=lambda x: handle_button_pres("Left"), + on_right=lambda x: handle_button_pres("Right"), + ) + + display.clear(color=color565(*colors["BLUE"])) + + display.draw_text8x8( + 10, + 10, + "Menu:", + color565(255, 255, 255), + color565(0, 0, 0), + ) + + display.draw_text8x8( + 230 - (8 * 4), + 10, + "PPAP", + color565(255, 255, 255), + color565(0, 0, 0), + ) + + def get_cursor(line: int): + nonlocal menu_index + if line == menu_index: + return "> " + else: + return " " + + def refresh_display(): + for i, line in enumerate(menu): display.draw_text8x8( 10, - 10, - f"Next color: {color}", + 18 + (i + 1) * 8, + get_cursor(i) + line, color565(255, 255, 255), color565(0, 0, 0), - 270, ) - sleep(1) - print(f"Clearing display to {color}, hlines={hlines}...") - try: - start = time_ns() - display.clear(hlines=hlines, color=color565(*rgb)) - end = time_ns() - print(f"Display cleared in {end - start} ms.") - except Exception as e: - print(e) - sleep(1) - sleep(5) + refresh_display() diff --git a/encoder.py b/encoder.py new file mode 100644 index 0000000..dc356eb --- /dev/null +++ b/encoder.py @@ -0,0 +1,10 @@ +from machine import Pin + + +class Encoder: + def __init__(self, pin_a: Pin, pin_b: Pin, on_left, on_right) -> None: + self.pin_a = pin_a + self.pin_b = pin_b + pin_a.irq(trigger=Pin.IRQ_RISING, handler=on_left) + pin_b.irq(trigger=Pin.IRQ_RISING, handler=on_right) + pass diff --git a/main.py b/main.py index 48c543d..dd6179f 100644 --- a/main.py +++ b/main.py @@ -13,5 +13,5 @@ import led_program import motor # _thread.start_new_thread(led_program.boostrap, ()) -# _thread.start_new_thread(display_program.display, ()) -_thread.start_new_thread(motor.motor_run, ()) +_thread.start_new_thread(display_program.display, ()) +# _thread.start_new_thread(motor.motor_run, ()) diff --git a/menu.py b/menu.py new file mode 100644 index 0000000..a6fe50a --- /dev/null +++ b/menu.py @@ -0,0 +1,76 @@ +from this import d + +from ili9341 import Display, color565 + + +class MenuItem: + def __init__(self, name: str, navigate: str): + self.name = name + self.navigate = navigate + + +class MenuPage: + UP = 0 + DOWN = 1 + + def __init__(self, title: str, item: list[MenuItem]): + self.title = title + self.items = item + self.items.append(MenuItem("Back", "Root")) + self.cursor = 0 + + def size(self) -> int: + return len(self.items) + + def move(self, direction: int): + match direction: + case self.UP: + self.cursor = (self.cursor + 1) % self.size() + case self.DOWN: + self.cursor = (self.cursor - 1) % self.size() + + def draw(self) -> str: + + lines: list[str] = [] + + for i, item in enumerate(self.items): + if i == self.cursor: + lines.append("> {item.name}\n") + else: + lines.append(" {item.name}\n") + + return "\n".join(lines) + + def current_at_cursor(self, cursor: int) -> MenuItem: + return self.items[cursor] + + +class DisplayMenu: + def __init__(self, display: Display, pages: list[MenuPage]): + self.display = display + self.pages = pages + self.cursor = 0 + self.current_page = self.pages[0] + self.update() + + def __draw_display_line(self, line: str, offset: int): + self.display.draw_text8x8( + 10, + 10 + (offset * 8), + line, + color565(255, 255, 255), + color565(0, 0, 0), + ) + + def move_up(self): + self.current_page.move(MenuPage.UP) + + def move_down(self): + self.current_page.move(MenuPage.DOWN) + + def select(self): + pass + + def update(self): + self.__draw_display_line(self.current_page.title, 0) + self.__draw_display_line(self.current_page.draw(), 2)