Files
Warlock-Studio-Universal/splash_screen.py
T
Iván Eduardo Chavez Ayub 08a897e38f Update 5.1.1
2025-12-31 23:47:43 -06:00

235 lines
7.6 KiB
Python

import math
import os
import random
from typing import Callable, Optional
import customtkinter as ctk
from PIL import Image
class SplashScreen(ctk.CTkToplevel):
def __init__(
self,
root_window,
app_title: str,
version: str,
asset_loader: Callable[[str], str],
theme_colors: dict,
# CAMBIO 1: Duración ultrarrápida (1.2 segundos)
duration_ms: int = 1200,
# CAMBIO 2: Callback para ejecutar al finalizar
on_complete: Optional[Callable] = None
):
super().__init__(root_window)
# --- CONFIGURACIÓN DE DATOS ---
self.asset_loader = asset_loader
self.theme = theme_colors
self.duration_ms = duration_ms
self.on_complete = on_complete # Guardamos la función a ejecutar al final
# Reducimos los pasos para que coincidan con la duración corta
self.steps_total = 60
self.current_step = 0
self.animation_running = True
# --- CONFIGURACIÓN DE PARTÍCULAS ---
self.particles = []
self.num_particles = 35 # Ligero y rápido
# --- CONFIGURACIÓN DE VENTANA (AL FRENTE) ---
self.overrideredirect(True)
self.attributes('-topmost', True)
self.lift()
self.focus_force()
# --- GEOMETRÍA Y CENTRADO ---
screen_width = self.winfo_screenwidth()
screen_height = self.winfo_screenheight()
width = 500
height = 320
x = (screen_width - width) // 2
y = (screen_height - height) // 2
self.geometry(f"{width}x{height}+{x}+{y}")
# --- CONSTRUCCIÓN DE UI ---
self._setup_ui(app_title, version, width, height)
# --- INICIAR ANIMACIONES ---
self._init_particles(width, height)
self._animate_particles()
# Iniciar carga casi inmediatamente
self.after(20, self._animate_loading)
def _setup_ui(self, app_title, version, width, height):
"""Construye las capas de la interfaz."""
# --- 1. CAPA DE FONDO (Canvas) ---
self.canvas = ctk.CTkCanvas(
self,
width=width,
height=height,
bg=self.theme['bg'],
highlightthickness=0,
bd=0
)
self.canvas.pack(fill="both", expand=True)
# Borde decorativo
self.canvas.create_rectangle(
2, 2, width-2, height-2,
outline=self.theme['accent'],
width=2
)
# --- 2. CAPA DE CONTENIDO ---
# A. Carga del Banner
banner_path = self.asset_loader(f"Assets{os.sep}banner.png")
has_banner = False
if os.path.exists(banner_path):
try:
pil_img = Image.open(banner_path)
banner_ctk = ctk.CTkImage(
light_image=pil_img, dark_image=pil_img, size=(460, 180))
self.banner_label = ctk.CTkLabel(
self,
image=banner_ctk,
text=""
)
self.banner_label.place(relx=0.5, rely=0.1, anchor="n")
has_banner = True
except Exception as e:
print(f"[SPLASH] Error cargando banner: {e}")
# B. Título de respaldo
if not has_banner:
self.title_label = ctk.CTkLabel(
self,
text=app_title.upper(),
font=ctk.CTkFont(family="Segoe UI", size=32, weight="bold"),
text_color=self.theme['app_name']
)
self.title_label.place(relx=0.5, rely=0.15, anchor="n")
# C. Barra de Progreso
self.progress_bar = ctk.CTkProgressBar(
self,
width=300,
height=8,
corner_radius=4,
progress_color=self.theme['accent'],
fg_color=self.theme['widget_bg'],
border_width=0
)
self.progress_bar.set(0)
self.progress_bar.place(relx=0.5, rely=0.7, anchor="center")
# D. Texto de estado
self.status_label = ctk.CTkLabel(
self,
text="Starting...",
font=ctk.CTkFont(family="Consolas", size=11),
text_color=self.theme['text_sec']
)
self.status_label.place(relx=0.5, rely=0.8, anchor="center")
# E. Etiqueta de Versión
self.version_label = ctk.CTkLabel(
self,
text=f"v{version} | Developed by Ivan-Ayub97",
font=ctk.CTkFont(family="Segoe UI", size=10),
text_color=self.theme['accent']
)
self.version_label.place(relx=0.5, rely=0.93, anchor="center")
def _init_particles(self, width, height):
particle_color = self.theme.get('text_sec', '#888888')
for _ in range(self.num_particles):
size = random.randint(2, 4)
x = random.randint(20, width - 20)
y = random.randint(20, height - 20)
# Un poco más rápido para dar sensación de fluidez
dx = random.uniform(-1.0, 1.0)
dy = random.uniform(-1.0, 1.0)
particle = {
'id': self.canvas.create_oval(
x, y, x+size, y+size,
fill=particle_color,
outline=""
),
'x': x, 'y': y, 'dx': dx, 'dy': dy,
'width': width, 'height': height
}
self.particles.append(particle)
def _animate_particles(self):
if not self.winfo_exists() or not self.animation_running:
return
for p in self.particles:
p['x'] += p['dx']
p['y'] += p['dy']
if p['x'] <= 5 or p['x'] >= p['width'] - 5:
p['dx'] *= -1
if p['y'] <= 5 or p['y'] >= p['height'] - 5:
p['dy'] *= -1
self.canvas.coords(p['id'], p['x'], p['y'], p['x']+3, p['y']+3)
self.after(33, self._animate_particles)
def _animate_loading(self):
messages = ["Loading...", "Modules...", "UI...", "Starting..."]
if not self.winfo_exists():
return
self.lift()
self.attributes('-topmost', True)
# Cálculo seguro de tiempo
step_interval = max(10, self.duration_ms // self.steps_total)
if self.current_step <= self.steps_total:
progress = self.current_step / self.steps_total
self.progress_bar.set(progress)
if self.current_step % 10 == 0:
msg_index = int((progress * 100) / (100 / len(messages)))
if msg_index < len(messages):
self.status_label.configure(text=messages[msg_index])
self.current_step += 1
self.after(step_interval, self._animate_loading)
else:
self.start_fade_out()
def start_fade_out(self):
self.animation_running = False
self._fade_step = 1.0
self._fade_out()
def _fade_out(self):
if not self.winfo_exists():
return
if self._fade_step > 0:
# Desvanecimiento muy rápido
self.attributes('-alpha', self._fade_step)
self._fade_step -= 0.1 # Salta de 10% en 10% para velocidad
self.after(20, self._fade_out)
else:
# 1. Destruimos la splash
self.destroy()
# 2. CAMBIO CRÍTICO: Ejecutamos el callback para abrir el programa principal
if self.on_complete:
self.on_complete()