Update app.py
This commit is contained in:
247
app.py
247
app.py
@@ -6,23 +6,18 @@ import customtkinter as ctk
|
|||||||
import minecraft_launcher_lib
|
import minecraft_launcher_lib
|
||||||
from tkinter import messagebox
|
from tkinter import messagebox
|
||||||
from PIL import Image, ImageTk, ImageFilter
|
from PIL import Image, ImageTk, ImageFilter
|
||||||
import urllib.request
|
|
||||||
import zipfile
|
|
||||||
import sys
|
|
||||||
import platform
|
|
||||||
|
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
# Paths
|
# Setup paths
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
CUSTOM_DIR = os.path.join(os.getcwd(), "blockbreak")
|
CUSTOM_DIR = os.path.join(os.getcwd(), "blockbreak")
|
||||||
JAVA_DIR = os.path.join(CUSTOM_DIR, "java")
|
|
||||||
os.makedirs(CUSTOM_DIR, exist_ok=True)
|
os.makedirs(CUSTOM_DIR, exist_ok=True)
|
||||||
os.makedirs(JAVA_DIR, exist_ok=True)
|
|
||||||
BG_PATH = os.path.join(os.getcwd(), "bg.png")
|
BG_PATH = os.path.join(os.getcwd(), "bg.png")
|
||||||
|
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
# GUI Setup
|
# GUI Setup
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
ctk.set_appearance_mode("dark")
|
ctk.set_appearance_mode("dark")
|
||||||
ctk.set_default_color_theme("dark-blue")
|
ctk.set_default_color_theme("dark-blue")
|
||||||
|
|
||||||
@@ -31,45 +26,54 @@ root.title("BlockBreak")
|
|||||||
root.geometry("800x600")
|
root.geometry("800x600")
|
||||||
root.minsize(600, 450)
|
root.minsize(600, 450)
|
||||||
|
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
# Background Canvas
|
# Background Canvas
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
bg_canvas = ctk.CTkCanvas(root, width=800, height=600, highlightthickness=0)
|
bg_canvas = ctk.CTkCanvas(root, width=800, height=600, highlightthickness=0)
|
||||||
bg_canvas.pack(fill="both", expand=True)
|
bg_canvas.pack(fill="both", expand=True)
|
||||||
|
|
||||||
bg_image = None # keep reference
|
bg_image = None
|
||||||
|
|
||||||
def load_and_blur_bg():
|
def reload_bg():
|
||||||
global bg_image
|
global bg_image
|
||||||
if os.path.exists(BG_PATH):
|
if not os.path.exists(BG_PATH):
|
||||||
pil_img = Image.open(BG_PATH).convert("RGB")
|
return
|
||||||
w, h = root.winfo_width(), root.winfo_height()
|
pil_img = Image.open(BG_PATH).convert("RGB")
|
||||||
pil_img = pil_img.resize((w, h))
|
|
||||||
pil_img = pil_img.filter(ImageFilter.GaussianBlur(4))
|
|
||||||
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: load_and_blur_bg())
|
w = root.winfo_width()
|
||||||
|
h = root.winfo_height()
|
||||||
|
|
||||||
# ----------------------------
|
pil_img = pil_img.resize((w, h))
|
||||||
# Loading Overlay for Fade-in
|
pil_img = pil_img.filter(ImageFilter.GaussianBlur(5))
|
||||||
# ----------------------------
|
|
||||||
loading_overlay = ctk.CTkFrame(root, fg_color="#1f1f1f", corner_radius=0)
|
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_overlay.place(relx=0, rely=0, relwidth=1, relheight=1)
|
||||||
loading_label = ctk.CTkLabel(loading_overlay, text="Loading...", font=("Arial", 36))
|
|
||||||
|
loading_label = ctk.CTkLabel(
|
||||||
|
loading_overlay, text="Loading...", font=("Arial", 36)
|
||||||
|
)
|
||||||
loading_label.place(relx=0.5, rely=0.5, anchor="center")
|
loading_label.place(relx=0.5, rely=0.5, anchor="center")
|
||||||
|
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
# Main Panel with curves + padding
|
# Main Panel UI
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
panel = ctk.CTkFrame(root, width=600, height=500, corner_radius=25, fg_color="#1f1f1f")
|
panel = ctk.CTkFrame(root, corner_radius=25, width=600, height=500, fg_color="#1f1f1f")
|
||||||
panel.place(relx=0.5, rely=0.5, anchor="center")
|
panel.place(relx=0.5, rely=0.5, anchor="center")
|
||||||
|
|
||||||
inner_panel = ctk.CTkFrame(panel, fg_color=None, corner_radius=0)
|
inner_panel = ctk.CTkFrame(panel, fg_color="transparent")
|
||||||
inner_panel.pack(padx=30, pady=20, fill="both", expand=True)
|
inner_panel.pack(padx=30, pady=30, fill="both", expand=True)
|
||||||
|
|
||||||
|
ctk.CTkLabel(inner_panel, text="BlockBreak", font=("Arial", 28)).pack(pady=(0, 20))
|
||||||
|
|
||||||
ctk.CTkLabel(inner_panel, text="BlockBreak", font=("Arial", 28)).pack(pady=(0, 15))
|
|
||||||
username_entry = ctk.CTkEntry(inner_panel, placeholder_text="Username", width=500)
|
username_entry = ctk.CTkEntry(inner_panel, placeholder_text="Username", width=500)
|
||||||
username_entry.pack(pady=10)
|
username_entry.pack(pady=10)
|
||||||
|
|
||||||
@@ -77,131 +81,136 @@ version_var = ctk.StringVar()
|
|||||||
version_dropdown = ctk.CTkComboBox(inner_panel, values=[], variable=version_var, width=500)
|
version_dropdown = ctk.CTkComboBox(inner_panel, values=[], variable=version_var, width=500)
|
||||||
version_dropdown.pack(pady=10)
|
version_dropdown.pack(pady=10)
|
||||||
|
|
||||||
# RAM slider
|
|
||||||
ram_var = ctk.StringVar(value="2G")
|
|
||||||
ctk.CTkLabel(inner_panel, text="RAM Allocation").pack(pady=(10,0))
|
|
||||||
ram_slider = ctk.CTkSegmentedButton(inner_panel, values=["2G - Default (fast + safe)","4G - More mods","6G - Maximum sensible"], command=lambda v: ram_var.set(v.split(" ")[0]))
|
|
||||||
ram_slider.pack(pady=5)
|
|
||||||
|
|
||||||
progress = ctk.CTkProgressBar(inner_panel, width=500)
|
progress = ctk.CTkProgressBar(inner_panel, width=500)
|
||||||
progress.set(0)
|
progress.set(0)
|
||||||
progress.pack(pady=10)
|
progress.pack(pady=10)
|
||||||
|
|
||||||
log_box = ctk.CTkTextbox(inner_panel, width=500, height=150)
|
log_box = ctk.CTkTextbox(inner_panel, width=500, height=140)
|
||||||
log_box.pack(pady=(10, 0))
|
log_box.pack(pady=10)
|
||||||
log_box.configure(state="disabled")
|
log_box.configure(state="disabled")
|
||||||
|
|
||||||
def log(msg):
|
def log(text):
|
||||||
log_box.configure(state="normal")
|
log_box.configure(state="normal")
|
||||||
log_box.insert("end", msg + "\n")
|
log_box.insert("end", text + "\n")
|
||||||
log_box.see("end")
|
log_box.see("end")
|
||||||
log_box.configure(state="disabled")
|
log_box.configure(state="disabled")
|
||||||
|
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
# Java installation (simple bundled version)
|
# Version handling
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
def ensure_java():
|
def get_versions():
|
||||||
java_exe = os.path.join(JAVA_DIR, "bin", "java.exe" if os.name == "nt" else "java")
|
|
||||||
if os.path.exists(java_exe):
|
|
||||||
log("Java found.")
|
|
||||||
return java_exe
|
|
||||||
|
|
||||||
log("Downloading Java...")
|
|
||||||
# We'll use a lightweight OpenJDK zip for Windows only here for simplicity
|
|
||||||
# For cross-platform, you'd need proper handling
|
|
||||||
java_url = "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.8%2B7/OpenJDK17U-jre_x64_windows_hotspot_17.0.8_7.zip"
|
|
||||||
zip_path = os.path.join(JAVA_DIR, "java.zip")
|
|
||||||
urllib.request.urlretrieve(java_url, zip_path)
|
|
||||||
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
|
||||||
zip_ref.extractall(JAVA_DIR)
|
|
||||||
os.remove(zip_path)
|
|
||||||
|
|
||||||
# Find java.exe in extracted folder
|
|
||||||
for root_dir, dirs, files in os.walk(JAVA_DIR):
|
|
||||||
if "java.exe" in files:
|
|
||||||
java_exe = os.path.join(root_dir, "java.exe")
|
|
||||||
log("Java installed successfully.")
|
|
||||||
return java_exe
|
|
||||||
raise FileNotFoundError("Java installation failed")
|
|
||||||
|
|
||||||
# ----------------------------
|
|
||||||
# Launcher Functions
|
|
||||||
# ----------------------------
|
|
||||||
def get_versions_with_status():
|
|
||||||
try:
|
try:
|
||||||
versions = minecraft_launcher_lib.utils.get_available_versions(CUSTOM_DIR)
|
versions = minecraft_launcher_lib.utils.get_available_versions(CUSTOM_DIR)
|
||||||
except Exception:
|
except:
|
||||||
versions = []
|
versions = []
|
||||||
items = []
|
|
||||||
|
result = []
|
||||||
for v in versions:
|
for v in versions:
|
||||||
v_id = v["id"]
|
vid = v["id"]
|
||||||
path = os.path.join(CUSTOM_DIR, "versions", v_id)
|
installed = os.path.exists(os.path.join(CUSTOM_DIR, "versions", vid))
|
||||||
if os.path.exists(path):
|
label = f"{vid} [Installed]" if installed else vid
|
||||||
items.append(f"{v_id} [Installed]")
|
result.append(label)
|
||||||
else:
|
return result
|
||||||
items.append(v_id)
|
|
||||||
return items
|
|
||||||
|
|
||||||
def update_progress(current, total):
|
# --------------------------------
|
||||||
if total > 0:
|
# Callback handler for minecraft-launcher-lib
|
||||||
progress.set(current / total)
|
# --------------------------------
|
||||||
|
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):
|
def download_and_launch(username, version):
|
||||||
try:
|
try:
|
||||||
java_path = ensure_java()
|
|
||||||
actual_version = version.replace(" [Installed]", "")
|
actual_version = version.replace(" [Installed]", "")
|
||||||
|
|
||||||
version_path = os.path.join(CUSTOM_DIR, "versions", actual_version)
|
version_path = os.path.join(CUSTOM_DIR, "versions", actual_version)
|
||||||
|
|
||||||
|
# Install if needed
|
||||||
if not os.path.exists(version_path):
|
if not os.path.exists(version_path):
|
||||||
log(f"Installing version {actual_version}...")
|
log(f"Installing Minecraft {actual_version}...")
|
||||||
minecraft_launcher_lib.install.install_minecraft_version(actual_version, CUSTOM_DIR, progress_callback=update_progress)
|
minecraft_launcher_lib.install.install_minecraft_version(
|
||||||
|
actual_version,
|
||||||
|
CUSTOM_DIR,
|
||||||
|
callback=CALLBACKS
|
||||||
|
)
|
||||||
log("Installation complete.")
|
log("Installation complete.")
|
||||||
else:
|
else:
|
||||||
log(f"Version {actual_version} already installed.")
|
log("Version already installed — skipping download.")
|
||||||
|
|
||||||
log("Generating launch command...")
|
log("Generating launch command...")
|
||||||
options = minecraft_launcher_lib.utils.generate_test_options()
|
|
||||||
options["username"] = username
|
opts = minecraft_launcher_lib.utils.generate_test_options()
|
||||||
options["uuid"] = options.get("uuid", "")
|
opts["username"] = username
|
||||||
options["token"] = ""
|
opts["token"] = ""
|
||||||
options["java_path"] = java_path
|
opts["uuid"] = opts.get("uuid", "")
|
||||||
options["max_memory"] = ram_var.get()
|
|
||||||
cmd = minecraft_launcher_lib.command.get_minecraft_command(actual_version, CUSTOM_DIR, options)
|
cmd = minecraft_launcher_lib.command.get_minecraft_command(
|
||||||
|
actual_version,
|
||||||
|
CUSTOM_DIR,
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
|
||||||
log("Launching Minecraft...")
|
log("Launching Minecraft...")
|
||||||
subprocess.Popen(cmd, cwd=CUSTOM_DIR)
|
subprocess.Popen(cmd, cwd=CUSTOM_DIR)
|
||||||
|
|
||||||
log("Minecraft launched!")
|
log("Minecraft launched!")
|
||||||
progress.set(0)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"Error: {e}")
|
messagebox.showerror("Error", str(e))
|
||||||
messagebox.showerror("Launch Failed", str(e))
|
log(f"ERROR: {str(e)}")
|
||||||
|
finally:
|
||||||
progress.set(0)
|
progress.set(0)
|
||||||
|
|
||||||
def launch_threaded():
|
def start_launch():
|
||||||
username = username_entry.get().strip()
|
username = username_entry.get().strip()
|
||||||
version = version_var.get().strip()
|
version = version_var.get().strip()
|
||||||
|
|
||||||
if not username or not version:
|
if not username or not version:
|
||||||
messagebox.showerror("Error", "Username and version required!")
|
messagebox.showerror("Error", "Username and version required!")
|
||||||
return
|
return
|
||||||
progress.set(0)
|
|
||||||
threading.Thread(target=download_and_launch, args=(username, version), daemon=True).start()
|
threading.Thread(target=download_and_launch, args=(username, version), daemon=True).start()
|
||||||
|
|
||||||
play_button = ctk.CTkButton(inner_panel, text="Play", command=launch_threaded, width=500, height=50)
|
play_button = ctk.CTkButton(
|
||||||
|
inner_panel, text="Play", command=start_launch, width=500, height=50
|
||||||
|
)
|
||||||
play_button.pack(pady=10)
|
play_button.pack(pady=10)
|
||||||
|
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
# Initialization + fade-in
|
# Fade-in (restored properly)
|
||||||
# ----------------------------
|
# --------------------------------
|
||||||
def initialize_launcher():
|
def full_fade_in():
|
||||||
version_dropdown.configure(values=get_versions_with_status())
|
reload_bg()
|
||||||
load_and_blur_bg()
|
version_dropdown.configure(values=get_versions())
|
||||||
# Fade-in simulation using overlay
|
|
||||||
for i in range(20, -1, -1):
|
# Simulated fade-in by brightening overlay
|
||||||
gray = int(i * 12.75)
|
for i in range(0, 25):
|
||||||
hex_gray = f"{gray:02x}"
|
shade = 255 - int(i * (255 / 25))
|
||||||
loading_overlay.configure(fg_color=f"#{hex_gray}{hex_gray}{hex_gray}")
|
hexcol = f"{shade:02x}"
|
||||||
time.sleep(0.03)
|
loading_overlay.configure(fg_color="#" + hexcol * 3)
|
||||||
|
time.sleep(0.015)
|
||||||
|
|
||||||
loading_overlay.destroy()
|
loading_overlay.destroy()
|
||||||
|
|
||||||
threading.Thread(target=initialize_launcher, daemon=True).start()
|
threading.Thread(target=full_fade_in, daemon=True).start()
|
||||||
|
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
|||||||
Reference in New Issue
Block a user