Skip to content

Commit 23f296d

Browse files
authored
better separation of views
1 parent be236bf commit 23f296d

File tree

3 files changed

+598
-717
lines changed

3 files changed

+598
-717
lines changed

discord/ui/core.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
"""
2+
The MIT License (MIT)
3+
4+
Copyright (c) 2021-present Pycord Development
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a
7+
copy of this software and associated documentation files (the "Software"),
8+
to deal in the Software without restriction, including without limitation
9+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
10+
and/or sell copies of the Software, and to permit persons to whom the
11+
Software is furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in
14+
all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22+
DEALINGS IN THE SOFTWARE.
23+
"""
24+
25+
from __future__ import annotations
26+
27+
import asyncio
28+
import os
29+
from itertools import groupby
30+
from typing import TYPE_CHECKING, Any, Callable, Iterator, TypeVar
31+
32+
from ..components import ActionRow as ActionRowComponent
33+
from ..components import Button as ButtonComponent
34+
from ..components import Component
35+
from ..components import Container as ContainerComponent
36+
from ..components import FileComponent
37+
from ..components import Label as LabelComponent
38+
from ..components import MediaGallery as MediaGalleryComponent
39+
from ..components import Section as SectionComponent
40+
from ..components import SelectMenu as SelectComponent
41+
from ..components import Separator as SeparatorComponent
42+
from ..components import TextDisplay as TextDisplayComponent
43+
from ..components import Thumbnail as ThumbnailComponent
44+
from ..components import _component_factory
45+
from ..utils import find, get
46+
from .action_row import ActionRow
47+
from .item import Item, ItemCallbackType
48+
from .view import View
49+
50+
__all__ = ("ComponentUI")
51+
52+
53+
if TYPE_CHECKING:
54+
from typing_extensions import Self
55+
56+
from ..interactions import Interaction, InteractionMessage
57+
from ..message import Message
58+
from ..state import ConnectionState
59+
from ..types.components import Component as ComponentPayload
60+
61+
class ComponentUI:
62+
"""The base structure for classes that contain :class:`~discord.ui.Item`.
63+
64+
.. versionadded:: 2.7
65+
66+
Parameters
67+
----------
68+
*items: :class:`Item`
69+
The initial items contained in this structure.
70+
timeout: Optional[:class:`float`]
71+
Timeout in seconds from last interaction with the UI before no longer accepting input. Defaults to 180.0.
72+
If ``None`` then there is no timeout.
73+
74+
Attributes
75+
----------
76+
timeout: Optional[:class:`float`]
77+
Timeout from last interaction with the UI before no longer accepting input.
78+
If ``None`` then there is no timeout.
79+
children: List[:class:`Item`]
80+
The list of children attached to this structure.
81+
"""
82+
83+
def __init__(
84+
self,
85+
*items: Item,
86+
timeout: float | None = 180.0,
87+
):
88+
self.timeout = timeout
89+
self.children: list[Item] = []
90+
for item in items:
91+
self.add_item(item)
92+
93+
loop = asyncio.get_running_loop()
94+
self.__cancel_callback: Callable[[View], None] | None = None
95+
self.__timeout_expiry: float | None = None
96+
self.__timeout_task: asyncio.Task[None] | None = None
97+
self.__stopped: asyncio.Future[bool] = loop.create_future()
98+
99+
def __repr__(self) -> str:
100+
return f"<{self.__class__.__name__} timeout={self.timeout} children={len(self.children)}>"
101+
102+
async def __timeout_task_impl(self) -> None:
103+
while True:
104+
# Guard just in case someone changes the value of the timeout at runtime
105+
if self.timeout is None:
106+
return
107+
108+
if self.__timeout_expiry is None:
109+
return self._dispatch_timeout()
110+
111+
# Check if we've elapsed our currently set timeout
112+
now = time.monotonic()
113+
if now >= self.__timeout_expiry:
114+
return self._dispatch_timeout()
115+
116+
# Wait N seconds to see if timeout data has been refreshed
117+
await asyncio.sleep(self.__timeout_expiry - now)
118+
119+
@property
120+
def _expires_at(self) -> float | None:
121+
if self.timeout:
122+
return time.monotonic() + self.timeout
123+
return None
124+
125+
def _dispatch_timeout(self):
126+
raise NotImplementedError
127+
128+
def to_components(self) -> list[dict[str, Any]]:
129+
return [item.to_component_dict() for item in self.children]
130+
131+
def get_item(self, custom_id: str | int) -> Item | None:
132+
"""Gets an item from this structure. Roughly equal to `utils.get(self.children, ...)`.
133+
If an :class:`int` is provided, the item will be retrieved by ``id``, otherwise by ``custom_id``.
134+
This method will also search nested items.
135+
136+
Parameters
137+
----------
138+
custom_id: Union[:class:`str`, :class:`int`]
139+
The id of the item to get
140+
141+
Returns
142+
-------
143+
Optional[:class:`Item`]
144+
The item with the matching ``custom_id`` or ``id`` if it exists.
145+
"""
146+
if not custom_id:
147+
return None
148+
attr = "id" if isinstance(custom_id, int) else "custom_id"
149+
child = find(lambda i: getattr(i, attr, None) == custom_id, self.children)
150+
if not child:
151+
for i in self.children:
152+
if hasattr(i, "get_item"):
153+
if child := i.get_item(custom_id):
154+
return child
155+
return child
156+
157+
def add_item(self, item: Item) -> Self:
158+
raise NotImplementedError
159+
160+
def remove_item(self, item: Item) -> Self:
161+
raise NotImplementedError
162+
163+
def clear_items(self) -> None:
164+
raise NotImplementedError
165+
166+
async def on_timeout(self) -> None:
167+
"""|coro|
168+
169+
A callback that is called when this structure's timeout elapses without being explicitly stopped.
170+
"""

0 commit comments

Comments
 (0)