Files
Dispatch/Dispatch_V0.1.1/gui/components/toggle_button.py
2026-04-29 08:18:54 +04:00

262 lines
10 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.
# -*- 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).