Files
dark_hal/splash_screen.py

493 lines
17 KiB
Python
Raw Normal View History

2026-03-13 12:56:43 -07:00
#!/usr/bin/env python3
"""
DarkHal 2.0 Splash Screen
A professional splash screen with logo, disclaimers, and loading animation.
"""
import tkinter as tk
from tkinter import ttk
import os
import sys
import time
import threading
from pathlib import Path
try:
from PIL import Image, ImageTk
PIL_AVAILABLE = True
except ImportError:
PIL_AVAILABLE = False
class SplashScreen:
"""Professional splash screen for DarkHal 2.0"""
def __init__(self, callback=None):
"""
Initialize splash screen.
Args:
callback (callable): Function to call when splash closes with selected hardware acceleration
"""
self.callback = callback
self.selected_acceleration = None
self.root = tk.Tk()
self.showing_logo = True
# References for dynamic logo sizing
self.logo_original = None # PIL.Image (original)
self.logo_photo = None # ImageTk.PhotoImage or tk.PhotoImage (current)
self.logo_label = None # tk.Label showing the logo
# References for dynamic logo sizing
self.logo_original = None # PIL.Image (original)
self.logo_photo = None # ImageTk.PhotoImage or tk.PhotoImage (current)
self.logo_label = None # tk.Label that displays the logo
# References for dynamic logo sizing
self.logo_original = None # PIL.Image (original)
self.logo_photo = None # ImageTk.PhotoImage or tk.PhotoImage (current)
self.logo_label = None # tk.Label showing the logo
self.setup_window()
self.create_logo_screen()
# Switch to main screen after 2.5 seconds
self.root.after(2500, self.switch_to_main_screen)
def setup_window(self):
"""Configure the splash window"""
# Remove window decorations
self.root.overrideredirect(True)
# Set window size - larger to accommodate all elements
width = 700
height = 600
# Center on screen
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
x = (screen_width - width) // 2
y = (screen_height - height) // 2
self.root.geometry(f"{width}x{height}+{x}+{y}")
self.root.configure(bg='#1a1a1a') # Dark background
# Make window topmost
self.root.attributes('-topmost', True)
# Set window icon if available
try:
icon_path = Path("assets/Halico.ico")
if icon_path.exists():
self.root.iconbitmap(str(icon_path))
except:
pass
def load_logo(self):
"""Load and prepare the logo image"""
logo_path = Path("assets/logo.png")
print(f"Looking for logo at: {logo_path.absolute()}")
print(f"Logo exists: {logo_path.exists()}")
print(f"PIL available: {PIL_AVAILABLE}")
if not logo_path.exists():
print("Logo file not found")
return None
try:
if PIL_AVAILABLE:
# Use Pillow for better image handling
image = Image.open(logo_path)
print(f"Original image size: {image.size}")
# Resize to fit in the fixed 75x75 box
image = image.resize((65, 65), Image.Resampling.LANCZOS)
print(f"Resized image to: {image.size}")
return ImageTk.PhotoImage(image)
else:
# Fallback to tkinter's basic image support
return tk.PhotoImage(file=str(logo_path))
except Exception as e:
print(f"Error loading logo: {e}")
return None
def create_logo_screen(self):
"""Create the initial logo-only screen"""
# Clear any existing widgets
for widget in self.root.winfo_children():
widget.destroy()
# Center container for logo
center_frame = tk.Frame(self.root, bg='#1a1a1a')
center_frame.pack(expand=True, fill=tk.BOTH)
logo_path = Path("assets/logo.png")
# Dynamic path if PIL available, else static fallback
if PIL_AVAILABLE:
self.logo_original = self._load_logo_original()
if self.logo_original is not None:
# Create label and center it; image set during update
self.logo_label = tk.Label(center_frame, bg='#1a1a1a', borderwidth=0, highlightthickness=0)
self.logo_label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
# Bind window resize to keep logo responsive
self.root.bind("<Configure>", self.update_logo_size)
# Initial sizing
self.update_logo_size()
return
elif logo_path.exists():
# PIL present but image failed -> fallback to static display using tk.PhotoImage
try:
self.logo_photo = tk.PhotoImage(file=str(logo_path))
self.logo_label = tk.Label(center_frame, image=self.logo_photo, bg='#1a1a1a')
self.logo_label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
return
except Exception:
pass
else:
# No PIL -> static display if possible
if logo_path.exists():
try:
self.logo_photo = tk.PhotoImage(file=str(logo_path))
self.logo_label = tk.Label(center_frame, image=self.logo_photo, bg='#1a1a1a')
self.logo_label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
return
except Exception:
pass
# Fallback - large app name
logo_text = tk.Label(center_frame, text="DarkHal 2.0", font=("Arial", 48, "bold"), fg='#00ff88', bg='#1a1a1a')
logo_text.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
def load_logo_large(self):
"""Load logo at original size for logo screen"""
logo_path = Path("assets/logo.png")
if not logo_path.exists():
return None
try:
if PIL_AVAILABLE:
image = Image.open(logo_path)
# Keep original size or resize to reasonable splash size
max_size = min(300, min(image.size))
image = image.resize((max_size, max_size), Image.Resampling.LANCZOS)
return ImageTk.PhotoImage(image)
else:
return tk.PhotoImage(file=str(logo_path))
except Exception as e:
print(f"Error loading large logo: {e}")
return None
def switch_to_main_screen(self):
"""Switch from logo screen to main text screen"""
self.showing_logo = False
# Stop handling resize events once the logo screen ends
try:
self.root.unbind("<Configure>")
except Exception:
pass
# Stop handling resize events once the logo screen ends
try:
self.root.unbind("<Configure>")
except Exception:
pass
try:
self.root.unbind("<Configure>")
except Exception:
pass
self.create_widgets()
def create_widgets(self):
"""Create the main text-only screen with buttons"""
# Clear any existing widgets
for widget in self.root.winfo_children():
widget.destroy()
# Main container
main_frame = tk.Frame(self.root, bg='#1a1a1a')
main_frame.pack(fill=tk.BOTH, expand=True, padx=30, pady=20)
# Application name
title_label = tk.Label(
main_frame,
text="DarkHal 2.0",
font=("Arial", 24, "bold"),
fg='#00ff88', # Green accent color
bg='#1a1a1a'
)
title_label.pack(pady=(0, 5))
# Subtitle
subtitle_label = tk.Label(
main_frame,
text="AI Model Management & Training Platform",
font=("Arial", 11),
fg='#cccccc',
bg='#1a1a1a'
)
subtitle_label.pack(pady=(0, 15))
# Warning/Disclaimer section
warning_frame = tk.Frame(main_frame, bg='#2a2a2a', relief=tk.RAISED, bd=1)
warning_frame.pack(fill=tk.X, pady=(0, 15))
warning_title = tk.Label(
warning_frame,
text="⚠️ IMPORTANT WARNING",
font=("Arial", 10, "bold"),
fg='#ff4444',
bg='#2a2a2a'
)
warning_title.pack(pady=(8, 5))
warning_text = """This software is provided "as is" without any warranties or guarantees.
The user assumes all responsibility for the use of this software and any
consequences that may arise from its use. The developers are not liable
for any damages, data loss, or other issues that may occur."""
warning_label = tk.Label(
warning_frame,
text=warning_text,
font=("Arial", 9),
fg='#cccccc',
bg='#2a2a2a',
wraplength=600,
justify=tk.CENTER
)
warning_label.pack(pady=(0, 8))
# Terms agreement section
terms_frame = tk.Frame(main_frame, bg='#1a1a1a')
terms_frame.pack(pady=(10, 15))
terms_label = tk.Label(
terms_frame,
text="By continuing you agree to our terms",
font=("Arial", 10),
fg='#ffaa00',
bg='#1a1a1a'
)
terms_label.pack()
# Agreement section
agreement_frame = tk.Frame(main_frame, bg='#1a1a1a')
agreement_frame.pack(pady=(10, 20))
agreement_label = tk.Label(
agreement_frame,
text="I agree",
font=("Arial", 12, "bold"),
fg='#00ff88',
bg='#1a1a1a'
)
agreement_label.pack(pady=(0, 15))
# Hardware acceleration buttons
buttons_frame = tk.Frame(agreement_frame, bg='#1a1a1a')
buttons_frame.pack()
# CUDA button
cuda_btn = tk.Button(
buttons_frame,
text="Start with CUDA",
font=("Arial", 11, "bold"),
bg='#00aa55',
fg='white',
activebackground='#00ff88',
activeforeground='white',
relief=tk.RAISED,
bd=2,
padx=20,
pady=8,
command=lambda: self.start_application('cuda')
)
cuda_btn.pack(side=tk.LEFT, padx=(0, 10))
# Intel button
intel_btn = tk.Button(
buttons_frame,
text="Start with Intel",
font=("Arial", 11, "bold"),
bg='#0078d4',
fg='white',
activebackground='#106ebe',
activeforeground='white',
relief=tk.RAISED,
bd=2,
padx=20,
pady=8,
command=lambda: self.start_application('intel')
)
intel_btn.pack(side=tk.LEFT, padx=5)
# CPU button
cpu_btn = tk.Button(
buttons_frame,
text="Start with CPU",
font=("Arial", 11, "bold"),
bg='#666666',
fg='white',
activebackground='#888888',
activeforeground='white',
relief=tk.RAISED,
bd=2,
padx=20,
pady=8,
command=lambda: self.start_application('cpu')
)
cpu_btn.pack(side=tk.LEFT, padx=(10, 0))
# Copyright section at bottom
copyright_frame = tk.Frame(main_frame, bg='#1a1a1a')
copyright_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=(20, 0))
copyright_label = tk.Label(
copyright_frame,
text="© 2025 Setec Labs",
font=("Arial", 9, "bold"),
fg='#888888',
bg='#1a1a1a'
)
copyright_label.pack(side=tk.LEFT)
author_label = tk.Label(
copyright_frame,
text="by ssSnake",
font=("Arial", 9, "italic"),
fg='#888888',
bg='#1a1a1a'
)
author_label.pack(side=tk.RIGHT)
def start_application(self, acceleration_type):
"""Start the application with selected hardware acceleration"""
self.selected_acceleration = acceleration_type
print(f"Starting DarkHal 2.0 with {acceleration_type.upper()} acceleration...")
self.close_splash()
def close_splash(self):
"""Close the splash screen and call callback"""
self.root.destroy()
if self.callback:
self.callback(self.selected_acceleration)
def show(self):
"""Show the splash screen"""
self.root.mainloop()
# --- Dynamic logo helpers ---
def _load_logo_original(self):
"""Load the original logo as a PIL Image (RGBA) without resizing."""
logo_path = Path("assets/logo.png")
if not logo_path.exists():
return None
try:
image = Image.open(logo_path)
return image.convert("RGBA")
except Exception as e:
print(f"Error loading original logo: {e}")
return None
def update_logo_size(self, event=None):
"""Resize the logo image dynamically to match the window size."""
if not self.showing_logo:
return
if self.logo_label is None or self.logo_original is None:
return
try:
# Current window size
win_w = max(1, self.root.winfo_width())
win_h = max(1, self.root.winfo_height())
# Target size: fraction of shortest side
target_box = int(min(win_w, win_h) * 0.45)
# Clamp to reasonable bounds
target_box = max(96, min(512, target_box))
ow, oh = self.logo_original.size
scale = min(target_box / ow, target_box / oh)
new_w = max(1, int(ow * scale))
new_h = max(1, int(oh * scale))
resized = self.logo_original.resize((new_w, new_h), Image.Resampling.LANCZOS)
self.logo_photo = ImageTk.PhotoImage(resized)
self.logo_label.configure(image=self.logo_photo)
except Exception as e:
print(f"Error dynamically resizing logo: {e}")
"""Resize the logo to fit dynamically within the current window size."""
if not self.showing_logo:
return # Only active on the logo screen
if self.logo_label is None or self.logo_original is None:
return
try:
# Current window size
win_w = max(1, self.root.winfo_width())
win_h = max(1, self.root.winfo_height())
# Target box as a fraction of the shortest side
target_box = int(min(win_w, win_h) * 0.45)
# Clamp to reasonable bounds
target_box = max(96, min(512, target_box))
ow, oh = self.logo_original.size
scale = min(target_box / ow, target_box / oh)
new_w = max(1, int(ow * scale))
new_h = max(1, int(oh * scale))
# Resize with high-quality resampling
resized = self.logo_original.resize((new_w, new_h), Image.Resampling.LANCZOS)
self.logo_photo = ImageTk.PhotoImage(resized)
self.logo_label.configure(image=self.logo_photo)
except Exception as e:
print(f"Error dynamically resizing logo: {e}")
class SplashManager:
"""Manager for showing splash screen and launching main application"""
def __init__(self, main_app_callback=None):
"""
Initialize splash manager.
Args:
main_app_callback (callable): Function to call when splash closes with acceleration type
"""
self.main_app_callback = main_app_callback
def show_splash_and_launch(self):
"""Show splash screen then launch main application"""
splash = SplashScreen(callback=self.launch_main_app)
splash.show()
def launch_main_app(self, acceleration_type):
"""Launch the main application after splash"""
if self.main_app_callback:
self.main_app_callback(acceleration_type)
else:
print(f"DarkHal 2.0 ready with {acceleration_type.upper() if acceleration_type else 'default'} acceleration!")
def demo_main_app(acceleration_type):
"""Demo main application for testing"""
root = tk.Tk()
root.title(f"DarkHal 2.0 - Main Application ({acceleration_type.upper()})")
root.geometry("800x600")
label = tk.Label(root, text=f"Welcome to DarkHal 2.0!\nRunning with {acceleration_type.upper()} acceleration",
font=("Arial", 16), justify=tk.CENTER)
label.pack(expand=True)
root.mainloop()
def main():
"""Main entry point for testing splash screen"""
print("Starting DarkHal 2.0 with splash screen...")
# Create splash manager
splash_manager = SplashManager(main_app_callback=demo_main_app)
# Show splash and launch app
splash_manager.show_splash_and_launch()
if __name__ == "__main__":
main()