dearfy 0.1.0a1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dearfy-0.1.0a1/LICENSE +9 -0
- dearfy-0.1.0a1/PKG-INFO +53 -0
- dearfy-0.1.0a1/README.md +38 -0
- dearfy-0.1.0a1/dearfy/__init__.py +19 -0
- dearfy-0.1.0a1/dearfy/action.py +309 -0
- dearfy-0.1.0a1/dearfy/app.py +168 -0
- dearfy-0.1.0a1/dearfy/app.pyi +94 -0
- dearfy-0.1.0a1/dearfy/base/__init__.py +11 -0
- dearfy-0.1.0a1/dearfy/base/domnode.py +106 -0
- dearfy-0.1.0a1/dearfy/base/domnode.pyi +41 -0
- dearfy-0.1.0a1/dearfy/base/handler.py +64 -0
- dearfy-0.1.0a1/dearfy/base/item.py +148 -0
- dearfy-0.1.0a1/dearfy/base/item.pyi +87 -0
- dearfy-0.1.0a1/dearfy/base/require_bases.py +62 -0
- dearfy-0.1.0a1/dearfy/base/spetific.py +32 -0
- dearfy-0.1.0a1/dearfy/field.py +149 -0
- dearfy-0.1.0a1/dearfy/functions.py +109 -0
- dearfy-0.1.0a1/dearfy/handlers/__init__.py +11 -0
- dearfy-0.1.0a1/dearfy/handlers/activated.py +17 -0
- dearfy-0.1.0a1/dearfy/handlers/clicked.py +30 -0
- dearfy-0.1.0a1/dearfy/handlers/deactivated.py +27 -0
- dearfy-0.1.0a1/dearfy/handlers/double_clicked.py +30 -0
- dearfy-0.1.0a1/dearfy/handlers/edited.py +17 -0
- dearfy-0.1.0a1/dearfy/logging.py +123 -0
- dearfy-0.1.0a1/dearfy/typing.py +21 -0
- dearfy-0.1.0a1/dearfy/units.py +0 -0
- dearfy-0.1.0a1/dearfy/validator.py +69 -0
- dearfy-0.1.0a1/dearfy/widgets/__init__.py +9 -0
- dearfy-0.1.0a1/dearfy/widgets/button.py +90 -0
- dearfy-0.1.0a1/dearfy/widgets/group.py +72 -0
- dearfy-0.1.0a1/dearfy/widgets/text.py +62 -0
- dearfy-0.1.0a1/dearfy/widgets/tooltip.py +60 -0
- dearfy-0.1.0a1/dearfy/widgets/window.py +91 -0
- dearfy-0.1.0a1/dearfy.egg-info/PKG-INFO +53 -0
- dearfy-0.1.0a1/dearfy.egg-info/SOURCES.txt +38 -0
- dearfy-0.1.0a1/dearfy.egg-info/dependency_links.txt +1 -0
- dearfy-0.1.0a1/dearfy.egg-info/requires.txt +4 -0
- dearfy-0.1.0a1/dearfy.egg-info/top_level.txt +1 -0
- dearfy-0.1.0a1/pyproject.toml +21 -0
- dearfy-0.1.0a1/setup.cfg +4 -0
dearfy-0.1.0a1/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Romanin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
dearfy-0.1.0a1/PKG-INFO
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dearfy
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: A library for simplifying the creation of complex GUIs using DearPyGUI.
|
|
5
|
+
Author-email: Romanin <semina054@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: <3.15,>=3.11
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: dearpygui>=2.3.1
|
|
11
|
+
Requires-Dist: loguru>=0.7.3
|
|
12
|
+
Requires-Dist: rich>=15.0.0
|
|
13
|
+
Requires-Dist: typing-extensions>=4.16.0
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# dearfy
|
|
17
|
+
|
|
18
|
+
## Desription
|
|
19
|
+
A library to simplify the creation of complex applications using DearPyGUI (based on DearImGUI).
|
|
20
|
+
|
|
21
|
+
## Using
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from dearfy.app import App, action
|
|
25
|
+
from dearfy.widgets import *
|
|
26
|
+
from dearfy.handlers import ClickedHandler
|
|
27
|
+
from dearfy.typing import Tag
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MyApp(App):
|
|
31
|
+
def compose(self) -> ComposeResult:
|
|
32
|
+
with Window(label='Title'):
|
|
33
|
+
with Group(horizontal=True):
|
|
34
|
+
with Text('Click for SURPRISE: '):
|
|
35
|
+
yield ClickedHandler(callback='test0')
|
|
36
|
+
yield Button(label='*click*', callback='test1')
|
|
37
|
+
|
|
38
|
+
# Attribute handling is used,
|
|
39
|
+
# i.e. all functions that start with `action_` will be wrapped in `Action`.
|
|
40
|
+
def action_test0(self, sender: Tag):
|
|
41
|
+
print('!!! SURPRISE #1 !!!')
|
|
42
|
+
|
|
43
|
+
# And it is possible not to write them through self,
|
|
44
|
+
# although if you decide to do it,
|
|
45
|
+
# you will have to wrap them in the action decorator anyway.
|
|
46
|
+
@action('test1')
|
|
47
|
+
def action_test1(self):
|
|
48
|
+
print('!!! SURPRISE #2 !!!')
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Installing
|
|
52
|
+
|
|
53
|
+
So far, the project is still under development. So I don't recommend to use it as a basis for any project.
|
dearfy-0.1.0a1/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# dearfy
|
|
2
|
+
|
|
3
|
+
## Desription
|
|
4
|
+
A library to simplify the creation of complex applications using DearPyGUI (based on DearImGUI).
|
|
5
|
+
|
|
6
|
+
## Using
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
from dearfy.app import App, action
|
|
10
|
+
from dearfy.widgets import *
|
|
11
|
+
from dearfy.handlers import ClickedHandler
|
|
12
|
+
from dearfy.typing import Tag
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MyApp(App):
|
|
16
|
+
def compose(self) -> ComposeResult:
|
|
17
|
+
with Window(label='Title'):
|
|
18
|
+
with Group(horizontal=True):
|
|
19
|
+
with Text('Click for SURPRISE: '):
|
|
20
|
+
yield ClickedHandler(callback='test0')
|
|
21
|
+
yield Button(label='*click*', callback='test1')
|
|
22
|
+
|
|
23
|
+
# Attribute handling is used,
|
|
24
|
+
# i.e. all functions that start with `action_` will be wrapped in `Action`.
|
|
25
|
+
def action_test0(self, sender: Tag):
|
|
26
|
+
print('!!! SURPRISE #1 !!!')
|
|
27
|
+
|
|
28
|
+
# And it is possible not to write them through self,
|
|
29
|
+
# although if you decide to do it,
|
|
30
|
+
# you will have to wrap them in the action decorator anyway.
|
|
31
|
+
@action('test1')
|
|
32
|
+
def action_test1(self):
|
|
33
|
+
print('!!! SURPRISE #2 !!!')
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Installing
|
|
37
|
+
|
|
38
|
+
So far, the project is still under development. So I don't recommend to use it as a basis for any project.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import loguru
|
|
2
|
+
import logging as std_logging
|
|
3
|
+
# > Local Imports
|
|
4
|
+
from dearfy.logging import LoguruRichHandler, spetific_format_log
|
|
5
|
+
|
|
6
|
+
# ! Logging
|
|
7
|
+
|
|
8
|
+
loguru.logger.configure(
|
|
9
|
+
handlers=[
|
|
10
|
+
{
|
|
11
|
+
'sink': LoguruRichHandler(
|
|
12
|
+
markup=True,
|
|
13
|
+
show_path=False,
|
|
14
|
+
),
|
|
15
|
+
'format': spetific_format_log,
|
|
16
|
+
'level': std_logging.NOTSET,
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
)
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import loguru
|
|
2
|
+
import inspect
|
|
3
|
+
import datetime
|
|
4
|
+
import threading
|
|
5
|
+
from enum import Enum, Flag, auto
|
|
6
|
+
# > Typing
|
|
7
|
+
from typing_extensions import (
|
|
8
|
+
Any,
|
|
9
|
+
Iterable,
|
|
10
|
+
Callable,
|
|
11
|
+
Literal,
|
|
12
|
+
TypeAlias, TypeVar,
|
|
13
|
+
)
|
|
14
|
+
# > Local Imports
|
|
15
|
+
from dearfy.typing import Tag
|
|
16
|
+
|
|
17
|
+
# ! Types
|
|
18
|
+
|
|
19
|
+
T = TypeVar('T')
|
|
20
|
+
|
|
21
|
+
ReturnType = TypeVar('ReturnType')
|
|
22
|
+
|
|
23
|
+
ActionName: TypeAlias = str
|
|
24
|
+
ActionGroup: TypeAlias = str
|
|
25
|
+
ActionIndeficator: TypeAlias = tuple[ActionName, ActionGroup]
|
|
26
|
+
ActionMethod: TypeAlias = \
|
|
27
|
+
Callable[[Tag, dict[str, Any] | str, Any | None], ReturnType] | \
|
|
28
|
+
Callable[[Tag, dict[str, Any] | str], ReturnType] | \
|
|
29
|
+
Callable[[Tag], ReturnType] | \
|
|
30
|
+
Callable[[], ReturnType]
|
|
31
|
+
|
|
32
|
+
class ActionState(Flag):
|
|
33
|
+
RUNNING = auto()
|
|
34
|
+
ENABLED = auto()
|
|
35
|
+
|
|
36
|
+
class ActionCallMode(Enum):
|
|
37
|
+
ONE = 0
|
|
38
|
+
MANY = 1
|
|
39
|
+
|
|
40
|
+
class ActionBlockMode(Enum):
|
|
41
|
+
NONE = 0
|
|
42
|
+
ALL = 1
|
|
43
|
+
GROUP = 2
|
|
44
|
+
SPETIFIC = 3
|
|
45
|
+
|
|
46
|
+
ActionCallModeLiteral: TypeAlias = Literal['one', 'many'] | Literal[0, 1]
|
|
47
|
+
ActionBlockModeLiteral: TypeAlias = Literal['none', 'all', 'group', 'spetific'] | Literal[0, 1, 2, 3]
|
|
48
|
+
|
|
49
|
+
def __sample_action_method__(sender: Tag, app_data: dict[str, Any] | str, user_data: Any | None) -> Any: ...
|
|
50
|
+
|
|
51
|
+
def _match_call_args(self: object | None, method: ActionMethod, *args: object) -> tuple[object, ...]:
|
|
52
|
+
params = list(inspect.signature(method).parameters.keys())
|
|
53
|
+
if self is not None:
|
|
54
|
+
if ('self' in params) or ('app' in params):
|
|
55
|
+
return (self, *(args[:len(params) - 1]))
|
|
56
|
+
return args[:len(params)]
|
|
57
|
+
|
|
58
|
+
# ! Methods
|
|
59
|
+
|
|
60
|
+
def validate_enum(enum_type: type[T], value: ActionBlockMode | str | int) -> T:
|
|
61
|
+
if isinstance(value, str):
|
|
62
|
+
value: T = getattr(enum_type, value.upper())
|
|
63
|
+
elif isinstance(value, int):
|
|
64
|
+
value: T = enum_type(value)
|
|
65
|
+
return value
|
|
66
|
+
|
|
67
|
+
# ! Action Class
|
|
68
|
+
|
|
69
|
+
class Action:
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
parent: 'Actioner',
|
|
73
|
+
name: str,
|
|
74
|
+
method: ActionMethod,
|
|
75
|
+
group: str='main',
|
|
76
|
+
callmode: ActionCallModeLiteral | ActionCallMode = ActionCallMode.MANY,
|
|
77
|
+
blockmode: ActionBlockModeLiteral | ActionBlockMode = ActionBlockMode.NONE,
|
|
78
|
+
blocks: Iterable[ActionName | tuple[ActionName, ActionGroup]]=[],
|
|
79
|
+
threaded: bool=False,
|
|
80
|
+
/
|
|
81
|
+
) -> None:
|
|
82
|
+
self.actions = parent
|
|
83
|
+
self.__name = name
|
|
84
|
+
self.__method = method
|
|
85
|
+
self.__group = group
|
|
86
|
+
self.__indeficator: tuple[str, str] = (name, group)
|
|
87
|
+
self.__threaded = threaded
|
|
88
|
+
self.__thread: threading.Thread | None = None
|
|
89
|
+
self.callmode = validate_enum(ActionCallMode, callmode)
|
|
90
|
+
self.blockmode = validate_enum(ActionBlockMode, blockmode)
|
|
91
|
+
self.blocks = list(blocks)
|
|
92
|
+
self.state = ActionState.ENABLED
|
|
93
|
+
self.last_call = None
|
|
94
|
+
|
|
95
|
+
# ^ Dunder Methods
|
|
96
|
+
|
|
97
|
+
def __str__(self) -> str:
|
|
98
|
+
return f'{self.__class__.__name__}(<{self.__name!r}, {self.__group!r}>)'
|
|
99
|
+
|
|
100
|
+
def __repr__(self) -> str:
|
|
101
|
+
return self.__str__()
|
|
102
|
+
|
|
103
|
+
def __hash__(self) -> int:
|
|
104
|
+
return hash(self.__indeficator)
|
|
105
|
+
|
|
106
|
+
def __eq__(self, other: 'Action | tuple[ActionName, ActionGroup]') -> bool:
|
|
107
|
+
if not (isinstance(other, Action) or isinstance(other, tuple)):
|
|
108
|
+
return False
|
|
109
|
+
return hash(self) == hash(other)
|
|
110
|
+
|
|
111
|
+
def __ne__(self, other: 'Action | tuple[ActionName, ActionGroup]') -> bool:
|
|
112
|
+
return not self.__eq__(other)
|
|
113
|
+
|
|
114
|
+
def __getattr__(self, name: str):
|
|
115
|
+
return getattr(__sample_action_method__, name)
|
|
116
|
+
|
|
117
|
+
# ^ Propetyes
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def name(self) -> str:
|
|
121
|
+
return self.__name
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def group(self) -> str:
|
|
125
|
+
return self.__group
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def indeficator(self) -> tuple[ActionName, ActionGroup]:
|
|
129
|
+
return self.__indeficator
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def method(self) -> ActionMethod:
|
|
133
|
+
return self.__method
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def blocked(self) -> bool:
|
|
137
|
+
for block_mode, owner, blocks in self.actions.blocks.copy():
|
|
138
|
+
if block_mode == ActionBlockMode.NONE:
|
|
139
|
+
continue
|
|
140
|
+
elif block_mode == ActionBlockMode.ALL:
|
|
141
|
+
return True
|
|
142
|
+
elif block_mode == ActionBlockMode.GROUP:
|
|
143
|
+
return owner[1] == self.__group
|
|
144
|
+
elif block_mode == ActionBlockMode.SPETIFIC:
|
|
145
|
+
for blocked_iderficator in blocks:
|
|
146
|
+
if isinstance(blocked_iderficator, str):
|
|
147
|
+
if blocked_iderficator == self.__name:
|
|
148
|
+
return True
|
|
149
|
+
elif isinstance(blocked_iderficator, tuple):
|
|
150
|
+
if blocked_iderficator == self.__indeficator:
|
|
151
|
+
return True
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def enabled(self) -> bool:
|
|
156
|
+
return ActionState.ENABLED in self.state
|
|
157
|
+
|
|
158
|
+
@enabled.setter
|
|
159
|
+
def enabled(self, value: bool) -> None:
|
|
160
|
+
if value:
|
|
161
|
+
self.state |= ActionState.ENABLED
|
|
162
|
+
else:
|
|
163
|
+
self.state &= ~ActionState.ENABLED
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def threaded(self) -> bool:
|
|
167
|
+
return self.__threaded
|
|
168
|
+
|
|
169
|
+
# ^ Action Methods
|
|
170
|
+
|
|
171
|
+
def can_call(self) -> bool:
|
|
172
|
+
return (not ((ActionCallMode.ONE == self.callmode) and (ActionState.RUNNING in self.state))) or self.blocked
|
|
173
|
+
|
|
174
|
+
# ^ Call Methods
|
|
175
|
+
|
|
176
|
+
def __call_main__(
|
|
177
|
+
self,
|
|
178
|
+
sender: Tag,
|
|
179
|
+
app_data: dict[str, Any] | str | None,
|
|
180
|
+
user_data: Any | None=None
|
|
181
|
+
) -> Any | None:
|
|
182
|
+
self.state |= ActionState.RUNNING
|
|
183
|
+
self.last_call = datetime.datetime.now()
|
|
184
|
+
self.actions.set_block(True, self.indeficator, self.blockmode, self.blocks)
|
|
185
|
+
try:
|
|
186
|
+
result = self.method(*_match_call_args(self.actions._app, self.method, sender, app_data, user_data))
|
|
187
|
+
except:
|
|
188
|
+
result = None
|
|
189
|
+
loguru.logger.exception('An error has occurred in action!')
|
|
190
|
+
self.actions.set_block(False, self.indeficator, self.blockmode, self.blocks)
|
|
191
|
+
self.state &= ~ActionState.RUNNING
|
|
192
|
+
return result
|
|
193
|
+
|
|
194
|
+
def __call_thread__(
|
|
195
|
+
self,
|
|
196
|
+
sender: Tag,
|
|
197
|
+
app_data: dict[str, Any] | str | None,
|
|
198
|
+
user_data: Any | None=None
|
|
199
|
+
) -> None:
|
|
200
|
+
self.state |= ActionState.RUNNING
|
|
201
|
+
self.last_call = datetime.datetime.now()
|
|
202
|
+
self.actions.set_block(True, self.indeficator, self.blockmode, self.blocks)
|
|
203
|
+
try:
|
|
204
|
+
self.method(*_match_call_args(self.actions._app, self.method, sender, app_data, user_data))
|
|
205
|
+
except:
|
|
206
|
+
loguru.logger.exception('An error has occurred in action!')
|
|
207
|
+
self.actions.set_block(False, self.indeficator, self.blockmode, self.blocks)
|
|
208
|
+
self.state &= ~ActionState.RUNNING
|
|
209
|
+
|
|
210
|
+
def __call__(
|
|
211
|
+
self,
|
|
212
|
+
sender: Tag,
|
|
213
|
+
app_data: dict[str, Any] | str | None,
|
|
214
|
+
user_data: Any | None=None
|
|
215
|
+
) -> Any | None:
|
|
216
|
+
loguru.logger.trace(f"[red]Call[/red]: {self!r}.__call__({sender!r}, {app_data!r}, {user_data!r})")
|
|
217
|
+
if not self.enabled:
|
|
218
|
+
return
|
|
219
|
+
if not self.__threaded:
|
|
220
|
+
if not self.can_call():
|
|
221
|
+
loguru.logger.trace(f"[yellow]Cancel[/yellow] action <{self.__indeficator}> because the call is not currently available.")
|
|
222
|
+
return
|
|
223
|
+
loguru.logger.trace(f"[green]Starting[/green] action <{self.__indeficator}> in [gray bold]simple mode[/gray bold].")
|
|
224
|
+
return self.__call_main__(sender, app_data, user_data)
|
|
225
|
+
else:
|
|
226
|
+
if not self.can_call():
|
|
227
|
+
loguru.logger.trace(f"[yellow]Cancel[/yellow] action <{self.__indeficator}> because the call is not currently available.")
|
|
228
|
+
return
|
|
229
|
+
if self.__thread is not None:
|
|
230
|
+
if self.__thread.is_alive():
|
|
231
|
+
loguru.logger.trace(f"[yellow]Cancel[/yellow] action <{self.__indeficator}> because the previous call [gray bold]in the thread[/gray bold] has not yet ended.")
|
|
232
|
+
return
|
|
233
|
+
loguru.logger.trace(f"[green]Starting[/green] action <{self.__indeficator}> in [gray bold]thread mode[/gray bold].")
|
|
234
|
+
self.__thread = threading.Thread(target=self.__call_thread__, args=(sender, app_data, user_data))
|
|
235
|
+
self.__thread.start()
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
# ! Actioner Class
|
|
239
|
+
|
|
240
|
+
class Actioner:
|
|
241
|
+
def __init__(self, app: object | None = None) -> None:
|
|
242
|
+
self._app = app
|
|
243
|
+
self.__set_block_semaphore = threading.Semaphore(1)
|
|
244
|
+
self.actions: dict[tuple[ActionName, ActionGroup], Action] = {}
|
|
245
|
+
self.blocks: list[
|
|
246
|
+
tuple[ActionBlockMode, tuple[ActionName, ActionGroup], list[ActionName | tuple[ActionName, ActionGroup]]]
|
|
247
|
+
] = []
|
|
248
|
+
|
|
249
|
+
def __str__(self) -> str:
|
|
250
|
+
return f'{self.__class__.__name__}({list(self.actions.values())})'
|
|
251
|
+
|
|
252
|
+
def __repr__(self) -> str:
|
|
253
|
+
return self.__str__()
|
|
254
|
+
|
|
255
|
+
def set_block(
|
|
256
|
+
self,
|
|
257
|
+
value: bool,
|
|
258
|
+
blocker_action: tuple[ActionName, ActionGroup],
|
|
259
|
+
blockmode: ActionBlockMode,
|
|
260
|
+
blocks: list[ActionName | tuple[ActionName, ActionGroup]],
|
|
261
|
+
/
|
|
262
|
+
) -> None:
|
|
263
|
+
self.__set_block_semaphore.acquire()
|
|
264
|
+
if value:
|
|
265
|
+
self.blocks.append((blockmode, blocker_action, blocks))
|
|
266
|
+
else:
|
|
267
|
+
indexs_needed_remove = [index for index, block in enumerate(self.blocks) if (block[1] == blocker_action)]
|
|
268
|
+
for index_needed_remove in indexs_needed_remove:
|
|
269
|
+
self.blocks.pop(index_needed_remove)
|
|
270
|
+
self.__set_block_semaphore.release()
|
|
271
|
+
|
|
272
|
+
def get(self, key: ActionIndeficator | ActionName, default: T=None) -> Action | T:
|
|
273
|
+
if isinstance(key, str):
|
|
274
|
+
for action_indeficator in self.actions.copy().keys():
|
|
275
|
+
if action_indeficator[0] == key:
|
|
276
|
+
return self.actions[action_indeficator]
|
|
277
|
+
elif isinstance(key, tuple):
|
|
278
|
+
return self.actions[key]
|
|
279
|
+
return default
|
|
280
|
+
|
|
281
|
+
def action(
|
|
282
|
+
self,
|
|
283
|
+
name: str,
|
|
284
|
+
group: str='main',
|
|
285
|
+
callmode: ActionCallModeLiteral | ActionCallMode = ActionCallMode.MANY,
|
|
286
|
+
blockmode: ActionBlockModeLiteral | ActionBlockMode = ActionBlockMode.NONE,
|
|
287
|
+
blocks: Iterable[ActionName | tuple[ActionName, ActionGroup]]=[],
|
|
288
|
+
threaded: bool=False,
|
|
289
|
+
):
|
|
290
|
+
def wrapper(method: ActionMethod):
|
|
291
|
+
action = Action(self, name, method, group, callmode, blockmode, blocks, threaded)
|
|
292
|
+
self.actions[action.indeficator] = action
|
|
293
|
+
return action
|
|
294
|
+
return wrapper
|
|
295
|
+
|
|
296
|
+
def add_action(
|
|
297
|
+
self,
|
|
298
|
+
method: ActionMethod,
|
|
299
|
+
name: str,
|
|
300
|
+
group: str='main',
|
|
301
|
+
callmode: ActionCallModeLiteral | ActionCallMode = ActionCallMode.MANY,
|
|
302
|
+
blockmode: ActionBlockModeLiteral | ActionBlockMode = ActionBlockMode.NONE,
|
|
303
|
+
blocks: Iterable[ActionName | tuple[ActionName, ActionGroup]]=[],
|
|
304
|
+
threaded: bool=False,
|
|
305
|
+
) -> None:
|
|
306
|
+
action = Action(self, name, method, group, callmode, blockmode, blocks, threaded)
|
|
307
|
+
if action.indeficator in self.actions:
|
|
308
|
+
self.actions[action.indeficator].enabled = False
|
|
309
|
+
self.actions[action.indeficator] = action
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import loguru
|
|
2
|
+
from enum import Enum
|
|
3
|
+
import dearpygui.dearpygui as dpg
|
|
4
|
+
# > Typing
|
|
5
|
+
from typing_extensions import TypeAlias, Iterator
|
|
6
|
+
# > Local Imports
|
|
7
|
+
from dearfy.base import Item, DOMNode
|
|
8
|
+
from dearfy.typing import Color, FilePath, Tag
|
|
9
|
+
from dearfy.field import field
|
|
10
|
+
from dearfy.action import Actioner, Action
|
|
11
|
+
from dearfy.functions import formatting_kwargs, get_method_needed
|
|
12
|
+
|
|
13
|
+
# ! Types
|
|
14
|
+
|
|
15
|
+
ComposeResult: TypeAlias = Iterator[Item]
|
|
16
|
+
|
|
17
|
+
# ! States
|
|
18
|
+
|
|
19
|
+
class AppState(Enum):
|
|
20
|
+
NONE = 0
|
|
21
|
+
PREPARING = 1
|
|
22
|
+
PREINIT = 2
|
|
23
|
+
INIT = 3
|
|
24
|
+
POSTINIT = 4
|
|
25
|
+
RUNNING = 5
|
|
26
|
+
|
|
27
|
+
# ! App Base Class
|
|
28
|
+
|
|
29
|
+
class App(DOMNode):
|
|
30
|
+
_node_children: list[Item]
|
|
31
|
+
_actioner: Actioner = Actioner()
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
title: str = 'Dearfy Viewport',
|
|
36
|
+
small_icon: FilePath | None = None,
|
|
37
|
+
large_icon: FilePath | None = None,
|
|
38
|
+
width: int = 1280,
|
|
39
|
+
height: int = 800,
|
|
40
|
+
x_pos: int = 100,
|
|
41
|
+
y_pos: int = 100,
|
|
42
|
+
min_width: int = 250,
|
|
43
|
+
max_width: int = 10000,
|
|
44
|
+
min_height: int = 250,
|
|
45
|
+
max_height: int = 10000,
|
|
46
|
+
resizable: bool = True,
|
|
47
|
+
vsync: bool = True,
|
|
48
|
+
always_on_top: bool = False,
|
|
49
|
+
decorated: bool = True,
|
|
50
|
+
clear_color: Color = (0, 0, 0, 255),
|
|
51
|
+
disable_close: bool = False,
|
|
52
|
+
minimized: bool = False,
|
|
53
|
+
maximized: bool = False
|
|
54
|
+
) -> None:
|
|
55
|
+
super().__init__()
|
|
56
|
+
self._state: AppState = AppState.NONE
|
|
57
|
+
self._gkwagrs = {
|
|
58
|
+
'create_viewport': {
|
|
59
|
+
'title': title,
|
|
60
|
+
'small_icon': field(small_icon, '', nullable=False),
|
|
61
|
+
'large_icon': field(large_icon, '', nullable=False),
|
|
62
|
+
'width': width,
|
|
63
|
+
'height': height,
|
|
64
|
+
'x_pos': x_pos,
|
|
65
|
+
'y_pos': y_pos,
|
|
66
|
+
'min_width': min_width,
|
|
67
|
+
'max_width': max_width,
|
|
68
|
+
'min_height': min_height,
|
|
69
|
+
'max_height': max_height,
|
|
70
|
+
'resizable': resizable,
|
|
71
|
+
'vsync': vsync,
|
|
72
|
+
'always_on_top': always_on_top,
|
|
73
|
+
'decorated': decorated,
|
|
74
|
+
'clear_color': clear_color,
|
|
75
|
+
'disable_close': disable_close,
|
|
76
|
+
},
|
|
77
|
+
'show_viewport': {
|
|
78
|
+
'minimized': minimized,
|
|
79
|
+
'maximized': maximized,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
self.__dearfy_compose__()
|
|
83
|
+
self.__dearfy_init_action_attributes__()
|
|
84
|
+
self._actioner._app = self
|
|
85
|
+
self._nodes.clear()
|
|
86
|
+
|
|
87
|
+
def __str__(self) -> str:
|
|
88
|
+
kwargs = {}
|
|
89
|
+
for item_kwargs in self._gkwagrs.values():
|
|
90
|
+
kwargs.update(item_kwargs)
|
|
91
|
+
kwargs = get_method_needed(self.__init__, **kwargs)
|
|
92
|
+
return f'{self.__class__.__name__}({formatting_kwargs(**kwargs)})'
|
|
93
|
+
|
|
94
|
+
def __dearfy_init_action_attributes__(self) -> None:
|
|
95
|
+
for attr_name in dir(self):
|
|
96
|
+
if attr_name.startswith('action_'):
|
|
97
|
+
attr = getattr(self, attr_name)
|
|
98
|
+
if isinstance(attr, Action):
|
|
99
|
+
continue
|
|
100
|
+
elif callable(attr):
|
|
101
|
+
self._actioner.add_action(attr, attr_name[7:])
|
|
102
|
+
|
|
103
|
+
def __dearfy_compose__(self) -> None:
|
|
104
|
+
self._nodes.append(self)
|
|
105
|
+
for child in self.compose():
|
|
106
|
+
if self._current_node:
|
|
107
|
+
self._current_node._add_child(child)
|
|
108
|
+
self._nodes.clear()
|
|
109
|
+
|
|
110
|
+
def __dearfy_preparing__(self) -> None:
|
|
111
|
+
for child in self._node_children:
|
|
112
|
+
child.__dearfy_preparing__(self)
|
|
113
|
+
|
|
114
|
+
def __dearfy_preinit__(self) -> None:
|
|
115
|
+
for child in self._node_children:
|
|
116
|
+
child.__dearfy_preinit__()
|
|
117
|
+
|
|
118
|
+
def __dearfy_init__(self) -> None:
|
|
119
|
+
for child in self._node_children:
|
|
120
|
+
child.__dearfy_init__()
|
|
121
|
+
|
|
122
|
+
def __dearfy_postinit__(self) -> None:
|
|
123
|
+
for child in self._node_children:
|
|
124
|
+
child.__dearfy_postinit__()
|
|
125
|
+
|
|
126
|
+
def __dearfy_destroy__(self) -> None:
|
|
127
|
+
for child in self._node_children:
|
|
128
|
+
child.__dearfy_destroy__()
|
|
129
|
+
|
|
130
|
+
def get_item(self, tag: Tag) -> Item:
|
|
131
|
+
try:
|
|
132
|
+
return self._node_main_parent._get_node_by_attr('tag', tag)
|
|
133
|
+
except AttributeError:
|
|
134
|
+
pass
|
|
135
|
+
raise RuntimeError(
|
|
136
|
+
"There is no Item with this tag. "
|
|
137
|
+
f"_node_main_parent={self._node_main_parent!r}"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def run(self) -> None:
|
|
141
|
+
self._state = AppState.PREPARING
|
|
142
|
+
self.__dearfy_preparing__()
|
|
143
|
+
loguru.logger.trace('[green]▬▬▬▬▬[/green] [yellow]AFTER PREPARING[/yellow] [green]▬▬▬▬▬[/green]')
|
|
144
|
+
loguru.logger.trace(self._to_rich_tree())
|
|
145
|
+
dpg.create_context()
|
|
146
|
+
dpg.create_viewport(**(self._gkwagrs['create_viewport']))
|
|
147
|
+
self._state = AppState.PREINIT
|
|
148
|
+
self.__dearfy_preinit__()
|
|
149
|
+
loguru.logger.trace('[green]▬▬▬▬▬[/green] [yellow]AFTER PREINIT[/yellow] [green]▬▬▬▬▬[/green]')
|
|
150
|
+
loguru.logger.trace(self._to_rich_tree())
|
|
151
|
+
self._state = AppState.INIT
|
|
152
|
+
self.__dearfy_init__()
|
|
153
|
+
loguru.logger.trace('[green]▬▬▬▬▬[/green] [yellow]AFTER INIT[/yellow] [green]▬▬▬▬▬[/green]')
|
|
154
|
+
loguru.logger.trace(self._to_rich_tree())
|
|
155
|
+
dpg.setup_dearpygui()
|
|
156
|
+
self._state = AppState.POSTINIT
|
|
157
|
+
self.__dearfy_postinit__()
|
|
158
|
+
loguru.logger.trace('[green]▬▬▬▬▬[/green] [yellow]AFTER POSTINIT[/yellow] [green]▬▬▬▬▬[/green]')
|
|
159
|
+
loguru.logger.trace(self._to_rich_tree())
|
|
160
|
+
self._state = AppState.RUNNING
|
|
161
|
+
dpg.show_viewport(**(self._gkwagrs['show_viewport']))
|
|
162
|
+
dpg.start_dearpygui()
|
|
163
|
+
dpg.destroy_context()
|
|
164
|
+
self._state = AppState.NONE
|
|
165
|
+
self.__dearfy_destroy__()
|
|
166
|
+
loguru.logger.trace(self._to_rich_tree())
|
|
167
|
+
|
|
168
|
+
action = App._actioner.action
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
|
|
2
|
+
# > Typing
|
|
3
|
+
from typing_extensions import TypeAlias, Iterable, Iterator, ClassVar
|
|
4
|
+
# > Local Imports
|
|
5
|
+
from dearfy.base import Item, DOMNode
|
|
6
|
+
from dearfy.typing import Color, FilePath, Tag
|
|
7
|
+
from dearfy.action import (
|
|
8
|
+
Actioner, Action,
|
|
9
|
+
ActionBlockMode, ActionBlockModeLiteral,
|
|
10
|
+
ActionCallMode, ActionCallModeLiteral,
|
|
11
|
+
ActionName, ActionGroup
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# ! Types
|
|
15
|
+
|
|
16
|
+
ComposeResult: TypeAlias = Iterator[Item]
|
|
17
|
+
|
|
18
|
+
# ! App Base Class
|
|
19
|
+
|
|
20
|
+
class App(DOMNode):
|
|
21
|
+
"""Base class for describing an application."""
|
|
22
|
+
|
|
23
|
+
_actioner: ClassVar[Actioner]
|
|
24
|
+
|
|
25
|
+
_node_children: list[Item]
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
title: str = 'Dearfy Viewport',
|
|
30
|
+
small_icon: FilePath | None = None,
|
|
31
|
+
large_icon: FilePath | None = None,
|
|
32
|
+
width: int = 1280,
|
|
33
|
+
height: int = 800,
|
|
34
|
+
x_pos: int = 100,
|
|
35
|
+
y_pos: int = 100,
|
|
36
|
+
min_width: int = 250,
|
|
37
|
+
max_width: int = 10000,
|
|
38
|
+
min_height: int = 250,
|
|
39
|
+
max_height: int = 10000,
|
|
40
|
+
resizable: bool = True,
|
|
41
|
+
vsync: bool = True,
|
|
42
|
+
always_on_top: bool = False,
|
|
43
|
+
decorated: bool = True,
|
|
44
|
+
clear_color: Color = (0, 0, 0, 255),
|
|
45
|
+
disable_close: bool = False,
|
|
46
|
+
minimized: bool = False,
|
|
47
|
+
maximized: bool = False
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Base class for describing an application.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
title (str, optional): Sets the title of the viewport. Defaults to 'Dearfy Viewport'.
|
|
53
|
+
small_icon (FilePath | None, optional): Sets the small icon that is found in the viewport's decorator bar. Must be \*.ico on Windows and either \*.ico or \*.png on Mac. Defaults to None.
|
|
54
|
+
large_icon (FilePath | None, optional): Sets the large icon that is found in the task bar while the app is running. Must be \*.ico on Windows and either \*.ico or \*.png on Mac. Defaults to None.
|
|
55
|
+
width (int, optional): Sets the width of the drawable space on the viewport. Defaults to 1280.
|
|
56
|
+
height (int, optional): Sets the height of the drawable space on the viewport. Defaults to 800.
|
|
57
|
+
x_pos (int, optional): Sets X position the viewport will be drawn in screen coordinates. Defaults to 100.
|
|
58
|
+
y_pos (int, optional): Sets Y position the viewport will be drawn in screen coordinates. Defaults to 100.
|
|
59
|
+
min_width (int, optional): Applies a minimuim limit to the width of the viewport. Defaults to 250.
|
|
60
|
+
max_width (int, optional): Applies a maximum limit to the width of the viewport. Defaults to 10000.
|
|
61
|
+
min_height (int, optional): Applies a minimuim limit to the height of the viewport. Defaults to 250.
|
|
62
|
+
max_height (int, optional): Applies a maximum limit to the height of the viewport. Defaults to 10000.
|
|
63
|
+
resizable (bool, optional): Enables and Disables user ability to resize the viewport. Defaults to True.
|
|
64
|
+
vsync (bool, optional): Enables and Disables the renderloop vsync limit. Vsync frame value is set by refresh rate of display. Defaults to True.
|
|
65
|
+
always_on_top (bool, optional): Forces the viewport to always be drawn ontop of all other viewports. Defaults to False.
|
|
66
|
+
decorated (bool, optional): Enabled and disabled the decorator bar at the top of the viewport. Defaults to True.
|
|
67
|
+
clear_color (Color, optional): Sets the color of the back of the viewport. Defaults to (0, 0, 0, 255).
|
|
68
|
+
disable_close (bool, optional): Disables the viewport close button. Can be used with set_exit_callback. Defaults to False.
|
|
69
|
+
minimized (bool, optional): Sets the state of the viewport to minimized. Defaults to False.
|
|
70
|
+
maximized (bool, optional): Sets the state of the viewport to maximized. Defaults to False.
|
|
71
|
+
"""
|
|
72
|
+
...
|
|
73
|
+
|
|
74
|
+
def compose(self) -> ComposeResult: ...
|
|
75
|
+
|
|
76
|
+
def __dearfy_compose__(self) -> None: ...
|
|
77
|
+
def __dearfy_preparing__(self) -> None: ...
|
|
78
|
+
def __dearfy_preinit__(self) -> None: ...
|
|
79
|
+
def __dearfy_init__(self) -> None: ...
|
|
80
|
+
def __dearfy_postinit__(self) -> None: ...
|
|
81
|
+
|
|
82
|
+
def get_item(self, tag: Tag) -> Item: ...
|
|
83
|
+
|
|
84
|
+
def run(self) -> None: ...
|
|
85
|
+
|
|
86
|
+
def action(
|
|
87
|
+
name: str,
|
|
88
|
+
group: str='main',
|
|
89
|
+
callmode: ActionCallModeLiteral | ActionCallMode = ActionCallMode.MANY,
|
|
90
|
+
blockmode: ActionBlockModeLiteral | ActionBlockMode = ActionBlockMode.NONE,
|
|
91
|
+
blocks: Iterable[ActionName | tuple[ActionName, ActionGroup]]=[],
|
|
92
|
+
threaded: bool=False,
|
|
93
|
+
) -> Action:
|
|
94
|
+
...
|