Compare commits

...

3 Commits

Author SHA1 Message Date
974ed1de0b release candidate 1 2025-12-06 17:37:11 +01:00
8025514ab3 adjusted objects and scene 2025-12-06 17:03:58 +01:00
095a18b0ea mostly done with rendering 2025-12-06 17:02:03 +01:00
8 changed files with 125 additions and 51 deletions

3
.idea/workspace.xml generated
View File

@ -6,9 +6,12 @@
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="ae2847d5-ce86-4d84-8e66-f1369f1438e2" name="Changes" comment=""> <list default="true" id="ae2847d5-ce86-4d84-8e66-f1369f1438e2" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/starsky_presenter/controller/controller.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/starsky_presenter/controller/controller.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/starsky_presenter/controller/window.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/starsky_presenter/controller/window.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/starsky_presenter/entry.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/starsky_presenter/entry.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/starsky_presenter/entry.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/starsky_presenter/entry.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/starsky_presenter/projection/screen.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/starsky_presenter/projection/screen.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/starsky_presenter/projection/screen.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/starsky_presenter/projection/screen.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/starsky_presenter/resources/objects.yml" beforeDir="false" afterPath="$PROJECT_DIR$/src/starsky_presenter/resources/objects.yml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/starsky_presenter/resources/objects.yml" beforeDir="false" afterPath="$PROJECT_DIR$/src/starsky_presenter/resources/objects.yml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/starsky_presenter/resources/scenes.yml" beforeDir="false" afterPath="$PROJECT_DIR$/src/starsky_presenter/resources/scenes.yml" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />

View File

@ -4,12 +4,13 @@ from time import time
class Controller(QObject): class Controller(QObject):
def __init__(self, window, screen, parent=None): def __init__(self, window, screen, max_scene, parent=None):
super().__init__(parent) super().__init__(parent)
self.window = window self.window = window
self.last_close_press = 0 self.last_close_press = 0
self.successful_clicks = 0 self.successful_clicks = 0
self.screen = screen self.screen = screen
self.max_scene = max_scene
@pyqtSlot() @pyqtSlot()
def close_application(self): def close_application(self):
@ -26,6 +27,8 @@ class Controller(QObject):
@pyqtSlot(int) @pyqtSlot(int)
def select_scene(self, scene: int): def select_scene(self, scene: int):
if scene >= self.max_scene:
return
self.screen.last_scene = self.screen.active_scene self.screen.last_scene = self.screen.active_scene
self.screen.active_scene = scene self.screen.active_scene = scene
self.screen.scene_time = 0 self.screen.scene_time = 0

View File

@ -8,12 +8,12 @@ from starsky_presenter.projection.screen import Screen
class ControlWindow: class ControlWindow:
def __init__(self, screen): def __init__(self, screen, max_scene):
self.app = QGuiApplication([]) self.app = QGuiApplication([])
self.running = True self.running = True
self.engine = QQmlApplicationEngine(self.app) self.engine = QQmlApplicationEngine(self.app)
self.controller = Controller(self, screen) self.controller = Controller(self, screen, max_scene)
self.engine.quit.connect(self.app.quit) self.engine.quit.connect(self.app.quit)
self.engine.rootContext().setContextProperty("controller_backend", self.controller) self.engine.rootContext().setContextProperty("controller_backend", self.controller)
self.engine.load(f"{Path(__file__).parent.parent}/resources/main_window.qml") self.engine.load(f"{Path(__file__).parent.parent}/resources/main_window.qml")

View File

@ -6,6 +6,7 @@ from importlib.readers import FileReader
from importlib.machinery import SourcelessFileLoader from importlib.machinery import SourcelessFileLoader
from io import BufferedReader from io import BufferedReader
from pathlib import Path from pathlib import Path
from time import time
import yaml import yaml
from random import random, seed from random import random, seed
@ -29,22 +30,28 @@ def cli_main():
scenes = yaml.safe_load(reader.open_resource('resources/scenes.yml')) scenes = yaml.safe_load(reader.open_resource('resources/scenes.yml'))
objects = yaml.safe_load(reader.open_resource('resources/objects.yml')) objects = yaml.safe_load(reader.open_resource('resources/objects.yml'))
scene_data = [{ obj['sid']: deepcopy(obj) for obj in objects["stars"] }] + [ scene_data = [
{ obj['sid']: dict_update(obj, scenes[i][obj['sid']]) if scenes[i] and obj['sid'] in scenes[i] else deepcopy(obj) for obj in objects["stars"] } dict_update(
{ obj['sid']: dict_update(obj, scenes[i][obj['sid']]) if scenes[i] and obj['sid'] in scenes[i] else deepcopy(obj) for obj in objects["stars"] },
{
'transition': scenes[i]['transition'],
}
)
if i < len(scenes) else { obj['sid']: deepcopy(obj) for obj in objects["stars"] } if i < len(scenes) else { obj['sid']: deepcopy(obj) for obj in objects["stars"] }
for i in range(0,8) for i in range(0,9)
] ]
star_references = {} star_references = {}
print(json.dumps(scenes, indent=4)) #print(json.dumps(scenes, indent=4))
print(json.dumps(objects, indent=4)) #print(json.dumps(objects, indent=4))
print(json.dumps(scene_data, indent=4)) #print(json.dumps(scene_data, indent=4))
def setup_star_instance(data: dict) -> Star: def setup_star_instance(data: dict) -> Star:
image_path = f"resources/{data['image']}" image_path = f"resources/{data['image']}"
image = pygame.image.load(reader.open_resource(image_path)) image = pygame.image.load(reader.open_resource(image_path))
data['image'] = image data['image'] = image
star = Star(**data) star = Star(**data)
star_references[data['sid']] = star star_references[data['sid']] = star
return star return star
@ -55,16 +62,31 @@ def cli_main():
# load the background image # load the background image
background = pygame.image.load(reader.open_resource(f"resources/{objects['background']}")) background = pygame.image.load(reader.open_resource(f"resources/{objects['background']}"))
screen = Screen(stars, background) screen = Screen(stars, background, objects['framerate'])
control_window = ControlWindow(screen) control_window = ControlWindow(screen, len(scenes))
last_time = time()
frame_count = 0
fps_update = objects['framerate']
star_time = time()
while control_window.is_running(): while control_window.is_running():
control_window.process_events() control_window.process_events()
screen.update(scene_data) screen.update(scene_data)
if frame_count % fps_update == 0:
now = time()
print(f"{time() - star_time:.3f}s: {1/((now - last_time) / fps_update):.02f} fps")
last_time = now
frame_count = 0
frame_count += 1
screen.close() screen.close()
control_window.close() control_window.close()
pygame.quit()
exit(0)

View File

@ -7,6 +7,20 @@ from math import sin, cos, pi
from time import time, sleep from time import time, sleep
from random import random 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: class Star:
def __init__(self, sid: str, x: int, y: int, alpha: float, rot: float, scale: float, offset: float, image: pygame.Surface): def __init__(self, sid: str, x: int, y: int, alpha: float, rot: float, scale: float, offset: float, image: pygame.Surface):
self.sid = sid self.sid = sid
@ -18,11 +32,12 @@ class Star:
self.offset = offset self.offset = offset
self.image = image self.image = image
class Screen: class Screen:
def __init__(self, stars: list[Star], background: Surface | None) -> None: def __init__(self, stars: list[Star], background: Surface | None, framerate: float = 30) -> None:
if not pygame.get_init(): if not pygame.get_init():
pygame.init() pygame.init()
self.screen = pygame.display.set_mode((1920, 1080)) self.screen = pygame.display.set_mode((1920, 1080), display=1, flags=pygame.FULLSCREEN | pygame.DOUBLEBUF | pygame.WINDOWTAKEFOCUS)
self.clock = pygame.time.Clock() self.clock = pygame.time.Clock()
self.stars = stars self.stars = stars
self.last = time() self.last = time()
@ -30,52 +45,66 @@ class Screen:
self.active_scene = 0 self.active_scene = 0
self.last_scene = 0 self.last_scene = 0
self.scene_time: float = 0 self.scene_time: float = 0
self.background = background 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): def update(self, scene_data):
if not self.background:
self.screen.fill((0, 0, 0)) self.screen.fill((0, 0, 0))
now = time() now = time()
delta = now - self.last delta = now - self.last
self.last = now self.last = now
self.scene_time += delta self.scene_time += delta
weight = sin(min(self.scene_time, pi/2)) ** 2 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, weight): def update_star_scene(star, old, new):
star.x = weight*new[star.sid]['x'] + (1-weight)*old[star.sid]['x'] star.x = weight_x*new[star.sid]['x'] + (1-weight_x)*old[star.sid]['x']
star.y = weight*new[star.sid]['y'] + (1-weight)*old[star.sid]['y'] 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.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'] star.scale = weight*new[star.sid]['scale'] + (1-weight)*old[star.sid]['scale']
# first draw the background, if any # first draw the background, if any
if self.background: if self.background:
ratio_x = self.screen.get_width() / self.background.get_width() self.screen.blit(self.background, self.bg_pos)
ratio_y = self.screen.get_height() / self.background.get_height()
scaled_background = transform.rotozoom(self.background, 0, max(ratio_x, ratio_y))
bg_pos = (
(self.screen.get_width() - scaled_background.get_width()) / 2,
(self.screen.get_height() - scaled_background.get_height()) / 2
)
self.screen.blit(scaled_background, bg_pos)
# draw all the star objects # draw all the star objects
for star in self.stars: for star in self.stars:
update_star_scene(star, scene_data[self.last_scene], scene_data[self.active_scene], weight) update_star_scene(star, scene_data[self.last_scene], scene_data[self.active_scene])
star.alpha += star.rot * delta star.alpha += star.rot * delta
scaled = transform.rotozoom(star.image, star.alpha, star.scale) 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() (w, h) = scaled.get_size()
pos = (star.x*self.screen.get_width() - w/2, star.y*self.screen.get_height() - h/2) pos = (star.x*self.screen.get_width() - w/2, star.y*self.screen.get_height() - h/2)
self.screen.blit(scaled, pos) self.screen.blit(scaled, pos)
overlay = pygame.surface.Surface((1920, 1080), depth=32)
overlay.set_alpha(int(255 * (1 - self.brightness))) self.overlay.set_alpha(int(255 * (1 - self.brightness)))
self.screen.blit(overlay, (0, 0)) self.screen.blit(self.overlay, (0, 0))
pygame.display.flip() pygame.display.flip()
self.clock.tick(60) self.clock.tick(self.framerate)
def close(self): def close(self):
pygame.quit() pygame.quit()

View File

@ -8,7 +8,8 @@ ApplicationWindow {
width: 800 width: 800
height: 600 height: 600
title: "StarSky Presenter" title: "StarSky Presenter"
flags: Qt.FramelessWindowHint flags: Qt.WindowCloseButtonHint | Qt.WindowStaysOnBottomHint
modality: Qt.ApplicationModal
Button { Button {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter

View File

@ -1,18 +1,11 @@
stars: stars:
- sid: star-bg-01 - sid: star-1
x: 0.1 x: 0.1
y: 0.1 y: 0.1
scale: 0.6 scale: 5
alpha: 0 alpha: 0
rot: 30 rot: 30
offset: 0 offset: 0
image: BasicStar1.png image: nice-star-1.png
- sid: star-bg-02
x: 0.3
y: 0.4
scale: 0.8
alpha: 25
rot: 29
offset: 0
image: BasicStar1.png
background: background-01.jpg background: background-01.jpg
framerate: 30

View File

@ -1,4 +1,27 @@
- star-bg-01: - star-1:
x: 0.6 x: 1.1
star-bg-02: y: 1.1
y: 0.6 transition:
t: 2
fade_in: true
fade_out: false
x: sin
y: cos
- star-1:
x: 0.5
y: 0.5
transition:
t: 2
fade_in: false
fade_out: true
x: cos
y: sin
- star-1:
x: -0.1
y: 1.1
transition:
t: 2
fade_in: true
fade_out: false
x: sin
y: cos