launcher pagination, mouse fix, refactor key handling. editor. rework boot sequence.
This commit is contained in:
parent
cec3533a3a
commit
5775bc25f2
5 changed files with 1041 additions and 693 deletions
2
build.py
2
build.py
|
|
@ -12,7 +12,7 @@ LEARN_PROJECT_URLS = [
|
||||||
"https://cdn-learn.adafruit.com/downloads/zip/3194658/Metro/Metro_RP2350_FlappyNyanCat.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/3196927/Metro/Metro_RP2350_Match3/match3_game.zip?timestamp={}",
|
||||||
"https://cdn-learn.adafruit.com/downloads/zip/3194422/Metro/Metro_RP2350_Breakout.zip?timestamp={}",
|
"https://cdn-learn.adafruit.com/downloads/zip/3194422/Metro/Metro_RP2350_Breakout.zip?timestamp={}",
|
||||||
# "",
|
"https://cdn-learn.adafruit.com/downloads/zip/3196755/Metro/Metro_RP2350_Chips_Challenge.zip?timestamp=1746112286",
|
||||||
]
|
]
|
||||||
|
|
||||||
def create_font_specific_zip(font_path: Path, src_dir: Path, learn_projects_dir: Path, output_dir: Path):
|
def create_font_specific_zip(font_path: Path, src_dir: Path, learn_projects_dir: Path, output_dir: Path):
|
||||||
|
|
|
||||||
707
src/boot.py
707
src/boot.py
|
|
@ -1,673 +1,54 @@
|
||||||
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
|
import os
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
import gc
|
|
||||||
|
|
||||||
import board
|
|
||||||
import displayio
|
|
||||||
import supervisor
|
import supervisor
|
||||||
from displayio import OnDiskBitmap, TileGrid, Group
|
from argv_file_helper import argv_filename
|
||||||
import adafruit_imageload
|
import json
|
||||||
import time
|
import storage
|
||||||
import math
|
|
||||||
import adafruit_tlv320
|
|
||||||
from audiocore import WaveFile
|
|
||||||
import audiobusio
|
|
||||||
|
|
||||||
BOX_SIZE = (235, 107)
|
supervisor.runtime.autoreload = False
|
||||||
TARGET_FPS = 70
|
|
||||||
|
|
||||||
display = supervisor.runtime.display
|
"""
|
||||||
display.auto_refresh = False
|
boot.py arguments
|
||||||
|
|
||||||
i2c = board.I2C()
|
0: storage readonly flag, False means writable to CircuitPython, True means read-only to CircuitPython
|
||||||
dac = adafruit_tlv320.TLV320DAC3100(i2c)
|
1: next code files
|
||||||
|
2-N: args to pass to next code file
|
||||||
|
|
||||||
# set sample rate & bit depth
|
"""
|
||||||
dac.configure_clocks(sample_rate=11030, bit_depth=16)
|
try:
|
||||||
|
arg_file = argv_filename(__file__)
|
||||||
|
print(f"arg files: {arg_file}")
|
||||||
|
with open(arg_file, "r") as f:
|
||||||
|
args = json.load(f)
|
||||||
|
|
||||||
# use headphones
|
print("args file found and loaded")
|
||||||
dac.headphone_output = True
|
os.remove(arg_file)
|
||||||
dac.headphone_volume = -15 # dB
|
print("args file removed")
|
||||||
|
|
||||||
wave_file = open("/boot_animation/ada_fruitjam_boot_jingle.wav", "rb")
|
readonly = args[0]
|
||||||
wave = WaveFile(wave_file)
|
next_code_file = None
|
||||||
audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN)
|
remaining_args = None
|
||||||
|
|
||||||
|
if len(args) >= 1:
|
||||||
|
next_code_file = args[1]
|
||||||
|
if len(args) >= 2:
|
||||||
|
remaining_args = args[2:]
|
||||||
|
|
||||||
|
if remaining_args is not None:
|
||||||
|
next_code_argv_filename = argv_filename(next_code_file)
|
||||||
|
with open(next_code_argv_filename, "w") as f:
|
||||||
|
f.write(json.dumps(remaining_args))
|
||||||
|
print("next code args written")
|
||||||
|
|
||||||
|
print(f"setting storage readonly to: {readonly}")
|
||||||
|
storage.remount("/", readonly=readonly)
|
||||||
|
|
||||||
|
next_code_file = next_code_file
|
||||||
|
supervisor.set_next_code_file(next_code_file)
|
||||||
|
print(f"launching: {next_code_file}")
|
||||||
|
# os.rename("/saves/.boot_py_argv", "/saves/.not_boot_py_argv")
|
||||||
|
|
||||||
|
|
||||||
class OvershootAnimator:
|
except OSError:
|
||||||
"""
|
print("launching boot animation")
|
||||||
A non-blocking animator that moves an element to a target with overshoot effect.
|
supervisor.set_next_code_file("boot_animation.py")
|
||||||
|
|
||||||
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:
|
|
||||||
self.element[0] = self.cur_sprite_index
|
|
||||||
self.last_sprite_frame_time = cur_time
|
|
||||||
self.cur_sprite_index += 1
|
|
||||||
|
|
||||||
if self.cur_sprite_index > self.sprite_anim_to_index:
|
|
||||||
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
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
apple_sprites, apple_sprites_palette = adafruit_imageload.load("/boot_animation/apple_spritesheet.bmp")
|
|
||||||
f_sprites, f_sprites_palette = adafruit_imageload.load("/boot_animation/f_spritesheet.bmp")
|
|
||||||
r_sprites, r_sprites_palette = adafruit_imageload.load("/boot_animation/r_spritesheet.bmp")
|
|
||||||
u_sprites, u_sprites_palette = adafruit_imageload.load("/boot_animation/u_spritesheet.bmp")
|
|
||||||
i_sprites, i_sprites_palette = adafruit_imageload.load("/boot_animation/i_spritesheet.bmp")
|
|
||||||
t_sprites, t_sprites_palette = adafruit_imageload.load("/boot_animation/t_spritesheet.bmp")
|
|
||||||
j_sprites, j_sprites_palette = adafruit_imageload.load("/boot_animation/j_spritesheet.bmp")
|
|
||||||
j_sprites_palette.make_transparent(0)
|
|
||||||
a_sprites, a_sprites_palette = adafruit_imageload.load("/boot_animation/a_spritesheet.bmp")
|
|
||||||
a_sprites_palette.make_transparent(0)
|
|
||||||
m_sprites, m_sprites_palette = adafruit_imageload.load("/boot_animation/m_spritesheet.bmp")
|
|
||||||
m_sprites_palette.make_transparent(0)
|
|
||||||
|
|
||||||
default_sprite_delay = 1 / 35
|
|
||||||
|
|
||||||
main_group = Group()
|
|
||||||
main_group.x = display.width // 2 - BOX_SIZE[0] // 2 - 30
|
|
||||||
main_group.y = display.height // 2 - BOX_SIZE[1] // 2 - 31
|
|
||||||
|
|
||||||
sliding_group = Group()
|
|
||||||
main_group.append(sliding_group)
|
|
||||||
|
|
||||||
letters_x_start = 83
|
|
||||||
letters_y_start = display.height
|
|
||||||
|
|
||||||
apple_tilegrid = TileGrid(apple_sprites, pixel_shader=apple_sprites_palette,
|
|
||||||
tile_width=73, tile_height=107, width=1, height=1)
|
|
||||||
f_tilegrid = TileGrid(f_sprites, pixel_shader=f_sprites_palette,
|
|
||||||
tile_width=32, tile_height=39, width=1, height=1)
|
|
||||||
r_tilegrid = TileGrid(r_sprites, pixel_shader=r_sprites_palette,
|
|
||||||
tile_width=32, tile_height=39, width=1, height=1)
|
|
||||||
u_tilegrid = TileGrid(u_sprites, pixel_shader=u_sprites_palette,
|
|
||||||
tile_width=32, tile_height=39, width=1, height=1)
|
|
||||||
i_tilegrid = TileGrid(i_sprites, pixel_shader=i_sprites_palette,
|
|
||||||
tile_width=16, tile_height=39, width=1, height=1)
|
|
||||||
t_tilegrid = TileGrid(t_sprites, pixel_shader=t_sprites_palette,
|
|
||||||
tile_width=32, tile_height=39, width=1, height=1)
|
|
||||||
j_tilegrid = TileGrid(j_sprites, pixel_shader=j_sprites_palette,
|
|
||||||
tile_width=32, tile_height=39, width=1, height=1)
|
|
||||||
a_tilegrid = TileGrid(a_sprites, pixel_shader=a_sprites_palette,
|
|
||||||
tile_width=32, tile_height=39, width=1, height=1)
|
|
||||||
m_tilegrid = TileGrid(m_sprites, pixel_shader=m_sprites_palette,
|
|
||||||
tile_width=43, tile_height=39, width=1, height=1)
|
|
||||||
|
|
||||||
coordinator = {
|
|
||||||
"steps": [
|
|
||||||
# Apple fly on
|
|
||||||
{
|
|
||||||
"type": "animation_step",
|
|
||||||
"tilegrid": apple_tilegrid,
|
|
||||||
"offscreen_loc": (0, -207),
|
|
||||||
"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": f_tilegrid,
|
|
||||||
"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": r_tilegrid,
|
|
||||||
"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": u_tilegrid,
|
|
||||||
"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": i_tilegrid,
|
|
||||||
"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": t_tilegrid,
|
|
||||||
"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": j_tilegrid,
|
|
||||||
"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": a_tilegrid,
|
|
||||||
"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": m_tilegrid,
|
|
||||||
"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["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,
|
|
||||||
"overshoot_pixels": 22,
|
|
||||||
"eased_value": None,
|
|
||||||
"sprite_anim_range": (19, 27),
|
|
||||||
"sprite_delay": 1 / 22,
|
|
||||||
"start_time": 3.0,
|
|
||||||
"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": 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": 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": 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": 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, -207),
|
|
||||||
"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": 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, -207),
|
|
||||||
"onscreen_loc": (0, 21),
|
|
||||||
"move_duration": 0.01,
|
|
||||||
"overshoot_pixels": 0,
|
|
||||||
"eased_value": None,
|
|
||||||
"sprite_anim_range": (12, 18),
|
|
||||||
"sprite_delay": 1 / 32,
|
|
||||||
"start_time": 8.75,
|
|
||||||
"sprite_anim_start": 0.0,
|
|
||||||
"started": False,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
display.root_group = main_group
|
|
||||||
|
|
||||||
start_time = time.monotonic()
|
|
||||||
|
|
||||||
audio.play(wave)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
now = time.monotonic()
|
|
||||||
still_going = True
|
|
||||||
|
|
||||||
for i in range(len(coordinator["steps"])):
|
|
||||||
step = coordinator["steps"][i]
|
|
||||||
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)
|
|
||||||
for _cur_step in coordinator["steps"]:
|
|
||||||
if "tilegrid" in _cur_step and isinstance(_cur_step["tilegrid"], TileGrid):
|
|
||||||
_cur_step["tilegrid"].pixel_shader[1] = step["color"]
|
|
||||||
|
|
||||||
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(target_frames_per_second=TARGET_FPS)
|
|
||||||
display.refresh()
|
|
||||||
|
|
||||||
if not still_going:
|
|
||||||
break
|
|
||||||
|
|
||||||
while audio.playing:
|
|
||||||
pass
|
|
||||||
676
src/boot_animation.py
Normal file
676
src/boot_animation.py
Normal file
|
|
@ -0,0 +1,676 @@
|
||||||
|
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
import gc
|
||||||
|
|
||||||
|
import board
|
||||||
|
import displayio
|
||||||
|
import supervisor
|
||||||
|
from displayio import OnDiskBitmap, TileGrid, Group
|
||||||
|
import adafruit_imageload
|
||||||
|
import time
|
||||||
|
import math
|
||||||
|
import adafruit_tlv320
|
||||||
|
from audiocore import WaveFile
|
||||||
|
import audiobusio
|
||||||
|
|
||||||
|
BOX_SIZE = (235, 107)
|
||||||
|
TARGET_FPS = 70
|
||||||
|
|
||||||
|
display = supervisor.runtime.display
|
||||||
|
display.auto_refresh = False
|
||||||
|
|
||||||
|
i2c = board.I2C()
|
||||||
|
dac = adafruit_tlv320.TLV320DAC3100(i2c)
|
||||||
|
|
||||||
|
# set sample rate & bit depth
|
||||||
|
dac.configure_clocks(sample_rate=11030, bit_depth=16)
|
||||||
|
|
||||||
|
# use headphones
|
||||||
|
dac.headphone_output = True
|
||||||
|
dac.headphone_volume = -15 # dB
|
||||||
|
|
||||||
|
wave_file = open("/boot_animation/ada_fruitjam_boot_jingle.wav", "rb")
|
||||||
|
wave = WaveFile(wave_file)
|
||||||
|
audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN)
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
self.element[0] = self.cur_sprite_index
|
||||||
|
self.last_sprite_frame_time = cur_time
|
||||||
|
self.cur_sprite_index += 1
|
||||||
|
|
||||||
|
if self.cur_sprite_index > self.sprite_anim_to_index:
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
apple_sprites, apple_sprites_palette = adafruit_imageload.load("/boot_animation/apple_spritesheet.bmp")
|
||||||
|
f_sprites, f_sprites_palette = adafruit_imageload.load("/boot_animation/f_spritesheet.bmp")
|
||||||
|
r_sprites, r_sprites_palette = adafruit_imageload.load("/boot_animation/r_spritesheet.bmp")
|
||||||
|
u_sprites, u_sprites_palette = adafruit_imageload.load("/boot_animation/u_spritesheet.bmp")
|
||||||
|
i_sprites, i_sprites_palette = adafruit_imageload.load("/boot_animation/i_spritesheet.bmp")
|
||||||
|
t_sprites, t_sprites_palette = adafruit_imageload.load("/boot_animation/t_spritesheet.bmp")
|
||||||
|
j_sprites, j_sprites_palette = adafruit_imageload.load("/boot_animation/j_spritesheet.bmp")
|
||||||
|
j_sprites_palette.make_transparent(0)
|
||||||
|
a_sprites, a_sprites_palette = adafruit_imageload.load("/boot_animation/a_spritesheet.bmp")
|
||||||
|
a_sprites_palette.make_transparent(0)
|
||||||
|
m_sprites, m_sprites_palette = adafruit_imageload.load("/boot_animation/m_spritesheet.bmp")
|
||||||
|
m_sprites_palette.make_transparent(0)
|
||||||
|
|
||||||
|
default_sprite_delay = 1 / 35
|
||||||
|
|
||||||
|
main_group = Group()
|
||||||
|
main_group.x = display.width // 2 - BOX_SIZE[0] // 2 - 30
|
||||||
|
main_group.y = display.height // 2 - BOX_SIZE[1] // 2 - 31
|
||||||
|
|
||||||
|
sliding_group = Group()
|
||||||
|
main_group.append(sliding_group)
|
||||||
|
|
||||||
|
letters_x_start = 83
|
||||||
|
letters_y_start = display.height
|
||||||
|
|
||||||
|
apple_tilegrid = TileGrid(apple_sprites, pixel_shader=apple_sprites_palette,
|
||||||
|
tile_width=73, tile_height=107, width=1, height=1)
|
||||||
|
f_tilegrid = TileGrid(f_sprites, pixel_shader=f_sprites_palette,
|
||||||
|
tile_width=32, tile_height=39, width=1, height=1)
|
||||||
|
r_tilegrid = TileGrid(r_sprites, pixel_shader=r_sprites_palette,
|
||||||
|
tile_width=32, tile_height=39, width=1, height=1)
|
||||||
|
u_tilegrid = TileGrid(u_sprites, pixel_shader=u_sprites_palette,
|
||||||
|
tile_width=32, tile_height=39, width=1, height=1)
|
||||||
|
i_tilegrid = TileGrid(i_sprites, pixel_shader=i_sprites_palette,
|
||||||
|
tile_width=16, tile_height=39, width=1, height=1)
|
||||||
|
t_tilegrid = TileGrid(t_sprites, pixel_shader=t_sprites_palette,
|
||||||
|
tile_width=32, tile_height=39, width=1, height=1)
|
||||||
|
j_tilegrid = TileGrid(j_sprites, pixel_shader=j_sprites_palette,
|
||||||
|
tile_width=32, tile_height=39, width=1, height=1)
|
||||||
|
a_tilegrid = TileGrid(a_sprites, pixel_shader=a_sprites_palette,
|
||||||
|
tile_width=32, tile_height=39, width=1, height=1)
|
||||||
|
m_tilegrid = TileGrid(m_sprites, pixel_shader=m_sprites_palette,
|
||||||
|
tile_width=43, tile_height=39, width=1, height=1)
|
||||||
|
|
||||||
|
coordinator = {
|
||||||
|
"steps": [
|
||||||
|
# Apple fly on
|
||||||
|
{
|
||||||
|
"type": "animation_step",
|
||||||
|
"tilegrid": apple_tilegrid,
|
||||||
|
"offscreen_loc": (0, -207),
|
||||||
|
"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": f_tilegrid,
|
||||||
|
"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": r_tilegrid,
|
||||||
|
"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": u_tilegrid,
|
||||||
|
"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": i_tilegrid,
|
||||||
|
"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": t_tilegrid,
|
||||||
|
"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": j_tilegrid,
|
||||||
|
"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": a_tilegrid,
|
||||||
|
"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": m_tilegrid,
|
||||||
|
"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["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,
|
||||||
|
"overshoot_pixels": 22,
|
||||||
|
"eased_value": None,
|
||||||
|
"sprite_anim_range": (19, 27),
|
||||||
|
"sprite_delay": 1 / 22,
|
||||||
|
"start_time": 3.0,
|
||||||
|
"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": 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": 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": 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": 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, -207),
|
||||||
|
"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": 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, -207),
|
||||||
|
"onscreen_loc": (0, 21),
|
||||||
|
"move_duration": 0.01,
|
||||||
|
"overshoot_pixels": 0,
|
||||||
|
"eased_value": None,
|
||||||
|
"sprite_anim_range": (12, 18),
|
||||||
|
"sprite_delay": 1 / 32,
|
||||||
|
"start_time": 8.75,
|
||||||
|
"sprite_anim_start": 0.0,
|
||||||
|
"started": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
display.root_group = main_group
|
||||||
|
|
||||||
|
start_time = time.monotonic()
|
||||||
|
|
||||||
|
audio.play(wave)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
now = time.monotonic()
|
||||||
|
still_going = True
|
||||||
|
|
||||||
|
for i in range(len(coordinator["steps"])):
|
||||||
|
step = coordinator["steps"][i]
|
||||||
|
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)
|
||||||
|
for _cur_step in coordinator["steps"]:
|
||||||
|
if "tilegrid" in _cur_step and isinstance(_cur_step["tilegrid"], TileGrid):
|
||||||
|
_cur_step["tilegrid"].pixel_shader[1] = step["color"]
|
||||||
|
|
||||||
|
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(target_frames_per_second=TARGET_FPS)
|
||||||
|
display.refresh()
|
||||||
|
|
||||||
|
if not still_going:
|
||||||
|
break
|
||||||
|
|
||||||
|
while audio.playing:
|
||||||
|
pass
|
||||||
|
|
||||||
|
supervisor.set_next_code_file("code.py")
|
||||||
|
supervisor.reload()
|
||||||
287
src/code.py
287
src/code.py
|
|
@ -8,6 +8,8 @@ loaded by adafruit_bitmap_font
|
||||||
import array
|
import array
|
||||||
import atexit
|
import atexit
|
||||||
import json
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
|
||||||
import displayio
|
import displayio
|
||||||
|
|
||||||
|
|
@ -23,7 +25,46 @@ from adafruit_anchored_tilegrid import AnchoredTileGrid
|
||||||
import adafruit_imageload
|
import adafruit_imageload
|
||||||
import adafruit_usb_host_descriptors
|
import adafruit_usb_host_descriptors
|
||||||
from adafruit_anchored_group import AnchoredGroup
|
from adafruit_anchored_group import AnchoredGroup
|
||||||
|
from adafruit_fruitjam.peripherals import request_display_config
|
||||||
|
from argv_file_helper import argv_filename
|
||||||
|
|
||||||
|
"""
|
||||||
|
desktop launcher code.py arguments
|
||||||
|
|
||||||
|
0: next code files
|
||||||
|
1-N: args to pass to next code file
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
arg_file = argv_filename(__file__)
|
||||||
|
print(f"arg files: {arg_file}")
|
||||||
|
with open(arg_file, "r") as f:
|
||||||
|
args = json.load(f)
|
||||||
|
os.remove(arg_file)
|
||||||
|
next_code_file = None
|
||||||
|
remaining_args = None
|
||||||
|
|
||||||
|
if len(args) > 0:
|
||||||
|
next_code_file = args[0]
|
||||||
|
if len(args) > 1:
|
||||||
|
remaining_args = args[1:]
|
||||||
|
|
||||||
|
if remaining_args is not None:
|
||||||
|
next_code_argv_filename = argv_filename(next_code_file)
|
||||||
|
with open(next_code_argv_filename, "w") as f:
|
||||||
|
f.write(json.dumps(remaining_args))
|
||||||
|
|
||||||
|
next_code_file = next_code_file
|
||||||
|
supervisor.set_next_code_file(next_code_file)
|
||||||
|
print(f"launching: {next_code_file}")
|
||||||
|
supervisor.reload()
|
||||||
|
# os.rename("/saves/.boot_py_argv", "/saves/.not_boot_py_argv")
|
||||||
|
|
||||||
|
except OSError:
|
||||||
|
# no args, just launch desktop
|
||||||
|
pass
|
||||||
|
|
||||||
|
request_display_config(720, 400)
|
||||||
display = supervisor.runtime.display
|
display = supervisor.runtime.display
|
||||||
|
|
||||||
scale = 1
|
scale = 1
|
||||||
|
|
@ -101,9 +142,11 @@ for device in usb.core.find(find_all=True):
|
||||||
#
|
#
|
||||||
# # assume the device is the mouse
|
# # assume the device is the mouse
|
||||||
# mouse = device
|
# mouse = device
|
||||||
mouse_interface_index, mouse_endpoint_address = adafruit_usb_host_descriptors.find_boot_mouse_endpoint(device)
|
_possible_interface_index, _possible_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:
|
if _possible_interface_index is not None and _possible_endpoint_address is not None:
|
||||||
mouse = device
|
mouse = device
|
||||||
|
mouse_interface_index = _possible_interface_index
|
||||||
|
mouse_endpoint_address = _possible_endpoint_address
|
||||||
print(f"mouse interface: {mouse_interface_index} endpoint_address: {hex(mouse_endpoint_address)}")
|
print(f"mouse interface: {mouse_interface_index} endpoint_address: {hex(mouse_endpoint_address)}")
|
||||||
|
|
||||||
mouse_was_attached = None
|
mouse_was_attached = None
|
||||||
|
|
@ -168,7 +211,7 @@ menu_grid = GridLayout(x=40, y=16, width=WIDTH, height=HEIGHT, grid_size=(config
|
||||||
divider_lines=False)
|
divider_lines=False)
|
||||||
main_group.append(menu_grid)
|
main_group.append(menu_grid)
|
||||||
|
|
||||||
menu_title_txt = Label(font, text=config["menu_title"])
|
menu_title_txt = Label(font, text="Fruit Jam OS")
|
||||||
menu_title_txt.anchor_point = (0.5, 0.5)
|
menu_title_txt.anchor_point = (0.5, 0.5)
|
||||||
menu_title_txt.anchored_position = (display.width // (2 * scale), 2)
|
menu_title_txt.anchored_position = (display.width // (2 * scale), 2)
|
||||||
main_group.append(menu_title_txt)
|
main_group.append(menu_title_txt)
|
||||||
|
|
@ -181,10 +224,9 @@ i = 0
|
||||||
pages = [{}]
|
pages = [{}]
|
||||||
|
|
||||||
cur_file_index = 0
|
cur_file_index = 0
|
||||||
cur_page = 0
|
|
||||||
for path in app_path.iterdir():
|
for path in app_path.iterdir():
|
||||||
print(path)
|
print(path)
|
||||||
cell_group = AnchoredGroup()
|
|
||||||
|
|
||||||
code_file = path / "code.py"
|
code_file = path / "code.py"
|
||||||
if not code_file.exists():
|
if not code_file.exists():
|
||||||
|
|
@ -216,23 +258,97 @@ for path in app_path.iterdir():
|
||||||
"icon": str(icon_file.absolute()) if icon_file is not None else None,
|
"icon": str(icon_file.absolute()) if icon_file is not None else None,
|
||||||
"file": str(code_file.absolute())
|
"file": str(code_file.absolute())
|
||||||
})
|
})
|
||||||
if apps[-1]["icon"] is None:
|
i += 1
|
||||||
|
def reuse_cell(grid_coords):
|
||||||
|
try:
|
||||||
|
cell_group = menu_grid.get_content(grid_coords)
|
||||||
|
return cell_group
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _create_cell_group(app):
|
||||||
|
cell_group = AnchoredGroup()
|
||||||
|
|
||||||
|
if app["icon"] is None:
|
||||||
icon_tg = displayio.TileGrid(bitmap=default_icon_bmp, pixel_shader=default_icon_palette)
|
icon_tg = displayio.TileGrid(bitmap=default_icon_bmp, pixel_shader=default_icon_palette)
|
||||||
cell_group.append(icon_tg)
|
cell_group.append(icon_tg)
|
||||||
else:
|
else:
|
||||||
icon_bmp, icon_palette = adafruit_imageload.load(apps[-1]["icon"])
|
icon_bmp, icon_palette = adafruit_imageload.load(app["icon"])
|
||||||
icon_tg = displayio.TileGrid(bitmap=icon_bmp, pixel_shader=icon_palette)
|
icon_tg = displayio.TileGrid(bitmap=icon_bmp, pixel_shader=icon_palette)
|
||||||
cell_group.append(icon_tg)
|
cell_group.append(icon_tg)
|
||||||
|
|
||||||
icon_tg.x = cell_width // 2 - icon_tg.tile_width // 2
|
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,
|
title_txt = TextBox(font, text=app["title"], width=WIDTH // config["width"], height=18,
|
||||||
align=TextBox.ALIGN_CENTER)
|
align=TextBox.ALIGN_CENTER)
|
||||||
cell_group.append(title_txt)
|
cell_group.append(title_txt)
|
||||||
title_txt.anchor_point = (0, 0)
|
title_txt.anchor_point = (0, 0)
|
||||||
title_txt.anchored_position = (0, icon_tg.y + icon_tg.tile_height)
|
title_txt.anchored_position = (0, icon_tg.y + icon_tg.tile_height)
|
||||||
app_titles.append(title_txt)
|
return cell_group
|
||||||
menu_grid.add_content(cell_group, grid_position=(i % config["width"], i // config["width"]), cell_size=(1, 1))
|
|
||||||
i += 1
|
|
||||||
|
def _reuse_cell_group(app, cell_group):
|
||||||
|
_unhide_cell_group(cell_group)
|
||||||
|
if app["icon"] is None:
|
||||||
|
icon_tg = cell_group[0]
|
||||||
|
icon_tg.bitmap = default_icon_bmp
|
||||||
|
icon_tg.pixel_shader = default_icon_palette
|
||||||
|
else:
|
||||||
|
icon_bmp, icon_palette = adafruit_imageload.load(app["icon"])
|
||||||
|
icon_tg = cell_group[0]
|
||||||
|
icon_tg.bitmap = icon_bmp
|
||||||
|
icon_tg.pixel_shader = icon_palette
|
||||||
|
|
||||||
|
icon_tg.x = cell_width // 2 - icon_tg.tile_width // 2
|
||||||
|
# title_txt = TextBox(font, text=app["title"], width=WIDTH // config["width"], height=18,
|
||||||
|
# align=TextBox.ALIGN_CENTER)
|
||||||
|
# cell_group.append(title_txt)
|
||||||
|
title_txt = cell_group[1]
|
||||||
|
title_txt.text = app["title"]
|
||||||
|
# title_txt.anchor_point = (0, 0)
|
||||||
|
# title_txt.anchored_position = (0, icon_tg.y + icon_tg.tile_height)
|
||||||
|
|
||||||
|
|
||||||
|
def _hide_cell_group(cell_group):
|
||||||
|
# hide the tilegrid
|
||||||
|
cell_group[0].hidden = True
|
||||||
|
# set the title to blank space
|
||||||
|
cell_group[1].text = " "
|
||||||
|
|
||||||
|
|
||||||
|
def _unhide_cell_group(cell_group):
|
||||||
|
# show tilegrid
|
||||||
|
cell_group[0].hidden = False
|
||||||
|
|
||||||
|
|
||||||
|
def display_page(page_index):
|
||||||
|
for grid_index in range(6):
|
||||||
|
grid_pos = (grid_index % config["width"], grid_index // config["width"])
|
||||||
|
try:
|
||||||
|
cur_app = apps[grid_index + (page_index * 6)]
|
||||||
|
except IndexError:
|
||||||
|
try:
|
||||||
|
cell_group = menu_grid.get_content(grid_pos)
|
||||||
|
_hide_cell_group(cell_group)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# skip to the next for loop iteration
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
cell_group = menu_grid.get_content(grid_pos)
|
||||||
|
_reuse_cell_group(cur_app, cell_group)
|
||||||
|
except KeyError:
|
||||||
|
cell_group = _create_cell_group(cur_app)
|
||||||
|
menu_grid.add_content(cell_group, grid_position=grid_pos, cell_size=(1, 1))
|
||||||
|
|
||||||
|
# app_titles.append(title_txt)
|
||||||
|
print(f"{grid_index} | {grid_index % config["width"], grid_index // config["width"]}")
|
||||||
|
|
||||||
|
|
||||||
|
cur_page = 0
|
||||||
|
display_page(cur_page)
|
||||||
|
|
||||||
left_bmp, left_palette = adafruit_imageload.load("launcher_assets/arrow_left.bmp")
|
left_bmp, left_palette = adafruit_imageload.load("launcher_assets/arrow_left.bmp")
|
||||||
left_palette.make_transparent(0)
|
left_palette.make_transparent(0)
|
||||||
|
|
@ -246,15 +362,18 @@ left_tg.anchored_position = (4, (display.height // 2 // scale) - 2)
|
||||||
right_tg = AnchoredTileGrid(bitmap=right_bmp, pixel_shader=right_palette)
|
right_tg = AnchoredTileGrid(bitmap=right_bmp, pixel_shader=right_palette)
|
||||||
right_tg.anchor_point = (1.0, 0.5)
|
right_tg.anchor_point = (1.0, 0.5)
|
||||||
right_tg.anchored_position = ((display.width // scale) - 4, (display.height // 2 // scale) - 2)
|
right_tg.anchored_position = ((display.width // scale) - 4, (display.height // 2 // scale) - 2)
|
||||||
|
original_arrow_btn_color = left_palette[2]
|
||||||
|
|
||||||
main_group.append(left_tg)
|
main_group.append(left_tg)
|
||||||
main_group.append(right_tg)
|
main_group.append(right_tg)
|
||||||
|
|
||||||
|
if len(apps) <= 6:
|
||||||
|
right_tg.hidden = True
|
||||||
|
left_tg.hidden = True
|
||||||
|
|
||||||
if mouse:
|
if mouse:
|
||||||
main_group.append(mouse_tg)
|
main_group.append(mouse_tg)
|
||||||
|
|
||||||
selected = 0
|
|
||||||
|
|
||||||
|
|
||||||
def atexit_callback():
|
def atexit_callback():
|
||||||
"""
|
"""
|
||||||
|
|
@ -265,27 +384,127 @@ def atexit_callback():
|
||||||
if mouse_was_attached and not mouse.is_kernel_driver_active(0):
|
if mouse_was_attached and not mouse.is_kernel_driver_active(0):
|
||||||
mouse.attach_kernel_driver(0)
|
mouse.attach_kernel_driver(0)
|
||||||
|
|
||||||
|
|
||||||
atexit.register(atexit_callback)
|
atexit.register(atexit_callback)
|
||||||
|
|
||||||
# print(f"apps: {apps}")
|
selected = None
|
||||||
|
|
||||||
|
|
||||||
|
def change_selected(new_selected):
|
||||||
|
global selected
|
||||||
|
# tuple means an item in the grid is selected
|
||||||
|
if isinstance(selected, tuple):
|
||||||
|
menu_grid.get_content(selected)[1].background_color = None
|
||||||
|
|
||||||
|
# TileGrid means arrow is selected
|
||||||
|
elif isinstance(selected, AnchoredTileGrid):
|
||||||
|
selected.pixel_shader[2] = original_arrow_btn_color
|
||||||
|
|
||||||
|
# tuple means an item in the grid is selected
|
||||||
|
if isinstance(new_selected, tuple):
|
||||||
|
menu_grid.get_content(new_selected)[1].background_color = 0x008800
|
||||||
|
# TileGrid means arrow is selected
|
||||||
|
elif isinstance(new_selected, AnchoredTileGrid):
|
||||||
|
new_selected.pixel_shader[2] = 0x008800
|
||||||
|
selected = new_selected
|
||||||
|
|
||||||
|
|
||||||
|
change_selected((0, 0))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_key_press(key):
|
||||||
|
global index, editor_index, cur_page
|
||||||
|
# print(key)
|
||||||
|
# up key
|
||||||
|
if key == "\x1b[A":
|
||||||
|
if isinstance(selected, tuple):
|
||||||
|
change_selected((selected[0], (selected[1] - 1) % 2))
|
||||||
|
elif selected is left_tg:
|
||||||
|
change_selected((0, 0))
|
||||||
|
elif selected is right_tg:
|
||||||
|
change_selected((2, 0))
|
||||||
|
|
||||||
|
|
||||||
|
# down key
|
||||||
|
elif key == "\x1b[B":
|
||||||
|
if isinstance(selected, tuple):
|
||||||
|
change_selected((selected[0], (selected[1] + 1) % 2))
|
||||||
|
elif selected is left_tg:
|
||||||
|
change_selected((0, 1))
|
||||||
|
elif selected is right_tg:
|
||||||
|
change_selected((2, 1))
|
||||||
|
# selected = min(len(config["apps"]) - 1, selected + 1)
|
||||||
|
|
||||||
|
# left key
|
||||||
|
elif key == "\x1b[D":
|
||||||
|
if isinstance(selected, tuple):
|
||||||
|
if selected[0] >= 1:
|
||||||
|
change_selected((selected[0] - 1, selected[1]))
|
||||||
|
elif not left_tg.hidden:
|
||||||
|
change_selected(left_tg)
|
||||||
|
else:
|
||||||
|
change_selected(((selected[0] - 1) % 3, selected[1]))
|
||||||
|
elif selected is left_tg:
|
||||||
|
change_selected(right_tg)
|
||||||
|
elif selected is right_tg:
|
||||||
|
change_selected((2, 0))
|
||||||
|
|
||||||
|
# right key
|
||||||
|
elif key == "\x1b[C":
|
||||||
|
if isinstance(selected, tuple):
|
||||||
|
if selected[0] <= 1:
|
||||||
|
change_selected((selected[0] + 1, selected[1]))
|
||||||
|
elif not right_tg.hidden:
|
||||||
|
change_selected(right_tg)
|
||||||
|
else:
|
||||||
|
change_selected(((selected[0] + 1) % 3, selected[1]))
|
||||||
|
elif selected is left_tg:
|
||||||
|
change_selected((0, 0))
|
||||||
|
elif selected is right_tg:
|
||||||
|
change_selected(left_tg)
|
||||||
|
|
||||||
|
elif key == "\n":
|
||||||
|
if isinstance(selected, tuple):
|
||||||
|
index = (selected[1] * 3 + selected[0]) + (cur_page * 6)
|
||||||
|
if index >= len(apps):
|
||||||
|
index = None
|
||||||
|
print("go!")
|
||||||
|
elif selected is left_tg:
|
||||||
|
if cur_page > 0:
|
||||||
|
cur_page -= 1
|
||||||
|
display_page(cur_page)
|
||||||
|
|
||||||
|
elif selected is right_tg:
|
||||||
|
if cur_page < math.ceil(len(apps) / 6) - 1:
|
||||||
|
cur_page += 1
|
||||||
|
display_page(cur_page)
|
||||||
|
|
||||||
|
elif key == "e":
|
||||||
|
if isinstance(selected, tuple):
|
||||||
|
editor_index = (selected[1] * 3 + selected[0]) + (cur_page * 6)
|
||||||
|
if editor_index >= len(apps):
|
||||||
|
editor_index = None
|
||||||
|
|
||||||
|
print("go!")
|
||||||
|
else:
|
||||||
|
print(f"unhandled key: {repr(key)}")
|
||||||
|
|
||||||
|
|
||||||
|
print(f"apps: {apps}")
|
||||||
|
print(mouse_interface_index, mouse_endpoint_address)
|
||||||
while True:
|
while True:
|
||||||
index = None
|
index = None
|
||||||
|
editor_index = None
|
||||||
|
|
||||||
available = supervisor.runtime.serial_bytes_available
|
available = supervisor.runtime.serial_bytes_available
|
||||||
if available:
|
if available:
|
||||||
c = sys.stdin.read(available)
|
c = sys.stdin.read(available)
|
||||||
print(repr(c))
|
print(repr(c))
|
||||||
app_titles[selected].background_color = None
|
# app_titles[selected].background_color = None
|
||||||
|
|
||||||
if c == "\x1b[A":
|
handle_key_press(c)
|
||||||
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)
|
print("selected", selected)
|
||||||
app_titles[selected].background_color = 0x008800
|
# app_titles[selected].background_color = 0x008800
|
||||||
|
|
||||||
if mouse:
|
if mouse:
|
||||||
try:
|
try:
|
||||||
|
|
@ -304,17 +523,27 @@ while True:
|
||||||
mouse_tg.y = max(0, min((display.height // scale) - 1, mouse_tg.y + mouse_buf[2]))
|
mouse_tg.y = max(0, min((display.height // scale) - 1, mouse_tg.y + mouse_buf[2]))
|
||||||
|
|
||||||
if mouse_buf[0] & (1 << 0) != 0:
|
if mouse_buf[0] & (1 << 0) != 0:
|
||||||
|
print("left click")
|
||||||
clicked_cell = menu_grid.which_cell_contains((mouse_tg.x, mouse_tg.y))
|
clicked_cell = menu_grid.which_cell_contains((mouse_tg.x, mouse_tg.y))
|
||||||
if clicked_cell is not None:
|
if clicked_cell is not None:
|
||||||
index = clicked_cell[1] * config["width"] + clicked_cell[0]
|
index = clicked_cell[1] * config["width"] + clicked_cell[0]
|
||||||
|
|
||||||
if index is not None:
|
if index is not None:
|
||||||
# print("index", index)
|
print("index", index)
|
||||||
# print(f"selected: {apps[index]}")
|
print(f"selected: {apps[index]}")
|
||||||
launch_file = apps[index]["file"]
|
launch_file = apps[index]["file"]
|
||||||
supervisor.set_next_code_file(launch_file, sticky_on_reload=True, reload_on_error=True,
|
supervisor.set_next_code_file(launch_file, sticky_on_reload=False, reload_on_error=True,
|
||||||
|
working_directory="/".join(launch_file.split("/")[:-1]))
|
||||||
|
supervisor.reload()
|
||||||
|
if editor_index is not None:
|
||||||
|
print("editor_index", editor_index)
|
||||||
|
print(f"editor selected: {apps[editor_index]}")
|
||||||
|
edit_file = apps[editor_index]["file"]
|
||||||
|
|
||||||
|
launch_file = "/code_editor.py"
|
||||||
|
with open(argv_filename(launch_file), "w") as f:
|
||||||
|
f.write(json.dumps([apps[editor_index]["file"]]))
|
||||||
|
|
||||||
|
supervisor.set_next_code_file(launch_file, sticky_on_reload=False, reload_on_error=True,
|
||||||
working_directory="/".join(launch_file.split("/")[:-1]))
|
working_directory="/".join(launch_file.split("/")[:-1]))
|
||||||
|
|
||||||
if mouse and not mouse.is_kernel_driver_active(0):
|
|
||||||
mouse.attach_kernel_driver(0)
|
|
||||||
supervisor.reload()
|
supervisor.reload()
|
||||||
|
|
|
||||||
62
src/code_editor.py
Normal file
62
src/code_editor.py
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
import supervisor
|
||||||
|
from displayio import Group, Palette, TileGrid
|
||||||
|
import terminalio
|
||||||
|
from lvfontio import OnDiskFont
|
||||||
|
from adafruit_display_text.bitmap_label import Label
|
||||||
|
from adafruit_bitmap_font import bitmap_font
|
||||||
|
from adafruit_editor import editor, picker
|
||||||
|
from tilepalettemapper import TilePaletteMapper
|
||||||
|
import json
|
||||||
|
from argv_file_helper import argv_filename
|
||||||
|
from adafruit_fruitjam.peripherals import request_display_config
|
||||||
|
|
||||||
|
request_display_config(720, 400)
|
||||||
|
display = supervisor.runtime.display
|
||||||
|
display.auto_refresh = True
|
||||||
|
|
||||||
|
main_group = Group()
|
||||||
|
|
||||||
|
display.root_group = main_group
|
||||||
|
|
||||||
|
font_palette = Palette(2)
|
||||||
|
font_palette[0] = 0x000000
|
||||||
|
font_palette[1] = 0xFFFFFF
|
||||||
|
|
||||||
|
|
||||||
|
font = terminalio.FONT
|
||||||
|
char_size = font.get_bounding_box()
|
||||||
|
screen_size = (display.width // char_size[0], display.height // char_size[1])
|
||||||
|
print(screen_size)
|
||||||
|
|
||||||
|
terminal_area = TileGrid(bitmap=font.bitmap, pixel_shader=font_palette, width=screen_size[0], height=screen_size[1],
|
||||||
|
tile_width=char_size[0], tile_height=char_size[1])
|
||||||
|
|
||||||
|
main_group.append(terminal_area)
|
||||||
|
|
||||||
|
terminal = terminalio.Terminal(terminal_area, font)
|
||||||
|
|
||||||
|
visible_cursor = Label(terminalio.FONT, text="",
|
||||||
|
color=0x000000, background_color=0xeeeeee, padding_left=1)
|
||||||
|
visible_cursor.hidden = False
|
||||||
|
visible_cursor.anchor_point = (0, 0)
|
||||||
|
visible_cursor.anchored_position = (0, 0)
|
||||||
|
main_group.append(visible_cursor)
|
||||||
|
|
||||||
|
|
||||||
|
file = None
|
||||||
|
try:
|
||||||
|
editor_argv_file = argv_filename(__file__)
|
||||||
|
with open(editor_argv_file, "r") as f:
|
||||||
|
argv_data = json.load(f)
|
||||||
|
file = argv_data[0]
|
||||||
|
os.remove(editor_argv_file)
|
||||||
|
except OSError:
|
||||||
|
file = "boot_out.txt"
|
||||||
|
|
||||||
|
print(f"opening {file}")
|
||||||
|
editor.edit(file, terminal, visible_cursor)
|
||||||
|
print("after edit")
|
||||||
|
# while True:
|
||||||
|
# pass
|
||||||
Loading…
Reference in a new issue