Files
blockbreak/app.py
2025-12-08 06:24:33 -05:00

217 lines
6.0 KiB
Python

import os
import subprocess
import threading
import time
import customtkinter as ctk
import minecraft_launcher_lib
from tkinter import messagebox
from PIL import Image, ImageTk, ImageFilter
# --------------------------------
# Setup paths
# --------------------------------
CUSTOM_DIR = os.path.join(os.getcwd(), "blockbreak")
os.makedirs(CUSTOM_DIR, exist_ok=True)
BG_PATH = os.path.join(os.getcwd(), "bg.png")
# --------------------------------
# GUI Setup
# --------------------------------
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("dark-blue")
root = ctk.CTk()
root.title("BlockBreak")
root.geometry("800x600")
root.minsize(600, 450)
# --------------------------------
# Background Canvas
# --------------------------------
bg_canvas = ctk.CTkCanvas(root, width=800, height=600, highlightthickness=0)
bg_canvas.pack(fill="both", expand=True)
bg_image = None
def reload_bg():
global bg_image
if not os.path.exists(BG_PATH):
return
pil_img = Image.open(BG_PATH).convert("RGB")
w = root.winfo_width()
h = root.winfo_height()
pil_img = pil_img.resize((w, h))
pil_img = pil_img.filter(ImageFilter.GaussianBlur(5))
bg_image = ImageTk.PhotoImage(pil_img)
bg_canvas.delete("bg")
bg_canvas.create_image(0, 0, anchor="nw", image=bg_image, tags="bg")
root.bind("<Configure>", lambda e: reload_bg())
# --------------------------------
# Fade-in loading overlay
# --------------------------------
loading_overlay = ctk.CTkFrame(root, fg_color="#000000")
loading_overlay.place(relx=0, rely=0, relwidth=1, relheight=1)
loading_label = ctk.CTkLabel(
loading_overlay, text="Loading...", font=("Arial", 36)
)
loading_label.place(relx=0.5, rely=0.5, anchor="center")
# --------------------------------
# Main Panel UI
# --------------------------------
panel = ctk.CTkFrame(root, corner_radius=25, width=600, height=500, fg_color="#1f1f1f")
panel.place(relx=0.5, rely=0.5, anchor="center")
inner_panel = ctk.CTkFrame(panel, fg_color="transparent")
inner_panel.pack(padx=30, pady=30, fill="both", expand=True)
ctk.CTkLabel(inner_panel, text="BlockBreak", font=("Arial", 28)).pack(pady=(0, 20))
username_entry = ctk.CTkEntry(inner_panel, placeholder_text="Username", width=500)
username_entry.pack(pady=10)
version_var = ctk.StringVar()
version_dropdown = ctk.CTkComboBox(inner_panel, values=[], variable=version_var, width=500)
version_dropdown.pack(pady=10)
progress = ctk.CTkProgressBar(inner_panel, width=500)
progress.set(0)
progress.pack(pady=10)
log_box = ctk.CTkTextbox(inner_panel, width=500, height=140)
log_box.pack(pady=10)
log_box.configure(state="disabled")
def log(text):
log_box.configure(state="normal")
log_box.insert("end", text + "\n")
log_box.see("end")
log_box.configure(state="disabled")
# --------------------------------
# Version handling
# --------------------------------
def get_versions():
try:
versions = minecraft_launcher_lib.utils.get_available_versions(CUSTOM_DIR)
except:
versions = []
result = []
for v in versions:
vid = v["id"]
installed = os.path.exists(os.path.join(CUSTOM_DIR, "versions", vid))
label = f"{vid} [Installed]" if installed else vid
result.append(label)
return result
# --------------------------------
# Callback handler for minecraft-launcher-lib
# --------------------------------
max_steps = 1
def cb_set_status(status: str):
log(f"[STATUS] {status}")
def cb_set_max(value: int):
global max_steps
max_steps = max(value, 1)
progress.set(0)
def cb_set_progress(value: int):
if max_steps != 0:
progress.set(value / max_steps)
CALLBACKS = {
"setStatus": cb_set_status,
"setProgress": cb_set_progress,
"setMax": cb_set_max
}
# --------------------------------
# Download + Launch Minecraft
# --------------------------------
def download_and_launch(username, version):
try:
actual_version = version.replace(" [Installed]", "")
version_path = os.path.join(CUSTOM_DIR, "versions", actual_version)
# Install if needed
if not os.path.exists(version_path):
log(f"Installing Minecraft {actual_version}...")
minecraft_launcher_lib.install.install_minecraft_version(
actual_version,
CUSTOM_DIR,
callback=CALLBACKS
)
log("Installation complete.")
else:
log("Version already installed — skipping download.")
log("Generating launch command...")
opts = minecraft_launcher_lib.utils.generate_test_options()
opts["username"] = username
opts["token"] = ""
opts["uuid"] = opts.get("uuid", "")
cmd = minecraft_launcher_lib.command.get_minecraft_command(
actual_version,
CUSTOM_DIR,
opts
)
log("Launching Minecraft...")
subprocess.Popen(cmd, cwd=CUSTOM_DIR)
log("Minecraft launched!")
except Exception as e:
messagebox.showerror("Error", str(e))
log(f"ERROR: {str(e)}")
finally:
progress.set(0)
def start_launch():
username = username_entry.get().strip()
version = version_var.get().strip()
if not username or not version:
messagebox.showerror("Error", "Username and version required!")
return
threading.Thread(target=download_and_launch, args=(username, version), daemon=True).start()
play_button = ctk.CTkButton(
inner_panel, text="Play", command=start_launch, width=500, height=50
)
play_button.pack(pady=10)
# --------------------------------
# Fade-in (restored properly)
# --------------------------------
def full_fade_in():
reload_bg()
version_dropdown.configure(values=get_versions())
# Simulated fade-in by brightening overlay
for i in range(0, 25):
shade = 255 - int(i * (255 / 25))
hexcol = f"{shade:02x}"
loading_overlay.configure(fg_color="#" + hexcol * 3)
time.sleep(0.015)
loading_overlay.destroy()
threading.Thread(target=full_fade_in, daemon=True).start()
root.mainloop()