commit 365d3207dcdd429f417c80cbaccb79fe36095a12 Author: Scott Shawcroft Date: Fri Apr 4 16:14:54 2025 -0700 wip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16c5327 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist + diff --git a/build.py b/build.py new file mode 100644 index 0000000..a9571e0 --- /dev/null +++ b/build.py @@ -0,0 +1,118 @@ +import os +import zipfile +import shutil +from pathlib import Path + +def create_font_specific_zip(font_path: Path, src_dir: Path, learn_projects_dir: Path, output_dir: Path): + # Get font name without extension + font_name = font_path.stem + + # Create output zip filename + output_zip = output_dir / f"fruit_jam_{font_name}.zip" + + # Create a clean temporary directory for building the zip + temp_dir = output_dir / "temp" + if temp_dir.exists(): + shutil.rmtree(temp_dir) + temp_dir.mkdir(parents=True) + + try: + # Copy src contents + shutil.copytree(src_dir, temp_dir, dirs_exist_ok=True) + + # Create fonts directory and copy the specific font + fonts_dir = temp_dir / "fonts" + fonts_dir.mkdir(parents=True, exist_ok=True) + shutil.copy2(font_path, fonts_dir / "terminal.lvfontbin") + + # Extract learn-projects contents into apps directory + apps_dir = temp_dir / "apps" + apps_dir.mkdir(parents=True, exist_ok=True) + for zip_path in learn_projects_dir.glob("*.zip"): + # Create app-specific directory using zip name without extension + app_name = zip_path.stem + app_dir = apps_dir / app_name + app_dir.mkdir(parents=True, exist_ok=True) + + # Extract zip contents and process them + with zipfile.ZipFile(zip_path, 'r') as zf: + # Find the directory containing code.py + code_dir = None + for path in zf.namelist(): + if path.endswith('/code.py'): + code_dir = str(Path(path).parent) + '/' + break + + if not code_dir: + print(f"Warning: No code.py found in {zip_path}") + continue + + # Extract files from the code.py directory to app directory + for path in zf.namelist(): + if path.startswith(code_dir): + # Skip the lib directory as we'll handle it separately + if 'lib/' in path: + continue + + # Get the relative path from code_dir + rel_path = path[len(code_dir):] + if rel_path: + # Extract the file + source = zf.open(path) + target = app_dir / rel_path + target.parent.mkdir(parents=True, exist_ok=True) + with open(target, 'wb') as f: + f.write(source.read()) + + # Handle lib directory specially - move to root + for path in zf.namelist(): + if '/lib/' in path: + # Get the part of the path after 'lib/' + lib_index = path.index('/lib/') + 5 # skip past '/lib/' + rel_path = path[lib_index:] + + # Skip directory entries + if not rel_path or path.endswith('/'): + continue + + # Extract the file to root lib directory + source = zf.open(path) + target = temp_dir / 'lib' / rel_path + # Ensure parent directory exists + target.parent.mkdir(parents=True, exist_ok=True) + # Write the file + with open(target, 'wb') as f: + f.write(source.read()) + + # Create the final zip file + with zipfile.ZipFile(output_zip, 'w', zipfile.ZIP_DEFLATED) as zf: + for file_path in temp_dir.rglob("*"): + if file_path.is_file(): + arcname = file_path.relative_to(temp_dir) + zf.write(file_path, arcname) + + print(f"Created {output_zip}") + + finally: + # Clean up temporary directory + shutil.rmtree(temp_dir, ignore_errors=True) + +def main(): + # Get the project root directory + root_dir = Path(__file__).parent + + # Set up paths + fonts_dir = root_dir / "fonts" + src_dir = root_dir / "src" + learn_projects_dir = root_dir / "learn-projects" + output_dir = root_dir / "dist" + + # Create output directory + output_dir.mkdir(parents=True, exist_ok=True) + + # Process each font + for font_path in fonts_dir.glob("*.lvfontbin"): + create_font_specific_zip(font_path, src_dir, learn_projects_dir, output_dir) + +if __name__ == "__main__": + main() diff --git a/fonts/ID.lvfontbin b/fonts/ID.lvfontbin new file mode 100644 index 0000000..f5bfcfa Binary files /dev/null and b/fonts/ID.lvfontbin differ diff --git a/fonts/cs.lvfontbin b/fonts/cs.lvfontbin new file mode 100644 index 0000000..5688bf2 Binary files /dev/null and b/fonts/cs.lvfontbin differ diff --git a/fonts/de_DE.lvfontbin b/fonts/de_DE.lvfontbin new file mode 100644 index 0000000..5688bf2 Binary files /dev/null and b/fonts/de_DE.lvfontbin differ diff --git a/fonts/el.lvfontbin b/fonts/el.lvfontbin new file mode 100644 index 0000000..2190dd6 Binary files /dev/null and b/fonts/el.lvfontbin differ diff --git a/fonts/en_GB.lvfontbin b/fonts/en_GB.lvfontbin new file mode 100644 index 0000000..f5bfcfa Binary files /dev/null and b/fonts/en_GB.lvfontbin differ diff --git a/fonts/en_US.lvfontbin b/fonts/en_US.lvfontbin new file mode 100644 index 0000000..f5bfcfa Binary files /dev/null and b/fonts/en_US.lvfontbin differ diff --git a/fonts/en_x_pirate.lvfontbin b/fonts/en_x_pirate.lvfontbin new file mode 100644 index 0000000..f5bfcfa Binary files /dev/null and b/fonts/en_x_pirate.lvfontbin differ diff --git a/fonts/es.lvfontbin b/fonts/es.lvfontbin new file mode 100644 index 0000000..5688bf2 Binary files /dev/null and b/fonts/es.lvfontbin differ diff --git a/fonts/fil.lvfontbin b/fonts/fil.lvfontbin new file mode 100644 index 0000000..f5bfcfa Binary files /dev/null and b/fonts/fil.lvfontbin differ diff --git a/fonts/fr.lvfontbin b/fonts/fr.lvfontbin new file mode 100644 index 0000000..5688bf2 Binary files /dev/null and b/fonts/fr.lvfontbin differ diff --git a/fonts/hi.lvfontbin b/fonts/hi.lvfontbin new file mode 100644 index 0000000..7a53e6f Binary files /dev/null and b/fonts/hi.lvfontbin differ diff --git a/fonts/it_IT.lvfontbin b/fonts/it_IT.lvfontbin new file mode 100644 index 0000000..5688bf2 Binary files /dev/null and b/fonts/it_IT.lvfontbin differ diff --git a/fonts/ja.lvfontbin b/fonts/ja.lvfontbin new file mode 100644 index 0000000..e550156 Binary files /dev/null and b/fonts/ja.lvfontbin differ diff --git a/fonts/ko.lvfontbin b/fonts/ko.lvfontbin new file mode 100644 index 0000000..9ef9875 Binary files /dev/null and b/fonts/ko.lvfontbin differ diff --git a/fonts/nl.lvfontbin b/fonts/nl.lvfontbin new file mode 100644 index 0000000..5688bf2 Binary files /dev/null and b/fonts/nl.lvfontbin differ diff --git a/fonts/pl.lvfontbin b/fonts/pl.lvfontbin new file mode 100644 index 0000000..5688bf2 Binary files /dev/null and b/fonts/pl.lvfontbin differ diff --git a/fonts/pt_BR.lvfontbin b/fonts/pt_BR.lvfontbin new file mode 100644 index 0000000..5688bf2 Binary files /dev/null and b/fonts/pt_BR.lvfontbin differ diff --git a/fonts/ru.lvfontbin b/fonts/ru.lvfontbin new file mode 100644 index 0000000..29be24e Binary files /dev/null and b/fonts/ru.lvfontbin differ diff --git a/fonts/sv.lvfontbin b/fonts/sv.lvfontbin new file mode 100644 index 0000000..5688bf2 Binary files /dev/null and b/fonts/sv.lvfontbin differ diff --git a/fonts/tr.lvfontbin b/fonts/tr.lvfontbin new file mode 100644 index 0000000..5688bf2 Binary files /dev/null and b/fonts/tr.lvfontbin differ diff --git a/fonts/zh_Latn_pinyin.lvfontbin b/fonts/zh_Latn_pinyin.lvfontbin new file mode 100644 index 0000000..5688bf2 Binary files /dev/null and b/fonts/zh_Latn_pinyin.lvfontbin differ diff --git a/learn-projects/Metro_RP2350_CircuitPython_Matrix.zip b/learn-projects/Metro_RP2350_CircuitPython_Matrix.zip new file mode 100644 index 0000000..eb23c9d Binary files /dev/null and b/learn-projects/Metro_RP2350_CircuitPython_Matrix.zip differ diff --git a/learn-projects/Metro_RP2350_Snake.zip b/learn-projects/Metro_RP2350_Snake.zip new file mode 100644 index 0000000..27cc6c1 Binary files /dev/null and b/learn-projects/Metro_RP2350_Snake.zip differ diff --git a/src/boot.py b/src/boot.py new file mode 100644 index 0000000..2518ddb --- /dev/null +++ b/src/boot.py @@ -0,0 +1,778 @@ +# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries +# +# SPDX-License-Identifier: MIT +import gc + +import displayio +import supervisor +from displayio import OnDiskBitmap, TileGrid, Group +import adafruit_imageload +import time +import math + +display = supervisor.runtime.display +display.auto_refresh = False + + +def make_alternate_platte(input_palette, target_color): + new_palette = displayio.Palette(len(input_palette)) + + for i in range(len(input_palette)): + new_palette[i] = input_palette[i] & target_color + new_palette.make_transparent(0) + return new_palette + + +class OvershootAnimator: + """ + A non-blocking animator that moves an element to a target with overshoot effect. + + Instead of blocking with sleep(), this class provides a tick() method that + should be called repeatedly by an external loop (e.g., game loop, UI event loop). + """ + + def __init__(self, element): + """ + Initialize the animator with an element to animate. + + Parameters: + - element: An object with x and y properties that will be animated + """ + self.element = element + self.pos_animating = False + self.start_time = 0 + self.start_x = 0 + self.start_y = 0 + self.target_x = 0 + self.target_y = 0 + self.overshoot_x = 0 + self.overshoot_y = 0 + self.duration = 0 + self.overshoot_pixels = 0 + self.eased_value = None + + self.cur_sprite_index = None + self.last_sprite_frame_time = -1 + self.sprite_anim_start_time = -1 + self.sprite_anim_from_index = None + self.sprite_anim_to_index = None + self.sprite_anim_delay = None + + def animate_to(self, target_x, target_y, duration=1.0, overshoot_pixels=20, + start_sprite_anim_at=None, sprite_delay=1 / 60, + sprite_from_index=None, sprite_to_index=None, eased_value=None): + + """ + Start a new animation to the specified target. + + Parameters: + - target_x, target_y: The final target coordinates + - duration: Total animation time in seconds + - overshoot_pixels: How many pixels to overshoot beyond the target + (use 0 for no overshoot) + """ + _now = time.monotonic() + + # Record starting position and time + self.start_x = self.element.x + self.start_y = self.element.y + self.start_time = _now + if start_sprite_anim_at is not None: + self.sprite_anim_start_time = _now + start_sprite_anim_at + self.sprite_anim_to_index = sprite_to_index + self.sprite_anim_from_index = sprite_from_index + self.cur_sprite_index = self.sprite_anim_from_index + self.sprite_anim_delay = sprite_delay + + # Store target position and parameters + self.target_x = target_x + self.target_y = target_y + self.duration = duration + self.overshoot_pixels = overshoot_pixels + + # Calculate distance to target + dx = target_x - self.start_x + dy = target_y - self.start_y + + # Calculate the direction vector (normalized) + distance = math.sqrt(dx * dx + dy * dy) + if distance <= 0: + # Already at target + return False + + dir_x = dx / distance + dir_y = dy / distance + + # Calculate overshoot position + self.overshoot_x = target_x + dir_x * overshoot_pixels + self.overshoot_y = target_y + dir_y * overshoot_pixels + + self.eased_value = eased_value + + # Start the animation + self.pos_animating = True + return True + + def sprite_anim_tick(self, cur_time): + if cur_time >= self.last_sprite_frame_time + self.sprite_anim_delay: + # print(f"cur idx: {self.cur_sprite_index}") + self.element[0] = self.cur_sprite_index + + self.last_sprite_frame_time = cur_time + # display.refresh() + self.cur_sprite_index += 1 + # print(f"cur idx: {self.cur_sprite_index} > {self.sprite_anim_to_index}") + if self.cur_sprite_index > self.sprite_anim_to_index: + # print("returning false from sprite_anim_tick") + self.cur_sprite_index = None + self.sprite_anim_from_index = None + self.sprite_anim_to_index = None + self.sprite_anim_delay = None + self.last_sprite_frame_time = -1 + self.sprite_anim_start_time = -1 + return False + + return True + + def tick(self): + """ + Update the animation based on the current time. + + This method should be called repeatedly until it returns False. + + Returns: + - True if the animation is still in progress + - False if the animation has completed + """ + still_sprite_animating = False + _now = time.monotonic() + if self.cur_sprite_index is not None: + if _now >= self.sprite_anim_start_time: + + still_sprite_animating = self.sprite_anim_tick(_now) + # print("sprite_still_animating", still_sprite_animating) + if not still_sprite_animating: + return False + else: + if not self.pos_animating: + # print("returning false cur_sprite_index was None and pos_animating False") + return False + + # Calculate elapsed time and progress + elapsed = _now - self.start_time + progress = elapsed / self.duration + + # Check if animation is complete + if progress >= 1.0: + # Ensure we end exactly at the target + if self.element.x != self.target_x or self.element.y != self.target_y: + self.element.x = self.target_x + self.element.y = self.target_y + # display.refresh() + self.pos_animating = False + if still_sprite_animating: + return True + else: + return False + + # Calculate the current position based on progress + if self.overshoot_pixels > 0: + # Two-phase animation with overshoot + if progress < 0.7: # Move smoothly toward overshoot position + # Use a single smooth curve to the overshoot point + eased = progress / 0.7 # Linear acceleration toward overshoot + # Apply slight ease-in to make it accelerate through the target point + eased = eased ** 1.2 + current_x = self.start_x + (self.overshoot_x - self.start_x) * eased + current_y = self.start_y + (self.overshoot_y - self.start_y) * eased + else: # Return from overshoot to target + sub_progress = (progress - 0.7) / 0.3 + # Decelerate toward final target + eased = 1 - (1 - sub_progress) ** 2 # ease-out quad + current_x = self.overshoot_x + (self.target_x - self.overshoot_x) * eased + current_y = self.overshoot_y + (self.target_y - self.overshoot_y) * eased + else: + # Simple ease-out when no overshoot is desired + if self.eased_value is None: + eased = 1 - (1 - progress) ** 4 + else: + eased = progress / self.eased_value + current_x = self.start_x + (self.target_x - self.start_x) * eased + current_y = self.start_y + (self.target_y - self.start_y) * eased + + # Update element position + self.element.x = int(current_x) + self.element.y = int(current_y) + # print(f"cur: {self.element.x}, {self.element.y}") + # display.refresh(target_frames_per_second=30) + + return True + + def is_animating(self): + """Check if an animation is currently in progress.""" + return self.pos_animating + + def cancel(self): + """Cancel the current animation.""" + self.pos_animating = False + + +# 8172592 +# apple_sprites, apple_sprites_palette = adafruit_imageload.load("apple_spritesheet.bmp") + +# 8342576 + +def color_sweep(color, palette, delay=0, start_at_index=1): + for i in range(start_at_index, len(palette)): + palette[i] = color + if delay > 0: + time.sleep(delay) + + +apple_sprites, apple_sprites_palette = adafruit_imageload.load("/boot_animation/apple_spritesheet_lined_6_indexed.bmp") +#print(f"free: {gc.mem_free()}") +# apple_sprites_palette.make_transparent(0) +color_sweep(0xffffff, apple_sprites_palette) + +f_sprites, f_sprites_palette = adafruit_imageload.load("/boot_animation/f_spritesheet_lined_6_indexed.bmp") +color_sweep(0xffffff, f_sprites_palette) +# f_sprites_palette.make_transparent(0) +r_sprites, r_sprites_palette = adafruit_imageload.load("/boot_animation/r_spritesheet_lined_6_indexed.bmp") +color_sweep(0xffffff, r_sprites_palette) +# r_sprites_palette.make_transparent(0) +u_sprites, u_sprites_palette = adafruit_imageload.load("/boot_animation/u_spritesheet_lined_6_indexed.bmp") +color_sweep(0xffffff, u_sprites_palette) +# u_sprites_palette.make_transparent(0) +i_sprites, i_sprites_palette = adafruit_imageload.load("/boot_animation/i_spritesheet_lined_6_indexed.bmp") +color_sweep(0xffffff, i_sprites_palette) +# i_sprites_palette.make_transparent(0) +t_sprites, t_sprites_palette = adafruit_imageload.load("/boot_animation/t_spritesheet_lined_6_indexed.bmp") +color_sweep(0xffffff, t_sprites_palette) +# t_sprites_palette.make_transparent(0) +j_sprites, j_sprites_palette = adafruit_imageload.load("/boot_animation/j_spritesheet_lined_6_indexed.bmp") +j_sprites_palette.make_transparent(0) +color_sweep(0xffffff, j_sprites_palette, start_at_index=2) +a_sprites, a_sprites_palette = adafruit_imageload.load("/boot_animation/a_spritesheet_lined_6_indexed.bmp") +a_sprites_palette.make_transparent(0) +color_sweep(0xffffff, a_sprites_palette, start_at_index=2) +m_sprites, m_sprites_palette = adafruit_imageload.load("/boot_animation/m_spritesheet_lined_6_indexed.bmp") +m_sprites_palette.make_transparent(0) +color_sweep(0xffffff, m_sprites_palette, start_at_index=2) + + +def color_sweep_all(color, delay=0.05, refresh_during=True): + _start = 4 + + for i in range(1, len(apple_sprites_palette)): + apple_sprites_palette[i] = color + + if _start <= i < _start+7: + f_sprites_palette[i-_start+1] = color + r_sprites_palette[i-_start+1] = color + u_sprites_palette[i-_start+1] = color + i_sprites_palette[i-_start+1] = color + t_sprites_palette[i-_start+1] = color + # print(f"i: {i} >= {len(apple_sprites_palette) -7}") + if i >= len(apple_sprites_palette) - 7: + _adjusted_idx = i - (len(apple_sprites_palette) - 7) + 2 + # print(f"adj idx: {_adjusted_idx}") + j_sprites_palette[_adjusted_idx] = color + a_sprites_palette[_adjusted_idx] = color + m_sprites_palette[_adjusted_idx] = color + + if i % 6 == 0 and refresh_during: + display.refresh() + if delay > 0: + time.sleep(delay) + display.refresh() +# apple_sprites = OnDiskBitmap("apple_spritesheet.bmp") +# apple_sprites_palette = apple_sprites.pixel_shader +# apple_sprites_palette.make_transparent(0) +# +# f_sprites = OnDiskBitmap("f_spritesheet.bmp") +# f_sprites_palette = f_sprites.pixel_shader +# f_sprites_palette.make_transparent(0) +# r_sprites = OnDiskBitmap("r_spritesheet.bmp") +# r_sprites_palette = r_sprites.pixel_shader +# r_sprites_palette.make_transparent(0) +# u_sprites = OnDiskBitmap("u_spritesheet.bmp") +# u_sprites_palette = u_sprites.pixel_shader +# u_sprites_palette.make_transparent(0) +# i_sprites = OnDiskBitmap("i_spritesheet.bmp") +# i_sprites_palette = i_sprites.pixel_shader +# i_sprites_palette.make_transparent(0) +# t_sprites = OnDiskBitmap("t_spritesheet.bmp") +# t_sprites_palette = t_sprites.pixel_shader +# t_sprites_palette.make_transparent(0) +# j_sprites = OnDiskBitmap("j_spritesheet.bmp") +# j_sprites_palette = j_sprites.pixel_shader +# j_sprites_palette.make_transparent(0) +# a_sprites = OnDiskBitmap("a_spritesheet.bmp") +# a_sprites_palette = a_sprites.pixel_shader +# a_sprites_palette.make_transparent(0) +# m_sprites = OnDiskBitmap("m_spritesheet.bmp") +# m_sprites_palette = m_sprites.pixel_shader +# m_sprites_palette.make_transparent(0) + +default_sprite_delay = 1 / 35 + +main_group = Group() +main_group.x = display.width // 4 +main_group.y = display.height // 4 + +sliding_group = Group() +main_group.append(sliding_group) + +letters_x_start = 83 +letters_y_start = display.height + +coordinator = { + "steps": [ + # Apple fly on + { + "type": "animation_step", + "tilegrid": TileGrid(apple_sprites, pixel_shader=apple_sprites_palette, + tile_width=73, tile_height=107, width=1, height=1), + "offscreen_loc": (0, -107), + "onscreen_loc": (0, 21), + "move_duration": 0.45, + "overshoot_pixels": 1, + "eased_value": None, + "sprite_anim_range": (0, 11), + "sprite_delay": 1 / 42, + "start_time": 0.0, + "sprite_anim_start": 0.347, + "started": False, + }, + # F fly on + { + "type": "animation_step", + "tilegrid": TileGrid(f_sprites, pixel_shader=f_sprites_palette, + tile_width=32, tile_height=39, width=1, height=1), + "offscreen_loc": (letters_x_start, letters_y_start), + "onscreen_loc": (letters_x_start, 67), + "move_duration": 0.45, + "overshoot_pixels": 20, + "eased_value": None, + "sprite_anim_range": (0, 15), + "sprite_delay": default_sprite_delay, + "start_time": 0.45, + "sprite_anim_start": 0.347, + "started": False, + + }, + # R fly on + { + "type": "animation_step", + "tilegrid": TileGrid(r_sprites, pixel_shader=r_sprites_palette, + tile_width=32, tile_height=39, width=1, height=1), + "offscreen_loc": (letters_x_start + 32 + 3 - 1, letters_y_start), + "onscreen_loc": (letters_x_start + 32 + 3 - 1, 67), + "move_duration": 0.45, + "overshoot_pixels": 20, + "eased_value": None, + "sprite_anim_range": (0, 15), + "sprite_delay": default_sprite_delay, + "start_time": 0.9, + "sprite_anim_start": 0.347, + "started": False, + }, + # Left slide everything + { + "type": "animation_step", + "tilegrid": sliding_group, + "offscreen_loc": (100, 0), + "onscreen_loc": (30, 0), + "move_duration": 1.75, + "overshoot_pixels": 0, + + "eased_value": 1, + "sprite_anim_range": None, + "sprite_delay": None, + "start_time": 0.9, + "sprite_anim_start": None, + "started": False, + }, + # U fly on + { + "type": "animation_step", + "tilegrid": TileGrid(u_sprites, pixel_shader=u_sprites_palette, + tile_width=32, tile_height=39, width=1, height=1), + "offscreen_loc": (letters_x_start + (32 + 3) * 2 - 2, letters_y_start), + "onscreen_loc": (letters_x_start + (32 + 3) * 2 - 2, 67), + "move_duration": 0.45, + "overshoot_pixels": 20, + "eased_value": None, + "sprite_anim_range": (0, 15), + "sprite_delay": default_sprite_delay, + "start_time": 1.35, + "sprite_anim_start": 0.347, + "started": False, + }, + # I fly on + { + "type": "animation_step", + "tilegrid": TileGrid(i_sprites, pixel_shader=i_sprites_palette, + tile_width=16, tile_height=39, width=1, height=1), + "offscreen_loc": (letters_x_start + (32 + 3) * 3 - 3, letters_y_start), + "onscreen_loc": (letters_x_start + (32 + 3) * 3 - 3, 67), + "move_duration": 0.45, + "overshoot_pixels": 20, + "eased_value": None, + "sprite_anim_range": (0, 15), + "sprite_delay": default_sprite_delay, + "start_time": 1.8, + "sprite_anim_start": 0.347, + "started": False, + }, + # T fly on + { + "type": "animation_step", + "tilegrid": TileGrid(t_sprites, pixel_shader=t_sprites_palette, + tile_width=32, tile_height=39, width=1, height=1), + "offscreen_loc": (letters_x_start + (32 + 3) * 3 + 16 + 3 - 4, letters_y_start), + "onscreen_loc": (letters_x_start + (32 + 3) * 3 + 16 + 3 - 4, 67), + "move_duration": 0.45, + "overshoot_pixels": 20, + "eased_value": None, + "sprite_anim_range": (0, 15), + "sprite_delay": default_sprite_delay, + "start_time": 2.25, + "sprite_anim_start": 0.347, + "started": False, + }, + # J fly on + { + "type": "animation_step", + "tilegrid": TileGrid(j_sprites, pixel_shader=j_sprites_palette, + tile_width=32, tile_height=39, width=1, height=1), + "offscreen_loc": (letters_x_start, letters_y_start), + "onscreen_loc": (letters_x_start, 50 + 39), + "move_duration": 0.45, + "overshoot_pixels": 4, + "eased_value": None, + "sprite_anim_range": (0, 15), + "sprite_delay": default_sprite_delay, + "start_time": 2.7, + # "sprite_anim_start": 0.347, + "sprite_anim_start": 0.4, + "started": False, + }, + # A fly on + { + "type": "animation_step", + "tilegrid": TileGrid(a_sprites, pixel_shader=a_sprites_palette, + tile_width=32, tile_height=39, width=1, height=1), + "offscreen_loc": (letters_x_start + 32 + 3 - 1, letters_y_start), + "onscreen_loc": (letters_x_start + 32 + 3 - 1, 50 + 39), + "move_duration": 0.45, + "overshoot_pixels": 4, + "eased_value": None, + "sprite_anim_range": (0, 15), + "sprite_delay": default_sprite_delay, + "start_time": 3.15, + "sprite_anim_start": 0.4, + "started": False, + }, + # M fly on + { + "type": "animation_step", + "tilegrid": TileGrid(m_sprites, pixel_shader=m_sprites_palette, + tile_width=43, tile_height=39, width=1, height=1), + "offscreen_loc": (letters_x_start + 32 + 3 + 32 + 2 - 1, letters_y_start), + "onscreen_loc": (letters_x_start + 32 + 3 + 32 + 2 - 1, 50 + 39), + "move_duration": 0.45, + "overshoot_pixels": 4, + "eased_value": None, + "sprite_anim_range": (0, 15), + "sprite_delay": default_sprite_delay, + "start_time": 3.6, + "sprite_anim_start": 0.4, + "started": False, + } + + ] +} + +for step in coordinator["steps"]: + if isinstance(step["tilegrid"], TileGrid): + sliding_group.append(step["tilegrid"]) + step["default_palette"] = step["tilegrid"].pixel_shader + # step["red_palette"] = make_alternate_platte(step["default_palette"], 0xff0000) + # step["yellow_palette"] = make_alternate_platte(step["default_palette"], 0xffff00) + # step["teal_palette"] = make_alternate_platte(step["default_palette"], 0x00ffff) + # step["pink_palette"] = make_alternate_platte(step["default_palette"], 0xff00ff) + # step["blue_palette"] = make_alternate_platte(step["default_palette"], 0x0000ff) + # step["green_palette"] = make_alternate_platte(step["default_palette"], 0x00ff00) + # step["tilegrid"].pixel_shader = step["red_palette"] + step["tilegrid"].x = step["offscreen_loc"][0] + step["tilegrid"].y = step["offscreen_loc"][1] + step["animator"] = OvershootAnimator(step["tilegrid"]) + +# F bounce up from J impact +coordinator["steps"].insert(8, + { + "type": "animation_step", + "tilegrid": coordinator["steps"][1]["tilegrid"], + "animator": coordinator["steps"][1]["animator"], + "offscreen_loc": (letters_x_start, letters_y_start), + "onscreen_loc": (letters_x_start, 52), + # "move_duration": 0.3, + "move_duration": 0.3, + "overshoot_pixels": 22, + "eased_value": None, + "sprite_anim_range": (19, 27), + "sprite_delay": 1 / 22, + # "start_time": 4.08, + # "start_time": 3.047, + "start_time": 3.0, + # "sprite_anim_start": 0.15, + "sprite_anim_start": 0.15, + "started": False, + }, + ) +# R bounce up from A impact +coordinator["steps"].insert(10, + { + "type": "animation_step", + "tilegrid": coordinator["steps"][2]["tilegrid"], + "animator": coordinator["steps"][2]["animator"], + "offscreen_loc": (letters_x_start + 32 + 3 - 1, letters_y_start), + "onscreen_loc": (letters_x_start + 32 + 3 - 1, 52), + "move_duration": 0.3, + "overshoot_pixels": 22, + "eased_value": None, + "sprite_anim_range": (19, 27), + "sprite_delay": 1 / 22, + # "start_time": 4.78, + # "start_time": 3.497, + "start_time": 3.45, + "sprite_anim_start": 0.15, + "started": False, + }, + ) +# U bounce up from M impact +coordinator["steps"].append( + { + "type": "animation_step", + "tilegrid": coordinator["steps"][4]["tilegrid"], + "animator": coordinator["steps"][4]["animator"], + "offscreen_loc": (letters_x_start + (32 + 3) * 2 - 2, letters_y_start), + "onscreen_loc": (letters_x_start + (32 + 3) * 2 - 2, 52), + "move_duration": 0.3, + "overshoot_pixels": 22, + "eased_value": None, + "sprite_anim_range": (19, 27), + "sprite_delay": 1 / 22, + # "start_time": 5.48, + # "start_time": 3.947, + "start_time": 3.9, + "sprite_anim_start": 0.15, + "started": False, + }, +) +# I bounce up from M impact +coordinator["steps"].append( + { + "type": "animation_step", + "tilegrid": coordinator["steps"][5]["tilegrid"], + "animator": coordinator["steps"][5]["animator"], + "offscreen_loc": (letters_x_start + (32 + 3) * 3 - 3, letters_y_start), + "onscreen_loc": (letters_x_start + (32 + 3) * 3 - 3, 52), + "move_duration": 0.3, + "overshoot_pixels": 22, + "eased_value": None, + "sprite_anim_range": (19, 27), + "sprite_delay": 1 / 22, + # "start_time": 5.58, + # "start_time": 4.047, + "start_time": 4.00, + "sprite_anim_start": 0.15, + "started": False, + }, +) +# T bounce up from M impact +coordinator["steps"].append( + { + "type": "animation_step", + "tilegrid": coordinator["steps"][6]["tilegrid"], + "animator": coordinator["steps"][6]["animator"], + "offscreen_loc": (letters_x_start + (32 + 3) * 3 + 16 + 3 - 4, letters_y_start), + "onscreen_loc": (letters_x_start + (32 + 3) * 3 + 16 + 3 - 4, 52), + "move_duration": 0.3, + "overshoot_pixels": 22, + "eased_value": None, + "sprite_anim_range": (19, 27), + "sprite_delay": 1 / 22, + # "start_time": 5.68, + # "start_time": 4.147, + "start_time": 4.1, + "sprite_anim_start": 0.15, + "started": False, + }, +) +# color red +coordinator["steps"].append( + { + "start_time": 4.75, + "type": "change_palette", + "new_palette": "red_palette", + "color": 0xff0000, + "started": False, + } +) +# color yellow +coordinator["steps"].append( + { + "start_time": 5, + "type": "change_palette", + "new_palette": "yellow_palette", + "color": 0xffff00, + "started": False, + } +) +# color teal +coordinator["steps"].append( + { + "start_time": 5.25, + "type": "change_palette", + "new_palette": "teal_palette", + "color": 0x00ffff, + "started": False, + } +) +# color pink +coordinator["steps"].append( + { + "start_time": 5.5, + "type": "change_palette", + "new_palette": "pink_palette", + "color": 0xff00ff, + "started": False, + } +) +# color blue +coordinator["steps"].append( + { + "start_time": 5.75, + "type": "change_palette", + "new_palette": "blue_palette", + "color": 0x0000ff, + "started": False, + } +) +# color green +coordinator["steps"].append( + { + "start_time": 6.00, + "type": "change_palette", + "new_palette": "green_palette", + "color": 0x00ff00, + "started": False, + } +) +# Apple eyes blink +coordinator["steps"].append( + { + "type": "animation_step", + "tilegrid": coordinator["steps"][0]["tilegrid"], + "animator": coordinator["steps"][0]["animator"], + "offscreen_loc": (0, -107), + "onscreen_loc": (0, 21), + "move_duration": 0.01, + "overshoot_pixels": 0, + "eased_value": None, + "sprite_anim_range": (12, 27), + "sprite_delay": 1 / 32, + # "start_time": 5.68, + "start_time": 6.65, + "sprite_anim_start": 0.0, + "started": False, + } +) +# Apple eyes blink again +coordinator["steps"].append( + { + "type": "animation_step", + "tilegrid": coordinator["steps"][0]["tilegrid"], + "animator": coordinator["steps"][0]["animator"], + "offscreen_loc": (0, -107), + "onscreen_loc": (0, 21), + "move_duration": 0.01, + "overshoot_pixels": 0, + "eased_value": None, + "sprite_anim_range": (13, 18), + "sprite_delay": 1 / 32, + # "start_time": 5.68, + "start_time": 8.75, + "sprite_anim_start": 0.0, + "started": False, + } +) + +display.root_group = main_group + +# hrule = displayio.Bitmap(200, 1, 1) +# hrule_palette = displayio.Palette(1) +# hrule_palette[0] = 0xff0000 +# hrule_tg = TileGrid(bitmap=hrule, pixel_shader=hrule_palette) +# main_group.append(hrule_tg) +# hrule_tg.y = 59 +# hrule_tg.x = 40 + +start_time = time.monotonic() + +while True: + + now = time.monotonic() + still_going = True + + for i, step in enumerate(coordinator["steps"]): + if now - start_time >= step["start_time"]: + if not step["started"]: + step["started"] = True + if step["type"] == "animation_step": + if step["sprite_anim_range"] is not None: + step["animator"].animate_to( + *step["onscreen_loc"], + duration=step["move_duration"], overshoot_pixels=step["overshoot_pixels"], + start_sprite_anim_at=step["sprite_anim_start"], + sprite_from_index=step["sprite_anim_range"][0], + sprite_to_index=step["sprite_anim_range"][1], + sprite_delay=step["sprite_delay"], eased_value=step["eased_value"], + ) + else: + step["animator"].animate_to( + *step["onscreen_loc"], + duration=step["move_duration"], overshoot_pixels=step["overshoot_pixels"], + eased_value=step["eased_value"] + ) + elif step["type"] == "change_palette": + color_sweep_all(step["color"], delay=0) + + if "animator" in step: + if i == len(coordinator["steps"]) - 1: + still_going = step["animator"].tick() + else: + step["animator"].tick() + else: + if i == len(coordinator["steps"]) - 1: + still_going = False + + display.refresh() + + if not still_going: + for step in coordinator["steps"]: + if step["type"] == "animation_step": + step["tilegrid"].x = step["offscreen_loc"][0] + step["tilegrid"].y = step["offscreen_loc"][1] + + step["started"] = False + + # reset the apple to no eyes sprite + coordinator["steps"][0]["tilegrid"][0] = 0 + + time.sleep(2) + color_sweep_all(0xffffff, delay=0, refresh_during=False) + display.refresh() + start_time = time.monotonic() diff --git a/src/boot_animation/a_spritesheet_lined_6_indexed.bmp b/src/boot_animation/a_spritesheet_lined_6_indexed.bmp new file mode 100644 index 0000000..c288696 Binary files /dev/null and b/src/boot_animation/a_spritesheet_lined_6_indexed.bmp differ diff --git a/src/boot_animation/apple_spritesheet_lined_6_indexed.bmp b/src/boot_animation/apple_spritesheet_lined_6_indexed.bmp new file mode 100644 index 0000000..31f687f Binary files /dev/null and b/src/boot_animation/apple_spritesheet_lined_6_indexed.bmp differ diff --git a/src/boot_animation/f_spritesheet_lined_6_indexed.bmp b/src/boot_animation/f_spritesheet_lined_6_indexed.bmp new file mode 100644 index 0000000..381d1bc Binary files /dev/null and b/src/boot_animation/f_spritesheet_lined_6_indexed.bmp differ diff --git a/src/boot_animation/i_spritesheet_lined_6_indexed.bmp b/src/boot_animation/i_spritesheet_lined_6_indexed.bmp new file mode 100644 index 0000000..95da7ba Binary files /dev/null and b/src/boot_animation/i_spritesheet_lined_6_indexed.bmp differ diff --git a/src/boot_animation/j_spritesheet_lined_6_indexed.bmp b/src/boot_animation/j_spritesheet_lined_6_indexed.bmp new file mode 100644 index 0000000..e98d718 Binary files /dev/null and b/src/boot_animation/j_spritesheet_lined_6_indexed.bmp differ diff --git a/src/boot_animation/m_spritesheet_lined_6_indexed.bmp b/src/boot_animation/m_spritesheet_lined_6_indexed.bmp new file mode 100644 index 0000000..7f3c796 Binary files /dev/null and b/src/boot_animation/m_spritesheet_lined_6_indexed.bmp differ diff --git a/src/boot_animation/r_spritesheet_lined_6_indexed.bmp b/src/boot_animation/r_spritesheet_lined_6_indexed.bmp new file mode 100644 index 0000000..68ea6ce Binary files /dev/null and b/src/boot_animation/r_spritesheet_lined_6_indexed.bmp differ diff --git a/src/boot_animation/t_spritesheet_lined_6_indexed.bmp b/src/boot_animation/t_spritesheet_lined_6_indexed.bmp new file mode 100644 index 0000000..742a09f Binary files /dev/null and b/src/boot_animation/t_spritesheet_lined_6_indexed.bmp differ diff --git a/src/boot_animation/u_spritesheet_lined_6_indexed.bmp b/src/boot_animation/u_spritesheet_lined_6_indexed.bmp new file mode 100644 index 0000000..6e37634 Binary files /dev/null and b/src/boot_animation/u_spritesheet_lined_6_indexed.bmp differ diff --git a/src/code.py b/src/code.py new file mode 100644 index 0000000..3e969c5 --- /dev/null +++ b/src/code.py @@ -0,0 +1,266 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT + +""" +This example uses adafruit_display_text.label to display text using a custom font +loaded by adafruit_bitmap_font +""" +import array + +import board +import displayio +import pathlib +import supervisor +import sys +import usb +from adafruit_display_text import label +from adafruit_bitmap_font import bitmap_font +from adafruit_display_text.text_box import TextBox +from adafruit_display_text.bitmap_label import Label +from adafruit_displayio_layout.layouts.grid_layout import GridLayout +from adafruit_anchored_tilegrid import AnchoredTileGrid +import adafruit_imageload +import adafruit_usb_host_descriptors +from sized_group import SizedGroup + +display = supervisor.runtime.display +font_file = "/fonts/terminal.lvfontbin" +font = bitmap_font.load_font(font_file) +main_group = displayio.Group() +display.root_group = main_group + +background_bmp = displayio.Bitmap(display.width, display.height, 1) +bg_palette = displayio.Palette(1) +bg_palette[0] = 0x222222 +bg_tg = displayio.TileGrid(bitmap=background_bmp, pixel_shader=bg_palette) +main_group.append(bg_tg) + +# load the mouse cursor bitmap +mouse_bmp = displayio.OnDiskBitmap("mouse_cursor.bmp") + +# make the background pink pixels transparent +mouse_bmp.pixel_shader.make_transparent(0) + +# create a TileGrid for the mouse, using its bitmap and pixel_shader +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 +#046d:c52f + + +#mouse = usb.core.find(idVendor=0x046d, idProduct=0xc52f) + +DIR_IN = 0x80 +mouse_interface_index, mouse_endpoint_address = None, None +mouse = None +#scan for connected USB device and loop over any found +print("scanning usb") +for device in usb.core.find(find_all=True): + # print device info + print(f"{device.idVendor:04x}:{device.idProduct:04x}") + print(device.manufacturer, device.product) + print() + config_descriptor = adafruit_usb_host_descriptors.get_configuration_descriptor( + device, 0 + ) + print(config_descriptor) + # + # i = 0 + # while i < len(config_descriptor): + # descriptor_len = config_descriptor[i] + # descriptor_type = config_descriptor[i + 1] + # if descriptor_type == adafruit_usb_host_descriptors.DESC_CONFIGURATION: + # config_value = config_descriptor[i + 5] + # print(f" value {config_value:d}") + # elif descriptor_type == adafruit_usb_host_descriptors.DESC_INTERFACE: + # interface_number = config_descriptor[i + 2] + # interface_class = config_descriptor[i + 5] + # interface_subclass = config_descriptor[i + 6] + # interface_protocol = config_descriptor[i + 7] + # print(f" interface[{interface_number:d}]") + # print( + # f" class {interface_class:02x} subclass {interface_subclass:02x}" + # ) + # print(f"protocol: {interface_protocol}") + # elif descriptor_type == adafruit_usb_host_descriptors.DESC_ENDPOINT: + # endpoint_address = config_descriptor[i + 2] + # if endpoint_address & DIR_IN: + # print(f" IN {endpoint_address:02x}") + # else: + # print(f" OUT {endpoint_address:02x}") + # i += descriptor_len + # print() + # + # # assume the device is the mouse + # mouse = device + mouse_interface_index, mouse_endpoint_address = adafruit_usb_host_descriptors.find_boot_mouse_endpoint(device) + if mouse_interface_index is not None and mouse_endpoint_address is not None: + mouse = device + print(f"mouse interface: {mouse_interface_index} endpoint_address: {hex(mouse_endpoint_address)}") + +if mouse is not None: + # detach the kernel driver if needed + if mouse.is_kernel_driver_active(0): + mouse.detach_kernel_driver(0) + + # set configuration on the mouse so we can use it + mouse.set_configuration() + +mouse_buf = array.array("b", [0] * 8) +WIDTH = 300 +HEIGHT = 192 + +config = { + "menu_title": "Launcher Menu", + "width": 3, + "height": 2, + "apps": [ + { + "title": "🐍Snake🐍", + "icon": "icon_snake.bmp", + "file": "code_snake_game.py" + }, + { + "title": "Nyan😺Flap", + "icon": "icon_flappynyan.bmp", + "file": "code_flappy_nyan.py" + }, + { + "title": "Memory🧠", + "icon": "icon_memory.bmp", + "file": "code_memory.py" + }, + { + "title": "Matrix", + "icon": "/apps/matrix/icon.bmp", + "file": "/apps/matrix/code.py" + }, + { + "title": "Breakout", + "icon": "icon_breakout.bmp", + "file": "code_breakout.py" + }, + { + "title": "Paint🖌️", + "icon": "icon_paint.bmp", + } + + ] +} + +cell_width = WIDTH // config["width"] + +default_icon_bmp, default_icon_palette = adafruit_imageload.load("default_icon_64.bmp") +default_icon_palette.make_transparent(0) +menu_grid = GridLayout(x=10, y=26, 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) +main_group.append(menu_title_txt) + +app_titles = [] +apps = [] +app_path = pathlib.Path("/apps") +i = 0 +for path in app_path.iterdir(): + print(path) + code_file = path / "code.py" + if not code_file.exists(): + continue + cell_group = SizedGroup() + icon_file = path / "icon.bmp" + if not icon_file.exists(): + icon_file = None + apps.append({ + "title": path.name, + "icon": str(icon_file.absolute()), + "file": str(code_file.absolute()) + }) + if apps[-1]["icon"] is None: + icon_tg = displayio.TileGrid(bitmap=default_icon_bmp, pixel_shader=default_icon_palette) + cell_group.append(icon_tg) + else: + icon_bmp, icon_palette = adafruit_imageload.load(apps[-1]["icon"]) + icon_tg = displayio.TileGrid(bitmap=icon_bmp, pixel_shader=icon_palette) + cell_group.append(icon_tg) + + icon_tg.x = cell_width//2 - icon_tg.tile_width//2 + title_txt = TextBox(font, text=apps[-1]["title"], width=WIDTH // config["width"], height=18, align=TextBox.ALIGN_CENTER) + cell_group.append(title_txt) + title_txt.anchor_point = (0,0) + title_txt.anchored_position = (0, icon_tg.y + icon_tg.tile_height) + app_titles.append(title_txt) + menu_grid.add_content(cell_group, grid_position=(i % config["width"], i // config["width"]), cell_size=(1, 1)) + i += 1 + +left_bmp, left_palette = adafruit_imageload.load("arrow_left.bmp") +left_palette.make_transparent(0) +right_bmp, right_palette = adafruit_imageload.load("arrow_right.bmp") +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) + +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) + +main_group.append(left_tg) +main_group.append(right_tg) + +if mouse: + main_group.append(mouse_tg) + +selected = 0 +while True: + index = None + + available = supervisor.runtime.serial_bytes_available + if available: + c = sys.stdin.read(available) + print(repr(c)) + app_titles[selected].background_color = None + + if c == "\x1b[A": + selected = max(0, selected - 1) + elif c == "\x1b[B": + selected = min(len(config["apps"]) - 1, selected + 1) + elif c == "\n": + index = selected + print("go!") + print("selected", selected) + app_titles[selected].background_color = 0x008800 + + if mouse: + try: + # 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) + except usb.core.USBTimeoutError: + # skip the rest of the loop if there is no data + count = 0 + + # 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])) + + if mouse_buf[0] & (1 << 0) != 0: + clicked_cell = menu_grid.which_cell_contains((mouse_tg.x, mouse_tg.y)) + if clicked_cell is not None: + 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") + + if mouse and not mouse.is_kernel_driver_active(0): + mouse.attach_kernel_driver(0) + supervisor.reload() diff --git a/src/sized_group.py b/src/sized_group.py new file mode 100644 index 0000000..b16f680 --- /dev/null +++ b/src/sized_group.py @@ -0,0 +1,49 @@ +import displayio + +class SizedGroup(displayio.Group): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + + @property + def size(self): + min_x = 0 + min_y = 0 + max_x = 0 + max_y = 0 + + for element in self: + # print(type(element)) + if type(element) == displayio.TileGrid: + if element.x < min_x: + min_x = element.x + if element.y < min_y: + min_y = element.y + + _element_max_x = element.x + (element.width * element.tile_width) + _element_max_y = element.y + (element.height * element.tile_height) + if _element_max_x > max_x: + max_x = _element_max_x + if _element_max_y > max_y: + max_y = _element_max_y + else: + if element.x < min_x: + min_x = element.x + if element.y < min_y: + min_y = element.y + + _element_max_x = element.x + (element.width * element.scale) + _element_max_y = element.y + (element.height * element.scale) + if _element_max_x > max_x: + max_x = _element_max_x + if _element_max_y > max_y: + max_y = _element_max_y + return max_x - min_x, max_y - min_y + + @property + def width(self): + return self.size[0] + + @property + def height(self): + return self.size[1]