Added resolution settings and updater modal with rollback functionality for launcher versions.
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user