119 lines
4.4 KiB
Python

import json
import pygame
from pygame import transform, gfxdraw, Vector2, Surface
from math import sin, cos, pi
from time import time, sleep
from random import random
INTERPOLATION = {
'sin2': lambda x: sin(x) ** 2,
'sin': lambda x: sin(x),
'cos': lambda x: 1 - cos(x),
'lin': lambda x: x
}
FADE = {
(True, True): lambda x: sin(x) ** 2, # fade both
(True, False): lambda x: 1 - cos(x), # fade in
(False, True): lambda x: sin(x), # fade out
(False, False): lambda x: x / (pi/2) # linear
}
class Star:
def __init__(self, sid: str, x: int, y: int, alpha: float, rot: float, scale: float, offset: float, image: pygame.Surface):
self.sid = sid
self.x = x
self.y = y
self.rot = rot
self.alpha = alpha
self.scale = scale
self.offset = offset
self.image = image
class Screen:
def __init__(self, stars: list[Star], background: Surface | None, framerate: float = 30) -> None:
if not pygame.get_init():
pygame.init()
# detect the number and sizes of screens
screen_sizes = pygame.display.get_desktop_sizes()
flags = pygame.DOUBLEBUF | pygame.WINDOWTAKEFOCUS | pygame.FULLSCREEN if len(screen_sizes) > 1 else 0
screen_size = screen_sizes[1] if len(screen_sizes) > 1 else screen_sizes[0]
display = 1 if len(screen_sizes) > 1 else 0
self.screen = pygame.display.set_mode(screen_size, display=display, flags=flags)
pygame.event.set_allowed([])
self.clock = pygame.time.Clock()
self.stars = stars
self.last = time()
self.brightness = 1.0
self.active_scene = 0
self.last_scene = 0
self.scene_time: float = 0
self.framerate: float = framerate
self.overlay = pygame.surface.Surface(self.screen.get_size(), depth=32)
if background:
ratio_x = self.screen.get_width() / background.get_width()
ratio_y = self.screen.get_height() / background.get_height()
scaled_background = transform.rotozoom(background, 0, max(ratio_x, ratio_y))
self.bg_pos = (
(self.screen.get_width() - scaled_background.get_width()) / 2,
(self.screen.get_height() - scaled_background.get_height()) / 2
)
self.background = scaled_background
else:
self.background = None
self.bg_pos = None
def update(self, scene_data):
if not self.background:
self.screen.fill((0, 0, 0))
now = time()
delta = now - self.last
self.last = now
self.scene_time += delta
fade_in = scene_data[self.active_scene]['transition']['fade_in']
fade_out = scene_data[self.active_scene]['transition']['fade_out']
t = FADE[(fade_in, fade_out)](min(self.scene_time / scene_data[self.active_scene]['transition']['t'], 1) * pi/2) * pi/2
weight_x = INTERPOLATION[scene_data[self.active_scene]['transition']['x']](t)
weight_y = INTERPOLATION[scene_data[self.active_scene]['transition']['y']](t)
weight = INTERPOLATION['sin2'](t)
def update_star_scene(star, old, new):
star.x = weight_x*new[star.sid]['x'] + (1-weight_x)*old[star.sid]['x']
star.y = weight_y*new[star.sid]['y'] + (1-weight_y)*old[star.sid]['y']
star.rot = weight*new[star.sid]['rot'] + (1-weight)*old[star.sid]['rot']
star.scale = weight*new[star.sid]['scale'] + (1-weight)*old[star.sid]['scale']
# first draw the background, if any
if self.background:
self.screen.blit(self.background, self.bg_pos)
# draw all the star objects
for star in self.stars:
update_star_scene(star, scene_data[self.last_scene], scene_data[self.active_scene])
star.alpha += star.rot * delta
computed_scale = star.scale * (self.screen.get_width() / star.image.get_width() / 100)
scaled = transform.rotozoom(star.image, star.alpha, computed_scale)
(w, h) = scaled.get_size()
pos = (star.x*self.screen.get_width() - w/2, star.y*self.screen.get_height() - h/2)
self.screen.blit(scaled, pos)
if self.brightness < 0.99:
self.overlay.set_alpha(int(255 * (1 - self.brightness)))
self.screen.blit(self.overlay, (0, 0))
pygame.display.flip()
self.clock.tick(self.framerate)
def close(self):
pygame.quit()