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,261 @@
# -*- coding: utf-8 -*-
# gui/components/toggle_button.py
"""Компонент кнопки-переключателя на основе SContainer."""
from PySide6.QtWidgets import QToolButton, QSizePolicy
from PySide6.QtCore import Slot
from gui.theme_bus import theme_bus
from gui.containers.s_container import SContainer
from gui.styles import APP_STYLES
class ToggleButton(SContainer):
"""Кнопка-переключатель на основе SContainer со стилями, зависящими от темы."""
def __init__(
self,
text: str,
index: int = 0,
width_percent: int | None = None,
height_percent: int | None = None,
margin: int | tuple[int, int, int, int] = (0, 2, 0, 2),
style: str | None = None,
active_style: str | None = None,
is_active: bool | None = None,
content_fit: bool = True,
parent=None,
):
super().__init__(
width_percent=width_percent,
height_percent=height_percent,
margin=margin,
style=style,
active_style=active_style,
is_active=is_active,
content_fit=content_fit,
parent=parent,
)
self.index = index
self._theme = "dark"
self._is_active = False
self._style_key_normal = None
self._style_key_active = None
self._button = QToolButton()
self._button.setText(text)
self._button.setProperty("widget_index", index)
self._button.setCheckable(True)
self._button.setSizePolicy(
QSizePolicy.Policy.Expanding,
QSizePolicy.Policy.Expanding,
)
super().add_widget(self._button)
self._button.toggled.connect(self._on_toggled)
if style is not None:
self._style_key_normal = style
self._style_key_active = active_style or style
if is_active is not None:
self._is_active = bool(is_active)
self.style()
theme_bus.theme_changed.connect(self.set_theme)
def _on_toggled(self, checked: bool) -> None:
self.style(is_active=checked)
def style(
self,
style_key: str | None = None,
active_key: str | None = None,
is_active: bool | None = None,
) -> None:
"""Короткий метод применения стиля. Можно задать ключи и активность явно."""
if style_key is not None:
self._style_key_normal = style_key
self._style_key_active = active_key or style_key
if is_active is not None:
self._is_active = bool(is_active)
if self._style_key_normal is not None:
normal_key = self._style_key_normal
active_key = self._style_key_active or self._style_key_normal
if self._theme == "light":
themed_normal = f"{normal_key}_LIGHT"
themed_active = f"{active_key}_LIGHT"
if themed_normal in APP_STYLES:
normal_key = themed_normal
if themed_active in APP_STYLES:
active_key = themed_active
key = active_key if self._is_active else normal_key
self._button.setStyleSheet(APP_STYLES.get(key, ""))
return
if self._theme == "light":
if self._is_active and "STANDARD_BUTTON_LIGHT_THEME_ACTIVE" in APP_STYLES:
self._button.setStyleSheet(APP_STYLES["STANDARD_BUTTON_LIGHT_THEME_ACTIVE"])
else:
self._button.setStyleSheet(APP_STYLES["STANDARD_BUTTON_LIGHT_THEME"])
return
if self._is_active:
self._button.setStyleSheet(APP_STYLES["STANDARD_BUTTON_DARK_THEME_ACTIVE"])
else:
self._button.setStyleSheet(APP_STYLES["STANDARD_BUTTON_DARK_THEME"])
@Slot(str)
def set_theme(self, theme: str) -> None:
"""Внешний слот: принимает 'dark' или 'light'."""
theme = (theme or "").strip().lower()
if theme not in ("dark", "light"):
return
if self._theme == theme:
return
self._theme = theme
self.style()
@property
def clicked(self):
return self._button.clicked
@property
def toggled(self):
return self._button.toggled
def click(self) -> None:
self._button.click()
def set_text(self, text: str) -> None:
self._button.setText(text)
def get_text(self) -> str:
return self._button.text()
def set_tooltip(self, text: str) -> None:
self._button.setToolTip(text)
def get_tooltip(self) -> str:
return self._button.toolTip()
def set_checkable(self, checkable: bool) -> None:
self._button.setCheckable(checkable)
def set_checked(self, checked: bool) -> None:
self._button.setChecked(checked)
def is_checked(self) -> bool:
return self._button.isChecked()
def set_enabled(self, enabled: bool) -> None:
self._button.setEnabled(enabled)
super().setEnabled(enabled)
def set_min_width(self, width: int) -> None:
self._button.setMinimumWidth(width)
super().setMinimumWidth(width)
def set_min_height(self, height: int) -> None:
self._button.setMinimumHeight(height)
super().setMinimumHeight(height)
def set_max_width(self, width: int) -> None:
self._button.setMaximumWidth(width)
super().setMaximumWidth(width)
def set_max_height(self, height: int) -> None:
self._button.setMaximumHeight(height)
super().setMaximumHeight(height)
def set_fixed_size(self, width: int, height: int) -> None:
self._button.setMinimumSize(width, height)
self._button.setMaximumSize(width, height)
super().setMinimumSize(width, height)
super().setMaximumSize(width, height)
def set_size_policy(self, horizontal, vertical) -> None:
self._button.setSizePolicy(horizontal, vertical)
super().setSizePolicy(horizontal, vertical)
def add_widget(self, widget, alignment=None):
raise NotImplementedError("ToggleButton может содержать только одну кнопку")
# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
#
# 1) Назначение модуля:
# Кнопка-переключатель (toggle) на основе QToolButton в SContainer,
# автоматически переключающая стиль при toggled и поддерживающая
# темизацию через theme_bus.
#
# 2) Зависимости модуля:
# Импорты: QToolButton, QSizePolicy (PySide6.QtWidgets),
# Slot (PySide6.QtCore),
# theme_bus (gui.theme_bus),
# SContainer (gui.containers.s_container),
# APP_STYLES (gui.styles)
# Хост-класс / базовый класс: SContainer
# Внешние библиотеки: PySide6 (обязательна)
#
# 3) Экспорт:
# Класс ToggleButton — публичный виджет-переключатель.
# Методы: style(), set_theme(), click(), set_text(), get_text(),
# set_tooltip(), get_tooltip(), set_checkable(), set_checked(),
# is_checked(), set_enabled(), set_min/max_width/height(),
# set_fixed_size(), set_size_policy().
# Свойства: clicked, toggled.
#
# 4) Состояние (поля):
# index: int — числовой индекс
# _theme: str — текущая тема
# _is_active: bool — признак активного состояния
# _style_key_normal: str|None — ключ нормального стиля
# _style_key_active: str|None — ключ активного стиля
# _button: QToolButton — внутренний виджет (checkable)
#
# 5) Последовательность действий и вызовов:
# __init__(text, index, ...)
# -> super().__init__(...)
# -> QToolButton() -> setText, setCheckable(True), setSizePolicy
# -> super().add_widget(_button)
# -> _button.toggled.connect(_on_toggled) — автосмена стиля
# -> style() -> theme_bus.theme_changed.connect(set_theme)
# _on_toggled(checked: bool)
# -> style(is_active=checked) — переключение стиля при нажатии
# style(style_key?, active_key?, is_active?)
# -> если _style_key_normal задан:
# -> проверить themed-варианты (_LIGHT) в APP_STYLES
# -> выбрать active или normal ключ
# -> иначе: STANDARD_BUTTON_*_THEME(_ACTIVE)
# -> _button.setStyleSheet(APP_STYLES[key])
#
# 6) Побочные эффекты:
# - Устанавливает stylesheet на QToolButton.
# - Подключается к theme_bus.theme_changed.
# - При toggle — автоматически меняет стиль.
#
# 7) Границы ответственности:
# Модуль НЕ хранит бизнес-логику переключения.
# НЕ группирует кнопки (для этого — RadioGroup/QButtonGroup).
# add_widget() заблокирован.
#
# 8) Обработка ошибок:
# add_widget() бросает NotImplementedError.
# set_theme() молча игнорирует невалидные значения.
#
# 9) Инварианты и контракты:
# - _button всегда checkable.
# - _is_active синхронизирован с checked-состоянием через _on_toggled.
# - _theme ∈ {"dark", "light"}.
#
# 10) Правило сопровождения:
# Отличие от Button: использует QToolButton (checkable по умолчанию),
# автоматически переключает стиль при toggled. Не путать с TabButton
# (специализирован для TabWidget).