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.
Files changed (40) hide show
  1. dearfy-0.1.0a1/LICENSE +9 -0
  2. dearfy-0.1.0a1/PKG-INFO +53 -0
  3. dearfy-0.1.0a1/README.md +38 -0
  4. dearfy-0.1.0a1/dearfy/__init__.py +19 -0
  5. dearfy-0.1.0a1/dearfy/action.py +309 -0
  6. dearfy-0.1.0a1/dearfy/app.py +168 -0
  7. dearfy-0.1.0a1/dearfy/app.pyi +94 -0
  8. dearfy-0.1.0a1/dearfy/base/__init__.py +11 -0
  9. dearfy-0.1.0a1/dearfy/base/domnode.py +106 -0
  10. dearfy-0.1.0a1/dearfy/base/domnode.pyi +41 -0
  11. dearfy-0.1.0a1/dearfy/base/handler.py +64 -0
  12. dearfy-0.1.0a1/dearfy/base/item.py +148 -0
  13. dearfy-0.1.0a1/dearfy/base/item.pyi +87 -0
  14. dearfy-0.1.0a1/dearfy/base/require_bases.py +62 -0
  15. dearfy-0.1.0a1/dearfy/base/spetific.py +32 -0
  16. dearfy-0.1.0a1/dearfy/field.py +149 -0
  17. dearfy-0.1.0a1/dearfy/functions.py +109 -0
  18. dearfy-0.1.0a1/dearfy/handlers/__init__.py +11 -0
  19. dearfy-0.1.0a1/dearfy/handlers/activated.py +17 -0
  20. dearfy-0.1.0a1/dearfy/handlers/clicked.py +30 -0
  21. dearfy-0.1.0a1/dearfy/handlers/deactivated.py +27 -0
  22. dearfy-0.1.0a1/dearfy/handlers/double_clicked.py +30 -0
  23. dearfy-0.1.0a1/dearfy/handlers/edited.py +17 -0
  24. dearfy-0.1.0a1/dearfy/logging.py +123 -0
  25. dearfy-0.1.0a1/dearfy/typing.py +21 -0
  26. dearfy-0.1.0a1/dearfy/units.py +0 -0
  27. dearfy-0.1.0a1/dearfy/validator.py +69 -0
  28. dearfy-0.1.0a1/dearfy/widgets/__init__.py +9 -0
  29. dearfy-0.1.0a1/dearfy/widgets/button.py +90 -0
  30. dearfy-0.1.0a1/dearfy/widgets/group.py +72 -0
  31. dearfy-0.1.0a1/dearfy/widgets/text.py +62 -0
  32. dearfy-0.1.0a1/dearfy/widgets/tooltip.py +60 -0
  33. dearfy-0.1.0a1/dearfy/widgets/window.py +91 -0
  34. dearfy-0.1.0a1/dearfy.egg-info/PKG-INFO +53 -0
  35. dearfy-0.1.0a1/dearfy.egg-info/SOURCES.txt +38 -0
  36. dearfy-0.1.0a1/dearfy.egg-info/dependency_links.txt +1 -0
  37. dearfy-0.1.0a1/dearfy.egg-info/requires.txt +4 -0
  38. dearfy-0.1.0a1/dearfy.egg-info/top_level.txt +1 -0
  39. dearfy-0.1.0a1/pyproject.toml +21 -0
  40. 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.
@@ -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.
@@ -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
+ ...