Add Dispatch_V0.1.1
This commit is contained in:
197
Dispatch_V0.1.1/gui/containers/s_container.py
Normal file
197
Dispatch_V0.1.1/gui/containers/s_container.py
Normal file
@@ -0,0 +1,197 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# gui/containers/s_container.py
|
||||
"""Универсальный контейнер с процентным масштабированием по обеим осям.
|
||||
|
||||
Orientation ("v" | "h") определяет направление стэкирования потомков.
|
||||
По умолчанию "v" — вертикальный стэк (самый частый паттерн).
|
||||
|
||||
При width_percent=None ось X — Expanding.
|
||||
При height_percent=None ось Y — Expanding.
|
||||
Таким образом:
|
||||
SContainer() → растягивается по обеим осям
|
||||
SContainer(width_percent=30) → ≡ VContainer (фикс. ширина, свободная высота)
|
||||
SContainer(height_percent=20) → ≡ HContainer (фикс. высота, свободная ширина)
|
||||
SContainer(width_percent=30, height_percent=50) → обе оси фиксированы
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QLayout, QWidget
|
||||
from .percent_sized_widget import PercentSizedWidget
|
||||
from .content_host import ContentHost
|
||||
|
||||
|
||||
class SContainer(PercentSizedWidget):
|
||||
"""Универсальный контейнер с процентным масштабированием."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width_percent: int | float | None = None,
|
||||
height_percent: int | float | None = None,
|
||||
margin: int | tuple[int, int, int, int] = 0,
|
||||
spacing: int = 0,
|
||||
orientation: str = "v",
|
||||
content_width_percent: int | None = None,
|
||||
content_height_percent: int | None = None,
|
||||
content_width: int | None = None,
|
||||
content_height: int | None = None,
|
||||
content_fit: bool = True,
|
||||
content_driven: bool = False,
|
||||
parent: QWidget | None = None,
|
||||
style: str | None = None,
|
||||
active_style: str | None = None,
|
||||
is_active: bool | None = None,
|
||||
):
|
||||
super().__init__(width_percent, height_percent, parent, content_driven=content_driven)
|
||||
self._init_stylable()
|
||||
self._auto_add_children = True
|
||||
|
||||
# Внешний layout — единственный, без промежуточного QGridLayout.
|
||||
if orientation == "h":
|
||||
self._layout: QLayout = QHBoxLayout(self)
|
||||
else:
|
||||
self._layout: QLayout = QVBoxLayout(self)
|
||||
self._layout.setSpacing(0)
|
||||
|
||||
if isinstance(margin, (list, tuple)) and len(margin) == 4:
|
||||
self._layout.setContentsMargins(*margin)
|
||||
else:
|
||||
self._layout.setContentsMargins(margin, margin, margin, margin)
|
||||
|
||||
self._content_host = ContentHost(
|
||||
width_percent=content_width_percent,
|
||||
height_percent=content_height_percent,
|
||||
orientation=orientation,
|
||||
margin=0,
|
||||
spacing=spacing,
|
||||
parent=self,
|
||||
)
|
||||
self._content_host.set_content_fit(content_fit)
|
||||
|
||||
if content_width is not None or content_height is not None:
|
||||
w = content_width if content_width is not None else self._content_host.sizeHint().width()
|
||||
h = content_height if content_height is not None else self._content_host.sizeHint().height()
|
||||
self._content_host.set_fixed_size(w, h)
|
||||
|
||||
self._layout.addWidget(self._content_host)
|
||||
|
||||
if style is not None or active_style is not None or is_active is not None:
|
||||
self._apply_style(style_key=style, active_key=active_style, is_active=is_active)
|
||||
|
||||
# ── публичный API ──
|
||||
|
||||
def add_widget(self, widget: QWidget, alignment=None) -> None:
|
||||
"""Добавляет виджет в layout контейнера."""
|
||||
self._content_host.add_widget(widget)
|
||||
|
||||
def insert_widget(self, index: int, widget: QWidget) -> None:
|
||||
"""Вставляет виджет в layout контента по индексу."""
|
||||
self._content_host.insert_widget(index, widget)
|
||||
|
||||
def remove_widget(self, widget: QWidget) -> None:
|
||||
"""Удаляет виджет из layout контента."""
|
||||
self._content_host.remove_widget(widget)
|
||||
|
||||
def add_widget_with_stretch(self, widget: QWidget, stretch: int, alignment=None) -> None:
|
||||
self._content_host.add_widget_with_stretch(widget, stretch)
|
||||
|
||||
def add_stretch(self, stretch: int = 1) -> None:
|
||||
self._content_host.add_stretch(stretch)
|
||||
|
||||
def invalidate_layout(self) -> None:
|
||||
self._content_host.get_layout().invalidate()
|
||||
|
||||
def get_layout(self) -> QLayout:
|
||||
return self._layout
|
||||
|
||||
def set_margins(self, margin: int | tuple[int, int, int, int]) -> None:
|
||||
if isinstance(margin, (list, tuple)) and len(margin) == 4:
|
||||
self._layout.setContentsMargins(*margin)
|
||||
else:
|
||||
self._layout.setContentsMargins(margin, margin, margin, margin)
|
||||
|
||||
def set_spacing(self, spacing: int) -> None:
|
||||
self._content_host.get_layout().setSpacing(spacing)
|
||||
|
||||
def set_alignment(self, alignment: str) -> None:
|
||||
raise NotImplementedError("Qt alignment for containers is disabled; use content springs.")
|
||||
|
||||
def set_widget_alignment(self, widget: QWidget, alignment: str) -> None:
|
||||
raise NotImplementedError("Qt alignment for containers is disabled; use content springs.")
|
||||
|
||||
def get_available_size_for_content(self) -> tuple[int, int]:
|
||||
"""Полезная внутренняя область (без учёта margin)."""
|
||||
margins = self._layout.contentsMargins()
|
||||
w = self.width() - margins.left() - margins.right()
|
||||
h = self.height() - margins.top() - margins.bottom()
|
||||
return max(0, w), max(0, h)
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module workflow notes
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# 1) Назначение модуля:
|
||||
# Универсальный контейнер с процентным масштабированием по обеим осям.
|
||||
# Ориентация ("v"|"h") определяет направление стэкирования потомков.
|
||||
# Является базовым классом для VContainer и HContainer.
|
||||
# Эквивалентности: SContainer(w%=30) ≡ VContainer(w%=30),
|
||||
# SContainer(h%=20) ≡ HContainer(h%=20).
|
||||
#
|
||||
# 2) Зависимости модуля:
|
||||
# Импорты: QVBoxLayout, QHBoxLayout, QLayout, QWidget (PySide6)
|
||||
# Хост/базовый класс: StylableMixin + PercentSizedWidget (MRO)
|
||||
# Внутренние: ContentHost (content_host.py), StylableMixin (stylable_mixin.py)
|
||||
# Внешние библиотеки: PySide6
|
||||
#
|
||||
# 3) Экспорт:
|
||||
# Класс SContainer — универсальный контейнер.
|
||||
# Методы: add_widget(), add_widget_with_stretch(), add_stretch(),
|
||||
# invalidate_layout(), get_layout(), set_margins(),
|
||||
# set_spacing(), set_alignment(), set_widget_alignment(),
|
||||
# get_available_size_for_content()
|
||||
#
|
||||
# 4) Состояние (поля):
|
||||
# _layout : QVBoxLayout|QHBoxLayout — внешний layout.
|
||||
# _content_host : ContentHost — промежуточный хост для потомков.
|
||||
# _auto_add_children : bool = True — дочерние виджеты авто-добавляются.
|
||||
#
|
||||
# 5) Последовательность действий и вызовов:
|
||||
# __init__(params) -> super().__init__(w%, h%, parent)
|
||||
# -> _init_stylable() -> создание QVBoxLayout/QHBoxLayout по orientation
|
||||
# -> setSpacing(0) на _layout -> setContentsMargins(margin)
|
||||
# -> создание ContentHost(orientation, spacing)
|
||||
# -> _content_host.set_content_fit(content_fit)
|
||||
# -> set_fixed_size(content_width, content_height) если заданы
|
||||
# -> _layout.addWidget(_content_host)
|
||||
# -> _apply_style() если style задан
|
||||
# add_widget(w) -> _content_host.add_widget(w)
|
||||
# set_spacing(s) -> _content_host.get_layout().setSpacing(s)
|
||||
#
|
||||
# 6) Побочные эффекты:
|
||||
# _auto_add_children = True — дочерние PercentSizedWidget авто-добавляются.
|
||||
# set_alignment() и set_widget_alignment() бросают NotImplementedError.
|
||||
# _apply_style() устанавливает stylesheet.
|
||||
# Подписка на theme_bus через StylableMixin.
|
||||
#
|
||||
# 7) Границы ответственности:
|
||||
# НЕ поддерживает spring-based alignment (в отличие от GridContainer).
|
||||
# НЕ содержит сетку — только линейный layout.
|
||||
# НЕ управляет scroll — это ScrollContainer.
|
||||
#
|
||||
# 8) Обработка ошибок:
|
||||
# set_alignment() → NotImplementedError.
|
||||
# set_widget_alignment() → NotImplementedError.
|
||||
# Для обоих: «Qt alignment for containers is disabled; use content springs.»
|
||||
#
|
||||
# 9) Инварианты и контракты:
|
||||
# - orientation ∈ {"v", "h"}, иначе умолчание — вертикальный.
|
||||
# - При w%=None → ось X = Expanding; при h%=None → ось Y = Expanding.
|
||||
# - spacing внешнего _layout всегда 0 (spacing применяется к ContentHost).
|
||||
# - alignment параметр в __init__ — deprecated, игнорируется.
|
||||
#
|
||||
# 10) Правило сопровождения:
|
||||
# Новая логика должна быть совместима с VContainer/HContainer —
|
||||
# они наследуют SContainer. Не вводить логику, специфичную только
|
||||
# для одной ориентации. Spacing: внешний layout = 0, внутренний
|
||||
# (ContentHost) = spacing параметр.
|
||||
Reference in New Issue
Block a user