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,191 @@
# -*- coding: utf-8 -*-
# gui/containers/scroll_container.py
"""Прокручиваемый контейнер с процентным sizing и контейнерным API проекта."""
from __future__ import annotations
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QFrame, QLayout, QScrollArea, QVBoxLayout, QWidget
from .content_host import ContentHost
from .percent_sized_widget import PercentSizedWidget
class ScrollContainer(PercentSizedWidget):
"""Контейнер-обёртка над QScrollArea с поддержкой percent sizing."""
def __init__(
self,
width_percent: int | float | None = None,
height_percent: int | float | None = None,
margin: int | tuple[int, int, int, int] = 0,
content_margins: int | tuple[int, int, int, int] = 0,
spacing: int = 0,
orientation: str = "v",
widget_resizable: bool = True,
vertical_scroll_bar_policy: str | Qt.ScrollBarPolicy = "as_needed",
horizontal_scroll_bar_policy: str | Qt.ScrollBarPolicy = "always_off",
content_fit: 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)
self._init_stylable()
self._auto_add_children = True
self._scroll_area = QScrollArea(self)
self._scroll_area.setWidgetResizable(bool(widget_resizable))
self._scroll_area.setFrameShape(QFrame.Shape.NoFrame)
self._content_host = ContentHost(
orientation=orientation,
margin=content_margins,
spacing=spacing,
parent=self,
)
self._content_host.set_content_fit(bool(content_fit))
self._content_host.get_layout().setSizeConstraint(QLayout.SizeConstraint.SetMinAndMaxSize)
self._scroll_area.setWidget(self._content_host)
self._layout: QVBoxLayout = QVBoxLayout(self)
self._layout.setSpacing(0)
self.set_margins(margin)
self._layout.addWidget(self._scroll_area)
self.set_vertical_scroll_bar_policy(vertical_scroll_bar_policy)
self.set_horizontal_scroll_bar_policy(horizontal_scroll_bar_policy)
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)
@staticmethod
def _normalize_scroll_policy(policy: str | Qt.ScrollBarPolicy) -> Qt.ScrollBarPolicy:
if isinstance(policy, Qt.ScrollBarPolicy):
return policy
token = str(policy or "").strip().lower()
mapping = {
"as_needed": Qt.ScrollBarPolicy.ScrollBarAsNeeded,
"always_off": Qt.ScrollBarPolicy.ScrollBarAlwaysOff,
"always_on": Qt.ScrollBarPolicy.ScrollBarAlwaysOn,
}
if token not in mapping:
raise ValueError(
"Unknown scroll policy. Allowed: 'as_needed', 'always_off', 'always_on'."
)
return mapping[token]
def add_widget(self, widget: QWidget, alignment=None) -> None:
self._content_host.add_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_content_margins(self, margin: int | tuple[int, int, int, int]) -> None:
layout = self._content_host.get_layout()
if isinstance(margin, (list, tuple)) and len(margin) == 4:
layout.setContentsMargins(*margin)
else:
layout.setContentsMargins(margin, margin, margin, margin)
def set_spacing(self, spacing: int) -> None:
self._content_host.get_layout().setSpacing(spacing)
def set_widget_resizable(self, enabled: bool) -> None:
self._scroll_area.setWidgetResizable(bool(enabled))
def set_vertical_scroll_bar_policy(self, policy: str | Qt.ScrollBarPolicy) -> None:
self._scroll_area.setVerticalScrollBarPolicy(self._normalize_scroll_policy(policy))
def set_horizontal_scroll_bar_policy(self, policy: str | Qt.ScrollBarPolicy) -> None:
self._scroll_area.setHorizontalScrollBarPolicy(self._normalize_scroll_policy(policy))
@property
def scroll_area(self) -> QScrollArea:
return self._scroll_area
# ---------------------------------------------------------------------------
# Module workflow notes
# ---------------------------------------------------------------------------
#
# 1) Назначение модуля:
# Прокручиваемый контейнер — обёртка над QScrollArea с поддержкой
# процентного sizing и контейнерного API проекта (add_widget, стилизация,
# тема). Используется для длинных списков, форм и панелей, которые
# не помещаются в видимую область.
#
# 2) Зависимости модуля:
# Импорты: Qt, QFrame, QLayout, QScrollArea, QVBoxLayout, QWidget (PySide6)
# Хост/базовый класс: StylableMixin + PercentSizedWidget (MRO)
# Внутренние: ContentHost (content_host.py), StylableMixin (stylable_mixin.py)
# Внешние библиотеки: PySide6
#
# 3) Экспорт:
# Класс ScrollContainer — прокручиваемый контейнер.
# Методы: add_widget(), add_widget_with_stretch(), add_stretch(),
# invalidate_layout(), get_layout(), set_margins(),
# set_content_margins(), set_spacing(), set_widget_resizable(),
# set_vertical_scroll_bar_policy(), set_horizontal_scroll_bar_policy()
# Свойство: scroll_area (доступ к QScrollArea).
#
# 4) Состояние (поля):
# _scroll_area : QScrollArea — Qt scroll area (NoFrame).
# _content_host : ContentHost — внутренний хост с layout для потомков.
# _layout : QVBoxLayout — внешний layout самого контейнера.
# _auto_add_children: bool = True — потомки авто-добавляются.
#
# 5) Последовательность действий и вызовов:
# __init__(params) -> super().__init__(w%, h%, parent)
# -> _init_stylable() -> создание QScrollArea (NoFrame)
# -> создание ContentHost(orientation, content_margins, spacing)
# -> _content_host.set_content_fit(content_fit)
# -> _content_host.get_layout().setSizeConstraint(SetMinAndMaxSize)
# -> _scroll_area.setWidget(_content_host)
# -> создание _layout (QVBoxLayout) -> _layout.addWidget(_scroll_area)
# -> set_vertical/horizontal_scroll_bar_policy()
# -> _apply_style() если style задан
# add_widget(w) -> _content_host.add_widget(w)
#
# 6) Побочные эффекты:
# ContentHost помещается внутрь QScrollArea как scrollable widget.
# SizeConstraint = SetMinAndMaxSize — контролирует поведение scroll.
# _auto_add_children = True — дочерние PercentSizedWidget авто-добавляются.
# _apply_style() устанавливает stylesheet на self.
#
# 7) Границы ответственности:
# НЕ управляет содержимым скролла — это делает ContentHost.
# НЕ реализует собственный scroll — делегирует QScrollArea.
# НЕ применяет alignment/springs.
#
# 8) Обработка ошибок:
# _normalize_scroll_policy: ValueError при невалидной строке политики.
# Допустимые значения: "as_needed", "always_off", "always_on".
#
# 9) Инварианты и контракты:
# - scroll_bar_policy ∈ {"as_needed", "always_off", "always_on"} или
# Qt.ScrollBarPolicy enum.
# - По умолчанию vertical = as_needed, horizontal = always_off.
# - content_fit по умолчанию False (в отличие от других контейнеров).
# - widget_resizable по умолчанию True.
#
# 10) Правило сопровождения:
# При изменении scroll-политик проверять комбинацию с content_fit
# и widget_resizable — они взаимозависимы. SizeConstraint
# (SetMinAndMaxSize) критичен для правильного поведения scroll.