Added resolution settings and updater modal with rollback functionality for launcher versions.

This commit is contained in:
2026-09-02 23:18:16 +02:00
parent bb90efa9d1
commit ba079ef6fa
4 changed files with 1065 additions and 213 deletions

View File

@@ -26,6 +26,7 @@ _DIALOG_PALETTES: dict[str, DialogPalette] = {
"error": DialogPalette("#cf604c", "#3a1d19", "×"),
"question": DialogPalette("#d98927", "#3a2819", "?"),
}
_BASE_TK_SCALING = 96.0 / 72.0
def dialog_palette(kind: DialogKind) -> DialogPalette:
@@ -111,6 +112,16 @@ def dragged_window_geometry(
)
def widget_ui_scale(widget: Any) -> float:
"""Return the launcher scale already configured on a Tk interpreter."""
try:
scaling = float(widget.tk.call("tk", "scaling"))
except (AttributeError, TypeError, ValueError):
return 1.0
return min(2.0, max(1.0, scaling / _BASE_TK_SCALING))
def _geometry_offset(value: int) -> str:
return f"+{value}" if value >= 0 else str(value)
@@ -138,7 +149,7 @@ def _bind_window_drag(handle: Any, *widgets: Any) -> None:
class DashboardChrome:
"""Controller for one frameless launcher window and its controls."""
"""Controller for one launcher window and its custom controls."""
def __init__(
self,
@@ -310,9 +321,10 @@ def create_window_chrome(
show_minimize: bool = False,
allow_close: bool = True,
) -> DashboardChrome:
"""Install polished managed chrome around one launcher-owned Tk window."""
"""Install the launcher's custom frame and window controls."""
import tkinter as tk
ui_scale = widget_ui_scale(root)
managed, identity_configured = _configure_x11_window(
root,
@@ -338,7 +350,7 @@ def create_window_chrome(
title_bar = tk.Frame(
surface,
background="#17100d",
height=32,
height=round(32 * ui_scale),
borderwidth=0,
highlightthickness=0,
cursor="fleur",
@@ -354,7 +366,7 @@ def create_window_chrome(
factor = icon_subsample_factor(
window_icon.width(),
window_icon.height(),
20,
round(20 * ui_scale),
)
title_icon = window_icon.subsample(factor, factor)
except (tk.TclError, ValueError):
@@ -363,8 +375,8 @@ def create_window_chrome(
mark = tk.Label(
title_bar,
image=title_icon,
width=22,
height=22,
width=round(22 * ui_scale),
height=round(22 * ui_scale),
background="#17100d",
borderwidth=0,
highlightthickness=0,
@@ -374,8 +386,8 @@ def create_window_chrome(
else:
mark = tk.Frame(
title_bar,
width=22,
height=22,
width=round(22 * ui_scale),
height=round(22 * ui_scale),
background="#17100d",
borderwidth=0,
highlightthickness=0,
@@ -414,10 +426,12 @@ def create_window_chrome(
font=("Sans", 12),
width=4,
borderwidth=0,
highlightthickness=0,
highlightthickness=2,
highlightbackground="#17100d",
highlightcolor="#f4bd61",
relief="flat",
cursor="hand2",
takefocus=False,
takefocus=True,
)
close_button.pack(side="right", fill="y")
minimize_button: Any = None
@@ -432,10 +446,12 @@ def create_window_chrome(
font=("Sans", 10, "bold"),
width=4,
borderwidth=0,
highlightthickness=0,
highlightthickness=2,
highlightbackground="#17100d",
highlightcolor="#f4bd61",
relief="flat",
cursor="hand2",
takefocus=False,
takefocus=True,
)
minimize_button.pack(side="right", fill="y")
content = tk.Frame(
@@ -489,9 +505,10 @@ def show_themed_dialog(
cancel_text: str = "CANCEL",
window_icon: Any | None = None,
) -> bool:
"""Show one frameless modal dialog matching the native launcher palette."""
"""Show one custom-framed modal dialog matching the launcher palette."""
import tkinter as tk
ui_scale = widget_ui_scale(parent)
palette = dialog_palette(kind)
dialog = tk.Toplevel(parent)
@@ -524,7 +541,11 @@ def show_themed_dialog(
border.pack(fill="both", expand=True)
surface = tk.Frame(border, background="#191311")
surface.pack(fill="both", expand=True, padx=1, pady=1)
header = tk.Frame(surface, background="#24170f", height=66)
header = tk.Frame(
surface,
background="#24170f",
height=round(66 * ui_scale),
)
header.pack(fill="x")
header.pack_propagate(False)
tk_accent = tk.Frame(header, background=palette.accent, width=5)
@@ -560,21 +581,24 @@ def show_themed_dialog(
font=("Sans", 12),
width=4,
borderwidth=0,
highlightthickness=0,
highlightthickness=2,
highlightbackground="#24170f",
highlightcolor="#f4bd61",
relief="flat",
cursor="hand2",
takefocus=False,
takefocus=True,
)
close_button.pack(side="right", fill="y")
body = tk.Frame(surface, background="#191311", padx=20, pady=20)
body.pack(fill="both", expand=True)
modal_icon_size = round(46 * ui_scale)
modal_icon: Any | None = None
if window_icon is not None:
try:
factor = icon_subsample_factor(
window_icon.width(),
window_icon.height(),
46,
modal_icon_size,
)
modal_icon = window_icon.subsample(factor, factor)
except (tk.TclError, ValueError):
@@ -582,8 +606,8 @@ def show_themed_dialog(
if modal_icon is not None:
icon_host = tk.Frame(
body,
width=46,
height=46,
width=modal_icon_size,
height=modal_icon_size,
background="#191311",
borderwidth=0,
highlightthickness=0,
@@ -601,24 +625,24 @@ def show_themed_dialog(
else:
icon = tk.Canvas(
body,
width=46,
height=46,
width=modal_icon_size,
height=modal_icon_size,
background="#191311",
borderwidth=0,
highlightthickness=0,
)
icon.create_oval(
3,
3,
43,
43,
round(3 * ui_scale),
round(3 * ui_scale),
modal_icon_size - round(3 * ui_scale),
modal_icon_size - round(3 * ui_scale),
fill=palette.accent_soft,
outline=palette.accent,
width=2,
)
icon.create_text(
23,
23,
modal_icon_size // 2,
modal_icon_size // 2,
text=palette.glyph,
fill=palette.accent,
font=("Sans", 17, "bold"),
@@ -632,7 +656,7 @@ def show_themed_dialog(
font=("Sans", 10),
justify="left",
anchor="w",
wraplength=420,
wraplength=round(420 * ui_scale),
)
message_label.pack(side="left", fill="both", expand=True, anchor="n")
footer = tk.Frame(surface, background="#100d0c", padx=18, pady=12)
@@ -660,7 +684,9 @@ def show_themed_dialog(
padx=16,
pady=7,
borderwidth=0,
highlightthickness=0,
highlightthickness=2,
highlightbackground="#100d0c",
highlightcolor="#f4bd61",
relief="flat",
cursor="hand2",
)
@@ -681,12 +707,16 @@ def show_themed_dialog(
)
cancel_button.pack(side="right", padx=(0, 8))
_bind_window_drag(dialog, header, header_copy, eyebrow, title_label)
dialog.protocol("WM_DELETE_WINDOW", lambda: finish(False))
dialog.bind("<Escape>", lambda _event: finish(False))
dialog.bind("<Return>", lambda _event: finish(True))
parent.update_idletasks()
dialog.update_idletasks()
width = 560
height = max(218, dialog.winfo_reqheight())
width = min(round(560 * ui_scale), max(1, dialog.winfo_screenwidth() - 64))
height = min(
max(round(218 * ui_scale), dialog.winfo_reqheight()),
max(1, dialog.winfo_screenheight() - 64),
)
dialog.geometry(
centered_window_geometry(
width,