"""现代化 UI 样式注册(跟随 ttkbootstrap 当前主题)。 由 backgroud_style_set() 调用一次。这里集中定义"配置项卡片化"、 "现代化标题栏"、"工具条"、"状态栏" 等所需的所有 ttk Style, 保持主题切换时颜色自动跟随。 """ from __future__ import annotations import ttkbootstrap as ttk def _hex_to_rgb(h: str) -> tuple[int, int, int]: h = h.lstrip("#") return int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16) def _rgb_to_hex(r: int, g: int, b: int) -> str: return f"#{r:02x}{g:02x}{b:02x}" def _mix(c1: str, c2: str, ratio: float) -> str: """按 ratio (0~1) 将 c1 与 c2 线性混合。""" r1, g1, b1 = _hex_to_rgb(c1) r2, g2, b2 = _hex_to_rgb(c2) return _rgb_to_hex( int(r1 * (1 - ratio) + r2 * ratio), int(g1 * (1 - ratio) + g2 * ratio), int(b1 * (1 - ratio) + b2 * ratio), ) def _is_dark(color: str) -> bool: r, g, b = _hex_to_rgb(color) # ITU-R BT.601 亮度 return (r * 299 + g * 587 + b * 114) / 1000 < 128 def _contrast_text(color: str, *, dark_text: str, light_text: str) -> str: return dark_text if _is_dark(color) else light_text def get_theme_palette() -> dict[str, str]: """返回当前主题的语义色板,供 ttk / tk 自定义控件共用。""" style = ttk.Style() theme = style.colors bg = theme.bg fg = theme.fg primary = theme.primary secondary = theme.secondary success = theme.success info = theme.info warning = theme.warning danger = theme.danger dark = theme.dark border = theme.border inputbg = theme.inputbg inputfg = getattr(theme, "inputfg", fg) dark_theme = _is_dark(bg) select_bg = getattr(theme, "selectbg", _mix(primary, bg, 0.30 if dark_theme else 0.12)) select_fg = getattr(theme, "selectfg", "#ffffff" if _is_dark(select_bg) else fg) if dark_theme: card_bg = _mix(bg, "#ffffff", 0.04) card_border = _mix(bg, fg, 0.18) header_fg = _contrast_text( "#444A51", dark_text="#ffffff", light_text="#1a1a1a", ) sidebar_bg = _mix(dark, bg, 0.18) sidebar_hover = _mix(sidebar_bg, "#ffffff", 0.07) sidebar_selected = _mix(sidebar_bg, "#ffffff", 0.14) sidebar_fg = _mix(fg, "#ffffff", 0.04) sidebar_muted = _mix(sidebar_fg, sidebar_bg, 0.45) muted_fg = _mix(fg, bg, 0.32) disabled_fg = _mix(fg, bg, 0.42) disabled_bg = _mix(inputbg, bg, 0.18) disabled_border = _mix(border, fg, 0.22) readonly_bg = _mix(inputbg, "#ffffff", 0.06) success_fg = _mix(success, "#ffffff", 0.08) warning_fg = _mix(warning, "#ffffff", 0.06) info_fg = _mix(info, "#ffffff", 0.06) statusbar_bg = _mix(bg, "#ffffff", 0.06) tooltip_bg = _mix(inputbg, bg, 0.08) tooltip_fg = inputfg tooltip_border = _mix(border, fg, 0.20) surface_alt_bg = _mix(card_bg, "#ffffff", 0.05) surface_hover_bg = _mix(card_bg, "#ffffff", 0.09) badge_bg = _mix(danger, bg, 0.12) badge_fg = "#ffffff" focus = _mix(primary, "#ffffff", 0.18) config_bg = _mix("#444A51", bg, 0.30) else: card_bg = inputbg card_border = border header_fg = bg config_bg = _mix(primary, bg, 0.25) sidebar_bg = _mix(primary, bg, 0.82) sidebar_hover = _mix(primary, bg, 0.72) sidebar_selected = primary sidebar_fg = fg sidebar_muted = _mix(fg, sidebar_bg, 0.35) muted_fg = _mix(fg, bg, 0.38) disabled_fg = _mix(fg, bg, 0.55) disabled_bg = _mix(bg, border, 0.18) disabled_border = _mix(border, bg, 0.18) readonly_bg = _mix(inputbg, primary, 0.04) success_fg = success warning_fg = _mix(warning, fg, 0.18) info_fg = info statusbar_bg = _mix(bg, dark, 0.04) tooltip_bg = inputbg tooltip_fg = inputfg tooltip_border = border surface_alt_bg = _mix(bg, dark, 0.03) surface_hover_bg = _mix(bg, dark, 0.05) badge_bg = danger badge_fg = "#ffffff" focus = _mix(primary, bg, 0.20) return { "bg": bg, "fg": fg, "primary": primary, "secondary": secondary, "success": success, "info": info, "warning": warning, "danger": danger, "border": border, "input_bg": inputbg, "input_fg": inputfg, "select_bg": select_bg, "select_fg": select_fg, "card_bg": card_bg, "card_border": card_border, "header_fg": header_fg, "sidebar_bg": sidebar_bg, "sidebar_hover": sidebar_hover, "sidebar_selected": sidebar_selected, "sidebar_fg": sidebar_fg, "sidebar_muted": sidebar_muted, "muted_fg": muted_fg, "disabled_fg": disabled_fg, "disabled_bg": disabled_bg, "disabled_border": disabled_border, "readonly_bg": readonly_bg, "success_fg": success_fg, "warning_fg": warning_fg, "info_fg": info_fg, "statusbar_bg": statusbar_bg, "tooltip_bg": tooltip_bg, "tooltip_fg": tooltip_fg, "tooltip_border": tooltip_border, "surface_alt_bg": surface_alt_bg, "surface_hover_bg": surface_hover_bg, "badge_bg": badge_bg, "badge_fg": badge_fg, "focus": focus, "config_bg": config_bg, } def apply_listbox_theme(widget) -> None: """将 tk.Listbox 颜色同步到当前主题。""" palette = get_theme_palette() widget.configure( background=palette["input_bg"], foreground=palette["input_fg"], highlightbackground=palette["border"], highlightcolor=palette["focus"], selectbackground=palette["select_bg"], selectforeground=palette["select_fg"], disabledforeground=palette["disabled_fg"], ) def apply_tooltip_theme(toplevel, label) -> None: """将 tooltip 的 tk.Toplevel / Label 同步到当前主题。""" palette = get_theme_palette() toplevel.configure(background=palette["tooltip_border"]) label.configure( bg=palette["tooltip_bg"], fg=palette["tooltip_fg"], highlightbackground=palette["tooltip_border"], ) def apply_modern_styles() -> None: """注册或刷新现代化样式集。可在主题切换后再次调用。""" style = ttk.Style() palette = get_theme_palette() bg = palette["bg"] fg = palette["fg"] primary = palette["primary"] secondary = palette["secondary"] info = palette["info"] card_bg = palette["card_bg"] card_border = palette["card_border"] header_bg = palette["config_bg"] header_fg = palette["header_fg"] dark_theme = _is_dark(bg) header_hover_bg = _mix(secondary, "#ffffff", 0.08) if _is_dark(secondary) else _mix(secondary, "#000000", 0.08) preview_fg = _mix(header_fg, header_bg, 0.35) sidebar_bg = palette["sidebar_bg"] sidebar_hover = palette["sidebar_hover"] sidebar_selected = palette["sidebar_selected"] sidebar_fg = palette["sidebar_fg"] sidebar_muted = palette["sidebar_muted"] muted_fg = palette["muted_fg"] disabled_fg = palette["disabled_fg"] disabled_bg = palette["disabled_bg"] disabled_border = palette["disabled_border"] readonly_bg = palette["readonly_bg"] success_fg = palette["success_fg"] warning_fg = palette["warning_fg"] # ---------------- 卡片 ---------------- style.configure( "Card.TFrame", background=card_bg, bordercolor=card_border, relief="solid", borderwidth=1, ) style.configure( "CardTitle.TLabel", background=card_bg, foreground=fg, font=("Segoe UI", 10, "bold"), ) style.configure( "CardBody.TLabel", background=card_bg, foreground=fg, font=("Segoe UI", 9), ) style.configure( "CardIcon.TLabel", background=card_bg, foreground=info if dark_theme else primary, font=("Segoe UI", 9, "bold"), ) # 内嵌于 Card 的容器(与 Card.TFrame 同背景,无边框) style.configure("CardInner.TFrame", background=card_bg, borderwidth=0) # ---------------- 配置项 Header ---------------- style.configure( "ConfigHeader.TFrame", background=header_bg, borderwidth=0, ) style.configure( "ConfigHeaderHover.TFrame", background=header_hover_bg, borderwidth=0, ) style.configure( "ConfigHeader.TLabel", background=header_bg, foreground=header_fg, font=("Segoe UI", 10, "bold"), ) style.configure( "ConfigHeaderHover.TLabel", background=header_hover_bg, foreground=header_fg, font=("Segoe UI", 10, "bold"), ) style.configure( "ConfigChevron.TLabel", background=header_bg, foreground=header_fg, font=("Segoe UI Symbol", 12, "bold"), ) style.configure( "ConfigPreview.TLabel", background=header_bg, foreground=preview_fg, font=("Segoe UI", 9), ) # ---------------- 通用文字语义 ---------------- style.configure("Muted.TLabel", background=bg, foreground=muted_fg) style.configure("SuccessState.TLabel", background=bg, foreground=success_fg) style.configure("WarningState.TLabel", background=bg, foreground=warning_fg) style.configure("InfoState.TLabel", background=bg, foreground=palette["info_fg"]) # ---------------- 顶部工具条 ---------------- style.configure("Toolbar.TFrame", background=bg, borderwidth=0) # 工具条上的次要按钮(清理配置等) style.configure( "ToolbarMuted.TButton", font=("Segoe UI", 9), padding=(10, 5), ) # ---------------- 区段标题(侧栏 / 卡片外) ---------------- style.configure( "SectionTitle.TLabel", background=bg, foreground=_mix(fg, bg, 0.45), font=("Segoe UI", 8, "bold"), ) style.configure("Sidebar.TFrame", background=sidebar_bg, borderwidth=0) # 侧栏内的小区段标题(侧栏背景是 primary) style.configure( "SidebarSection.TLabel", background=sidebar_bg, foreground=sidebar_muted, font=("Segoe UI", 8, "bold"), ) # 侧栏顶部品牌区 brand_bg = _mix(sidebar_bg, "#ffffff", 0.05) if dark_theme else _mix(sidebar_bg, "#000000", 0.05) style.configure( "SidebarBrand.TFrame", background=brand_bg, borderwidth=0, ) style.configure( "SidebarBrand.TLabel", background=brand_bg, foreground=palette["badge_fg"], font=("Segoe UI Semibold", 12), ) style.configure( "SidebarBadge.TLabel", background=palette["badge_bg"], foreground=palette["badge_fg"], font=("微软雅黑", 8, "bold"), anchor="center", padding=(6, 2), ) # ---------------- 结果区无边框标题行 ---------------- style.configure("ResultHeader.TFrame", background=bg, borderwidth=0) style.configure( "ResultHeader.TLabel", background=bg, foreground=fg, font=("Segoe UI", 11, "bold"), ) # ---------------- 状态栏 ---------------- statusbar_bg = palette["statusbar_bg"] statusbar_fg = _mix(fg, bg, 0.15) style.configure( "StatusBar.TFrame", background=statusbar_bg, borderwidth=0, ) style.configure( "StatusBar.TLabel", background=statusbar_bg, foreground=statusbar_fg, font=("Segoe UI", 9), padding=(10, 4), ) style.configure( "StatusBarAccent.TLabel", background=statusbar_bg, foreground=info if dark_theme else primary, font=("Segoe UI", 9, "bold"), padding=(10, 4), ) # ---------------- 深色禁用态 / 只读态增强 ---------------- style.map( "TLabel", foreground=[("disabled", disabled_fg)], ) style.map( "TButton", foreground=[("disabled", disabled_fg)], background=[("disabled", disabled_bg)], bordercolor=[("disabled", disabled_border)], darkcolor=[("disabled", disabled_bg)], lightcolor=[("disabled", disabled_bg)], ) style.map( "TEntry", foreground=[("disabled", disabled_fg)], fieldbackground=[("disabled", disabled_bg), ("readonly", readonly_bg)], bordercolor=[("disabled", disabled_border), ("readonly", disabled_border)], ) style.map( "TCombobox", foreground=[("disabled", disabled_fg), ("readonly", fg)], fieldbackground=[("disabled", disabled_bg), ("readonly", readonly_bg)], bordercolor=[("disabled", disabled_border), ("readonly", disabled_border)], arrowcolor=[("disabled", disabled_fg), ("readonly", muted_fg)], ) # ---------------- Sidebar 按钮(保留兼容名) ---------------- style.configure( "Sidebar.TButton", background=sidebar_bg, foreground=sidebar_fg, font=("Segoe UI", 10), padding=(18, 9), borderwidth=0, anchor="w", ) style.map( "Sidebar.TButton", background=[ ("active", sidebar_hover), ("pressed", sidebar_selected), ], foreground=[("active", "#ffffff" if _is_dark(sidebar_hover) else sidebar_fg)], ) style.configure( "SidebarSelected.TButton", background=sidebar_selected, foreground=_contrast_text(sidebar_selected, dark_text=palette["badge_fg"], light_text=sidebar_fg), font=("Segoe UI Semibold", 10), padding=(18, 9), borderwidth=0, anchor="w", ) style.map( "SidebarSelected.TButton", background=[ ("active", _mix(sidebar_selected, "#ffffff", 0.06) if dark_theme else _mix(sidebar_selected, "#000000", 0.06)), ("pressed", _mix(sidebar_selected, "#000000", 0.08)), ], )