220 lines
8.5 KiB
Python
220 lines
8.5 KiB
Python
# -*- coding: utf-8 -*-
|
||
# gui/components/tab_button.py
|
||
|
||
from PySide6.QtWidgets import QPushButton, QSizePolicy
|
||
from PySide6.QtCore import Slot
|
||
from gui.containers.s_container import SContainer
|
||
from gui.styles import APP_STYLES
|
||
from gui.theme_bus import theme_bus
|
||
|
||
|
||
class TabButton(SContainer):
|
||
"""Обёртка кнопки вкладки со стилями, зависящими от темы."""
|
||
|
||
def __init__(self, text: str, index: int, **kwargs):
|
||
width_percent = kwargs.get("width_percent", None)
|
||
height_percent = kwargs.get("height_percent", None)
|
||
margin = kwargs.get("margin", [0, 2, 0, 2])
|
||
style = kwargs.get("style", None)
|
||
active_style = kwargs.get("active_style", None)
|
||
is_active = kwargs.get("is_active", None)
|
||
content_fit = kwargs.get("content_fit", True)
|
||
|
||
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,
|
||
)
|
||
|
||
self.index = index
|
||
self._theme = "dark"
|
||
|
||
self._button = QPushButton(text)
|
||
self._button.setProperty("tab_index", index)
|
||
|
||
super().add_widget(self._button)
|
||
|
||
self._button.setSizePolicy(
|
||
QSizePolicy.Policy.Expanding,
|
||
QSizePolicy.Policy.Expanding
|
||
)
|
||
|
||
self._style_key_normal = style or "TAB_BUTTON_NORMAL"
|
||
self._style_key_active = active_style or "TAB_BUTTON_ACTIVE"
|
||
self._is_active = bool(is_active) if is_active is not None else False
|
||
self.style()
|
||
theme_bus.theme_changed.connect(self.set_theme)
|
||
|
||
def style(
|
||
self,
|
||
style_key: str | None = None,
|
||
active_key: str | None = None,
|
||
is_active: bool | 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)
|
||
normal_key = self._style_key_normal
|
||
active_key_to_use = self._style_key_active
|
||
|
||
if self._theme == "light":
|
||
themed_normal = f"{normal_key}_LIGHT"
|
||
themed_active = f"{active_key_to_use}_LIGHT"
|
||
if themed_normal in APP_STYLES:
|
||
normal_key = themed_normal
|
||
if themed_active in APP_STYLES:
|
||
active_key_to_use = themed_active
|
||
|
||
key = active_key_to_use if self._is_active else normal_key
|
||
self._button.setStyleSheet(APP_STYLES.get(key, ""))
|
||
|
||
@Slot(str)
|
||
def set_theme(self, theme: str):
|
||
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
|
||
|
||
def click(self):
|
||
self._button.click()
|
||
|
||
def set_text(self, text: str):
|
||
self._button.setText(text)
|
||
|
||
def get_text(self) -> str:
|
||
return self._button.text()
|
||
|
||
def set_enabled(self, enabled: bool) -> None:
|
||
self._button.setEnabled(enabled)
|
||
super().setEnabled(enabled)
|
||
|
||
def set_tooltip(self, text: str) -> None:
|
||
self._button.setToolTip(text)
|
||
|
||
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_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_size_policy(self, horizontal, vertical) -> None:
|
||
self._button.setSizePolicy(horizontal, vertical)
|
||
super().setSizePolicy(horizontal, vertical)
|
||
|
||
def set_property(self, name: str, value) -> None:
|
||
super().setProperty(name, value)
|
||
self._button.setProperty(name, value)
|
||
|
||
def add_widget(self, widget, alignment=None):
|
||
raise NotImplementedError("TabButton can contain only one QPushButton")
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Module workflow notes
|
||
# ---------------------------------------------------------------------------
|
||
#
|
||
# 1) Назначение модуля:
|
||
# Кнопка вкладки для TabWidget, реализованная как SContainer
|
||
# с QPushButton внутри и стилями, зависящими от темы.
|
||
# Поддерживает нормальное и активное состояние для визуализации
|
||
# текущей вкладки.
|
||
#
|
||
# 2) Зависимости модуля:
|
||
# Импорты: QPushButton, QSizePolicy (PySide6.QtWidgets),
|
||
# Slot (PySide6.QtCore),
|
||
# SContainer (gui.containers.s_container),
|
||
# APP_STYLES (gui.styles),
|
||
# theme_bus (gui.theme_bus)
|
||
# Хост-класс / базовый класс: SContainer
|
||
# Внешние библиотеки: PySide6 (обязательна)
|
||
#
|
||
# 3) Экспорт:
|
||
# Класс TabButton — кнопка вкладки.
|
||
# Методы: style(), set_theme(), click(), set_text(), get_text(),
|
||
# set_enabled(), set_tooltip(), set_checkable(), set_checked(),
|
||
# is_checked(), set_min/max_width/height(), set_fixed_size(),
|
||
# set_size_policy(), set_property().
|
||
# Свойство: clicked.
|
||
#
|
||
# 4) Состояние (поля):
|
||
# index: int — номер вкладки
|
||
# _theme: str — текущая тема
|
||
# _button: QPushButton — внутренняя кнопка
|
||
# _style_key_normal: str — ключ нормального стиля (TAB_BUTTON_NORMAL)
|
||
# _style_key_active: str — ключ активного стиля (TAB_BUTTON_ACTIVE)
|
||
# _is_active: bool — признак активной вкладки
|
||
#
|
||
# 5) Последовательность действий и вызовов:
|
||
# __init__(text, index, **kwargs)
|
||
# -> super().__init__(...)
|
||
# -> QPushButton(text) -> setProperty("tab_index", index)
|
||
# -> super().add_widget(_button) -> setSizePolicy(Expanding)
|
||
# -> style() -> theme_bus.theme_changed.connect(set_theme)
|
||
# style(style_key?, active_key?, is_active?)
|
||
# -> для light-темы: проверить суффикс _LIGHT в APP_STYLES
|
||
# -> выбрать active или normal ключ
|
||
# -> _button.setStyleSheet(APP_STYLES[key])
|
||
# Вызывается из TabWidget._apply_active_style(index)
|
||
#
|
||
# 6) Побочные эффекты:
|
||
# - Устанавливает stylesheet на QPushButton.
|
||
# - Подключается к theme_bus.theme_changed.
|
||
#
|
||
# 7) Границы ответственности:
|
||
# Модуль НЕ управляет содержимым вкладок.
|
||
# НЕ переключает страницы — это делает TabWidget.
|
||
# add_widget() заблокирован.
|
||
#
|
||
# 8) Обработка ошибок:
|
||
# add_widget() бросает NotImplementedError.
|
||
# set_theme() молча игнорирует невалидные значения.
|
||
#
|
||
# 9) Инварианты и контракты:
|
||
# - Контейнер содержит ровно одну QPushButton.
|
||
# - _theme ∈ {"dark", "light"}.
|
||
# - index совпадает с порядковым номером вкладки в TabWidget.
|
||
#
|
||
# 10) Правило сопровождения:
|
||
# Стили TAB_BUTTON_NORMAL / TAB_BUTTON_ACTIVE должны быть
|
||
# определены в APP_STYLES. Для light-темы — суффикс _LIGHT.
|
||
# Не менять index после добавления в TabWidget (переиндексация
|
||
# делается в TabWidget.remove_tab()).
|