Первый выпуск 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>
This commit is contained in:
156
sysdevcleaner/gui/dpg_theme.py
Normal file
156
sysdevcleaner/gui/dpg_theme.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""Тёмная и светлая темы для 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
|
||||
Reference in New Issue
Block a user