Files
SysDevCleaner/sysdevcleaner/gui/dpg_theme.py
HeagBoKaT b01cf37e66 Первый выпуск SysDevCleaner
Анализ Windows и очистка кешей, мусора и остатков разработки.

Ядро:
- каталог правил: 228 правил путей (496 шаблонов) + 43 правила артефактов
  проектов — Windows, браузеры, кеши сборки, IDE, приложения, пакетные
  менеджеры;
- рекурсивный поиск node_modules, target, build, __pycache__, .venv и т.п.
  с проверкой маркеров проекта, чтобы не ловить ложные срабатывания;
- поиск крупных файлов и дубликатов (хеш по началу/середине/концу и полная
  сверка), инвентаризация установленных программ через реестр;
- ограничение области сканирования по дискам;
- рекомендации и текстовый отчёт.

Безопасность:
- четыре уровня риска, автоматически отмечается только «Безопасно»;
- список защищённых путей проверяется и при выборе, и перед удалением;
- для каталогов вроде %TEMP% удаляется содержимое, а не сам каталог;
- удаление в Корзину по умолчанию, пробный запуск, журнал очисток.

Интерфейс на Dear PyGui:
- JetBrains Mono Nerd Font: кириллица и иконки в одном шрифте;
- тёмная и светлая темы, адаптивная раскладка;
- заголовок окна ставится через SetWindowTextW — Dear PyGui отдаёт его
  в ANSI-функцию как UTF-8 и портит кириллицу;
- запуск всегда с правами администратора: манифест UAC в exe и
  самоперезапуск через ShellExecuteW при работе из исходников.

Сборка в один exe через PyInstaller со вшитым шрифтом и значком.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 14:55:00 +03:00

157 lines
6.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Тёмная и светлая темы для Dear PyGui."""
from __future__ import annotations
import dearpygui.dearpygui as dpg
from ..core.model import Risk
DARK = {
"bg": (27, 29, 34),
"bg2": (34, 37, 43),
"bg3": (42, 46, 54),
"fg": (230, 232, 236),
"fg_dim": (154, 161, 173),
"accent": (74, 144, 217),
"accent_hi": (95, 164, 232),
"ok": (62, 181, 110),
"warn": (217, 154, 43),
"danger": (217, 83, 79),
"border": (52, 57, 67),
"sel": (47, 58, 74),
}
LIGHT = {
"bg": (244, 245, 247),
"bg2": (255, 255, 255),
"bg3": (233, 235, 239),
"fg": (29, 33, 39),
"fg_dim": (95, 103, 115),
"accent": (47, 111, 181),
"accent_hi": (61, 130, 207),
"ok": (30, 122, 69),
"warn": (165, 113, 15),
"danger": (184, 51, 47),
"border": (207, 212, 220),
"sel": (214, 228, 245),
}
def risk_color(risk: Risk, palette: dict) -> tuple[int, int, int]:
return {
Risk.SAFE: palette["ok"],
Risk.LOW: palette["accent_hi"],
Risk.MEDIUM: palette["warn"],
Risk.HIGH: palette["danger"],
}[risk]
def _rgba(c: tuple[int, int, int], a: int = 255) -> tuple[int, int, int, int]:
return (c[0], c[1], c[2], a)
def build_theme(p: dict) -> int:
"""Глобальная тема под палитру ``p``."""
with dpg.theme() as theme:
with dpg.theme_component(dpg.mvAll):
col = dpg.add_theme_color
col(dpg.mvThemeCol_WindowBg, _rgba(p["bg"]))
col(dpg.mvThemeCol_ChildBg, _rgba(p["bg"]))
col(dpg.mvThemeCol_PopupBg, _rgba(p["bg2"]))
col(dpg.mvThemeCol_Border, _rgba(p["border"]))
col(dpg.mvThemeCol_BorderShadow, (0, 0, 0, 0))
col(dpg.mvThemeCol_Text, _rgba(p["fg"]))
col(dpg.mvThemeCol_TextDisabled, _rgba(p["fg_dim"]))
col(dpg.mvThemeCol_FrameBg, _rgba(p["bg3"]))
col(dpg.mvThemeCol_FrameBgHovered, _rgba(p["border"]))
col(dpg.mvThemeCol_FrameBgActive, _rgba(p["sel"]))
col(dpg.mvThemeCol_TitleBg, _rgba(p["bg2"]))
col(dpg.mvThemeCol_TitleBgActive, _rgba(p["bg2"]))
col(dpg.mvThemeCol_TitleBgCollapsed, _rgba(p["bg2"]))
col(dpg.mvThemeCol_MenuBarBg, _rgba(p["bg2"]))
col(dpg.mvThemeCol_Button, _rgba(p["bg3"]))
col(dpg.mvThemeCol_ButtonHovered, _rgba(p["border"]))
col(dpg.mvThemeCol_ButtonActive, _rgba(p["sel"]))
col(dpg.mvThemeCol_Header, _rgba(p["bg2"]))
col(dpg.mvThemeCol_HeaderHovered, _rgba(p["sel"]))
col(dpg.mvThemeCol_HeaderActive, _rgba(p["sel"]))
col(dpg.mvThemeCol_Tab, _rgba(p["bg"]))
col(dpg.mvThemeCol_TabHovered, _rgba(p["bg3"]))
col(dpg.mvThemeCol_TabActive, _rgba(p["bg2"]))
col(dpg.mvThemeCol_TabUnfocused, _rgba(p["bg"]))
col(dpg.mvThemeCol_TabUnfocusedActive, _rgba(p["bg2"]))
col(dpg.mvThemeCol_CheckMark, _rgba(p["accent_hi"]))
col(dpg.mvThemeCol_SliderGrab, _rgba(p["accent"]))
col(dpg.mvThemeCol_SliderGrabActive, _rgba(p["accent_hi"]))
col(dpg.mvThemeCol_PlotHistogram, _rgba(p["accent"]))
col(dpg.mvThemeCol_PlotHistogramHovered, _rgba(p["accent_hi"]))
col(dpg.mvThemeCol_ScrollbarBg, _rgba(p["bg"]))
col(dpg.mvThemeCol_ScrollbarGrab, _rgba(p["bg3"]))
col(dpg.mvThemeCol_ScrollbarGrabHovered, _rgba(p["border"]))
col(dpg.mvThemeCol_ScrollbarGrabActive, _rgba(p["accent"]))
col(dpg.mvThemeCol_Separator, _rgba(p["border"]))
col(dpg.mvThemeCol_TableHeaderBg, _rgba(p["bg3"]))
col(dpg.mvThemeCol_TableBorderStrong, _rgba(p["border"]))
col(dpg.mvThemeCol_TableBorderLight, _rgba(p["border"]))
col(dpg.mvThemeCol_TableRowBg, _rgba(p["bg"]))
col(dpg.mvThemeCol_TableRowBgAlt, _rgba(p["bg2"], 120))
st = dpg.add_theme_style
st(dpg.mvStyleVar_FrameRounding, 4)
st(dpg.mvStyleVar_ChildRounding, 6)
st(dpg.mvStyleVar_PopupRounding, 6)
st(dpg.mvStyleVar_WindowRounding, 6)
st(dpg.mvStyleVar_GrabRounding, 4)
st(dpg.mvStyleVar_TabRounding, 4)
st(dpg.mvStyleVar_ScrollbarRounding, 6)
st(dpg.mvStyleVar_FramePadding, 10, 6)
st(dpg.mvStyleVar_ItemSpacing, 9, 7)
st(dpg.mvStyleVar_ItemInnerSpacing, 7, 5)
st(dpg.mvStyleVar_WindowPadding, 14, 12)
st(dpg.mvStyleVar_CellPadding, 7, 5)
st(dpg.mvStyleVar_ScrollbarSize, 13)
st(dpg.mvStyleVar_WindowBorderSize, 0)
st(dpg.mvStyleVar_FrameBorderSize, 0)
return theme
def build_accent_button_theme(p: dict, base: str = "accent") -> int:
"""Тема для выделенной кнопки (основное действие / опасное действие)."""
color = p[base]
hi = tuple(min(255, v + 22) for v in color)
with dpg.theme() as theme:
with dpg.theme_component(dpg.mvAll):
dpg.add_theme_color(dpg.mvThemeCol_Button, _rgba(color))
dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, _rgba(hi))
dpg.add_theme_color(dpg.mvThemeCol_ButtonActive, _rgba(color))
dpg.add_theme_color(dpg.mvThemeCol_Text, (255, 255, 255, 255))
dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 14, 8)
return theme
def build_card_theme(p: dict) -> int:
"""Тема для «карточки» — дочернего окна с более светлым фоном."""
with dpg.theme() as theme:
with dpg.theme_component(dpg.mvAll):
dpg.add_theme_color(dpg.mvThemeCol_ChildBg, _rgba(p["bg2"]))
dpg.add_theme_style(dpg.mvStyleVar_ChildRounding, 8)
dpg.add_theme_style(dpg.mvStyleVar_WindowPadding, 16, 14)
return theme
def build_bar_theme(p: dict, color_key: str) -> int:
"""Тема полосы заполнения (диски, категории)."""
with dpg.theme() as theme:
with dpg.theme_component(dpg.mvAll):
dpg.add_theme_color(dpg.mvThemeCol_PlotHistogram, _rgba(p[color_key]))
dpg.add_theme_color(dpg.mvThemeCol_FrameBg, _rgba(p["bg3"]))
dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 3)
return theme