diff --git a/discord/components.py b/discord/components.py index 962be86f7..f06eda2f6 100644 --- a/discord/components.py +++ b/discord/components.py @@ -732,17 +732,13 @@ class SectionComponent(Component): def __init__(self, data: SectionComponentPayload, state: Optional[ConnectionState]) -> None: self.components: List[SectionComponentType] = [] + self.accessory: Component = _component_factory(data['accessory'], state) for component_data in data['components']: component = _component_factory(component_data, state) if component is not None: self.components.append(component) # type: ignore # should be the correct type here - try: - self.accessory: Optional[Component] = _component_factory(data['accessory']) # type: ignore - except KeyError: - self.accessory = None - @property def type(self) -> Literal[ComponentType.section]: return ComponentType.section @@ -751,9 +747,8 @@ class SectionComponent(Component): payload: SectionComponentPayload = { 'type': self.type.value, 'components': [c.to_dict() for c in self.components], + 'accessory': self.accessory.to_dict() } - if self.accessory: - payload['accessory'] = self.accessory.to_dict() return payload diff --git a/discord/types/components.py b/discord/types/components.py index 98201817a..bb241c9ac 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -128,7 +128,7 @@ class SelectMenu(SelectComponent): class SectionComponent(ComponentBase): type: Literal[9] components: List[Union[TextComponent, ButtonComponent]] - accessory: NotRequired[ComponentBase] + accessory: ComponentBase class TextComponent(ComponentBase): diff --git a/discord/ui/container.py b/discord/ui/container.py index b49e1a700..2acf95d20 100644 --- a/discord/ui/container.py +++ b/discord/ui/container.py @@ -29,6 +29,7 @@ from .item import Item from .view import View, _component_to_item from .dynamic import DynamicItem from ..enums import ComponentType +from ..utils import MISSING if TYPE_CHECKING: from typing_extensions import Self @@ -61,13 +62,20 @@ class Container(View, Item[V]): timeout: Optional[:class:`float`] Timeout in seconds from last interaction with the UI before no longer accepting input. If ``None`` then there is no timeout. + row: Optional[:class:`int`] + The relative row this container belongs to. By default + items are arranged automatically into those rows. If you'd + like to control the relative positioning of the row then + passing an index is advised. For example, row=1 will show + up before row=2. Defaults to ``None``, which is automatic + ordering. The row number must be between 0 and 9 (i.e. zero indexed) """ __discord_ui_container__ = True def __init__( self, - children: List[Item[Any]], + children: List[Item[Any]] = MISSING, *, accent_colour: Optional[Colour] = None, accent_color: Optional[Color] = None, @@ -76,9 +84,10 @@ class Container(View, Item[V]): row: Optional[int] = None, ) -> None: super().__init__(timeout=timeout) - if len(children) + len(self._children) > 10: - raise ValueError('maximum number of components exceeded') - self._children.extend(children) + if children is not MISSING: + if len(children) + len(self._children) > 10: + raise ValueError('maximum number of components exceeded') + self._children.extend(children) self.spoiler: bool = spoiler self._colour = accent_colour or accent_color @@ -87,11 +96,6 @@ class Container(View, Item[V]): self._rendered_row: Optional[int] = None self.row: Optional[int] = row - def _init_children(self) -> List[Item[Self]]: - if self.__weights.max_weight != 10: - self.__weights.max_weight = 10 - return super()._init_children() - @property def children(self) -> List[Item[Self]]: """List[:class:`Item`]: The children of this container.""" diff --git a/discord/ui/section.py b/discord/ui/section.py index f8b8ea4e2..ce87b99f4 100644 --- a/discord/ui/section.py +++ b/discord/ui/section.py @@ -28,6 +28,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, TypeVar, U from .item import Item from .text_display import TextDisplay from ..enums import ComponentType +from ..utils import MISSING if TYPE_CHECKING: from typing_extensions import Self @@ -37,6 +38,8 @@ if TYPE_CHECKING: V = TypeVar('V', bound='View', covariant=True) +__all__ = ('Section',) + class Section(Item[V]): """Represents a UI section. @@ -47,8 +50,8 @@ class Section(Item[V]): ---------- children: List[Union[:class:`str`, :class:`TextDisplay`]] The text displays of this section. Up to 3. - accessory: Optional[:class:`Item`] - The section accessory. Defaults to ``None``. + accessory: :class:`Item` + The section accessory. row: Optional[:class:`int`] The relative row this section belongs to. By default items are arranged automatically into those rows. If you'd @@ -65,16 +68,23 @@ class Section(Item[V]): def __init__( self, - children: List[Union[Item[Any], str]], + children: List[Union[Item[Any], str]] = MISSING, *, - accessory: Optional[Item[Any]] = None, + accessory: Item[Any], row: Optional[int] = None, ) -> None: super().__init__() - if len(children) > 3: - raise ValueError('maximum number of children exceeded') - self._children: List[Item[Any]] = [c if isinstance(c, Item) else TextDisplay(c) for c in children] - self.accessory: Optional[Item[Any]] = accessory + self._children: List[Item[Any]] = [] + if children is not MISSING: + if len(children) > 3: + raise ValueError('maximum number of children exceeded') + self._children.extend( + [ + c if isinstance(c, Item) + else TextDisplay(c) for c in children + ], + ) + self.accessory: Item[Any] = accessory self.row = row @@ -106,13 +116,14 @@ class Section(Item[V]): Parameters ---------- - item: Union[:class:`str`, :class:`TextDisplay`] - The text display to add. + item: Union[:class:`str`, :class:`Item`] + The items to append, if it is a string it automatically wrapped around + :class:`TextDisplay`. Raises ------ TypeError - A :class:`TextDisplay` was not passed. + An :class:`Item` or :class:`str` was not passed. ValueError Maximum number of children has been exceeded (3). """ @@ -161,14 +172,13 @@ class Section(Item[V]): return cls( children=[_component_to_item(c) for c in component.components], - accessory=_component_to_item(component.accessory) if component.accessory else None, + accessory=_component_to_item(component.accessory), ) def to_component_dict(self) -> Dict[str, Any]: data = { 'components': [c.to_component_dict() for c in self._children], 'type': self.type.value, + 'accessory': self.accessory.to_component_dict() } - if self.accessory: - data['accessory'] = self.accessory.to_component_dict() return data diff --git a/discord/ui/text_display.py b/discord/ui/text_display.py index 9a70bd247..1bf88678d 100644 --- a/discord/ui/text_display.py +++ b/discord/ui/text_display.py @@ -60,14 +60,14 @@ class TextDisplay(Item[V]): def __init__(self, content: str, *, row: Optional[int] = None) -> None: super().__init__() self.content: str = content - self._underlying = TextDisplayComponent._raw_construct( - content=content, - ) self.row = row def to_component_dict(self): - return self._underlying.to_dict() + return { + 'type': self.type.value, + 'content': self.content, + } @property def width(self): @@ -75,7 +75,7 @@ class TextDisplay(Item[V]): @property def type(self) -> Literal[ComponentType.text_display]: - return self._underlying.type + return ComponentType.text_display def _is_v2(self) -> bool: return True diff --git a/discord/ui/view.py b/discord/ui/view.py index e701d09e9..6ac69d66e 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE. """ from __future__ import annotations -from typing import Any, Callable, ClassVar, Coroutine, Dict, Iterator, List, Optional, Sequence, TYPE_CHECKING, Tuple, Type +from typing import Any, Callable, ClassVar, Coroutine, Dict, Iterator, List, Optional, Sequence, TYPE_CHECKING, Tuple, Type, Union from functools import partial from itertools import groupby @@ -119,13 +119,11 @@ class _ViewWeights: # fmt: off __slots__ = ( 'weights', - 'max_weight', ) # fmt: on def __init__(self, children: List[Item]): self.weights: List[int] = [0, 0, 0, 0, 0] - self.max_weight: int = 5 key = lambda i: sys.maxsize if i.row is None else i.row children = sorted(children, key=key) @@ -146,8 +144,8 @@ class _ViewWeights: self.weights.extend([0, 0, 0, 0, 0]) if item.row is not None: total = self.weights[item.row] + item.width - if total > self.max_weight: - raise ValueError(f'item would not fit at row {item.row} ({total} > {self.max_weight} width)') + if total > 5: + raise ValueError(f'item would not fit at row {item.row} ({total} > 5 width)') self.weights[item.row] = total item._rendered_row = item.row else: @@ -196,15 +194,15 @@ class View: __discord_ui_view__: ClassVar[bool] = True __discord_ui_modal__: ClassVar[bool] = False __discord_ui_container__: ClassVar[bool] = False - __view_children_items__: ClassVar[List[ItemCallbackType[Any, Any]]] = [] + __view_children_items__: ClassVar[List[Union[ItemCallbackType[Any, Any], Item[Any]]]] = [] def __init_subclass__(cls) -> None: super().__init_subclass__() - children: Dict[str, ItemCallbackType[Any, Any]] = {} + children: Dict[str, Union[ItemCallbackType[Any, Any], Item]] = {} for base in reversed(cls.__mro__): for name, member in base.__dict__.items(): - if hasattr(member, '__discord_ui_model_type__'): + if hasattr(member, '__discord_ui_model_type__') or isinstance(member, Item): children[name] = member if len(children) > 25: @@ -214,12 +212,16 @@ class View: def _init_children(self) -> List[Item[Self]]: children = [] + for func in self.__view_children_items__: - item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__) - item.callback = _ViewCallback(func, self, item) # type: ignore - item._view = self - setattr(self, func.__name__, item) - children.append(item) + if isinstance(func, Item): + children.append(func) + else: + item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__) + item.callback = _ViewCallback(func, self, item) # type: ignore + item._view = self + setattr(self, func.__name__, item) + children.append(item) return children def __init__(self, *, timeout: Optional[float] = 180.0): @@ -275,7 +277,13 @@ class View: # v2 components def key(item: Item) -> int: - return item._rendered_row or 0 + if item._rendered_row is not None: + return item._rendered_row + + try: + return self._children.index(item) + except ValueError: + return 0 # instead of grouping by row we will sort it so it is added # in order and should work as the original implementation @@ -290,7 +298,7 @@ class View: index = rows_index.get(row) if index is not None: - components[index]['components'].append(child) + components[index]['components'].append(child.to_component_dict()) else: components.append( {