# -*- coding: utf-8 -*- # gui/containers/content_host.py from PySide6.QtCore import Qt from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QSizePolicy from .percent_sized_widget import PercentSizedWidget class ContentHost(PercentSizedWidget): """Внутренний хост контента с процентными размерами и базовым layout.""" def __init__( self, width_percent: int | None = None, height_percent: int | None = None, orientation: str = "v", margin: int | tuple[int, int, int, int] = 0, spacing: int = 0, parent: QWidget | None = None, ): super().__init__(width_percent, height_percent, parent) if orientation == "h": self._layout = QHBoxLayout(self) else: self._layout = QVBoxLayout(self) self._layout.setSpacing(spacing) if isinstance(margin, (list, tuple)) and len(margin) == 4: self._layout.setContentsMargins(*margin) else: self._layout.setContentsMargins(margin, margin, margin, margin) # Контент-хост по умолчанию растягивается внутри контейнера self.set_content_fit(True) def set_content_fit(self, expand: bool) -> None: """Управление растягиванием контента внутри контейнера.""" if expand: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) else: self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) def _auto_add_to_parent(self) -> None: """Контент-хост добавляется вручную контейнером.""" return def add_widget(self, widget: QWidget, alignment=None) -> None: """Добавляет виджет в layout контейнера.""" self._layout.addWidget(widget) self._notify_children_layout_changed() def insert_widget(self, index: int, widget: QWidget) -> None: """Вставляет виджет в layout контейнера по индексу.""" self._layout.insertWidget(index, widget) self._notify_children_layout_changed() def remove_widget(self, widget: QWidget) -> None: """Удаляет виджет из layout контейнера.""" self._layout.removeWidget(widget) self._notify_children_layout_changed() def add_widget_with_stretch(self, widget: QWidget, stretch: int, alignment=None) -> None: """Добавляет виджет с stretch в layout контейнера.""" self._layout.addWidget(widget, stretch) self._notify_children_layout_changed() def add_stretch(self, stretch: int = 1) -> None: self._layout.addStretch(stretch) def get_layout(self): return self._layout def _notify_children_layout_changed(self) -> None: """Канал «состав детей»: после изменения списка детей host'а просим каждого percent-sized потомка перепланировать свой пересчёт. Коалесцирование обеспечивается флагом _update_pending в каждом потомке — несколько add_widget(...) подряд дают только один singleShot(0) на потомка. """ for index in range(self._layout.count()): item = self._layout.itemAt(index) if item is None: continue child_widget = item.widget() if isinstance(child_widget, PercentSizedWidget): child_widget.schedule_percent_update() # --------------------------------------------------------------------------- # Module workflow notes # --------------------------------------------------------------------------- # # 1) Назначение модуля: # Внутренний хост контента — промежуточный виджет, который размещается # внутри контейнеров (SContainer, GridContainer, StackContainer и пр.) # и содержит реальные дочерние виджеты. Поддерживает процентные размеры, # вертикальную/горизонтальную ориентацию layout'а, отступы и spacing. # Является деталью реализации контейнеров, а не частью публичного API. # # 2) Зависимости модуля: # Импорты: Qt, QVBoxLayout, QHBoxLayout, QWidget, QSizePolicy (PySide6) # Хост/базовый класс: PercentSizedWidget (percent_sized_widget.py) # Внешние библиотеки: PySide6 # # 3) Экспорт: # Класс ContentHost — внутренний хост контента с layout. # Методы: set_content_fit(), add_widget(), add_widget_with_stretch(), # add_stretch(), get_layout() # # 4) Состояние (поля): # _layout : QVBoxLayout | QHBoxLayout — layout для размещения потомков, # выбирается по параметру orientation ("v"/"h"). # # 5) Последовательность действий и вызовов: # __init__(params) -> super().__init__(width_percent, height_percent) # -> создание QVBoxLayout/QHBoxLayout # -> setSpacing() -> setContentsMargins() # -> set_content_fit(True) — SizePolicy = Expanding по умолчанию # add_widget(widget) -> _layout.addWidget(widget) # add_widget_with_stretch(widget, stretch) -> _layout.addWidget(widget, stretch) # # 6) Побочные эффекты: # Устанавливает SizePolicy на self при вызове set_content_fit(). # _auto_add_to_parent() переопределён как no-op — ContentHost добавляется # контейнером вручную, а не через механизм авто-добавления PercentSizedWidget. # # 7) Границы ответственности: # НЕ управляет собственными процентами — это делает PercentSizedWidget. # НЕ стилизуется (не наследует StylableMixin). # НЕ дублирует логику контейнера; лишь предоставляет layout. # # 8) Обработка ошибок: # Нет явной обработки; некорректные margin/spacing молча приведут # к ошибке Qt. Если margin — не tuple(4), воспринимается как int. # # 9) Инварианты и контракты: # - orientation ∈ {"v", "h"}, иначе умолчание — вертикальный. # - margin: int или tuple(4). Иной тип вызовет ошибку setContentsMargins. # - _auto_add_to_parent всегда возвращает None. # # 10) Правило сопровождения: # Не расширять публичный интерфейс ContentHost — он должен оставаться # тонкой обёрткой. Новые фичи (стилизация, alignment) добавлять # в контейнеры, использующие ContentHost, а не сюда.