Files
Block-Party-Firmware/tools/animation-tools/UI.py
2024-08-28 22:55:52 -04:00

210 lines
9.1 KiB
Python

from itertools import product
from pygame_widgets.button import Button
from pygame_widgets.slider import Slider
from pygame_widgets.dropdown import Dropdown
from pygame_widgets.textbox import TextBox
from Scene import Scene
from Shapes import *
import pygame
from AnimationExporter import *
class ColorPicker:
def __init__(self, screen, x_pos: int, y_pos: int, width: int, height: int):
width = max(50, width)
slider_colors = ((255,0,0),(0,255,0),(0,0,255))
self.sliders: list[Slider] = [
Slider(screen, int(x_pos + i*(width/3)), y_pos, int((width-50)/3), height, min=0, max=255, step=1, vertical=True, handleColour=slider_colors[i]) for i in range(3)
]
for slider in self.sliders:
slider.enable()
def get_color(self) -> tuple[int]:
return tuple([slider.getValue() for slider in self.sliders])
def set_color(self, color: tuple[int]):
for i, slider in enumerate(self.sliders):
slider.setValue(color[i])
class SceneStore:
def __init__(self, scene: Scene, fill: FillInterpolation, fade: FrameInterpolation, delay: int):
self.scene: Scene = scene
self.fill: FillInterpolation = fill
self.fade: FrameInterpolation = fade
self.delay: int = delay
class SceneManager:
def __init__(self, window, color_picker: ColorPicker):
self.file_data: str = ""
self._scenes: list[SceneStore] = [SceneStore(self.new_scene(), FillInterpolation.NO_FILL, FrameInterpolation.FADE, 1000)]
self._current_scene_idx: int = 0
self.window = window
self.color_picker = color_picker
self._selected_meshes: list[Mesh] = []
def save_frame_to_file(self):
with open("tools/animation-tools/output.txt", 'w') as file:
for i, scene in enumerate(self._scenes):
file.write(scene_to_frame(scene.scene, scene.fill, scene.fade, scene.delay, i))
def generate_meshes(self) -> list[Mesh]:
# generate a list of cubes
meshes: list[Mesh] = []
for origin in product([-1, 0, 1],[-1, 0, 1],[-1, 0, 1]):
cube = Cube()
cube.scale((0.35, 0.35, 0.35))
cube.set_position(origin)
cube.set_face_color((0, 0, 0))
meshes.append(cube)
return meshes
def new_scene(self) -> Scene:
return Scene(self.generate_meshes(), 90, 5)
def draw(self):
for mesh in self._selected_meshes:
mesh.set_face_color(self.color_picker.get_color())
self.get_current_scene().scene.draw(self.window)
def get_current_scene(self) -> SceneStore:
return self._scenes[self._current_scene_idx]
def click_mesh(self, coordinates: tuple[int, int]):
mesh = self.get_current_scene().scene.get_mesh_from_xy(coordinates)
if mesh == None:
return
if mesh in self._selected_meshes:
mesh.set_edge_color((0,0,0))
self._selected_meshes.remove(mesh)
else:
mesh.set_edge_color((255,0,0))
self._selected_meshes.append(mesh)
def deselect_all_mesh(self):
for mesh in self._selected_meshes:
mesh.set_edge_color((0,0,0))
self._selected_meshes = []
def next_scene(self):
self.deselect_all_mesh()
current_angles = self.get_current_scene().scene.euler_angles
if len(self._scenes)-1 == self._current_scene_idx:
self._scenes.append(SceneStore(self.new_scene(), FillInterpolation.NO_FILL, FrameInterpolation.FADE, 1000))
self._current_scene_idx += 1
self.get_current_scene().scene.euler_angles = [angle for angle in current_angles]
def last_scene(self):
if self._current_scene_idx > 0:
current_angles = self.get_current_scene().scene.euler_angles
self._current_scene_idx -= 1
self.get_current_scene().scene.euler_angles = [angle for angle in current_angles]
self.deselect_all_mesh()
def update_scene_options(self, fill: FillInterpolation, fade: FrameInterpolation, delay: int):
cur_scene: SceneStore = self.get_current_scene()
cur_scene.fill = fill
cur_scene.fade = fade
cur_scene.delay = delay
class AnimatorUI:
def __init__(self, window):
scr_wdt, scr_hgt = window.get_size()
self.window = window
self.colorPicker: ColorPicker = ColorPicker(self.window, *self.rel2abs(5, 5, 20, 60))
self.sceneManager: SceneManager = SceneManager(window, self.colorPicker)
self.save_button: Button = Button(self.window, *self.rel2abs(5, 90, 10, 10), text="Save",onClick=self.sceneManager.save_frame_to_file)
self.next_frame_button: Button = Button(self.window, *self.rel2abs(75, 90, 25, 5), text="Next Frame",onClick=self._next_scene)
self.last_frame_button: Button = Button(self.window, *self.rel2abs(50, 90, 25, 5), text="last Frame",onClick=self._last_scene)
self.trackMouseMotion: bool = False
self.fill_dropdown: Dropdown = Dropdown(self.window, *self.rel2abs(30, 0, 40, 5), name="Fill Type",
choices=[option.value for option in FillInterpolation], onRelease=self.set_update_options_flag)
self.fade_dropdown: Dropdown = Dropdown(self.window, *self.rel2abs(70, 0, 20, 5), name="Fade Type",
choices=[option.value for option in FrameInterpolation], onRelease=self.set_update_options_flag)
self.updateOptions = False
self._set_dropdown_options()
self.delay_text_entry: TextBox = TextBox(self.window, *self.rel2abs(30, 5, 60, 7), placeholderText="Transition Time (ms)", onSubmit=self.set_update_options_flag)
# Make a frame counter as a button but make it not look like a button
default_color=(150,150,150)
self.frame_counter_text: Button = Button(self.window, *self.rel2abs(15, 90, 10, 10), text="0", inactiveColour=default_color, hoverColour=default_color, pressedColour=default_color)
def rel2abs(self, x: float, y: float, width: float, height: float) -> tuple[int,int,int,int]:
scr_wdt, scr_hgt = self.window.get_size()
x_abs = int(x*scr_wdt/100)
y_abs = int(y*scr_hgt/100)
w_abs = int(width*scr_wdt/100)
h_abs = int(height*scr_hgt/100)
return (x_abs, y_abs, w_abs, h_abs)
def update_interaction(self, game_event):
if not self.trackMouseMotion:
pygame.mouse.get_rel()
if game_event.type == pygame.MOUSEBUTTONDOWN:
if game_event.button == 2: # middle mouse click
self.trackMouseMotion = True
elif game_event.button == 1: # left click
self.sceneManager.click_mesh(pygame.mouse.get_pos())
elif game_event.type == pygame.MOUSEBUTTONUP:
if game_event.button == 2: # middle mouse release
self.trackMouseMotion = False
elif self.trackMouseMotion and game_event.type == pygame.MOUSEMOTION:
mouseMovement = pygame.mouse.get_rel()
current_scene = self.sceneManager.get_current_scene()
current_scene.euler_angles[0] -= mouseMovement[1]
current_scene.euler_angles[1] -= mouseMovement[0]
def draw(self):
if self.updateOptions:
self.set_scene_options()
# make the preview window for the color picker
pygame.draw.rect(self.window, self.colorPicker.get_color(), self.rel2abs(11, 70, 5, 5))
pygame.draw.rect(self.window, (0,0,0), self.rel2abs(11, 70, 5, 5), 2)
self.sceneManager.draw()
def _next_scene(self):
self.sceneManager.next_scene()
self._set_dropdown_options()
self.frame_counter_text.setText(str(self.sceneManager._current_scene_idx))
def _last_scene(self):
self.sceneManager.last_scene()
self._set_dropdown_options()
self.frame_counter_text.setText(str(self.sceneManager._current_scene_idx))
def _set_dropdown_options(self):
# cursed method to set dropdown options because for some reason pygame_widgets dropdown doesn't allow you to manually set the dropdown option
scene = self.sceneManager.get_current_scene()
for choice in self.fill_dropdown._Dropdown__choices:
if choice.text.find(scene.fill) != -1:
self.fill_dropdown.chosen = choice
break
for choice in self.fade_dropdown._Dropdown__choices:
if choice.text.find(scene.fade) != -1:
self.fade_dropdown.chosen = choice
break
def set_update_options_flag(self):
self.updateOptions = True
def set_scene_options(self):
self.updateOptions = False
delay_time = 1000
try:
delay_time = int(self.delay_text_entry.getText())
except ValueError:
self.delay_text_entry.setText(str(delay_time))
self.sceneManager.update_scene_options(self.fill_dropdown.getSelected(), self.fade_dropdown.getSelected(), delay_time)