Add Dispatch_V0.1.1

This commit is contained in:
2026-04-29 08:18:54 +04:00
commit a7ede6ded4
404 changed files with 39167 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
# gui/containers/_widget_style_service.py
"""Сервис локальной стилизации host-виджета без протекания style в subtree."""
from __future__ import annotations
import re
from typing import Optional, TYPE_CHECKING
from PySide6.QtCore import Qt
from gui.styles import APP_STYLES
if TYPE_CHECKING:
from PySide6.QtWidgets import QWidget
_SELECTOR_BLOCK_RE = re.compile(r"([^{}]+)\{")
class WidgetStyleService:
"""Хранит explicit/inherited/effective style и применяет QSS только к host."""
def __init__(self, host: QWidget) -> None:
self._host = host
self._theme = "dark" if host.palette().window().color().lightness() < 128 else "light"
self._is_active = False
self._explicit_style_key: Optional[str] = None
self._explicit_active_style_key: Optional[str] = None
self._inherited_style_key: Optional[str] = None
self._last_style_role = ""
self._last_stylesheet = ""
self._last_styled_background = False
@property
def explicit_style_key(self) -> Optional[str]:
return self._explicit_style_key
@property
def inherited_style_key(self) -> Optional[str]:
return self._inherited_style_key
@property
def effective_style_key(self) -> Optional[str]:
return self._explicit_style_key or self._inherited_style_key
def has_explicit_style(self) -> bool:
return self._explicit_style_key is not None
def apply(
self,
style_key: Optional[str] = None,
active_key: Optional[str] = None,
is_active: Optional[bool] = None,
) -> bool:
previous_effective = self.effective_style_key
previous_render = self._current_render_key()
if style_key is not None:
self._explicit_style_key = style_key
self._explicit_active_style_key = style_key if active_key is None else active_key
elif active_key is not None:
self._explicit_active_style_key = active_key
if is_active is not None:
self._is_active = bool(is_active)
effective_changed = previous_effective != self.effective_style_key
if effective_changed or previous_render != self._current_render_key():
self._refresh_host()
return effective_changed
def set_inherited_style(self, style_key: Optional[str]) -> bool:
previous_effective = self.effective_style_key
previous_render = self._current_render_key()
self._inherited_style_key = style_key
effective_changed = previous_effective != self.effective_style_key
if effective_changed or previous_render != self._current_render_key():
self._refresh_host()
return effective_changed
def handle_theme_changed(self, theme: str) -> None:
theme = (theme or "").strip().lower()
if theme in {"dark", "light"} and theme != self._theme:
self._theme = theme
self._refresh_host()
def _refresh_host(self) -> None:
style_role = self.effective_style_key or ""
render_key = self._current_render_key()
resolved_key = self._resolve_theme_key(render_key)
css = APP_STYLES.get(resolved_key, "") if resolved_key else ""
stylesheet = self._scope_css_to_host(css, style_role) if css and style_role else ""
styled_background = bool(css)
if style_role != self._last_style_role:
self._host.setProperty("style_role", style_role)
self._last_style_role = style_role
if stylesheet != self._last_stylesheet:
self._host.setStyleSheet(stylesheet)
self._last_stylesheet = stylesheet
if styled_background != self._last_styled_background:
self._host.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, styled_background)
self._last_styled_background = styled_background
def _resolve_theme_key(self, style_key: Optional[str]) -> Optional[str]:
if not style_key:
return None
themed_key = f"{style_key}_{self._theme.upper()}"
if themed_key in APP_STYLES:
return themed_key
return style_key if style_key in APP_STYLES else None
def _current_render_key(self) -> Optional[str]:
return self._explicit_active_style_key if self._is_active else self.effective_style_key
def _scope_css_to_host(self, css: str, style_role: str) -> str:
def replace(match: re.Match[str]) -> str:
selectors = []
for raw_selector in match.group(1).split(","):
selector = raw_selector.strip()
selectors.append(self._scope_selector(selector, style_role))
return ", ".join(selectors) + " {"
return _SELECTOR_BLOCK_RE.sub(replace, css)
@staticmethod
def _scope_selector(selector: str, style_role: str) -> str:
if not selector or any(token in selector for token in (" ", ">", "+", "~")):
return selector
if 'style_role="' in selector:
return selector
pseudo_index = selector.find(":")
base = selector if pseudo_index < 0 else selector[:pseudo_index]
suffix = "" if pseudo_index < 0 else selector[pseudo_index:]
return f'{base}[style_role="{style_role}"]{suffix}'