diff --git a/build.py b/build.py index a9571e0..fac5cf3 100644 --- a/build.py +++ b/build.py @@ -1,7 +1,19 @@ import os +import time import zipfile import shutil from pathlib import Path +import requests + +LEARN_PROJECT_URLS = [ + "https://cdn-learn.adafruit.com/downloads/zip/3194974/Metro/Metro_RP2350_Snake.zip?timestamp={}", + "https://cdn-learn.adafruit.com/downloads/zip/3195762/Metro/Metro_RP2350_Memory/memory_game.zip?timestamp={}", + "https://cdn-learn.adafruit.com/downloads/zip/3195805/Metro/Metro_RP2350_CircuitPython_Matrix.zip?timestamp={}", + "https://cdn-learn.adafruit.com/downloads/zip/3194658/Metro/Metro_RP2350_FlappyNyanCat.zip?timestamp={}", + "https://cdn-learn.adafruit.com/downloads/zip/3196927/Metro/Metro_RP2350_Match3/match3_game.zip?timestamp={}", + "https://cdn-learn.adafruit.com/downloads/zip/3194422/Metro/Metro_RP2350_Breakout.zip?timestamp={}", + # "", +] def create_font_specific_zip(font_path: Path, src_dir: Path, learn_projects_dir: Path, output_dir: Path): # Get font name without extension @@ -53,6 +65,10 @@ def create_font_specific_zip(font_path: Path, src_dir: Path, learn_projects_dir: # Skip the lib directory as we'll handle it separately if 'lib/' in path: continue + if path.endswith("/"): + # skip directories, they will get created by + # mkdir(parents=True) below + continue # Get the relative path from code_dir rel_path = path[len(code_dir):] @@ -97,7 +113,22 @@ def create_font_specific_zip(font_path: Path, src_dir: Path, learn_projects_dir: # Clean up temporary directory shutil.rmtree(temp_dir, ignore_errors=True) + +def download_learn_projects(): + for url in LEARN_PROJECT_URLS: + response = requests.get(url.format(int(time.time())), allow_redirects=True) + resp_url = response.url + #print(resp_url) + filename = resp_url.split("/")[-1].split("?")[0] + with open(f"learn-projects/{filename}", 'wb') as f: + f.write(response.content) + + def main(): + + # download all learn project zips + download_learn_projects() + # Get the project root directory root_dir = Path(__file__).parent diff --git a/learn-projects/Metro_RP2350_CircuitPython_Matrix.zip b/learn-projects/Metro_RP2350_CircuitPython_Matrix.zip deleted file mode 100644 index eb23c9d..0000000 Binary files a/learn-projects/Metro_RP2350_CircuitPython_Matrix.zip and /dev/null differ diff --git a/learn-projects/Metro_RP2350_Snake.zip b/learn-projects/Metro_RP2350_Snake.zip deleted file mode 100644 index 27cc6c1..0000000 Binary files a/learn-projects/Metro_RP2350_Snake.zip and /dev/null differ diff --git a/src/code.py b/src/code.py index acf9a82..099d566 100644 --- a/src/code.py +++ b/src/code.py @@ -6,12 +6,15 @@ This example uses adafruit_display_text.label to display text using a custom fon loaded by adafruit_bitmap_font """ import array +import atexit +import json import displayio -import pathlib + import supervisor import sys import usb +import adafruit_pathlib as pathlib from adafruit_bitmap_font import bitmap_font from adafruit_display_text.text_box import TextBox from adafruit_display_text.bitmap_label import Label @@ -22,9 +25,14 @@ import adafruit_usb_host_descriptors from adafruit_anchored_group import AnchoredGroup display = supervisor.runtime.display + +scale = 1 +if display.width > 360: + scale = 2 + font_file = "/fonts/terminal.lvfontbin" font = bitmap_font.load_font(font_file) -main_group = displayio.Group() +main_group = displayio.Group(scale=scale) display.root_group = main_group background_bmp = displayio.Bitmap(display.width, display.height, 1) @@ -43,8 +51,8 @@ mouse_bmp.pixel_shader.make_transparent(0) mouse_tg = displayio.TileGrid(mouse_bmp, pixel_shader=mouse_bmp.pixel_shader) # move it to the center of the display -mouse_tg.x = display.width // 2 -mouse_tg.y = display.height // 2 +mouse_tg.x = display.width // (2 * scale) +mouse_tg.y = display.height // (2 * scale) # 046d:c52f @@ -98,17 +106,21 @@ for device in usb.core.find(find_all=True): mouse = device print(f"mouse interface: {mouse_interface_index} endpoint_address: {hex(mouse_endpoint_address)}") +mouse_was_attached = None if mouse is not None: # detach the kernel driver if needed if mouse.is_kernel_driver_active(0): + mouse_was_attached = True mouse.detach_kernel_driver(0) + else: + mouse_was_attached = False # set configuration on the mouse so we can use it mouse.set_configuration() mouse_buf = array.array("b", [0] * 8) -WIDTH = 300 -HEIGHT = 192 +WIDTH = 280 +HEIGHT = 182 config = { "menu_title": "Launcher Menu", @@ -152,31 +164,56 @@ cell_width = WIDTH // config["width"] default_icon_bmp, default_icon_palette = adafruit_imageload.load("launcher_assets/default_icon.bmp") default_icon_palette.make_transparent(0) -menu_grid = GridLayout(x=10, y=26, width=WIDTH, height=HEIGHT, grid_size=(config["width"], config["height"]), +menu_grid = GridLayout(x=40, y=16, width=WIDTH, height=HEIGHT, grid_size=(config["width"], config["height"]), divider_lines=False) main_group.append(menu_grid) menu_title_txt = Label(font, text=config["menu_title"]) menu_title_txt.anchor_point = (0.5, 0.5) -menu_title_txt.anchored_position = (display.width // 2, 2) +menu_title_txt.anchored_position = (display.width // (2 * scale), 2) main_group.append(menu_title_txt) app_titles = [] apps = [] app_path = pathlib.Path("/apps") i = 0 + +pages = [{}] + +cur_file_index = 0 +cur_page = 0 for path in app_path.iterdir(): print(path) + cell_group = AnchoredGroup() + code_file = path / "code.py" if not code_file.exists(): continue - cell_group = AnchoredGroup() - icon_file = path / "icon.bmp" + + metadata_file = path / "metadata.json" + if not metadata_file.exists(): + metadata_file = None + metadata = None + if metadata_file is not None: + with open(metadata_file.absolute(), "r") as f: + metadata = json.load(f) + + if metadata is not None and "icon" in metadata: + icon_file = path / metadata["icon"] + else: + icon_file = path / "icon.bmp" + if not icon_file.exists(): icon_file = None + + if metadata is not None and "title" in metadata: + title = metadata["title"] + else: + title = path.name + apps.append({ - "title": path.name, - "icon": str(icon_file.absolute()), + "title": title, + "icon": str(icon_file.absolute()) if icon_file is not None else None, "file": str(code_file.absolute()) }) if apps[-1]["icon"] is None: @@ -203,12 +240,12 @@ right_bmp, right_palette = adafruit_imageload.load("launcher_assets/arrow_right. right_palette.make_transparent(0) left_tg = AnchoredTileGrid(bitmap=left_bmp, pixel_shader=left_palette) -left_tg.anchor_point = (0, 1.0) -left_tg.anchored_position = (10, display.height - 2) +left_tg.anchor_point = (0, 0.5) +left_tg.anchored_position = (4, (display.height // 2 // scale) - 2) right_tg = AnchoredTileGrid(bitmap=right_bmp, pixel_shader=right_palette) -right_tg.anchor_point = (1.0, 1.0) -right_tg.anchored_position = (display.width - 10, display.height - 2) +right_tg.anchor_point = (1.0, 0.5) +right_tg.anchored_position = ((display.width // scale) - 4, (display.height // 2 // scale) - 2) main_group.append(left_tg) main_group.append(right_tg) @@ -217,6 +254,20 @@ if mouse: main_group.append(mouse_tg) selected = 0 + + +def atexit_callback(): + """ + re-attach USB devices to kernel if needed. + :return: + """ + print("inside atexit callback") + if mouse_was_attached and not mouse.is_kernel_driver_active(0): + mouse.attach_kernel_driver(0) + +atexit.register(atexit_callback) + +# print(f"apps: {apps}") while True: index = None @@ -241,7 +292,7 @@ while True: # attempt to read data from the mouse # 10ms timeout, so we don't block long if there # is no data - count = mouse.read(mouse_endpoint_address, mouse_buf, timeout=10) + count = mouse.read(mouse_endpoint_address, mouse_buf, timeout=20) except usb.core.USBTimeoutError: # skip the rest of the loop if there is no data count = 0 @@ -249,8 +300,8 @@ while True: # update the mouse tilegrid x and y coordinates # based on the delta values read from the mouse if count > 0: - mouse_tg.x = max(0, min(display.width - 1, mouse_tg.x + mouse_buf[1])) - mouse_tg.y = max(0, min(display.height - 1, mouse_tg.y + mouse_buf[2])) + mouse_tg.x = max(0, min((display.width // scale) - 1, mouse_tg.x + mouse_buf[1])) + mouse_tg.y = max(0, min((display.height // scale) - 1, mouse_tg.y + mouse_buf[2])) if mouse_buf[0] & (1 << 0) != 0: clicked_cell = menu_grid.which_cell_contains((mouse_tg.x, mouse_tg.y)) @@ -258,8 +309,11 @@ while True: index = clicked_cell[1] * config["width"] + clicked_cell[0] if index is not None: - supervisor.set_next_code_file(config["apps"][index]["file"], sticky_on_reload=True, reload_on_error=True, - working_directory="/apps/matrix") + # print("index", index) + # print(f"selected: {apps[index]}") + launch_file = apps[index]["file"] + supervisor.set_next_code_file(launch_file, sticky_on_reload=True, reload_on_error=True, + working_directory="/".join(launch_file.split("/")[:-1])) if mouse and not mouse.is_kernel_driver_active(0): mouse.attach_kernel_driver(0)