Reworked the modal ui's for launcher, settings, updates and game pre-loader.
This commit is contained in:
251
ptlinux/ui.py
251
ptlinux/ui.py
@@ -14,7 +14,10 @@ import sys
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from typing import Callable, Mapping
|
from typing import TYPE_CHECKING, Callable, Mapping
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from tkinter import Frame, PhotoImage, Toplevel
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from .changelog import (
|
from .changelog import (
|
||||||
@@ -75,10 +78,17 @@ from .server_status import (
|
|||||||
ServerStatusError,
|
ServerStatusError,
|
||||||
fetch_server_status,
|
fetch_server_status,
|
||||||
)
|
)
|
||||||
|
from .tk_chrome import (
|
||||||
|
centered_window_geometry,
|
||||||
|
create_dashboard_chrome,
|
||||||
|
create_window_chrome,
|
||||||
|
load_square_window_icon,
|
||||||
|
show_themed_dialog,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
MAX_NEWS_CARDS = 3
|
MAX_NEWS_CARDS = 3
|
||||||
WINDOW_SIZE = "1000x750"
|
WINDOW_SIZE = "1002x784"
|
||||||
NEWS_CARD_HEIGHT = 242
|
NEWS_CARD_HEIGHT = 242
|
||||||
NEWS_IMAGE_WIDTH = 292
|
NEWS_IMAGE_WIDTH = 292
|
||||||
NEWS_IMAGE_HEIGHT = 118
|
NEWS_IMAGE_HEIGHT = 118
|
||||||
@@ -517,7 +527,7 @@ def preloader_status_text(
|
|||||||
return f"Step {step} of {total} · {value}"
|
return f"Step {step} of {total} · {value}"
|
||||||
|
|
||||||
|
|
||||||
def freeze_preloader_geometry(frame: tk.Misc) -> tuple[int, int]:
|
def freeze_preloader_geometry(frame: Frame) -> tuple[int, int]:
|
||||||
"""Lock a measured preloader frame so later status text cannot reflow it."""
|
"""Lock a measured preloader frame so later status text cannot reflow it."""
|
||||||
|
|
||||||
frame.update_idletasks()
|
frame.update_idletasks()
|
||||||
@@ -654,7 +664,7 @@ class TransferRateEstimator:
|
|||||||
def run_dashboard(paths: AppPaths) -> None:
|
def run_dashboard(paths: AppPaths) -> None:
|
||||||
try:
|
try:
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import filedialog, messagebox, ttk
|
from tkinter import filedialog, ttk
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
raise DashboardError(
|
raise DashboardError(
|
||||||
"Python Tk is missing. Install your distribution's python3-tk package "
|
"Python Tk is missing. Install your distribution's python3-tk package "
|
||||||
@@ -665,29 +675,43 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
root = tk.Tk(className=APPLICATION_WM_CLASS)
|
root = tk.Tk(className=APPLICATION_WM_CLASS)
|
||||||
root.title(f"{APPLICATION_NAME} — Linux Launcher")
|
root.title(f"{APPLICATION_NAME} — Linux Launcher")
|
||||||
root.iconname(APPLICATION_NAME)
|
root.iconname(APPLICATION_NAME)
|
||||||
window_icon = None
|
window_icon: PhotoImage | None = None
|
||||||
window_icon_path = find_application_icon()
|
window_icon_path = find_application_icon()
|
||||||
if window_icon_path is not None:
|
if window_icon_path is not None:
|
||||||
try:
|
try:
|
||||||
window_icon = tk.PhotoImage(master=root, file=str(window_icon_path))
|
loaded_window_icon = load_square_window_icon(root, window_icon_path)
|
||||||
root.iconphoto(True, window_icon)
|
root.iconphoto(True, loaded_window_icon)
|
||||||
|
window_icon = loaded_window_icon
|
||||||
except (OSError, tk.TclError) as icon_error:
|
except (OSError, tk.TclError) as icon_error:
|
||||||
print(
|
print(
|
||||||
f"Warning: could not load the launcher icon: {icon_error}",
|
f"Warning: could not load the launcher icon: {icon_error}",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def apply_window_icon(window: Toplevel) -> None:
|
||||||
|
icon = window_icon
|
||||||
|
if icon is not None:
|
||||||
|
window.iconphoto(False, icon)
|
||||||
|
|
||||||
root.geometry(WINDOW_SIZE)
|
root.geometry(WINDOW_SIZE)
|
||||||
root.resizable(False, False)
|
root.resizable(False, False)
|
||||||
root.configure(background="#0c0908")
|
root.configure(background="#0c0908")
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
|
dashboard_chrome = create_dashboard_chrome(
|
||||||
|
root,
|
||||||
|
title="PRISTONTALE EU",
|
||||||
|
subtitle="NATIVE LINUX LAUNCHER",
|
||||||
|
)
|
||||||
|
dashboard_content = dashboard_chrome.content
|
||||||
try:
|
try:
|
||||||
startup_settings = ensure_settings(paths)
|
startup_settings = ensure_settings(paths)
|
||||||
except (OSError, ValueError) as startup_error:
|
except (OSError, ValueError) as startup_error:
|
||||||
root.deiconify()
|
root.deiconify()
|
||||||
messagebox.showerror(
|
show_themed_dialog(
|
||||||
"Launcher settings",
|
root,
|
||||||
f"The launcher could not load its settings:\n\n{startup_error}",
|
title="Launcher settings",
|
||||||
parent=root,
|
message=f"The launcher could not load its settings:\n\n{startup_error}",
|
||||||
|
kind="error",
|
||||||
)
|
)
|
||||||
root.destroy()
|
root.destroy()
|
||||||
return
|
return
|
||||||
@@ -1091,7 +1115,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
startup_started_at = time.monotonic()
|
startup_started_at = time.monotonic()
|
||||||
startup_status = tk.StringVar(value="Loading dashboard contents…")
|
startup_status = tk.StringVar(value="Loading dashboard contents…")
|
||||||
startup_overlay = tk.Frame(
|
startup_overlay = tk.Frame(
|
||||||
root,
|
dashboard_content,
|
||||||
background="#0c0908",
|
background="#0c0908",
|
||||||
borderwidth=0,
|
borderwidth=0,
|
||||||
highlightthickness=0,
|
highlightthickness=0,
|
||||||
@@ -1289,7 +1313,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
startup_timeout_after = None
|
startup_timeout_after = None
|
||||||
begin_dashboard_transition(timed_out=True)
|
begin_dashboard_transition(timed_out=True)
|
||||||
|
|
||||||
frame = ttk.Frame(root, style="Root.TFrame", padding=12)
|
frame = ttk.Frame(dashboard_content, style="Root.TFrame", padding=12)
|
||||||
frame.pack(fill="both", expand=True)
|
frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
header = ttk.Frame(frame, style="Root.TFrame")
|
header = ttk.Frame(frame, style="Root.TFrame")
|
||||||
@@ -1535,6 +1559,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
activity_window.geometry("800x400")
|
activity_window.geometry("800x400")
|
||||||
activity_window.resizable(False, False)
|
activity_window.resizable(False, False)
|
||||||
activity_window.configure(background="#0c0908")
|
activity_window.configure(background="#0c0908")
|
||||||
|
apply_window_icon(activity_window)
|
||||||
log = tk.Text(
|
log = tk.Text(
|
||||||
activity_window,
|
activity_window,
|
||||||
background="#100d0c",
|
background="#100d0c",
|
||||||
@@ -1577,6 +1602,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
preloader: tk.Toplevel | None = None
|
preloader: tk.Toplevel | None = None
|
||||||
preloader_status: tk.StringVar | None = None
|
preloader_status: tk.StringVar | None = None
|
||||||
preloader_progress: ttk.Progressbar | None = None
|
preloader_progress: ttk.Progressbar | None = None
|
||||||
|
preloader_image_references: list[tk.PhotoImage] = []
|
||||||
settings_window: tk.Toplevel | None = None
|
settings_window: tk.Toplevel | None = None
|
||||||
dashboard_debug_window: tk.Toplevel | None = None
|
dashboard_debug_window: tk.Toplevel | None = None
|
||||||
dashboard_debug_output: tk.Text | None = None
|
dashboard_debug_output: tk.Text | None = None
|
||||||
@@ -1753,37 +1779,64 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
if preloader is not None and preloader.winfo_exists():
|
if preloader is not None and preloader.winfo_exists():
|
||||||
return
|
return
|
||||||
window = tk.Toplevel(root)
|
window = tk.Toplevel(root)
|
||||||
|
window.withdraw()
|
||||||
preloader = window
|
preloader = window
|
||||||
window.title("Launching PristonTale EU")
|
window.title("Launching PristonTale EU")
|
||||||
window.configure(background="#0c0908")
|
window.configure(background="#0c0908")
|
||||||
window.resizable(False, False)
|
window.resizable(False, False)
|
||||||
window.protocol("WM_DELETE_WINDOW", lambda: None)
|
window.protocol("WM_DELETE_WINDOW", lambda: None)
|
||||||
window.transient(root)
|
window.transient(root)
|
||||||
window.attributes("-topmost", True)
|
apply_window_icon(window)
|
||||||
|
loader_chrome = create_window_chrome(
|
||||||
|
window,
|
||||||
|
title="",
|
||||||
|
subtitle="",
|
||||||
|
show_title_bar=False,
|
||||||
|
show_close=False,
|
||||||
|
show_minimize=False,
|
||||||
|
allow_close=False,
|
||||||
|
)
|
||||||
status_variable = tk.StringVar(value="Starting launcher process…")
|
status_variable = tk.StringVar(value="Starting launcher process…")
|
||||||
preloader_status = status_variable
|
preloader_status = status_variable
|
||||||
content = ttk.Frame(window, style="Root.TFrame", padding=26)
|
content = ttk.Frame(
|
||||||
content.pack(fill="both", expand=True)
|
loader_chrome.content,
|
||||||
ttk.Label(content, text="PRISTONTALE EU", style="Brand.TLabel").pack(anchor="w")
|
style="Root.TFrame",
|
||||||
ttk.Label(content, text="LAUNCHING THE CLIENT", style="Tagline.TLabel").pack(
|
padding=(34, 26),
|
||||||
anchor="w",
|
|
||||||
pady=(2, 18),
|
|
||||||
)
|
)
|
||||||
|
content.pack(fill="both", expand=True)
|
||||||
|
logo_source = window_icon
|
||||||
|
if logo_source is not None:
|
||||||
|
factor = max(
|
||||||
|
1,
|
||||||
|
math.ceil(logo_source.width() / 88),
|
||||||
|
math.ceil(logo_source.height() / 88),
|
||||||
|
)
|
||||||
|
modal_logo = logo_source.subsample(factor, factor)
|
||||||
|
preloader_image_references.clear()
|
||||||
|
preloader_image_references.append(modal_logo)
|
||||||
|
tk.Label(
|
||||||
|
content,
|
||||||
|
image=modal_logo,
|
||||||
|
background="#0c0908",
|
||||||
|
borderwidth=0,
|
||||||
|
highlightthickness=0,
|
||||||
|
).pack(anchor="center", pady=(0, 16))
|
||||||
ttk.Label(
|
ttk.Label(
|
||||||
content,
|
content,
|
||||||
text="Please wait while the game prepares its window.",
|
text="Preparing the game window…",
|
||||||
style="PreloaderStatus.TLabel",
|
style="PreloaderStatus.TLabel",
|
||||||
).pack(anchor="w")
|
anchor="center",
|
||||||
|
).pack(fill="x")
|
||||||
status_label = ttk.Label(
|
status_label = ttk.Label(
|
||||||
content,
|
content,
|
||||||
textvariable=status_variable,
|
textvariable=status_variable,
|
||||||
style="PreloaderMuted.TLabel",
|
style="PreloaderMuted.TLabel",
|
||||||
width=PRELOADER_STATUS_CHARACTERS,
|
width=PRELOADER_STATUS_CHARACTERS,
|
||||||
anchor="w",
|
anchor="center",
|
||||||
)
|
)
|
||||||
status_label.pack(
|
status_label.pack(
|
||||||
fill="x",
|
fill="x",
|
||||||
pady=(4, 10),
|
pady=(5, 11),
|
||||||
)
|
)
|
||||||
progress_widget = ttk.Progressbar(
|
progress_widget = ttk.Progressbar(
|
||||||
content,
|
content,
|
||||||
@@ -1795,12 +1848,23 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
preloader_progress = progress_widget
|
preloader_progress = progress_widget
|
||||||
progress_widget.pack(fill="x")
|
progress_widget.pack(fill="x")
|
||||||
freeze_preloader_geometry(content)
|
freeze_preloader_geometry(content)
|
||||||
|
root.update_idletasks()
|
||||||
window.update_idletasks()
|
window.update_idletasks()
|
||||||
width = window.winfo_reqwidth()
|
width = window.winfo_reqwidth()
|
||||||
height = window.winfo_reqheight()
|
height = window.winfo_reqheight()
|
||||||
x = (window.winfo_screenwidth() - width) // 2
|
window.geometry(
|
||||||
y = (window.winfo_screenheight() - height) // 2
|
centered_window_geometry(
|
||||||
window.geometry(f"{width}x{height}+{x}+{y}")
|
width,
|
||||||
|
height,
|
||||||
|
parent_x=root.winfo_rootx(),
|
||||||
|
parent_y=root.winfo_rooty(),
|
||||||
|
parent_width=root.winfo_width(),
|
||||||
|
parent_height=root.winfo_height(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
window.deiconify()
|
||||||
|
window.lift()
|
||||||
|
window.grab_set()
|
||||||
window.focus_force()
|
window.focus_force()
|
||||||
|
|
||||||
def set_preloader_status(value: str, step: int | None = None, total: int | None = None) -> None:
|
def set_preloader_status(value: str, step: int | None = None, total: int | None = None) -> None:
|
||||||
@@ -1812,10 +1876,15 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
def close_game_preloader() -> None:
|
def close_game_preloader() -> None:
|
||||||
nonlocal preloader, preloader_status, preloader_progress
|
nonlocal preloader, preloader_status, preloader_progress
|
||||||
if preloader is not None and preloader.winfo_exists():
|
if preloader is not None and preloader.winfo_exists():
|
||||||
|
try:
|
||||||
|
preloader.grab_release()
|
||||||
|
except tk.TclError:
|
||||||
|
pass
|
||||||
preloader.destroy()
|
preloader.destroy()
|
||||||
preloader = None
|
preloader = None
|
||||||
preloader_status = None
|
preloader_status = None
|
||||||
preloader_progress = None
|
preloader_progress = None
|
||||||
|
preloader_image_references.clear()
|
||||||
|
|
||||||
def run_operation(
|
def run_operation(
|
||||||
label: str,
|
label: str,
|
||||||
@@ -2720,6 +2789,7 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
window.geometry("900x640")
|
window.geometry("900x640")
|
||||||
window.minsize(700, 440)
|
window.minsize(700, 440)
|
||||||
window.configure(background="#0c0908")
|
window.configure(background="#0c0908")
|
||||||
|
apply_window_icon(window)
|
||||||
debug_header = tk.Frame(window, background="#24170f", padx=16, pady=12)
|
debug_header = tk.Frame(window, background="#24170f", padx=16, pady=12)
|
||||||
debug_header.pack(fill="x")
|
debug_header.pack(fill="x")
|
||||||
tk.Label(
|
tk.Label(
|
||||||
@@ -3103,10 +3173,14 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
start_new_session=True,
|
start_new_session=True,
|
||||||
)
|
)
|
||||||
except OSError as restart_error:
|
except OSError as restart_error:
|
||||||
messagebox.showerror(
|
show_themed_dialog(
|
||||||
"Launcher update",
|
root,
|
||||||
f"The update was installed, but the launcher could not restart: {restart_error}",
|
title="Launcher update",
|
||||||
parent=root,
|
message=(
|
||||||
|
"The update was installed, but the launcher could not "
|
||||||
|
f"restart: {restart_error}"
|
||||||
|
),
|
||||||
|
kind="error",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
root.destroy()
|
root.destroy()
|
||||||
@@ -3342,30 +3416,40 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
return
|
return
|
||||||
if not check.available:
|
if not check.available:
|
||||||
if required_reason is None:
|
if required_reason is None:
|
||||||
messagebox.showinfo(
|
show_themed_dialog(
|
||||||
"Launcher update",
|
root,
|
||||||
check.reason,
|
title="Launcher is up to date",
|
||||||
parent=root,
|
message=check.reason,
|
||||||
|
kind="success",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
messagebox.showwarning(
|
show_themed_dialog(
|
||||||
"Compatibility update required",
|
root,
|
||||||
"The installed game needs a launcher compatibility update:\n\n"
|
title="Compatibility update required",
|
||||||
f"{required_reason}\n\n{check.reason}",
|
message=(
|
||||||
parent=root,
|
"The installed game needs a launcher compatibility update:\n\n"
|
||||||
|
f"{required_reason}\n\n{check.reason}"
|
||||||
|
),
|
||||||
|
kind="warning",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if not check.supported:
|
if not check.supported:
|
||||||
messagebox.showwarning(
|
show_themed_dialog(
|
||||||
"Manual launcher update required",
|
root,
|
||||||
check.reason,
|
title="Manual launcher update required",
|
||||||
parent=root,
|
message=check.reason,
|
||||||
|
kind="warning",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if not messagebox.askyesno(
|
if not show_themed_dialog(
|
||||||
"Update launcher",
|
root,
|
||||||
f"Install signed launcher version {check.latest_version} and restart now?",
|
title="Update launcher",
|
||||||
parent=root,
|
message=(
|
||||||
|
f"Install signed launcher version {check.latest_version} and restart now?"
|
||||||
|
),
|
||||||
|
kind="question",
|
||||||
|
confirm=True,
|
||||||
|
accept_text="INSTALL & RESTART",
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
run_operation(
|
run_operation(
|
||||||
@@ -3385,11 +3469,14 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
official_snapshot,
|
official_snapshot,
|
||||||
)
|
)
|
||||||
except OSError as cache_error:
|
except OSError as cache_error:
|
||||||
messagebox.showerror(
|
show_themed_dialog(
|
||||||
"Realm selection",
|
root,
|
||||||
"The launcher could not save the official realm selection:\n\n"
|
title="Realm selection",
|
||||||
f"{cache_error}",
|
message=(
|
||||||
parent=root,
|
"The launcher could not save the official realm selection:\n\n"
|
||||||
|
f"{cache_error}"
|
||||||
|
),
|
||||||
|
kind="error",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
launch_settings: Settings | None = None
|
launch_settings: Settings | None = None
|
||||||
@@ -3469,21 +3556,36 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
try:
|
try:
|
||||||
current_settings = ensure_settings(paths)
|
current_settings = ensure_settings(paths)
|
||||||
except (OSError, ValueError) as error:
|
except (OSError, ValueError) as error:
|
||||||
messagebox.showerror(
|
show_themed_dialog(
|
||||||
"Settings",
|
root,
|
||||||
f"The launcher could not load its settings:\n\n{error}",
|
title="Settings",
|
||||||
parent=root,
|
message=f"The launcher could not load its settings:\n\n{error}",
|
||||||
|
kind="error",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
dialog = tk.Toplevel(root)
|
dialog = tk.Toplevel(root)
|
||||||
|
dialog.withdraw()
|
||||||
settings_window = dialog
|
settings_window = dialog
|
||||||
dialog.title("PristonTale EU — Settings")
|
dialog.title("PristonTale EU — Settings")
|
||||||
dialog.geometry("840x680")
|
dialog.geometry("842x714")
|
||||||
dialog.resizable(False, False)
|
dialog.resizable(False, False)
|
||||||
dialog.configure(background="#0c0908")
|
dialog.configure(background="#0c0908")
|
||||||
dialog.transient(root)
|
dialog.transient(root)
|
||||||
|
apply_window_icon(dialog)
|
||||||
|
settings_chrome = create_window_chrome(
|
||||||
|
dialog,
|
||||||
|
title="PRISTONTALE EU",
|
||||||
|
subtitle="LAUNCHER SETTINGS",
|
||||||
|
show_close=True,
|
||||||
|
show_minimize=False,
|
||||||
|
allow_close=True,
|
||||||
|
)
|
||||||
|
|
||||||
settings_header = tk.Frame(dialog, background="#24170f", height=118)
|
settings_header = tk.Frame(
|
||||||
|
settings_chrome.content,
|
||||||
|
background="#24170f",
|
||||||
|
height=118,
|
||||||
|
)
|
||||||
settings_header.pack(fill="x")
|
settings_header.pack(fill="x")
|
||||||
settings_header.pack_propagate(False)
|
settings_header.pack_propagate(False)
|
||||||
tk.Frame(settings_header, background="#d98927", width=5).pack(side="left", fill="y")
|
tk.Frame(settings_header, background="#d98927", width=5).pack(side="left", fill="y")
|
||||||
@@ -3511,7 +3613,11 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
font=("Sans", 9),
|
font=("Sans", 9),
|
||||||
).pack(anchor="w", pady=(2, 0))
|
).pack(anchor="w", pady=(2, 0))
|
||||||
|
|
||||||
content = ttk.Frame(dialog, style="Root.TFrame", padding=(16, 14, 16, 16))
|
content = ttk.Frame(
|
||||||
|
settings_chrome.content,
|
||||||
|
style="Root.TFrame",
|
||||||
|
padding=(16, 14, 16, 16),
|
||||||
|
)
|
||||||
content.pack(fill="both", expand=True)
|
content.pack(fill="both", expand=True)
|
||||||
content.columnconfigure(0, weight=1, uniform="settings-card")
|
content.columnconfigure(0, weight=1, uniform="settings-card")
|
||||||
content.columnconfigure(1, weight=1, uniform="settings-card")
|
content.columnconfigure(1, weight=1, uniform="settings-card")
|
||||||
@@ -3607,6 +3713,8 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
settings_window.destroy()
|
settings_window.destroy()
|
||||||
settings_window = None
|
settings_window = None
|
||||||
|
|
||||||
|
settings_chrome.set_close_command(close_settings)
|
||||||
|
|
||||||
def save_current_settings() -> None:
|
def save_current_settings() -> None:
|
||||||
try:
|
try:
|
||||||
updated_settings = update_settings(
|
updated_settings = update_settings(
|
||||||
@@ -3619,7 +3727,12 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
game_source=game_source_value.get().strip() or None,
|
game_source=game_source_value.get().strip() or None,
|
||||||
)
|
)
|
||||||
except (OSError, ValueError) as error:
|
except (OSError, ValueError) as error:
|
||||||
messagebox.showerror("Settings", str(error), parent=dialog)
|
show_themed_dialog(
|
||||||
|
dialog,
|
||||||
|
title="Settings",
|
||||||
|
message=str(error),
|
||||||
|
kind="error",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
append_log(
|
append_log(
|
||||||
f"Saved launcher settings to {paths.settings_file}: "
|
f"Saved launcher settings to {paths.settings_file}: "
|
||||||
@@ -3752,7 +3865,20 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
).pack(side="left", padx=(8, 0))
|
).pack(side="left", padx=(8, 0))
|
||||||
|
|
||||||
dialog.protocol("WM_DELETE_WINDOW", close_settings)
|
dialog.protocol("WM_DELETE_WINDOW", close_settings)
|
||||||
|
dialog.update_idletasks()
|
||||||
|
dialog.geometry(
|
||||||
|
centered_window_geometry(
|
||||||
|
842,
|
||||||
|
714,
|
||||||
|
parent_x=root.winfo_rootx(),
|
||||||
|
parent_y=root.winfo_rooty(),
|
||||||
|
parent_width=root.winfo_width(),
|
||||||
|
parent_height=root.winfo_height(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
dialog.deiconify()
|
||||||
dialog.lift()
|
dialog.lift()
|
||||||
|
dialog.grab_set()
|
||||||
dialog.focus_set()
|
dialog.focus_set()
|
||||||
|
|
||||||
settings_button.configure(command=open_settings)
|
settings_button.configure(command=open_settings)
|
||||||
@@ -3779,10 +3905,11 @@ def run_dashboard(paths: AppPaths) -> None:
|
|||||||
# and any active launcher operation have finished.
|
# and any active launcher operation have finished.
|
||||||
if operation_active or game_clients.active_count:
|
if operation_active or game_clients.active_count:
|
||||||
set_dashboard_debug(False)
|
set_dashboard_debug(False)
|
||||||
root.iconify()
|
dashboard_chrome.minimize()
|
||||||
return
|
return
|
||||||
root.destroy()
|
root.destroy()
|
||||||
|
|
||||||
|
dashboard_chrome.set_close_command(close_dashboard)
|
||||||
root.protocol("WM_DELETE_WINDOW", close_dashboard)
|
root.protocol("WM_DELETE_WINDOW", close_dashboard)
|
||||||
|
|
||||||
def update_news_viewport(_event: object | None = None) -> None:
|
def update_news_viewport(_event: object | None = None) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user