diff --git a/discord/ui/action_row.py b/discord/ui/action_row.py index c7f7a2b7b..38fc3daaa 100644 --- a/discord/ui/action_row.py +++ b/discord/ui/action_row.py @@ -24,12 +24,12 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations +import copy from typing import ( TYPE_CHECKING, Any, Callable, ClassVar, - Coroutine, Dict, Generator, List, @@ -42,7 +42,7 @@ from typing import ( overload, ) -from .item import Item, ContainedItemCallbackType as ItemCallbackType +from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback from .button import Button, button as _button from .select import select as _select, Select, UserSelect, RoleSelect, ChannelSelect, MentionableSelect from ..components import ActionRow as ActionRowComponent @@ -65,7 +65,6 @@ if TYPE_CHECKING: ) from ..emoji import Emoji from ..components import SelectOption - from ..interactions import Interaction from .container import Container from .dynamic import DynamicItem @@ -77,18 +76,6 @@ V = TypeVar('V', bound='LayoutView', covariant=True) __all__ = ('ActionRow',) -class _ActionRowCallback: - __slots__ = ('row', 'callback', 'item') - - def __init__(self, callback: ItemCallbackType[S, Any], row: ActionRow, item: Item[Any]) -> None: - self.callback: ItemCallbackType[Any, Any] = callback - self.row: ActionRow = row - self.item: Item[Any] = item - - def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]: - return self.callback(self.row, interaction, self.item) - - class ActionRow(Item[V]): r"""Represents a UI action row. @@ -143,8 +130,9 @@ class ActionRow(Item[V]): ) -> None: super().__init__() self._children: List[Item[V]] = self._init_children() - self._children.extend(children) self._weight: int = sum(i.width for i in self._children) + for child in children: + self.add_item(child) if self._weight > 5: raise ValueError('maximum number of children exceeded') @@ -173,8 +161,8 @@ class ActionRow(Item[V]): for func in self.__action_row_children_items__: item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__) - item.callback = _ActionRowCallback(func, self, item) # type: ignore - item._parent = getattr(func, '__discord_ui_parent__', self) + item.callback = _ItemCallback(func, self, item) # type: ignore + item._parent = self setattr(self, func.__name__, item) children.append(item) return children @@ -184,6 +172,23 @@ class ActionRow(Item[V]): for child in self._children: child._view = view + def copy(self) -> ActionRow[V]: + new = copy.copy(self) + children = [] + for child in new._children: + newch = child.copy() + newch._parent = new + if isinstance(newch.callback, _ItemCallback): + newch.callback.parent = new + children.append(newch) + new._children = children + new._parent = self._parent + new._update_view(self.view) + return new + + def __deepcopy__(self, memo) -> ActionRow[V]: + return self.copy() + def _has_children(self): return True diff --git a/discord/ui/button.py b/discord/ui/button.py index f80065963..4c1e4cc89 100644 --- a/discord/ui/button.py +++ b/discord/ui/button.py @@ -30,7 +30,7 @@ import inspect import os -from .item import Item, ContainedItemCallbackType as ItemCallbackType +from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback from ..enums import ButtonStyle, ComponentType from ..partial_emoji import PartialEmoji, _EmojiTag from ..components import Button as ButtonComponent @@ -304,6 +304,9 @@ class Button(Item[V]): sku_id=self.sku_id, id=self.id, ) + if isinstance(new.callback, _ItemCallback): + new.callback.item = new + new._update_view(self.view) return new def __deepcopy__(self, memo) -> Self: diff --git a/discord/ui/container.py b/discord/ui/container.py index 3025e17f8..600b0687b 100644 --- a/discord/ui/container.py +++ b/discord/ui/container.py @@ -29,7 +29,6 @@ from typing import ( TYPE_CHECKING, Any, ClassVar, - Coroutine, Dict, Generator, List, @@ -39,7 +38,7 @@ from typing import ( Union, ) -from .item import Item, ContainedItemCallbackType as ItemCallbackType +from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback from .view import _component_to_item, LayoutView from ..enums import ComponentType from ..utils import get as _utils_get @@ -49,7 +48,6 @@ if TYPE_CHECKING: from typing_extensions import Self from ..components import Container as ContainerComponent - from ..interactions import Interaction from .dynamic import DynamicItem S = TypeVar('S', bound='Container', covariant=True) @@ -58,18 +56,6 @@ V = TypeVar('V', bound='LayoutView', covariant=True) __all__ = ('Container',) -class _ContainerCallback: - __slots__ = ('container', 'callback', 'item') - - def __init__(self, callback: ItemCallbackType[S, Any], container: Container, item: Item[Any]) -> None: - self.callback: ItemCallbackType[Any, Any] = callback - self.container: Container = container - self.item: Item[Any] = item - - def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]: - return self.callback(self.container, interaction, self.item) - - class Container(Item[V]): r"""Represents a UI container. @@ -163,7 +149,7 @@ class Container(Item[V]): # action rows can be created inside containers, and then callbacks can exist here # so we create items based off them item: Item = raw.__discord_ui_model_type__(**raw.__discord_ui_model_kwargs__) - item.callback = _ContainerCallback(raw, self, item) # type: ignore + item.callback = _ItemCallback(raw, self, item) # type: ignore setattr(self, raw.__name__, item) # this should not fail because in order for a function to be here it should be from # an action row and must have passed the check in __init_subclass__, but still @@ -196,6 +182,15 @@ class Container(Item[V]): child._update_view(view) return True + def copy(self) -> Container[V]: + new = copy.deepcopy(self) + for child in new._children: + newch = child.copy() + newch._parent = new + new._parent = self._parent + new._update_view(self.view) + return new + def _has_children(self): return True diff --git a/discord/ui/item.py b/discord/ui/item.py index 8f716559c..4c0dd6110 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -55,6 +55,21 @@ ItemCallbackType = Callable[[V, Interaction[Any], I], Coroutine[Any, Any, Any]] ContainedItemCallbackType = Callable[[C, Interaction[Any], I], Coroutine[Any, Any, Any]] +class _ItemCallback: + __slots__ = ('parent', 'callback', 'item') + + def __init__(self, callback: ContainedItemCallbackType[Any, Any], parent: Any, item: Item[Any]) -> None: + self.callback: ItemCallbackType[Any, Any] = callback + self.parent: Any = parent + self.item: Item[Any] = item + + def __repr__(self) -> str: + return f'' + + def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]: + return self.callback(self.parent, interaction, self.item) + + class Item(Generic[V]): """Represents the base UI item that all UI components inherit from. diff --git a/discord/ui/select.py b/discord/ui/select.py index 7668619c6..b003f8fcb 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -39,10 +39,11 @@ from typing import ( Sequence, ) from contextvars import ContextVar +import copy import inspect import os -from .item import Item, ContainedItemCallbackType as ItemCallbackType +from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback from ..enums import ChannelType, ComponentType, SelectDefaultValueType from ..partial_emoji import PartialEmoji from ..emoji import Emoji @@ -70,7 +71,7 @@ __all__ = ( ) if TYPE_CHECKING: - from typing_extensions import TypeAlias, TypeGuard + from typing_extensions import TypeAlias, TypeGuard, Self from .view import BaseView from .action_row import ActionRow @@ -269,6 +270,14 @@ class BaseSelect(Item[V]): self.row = row self._values: List[PossibleValue] = [] + def copy(self) -> Self: + new = copy.copy(self) + if isinstance(new.callback, _ItemCallback): + new.callback.item = new + new._parent = self._parent + new._update_view(self.view) + return new + @property def id(self) -> Optional[int]: """Optional[:class:`int`]: The ID of this select.""" diff --git a/discord/ui/view.py b/discord/ui/view.py index 252a21dbb..36d95d8a3 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -28,7 +28,6 @@ from typing import ( Any, Callable, ClassVar, - Coroutine, Dict, Generator, Iterator, @@ -50,7 +49,7 @@ import sys import time import os -from .item import Item, ItemCallbackType +from .item import Item, ItemCallbackType, _ItemCallback from .select import Select from .dynamic import DynamicItem from ..components import ( @@ -207,18 +206,6 @@ class _ViewWeights: self.weights = [0, 0, 0, 0, 0] -class _ViewCallback: - __slots__ = ('view', 'callback', 'item') - - def __init__(self, callback: ItemCallbackType[Any, Any], view: BaseView, item: Item[BaseView]) -> None: - self.callback: ItemCallbackType[Any, Any] = callback - self.view: BaseView = view - self.item: Item[BaseView] = item - - def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]: - return self.callback(self.view, interaction, self.item) - - class BaseView: __discord_ui_view__: ClassVar[bool] = False __discord_ui_modal__: ClassVar[bool] = False @@ -252,13 +239,13 @@ class BaseView: item._update_view(self) parent = getattr(item, '__discord_ui_parent__', None) if parent and parent._view is None: - parent._view = self + parent._update_view(self) children.append(item) parents[raw] = item else: item: Item = raw.__discord_ui_model_type__(**raw.__discord_ui_model_kwargs__) - item.callback = _ViewCallback(raw, self, item) # type: ignore - item._view = self + item.callback = _ItemCallback(raw, self, item) # type: ignore + item._update_view(self) if isinstance(item, Select): item.options = [option.copy() for option in item.options] setattr(self, raw.__name__, item)