├── .gitignore ├── LICENSE ├── README.md ├── dearpypixl ├── __init__.py ├── __main__.py ├── _errors.py ├── _interface.py ├── _mkstub.py ├── _parsing.py ├── _tools.py ├── _typing.py ├── api.py ├── color.py ├── color.pyi ├── console.py ├── constants.py ├── events.py ├── grid.py ├── items.py ├── items.pyi ├── menu.py ├── py.typed ├── style.py ├── style.pyi ├── themes.py ├── theming.py └── typing.py ├── pyproject.toml └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # [Project .gitignore] 2 | _debug.py 3 | 4 | 5 | 6 | # [OTHER .gitignore] 7 | .files/ 8 | .tests/ 9 | .scripts/ 10 | .vscode/ 11 | 12 | 13 | 14 | # the below can be found amongst files at 15 | 16 | # [RUST .gitignore] 17 | 18 | debug/ 19 | target/ 20 | 21 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 22 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 23 | Cargo.lock 24 | 25 | # These are backup files generated by rustfmt 26 | **/*.rs.bk 27 | 28 | # MSVC Windows builds of rustc generate these, which store debugging information 29 | *.pdb 30 | 31 | 32 | 33 | 34 | # [PYTHON .gitignore] 35 | 36 | # Byte-compiled / optimized / DLL files 37 | __pycache__/ 38 | *.py[cod] 39 | *$py.class 40 | 41 | # C extensions 42 | *.so 43 | 44 | # Distribution / packaging 45 | .Python 46 | build/ 47 | develop-eggs/ 48 | dist/ 49 | downloads/ 50 | eggs/ 51 | .eggs/ 52 | lib/ 53 | lib64/ 54 | parts/ 55 | sdist/ 56 | var/ 57 | wheels/ 58 | share/python-wheels/ 59 | *.egg-info/ 60 | .installed.cfg 61 | *.egg 62 | .pypirc 63 | MANIFEST 64 | 65 | # PyInstaller 66 | # Usually these files are written by a python script from a template 67 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 68 | *.manifest 69 | *.spec 70 | 71 | # Installer logs 72 | pip-log.txt 73 | pip-delete-this-directory.txt 74 | 75 | # Unit test / coverage reports 76 | htmlcov/ 77 | .tox/ 78 | .nox/ 79 | .coverage 80 | .coverage.* 81 | .cache 82 | nosetests.xml 83 | coverage.xml 84 | *.cover 85 | *.py,cover 86 | .hypothesis/ 87 | .pytest_cache/ 88 | cover/ 89 | 90 | # Translations 91 | *.mo 92 | *.pot 93 | 94 | # Django stuff: 95 | *.log 96 | local_settings.py 97 | db.sqlite3 98 | db.sqlite3-journal 99 | 100 | # Flask stuff: 101 | instance/ 102 | .webassets-cache 103 | 104 | # Scrapy stuff: 105 | .scrapy 106 | 107 | # Sphinx documentation 108 | docs/_build/ 109 | 110 | # PyBuilder 111 | .pybuilder/ 112 | target/ 113 | 114 | # Jupyter Notebook 115 | .ipynb_checkpoints 116 | 117 | # IPython 118 | profile_default/ 119 | ipython_config.py 120 | 121 | # pyenv 122 | # For a library or package, you might want to ignore these files since the code is 123 | # intended to run in multiple environments; otherwise, check them in: 124 | # .python-version 125 | 126 | # pipenv 127 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 128 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 129 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 130 | # install all needed dependencies. 131 | #Pipfile.lock 132 | 133 | # poetry 134 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 135 | # This is especially recommended for binary packages to ensure reproducibility, and is more 136 | # commonly ignored for libraries. 137 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 138 | #poetry.lock 139 | 140 | # pdm 141 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 142 | #pdm.lock 143 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 144 | # in version control. 145 | # https://pdm.fming.dev/#use-with-ide 146 | .pdm.toml 147 | 148 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 149 | __pypackages__/ 150 | 151 | # Celery stuff 152 | celerybeat-schedule 153 | celerybeat.pid 154 | 155 | # SageMath parsed files 156 | *.sage.py 157 | 158 | # Environments 159 | .env 160 | .venv 161 | env/ 162 | venv/ 163 | ENV/ 164 | env.bak/ 165 | venv.bak/ 166 | 167 | # Spyder project settings 168 | .spyderproject 169 | .spyproject 170 | 171 | # Rope project settings 172 | .ropeproject 173 | 174 | # mkdocs documentation 175 | /site 176 | 177 | # mypy 178 | .mypy_cache/ 179 | .dmypy.json 180 | dmypy.json 181 | 182 | # Pyre type checker 183 | .pyre/ 184 | 185 | # pytype static type analyzer 186 | .pytype/ 187 | 188 | # pyright 189 | pyrightconfig.json 190 | 191 | # Cython debug symbols 192 | cython_debug/ 193 | 194 | # PyCharm 195 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 196 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 197 | # and can be added to the global gitignore or merged into this file. For a more nuclear 198 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 199 | #.idea/ 200 | 201 | 202 | 203 | 204 | [C/C++ .gitignore] 205 | 206 | # Prerequisites 207 | *.d 208 | 209 | # Object files 210 | *.slo 211 | *.lo 212 | *.o 213 | *.ko 214 | *.obj 215 | *.elf 216 | 217 | # Linker output 218 | *.ilk 219 | *.map 220 | *.exp 221 | 222 | # Precompiled Headers 223 | *.gch 224 | *.pch 225 | 226 | # Libraries 227 | *.lib 228 | *.a 229 | *.la 230 | *.lo 231 | 232 | # Shared objects (inc. Windows DLLs) 233 | *.dll 234 | *.so 235 | *.so.* 236 | *.dylib 237 | 238 | # Executables 239 | *.exe 240 | *.out 241 | *.app 242 | *.i*86 243 | *.x86_64 244 | *.hex 245 | 246 | # Debug files 247 | *.dSYM/ 248 | *.su 249 | *.idb 250 | *.pdb 251 | 252 | # Kernel Module Compile Results 253 | *.mod* 254 | *.cmd 255 | .tmp_versions/ 256 | modules.order 257 | Module.symvers 258 | Mkfile.old 259 | dkms.conf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Anthony Doupe 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. 10 | -------------------------------------------------------------------------------- /dearpypixl/__init__.py: -------------------------------------------------------------------------------- 1 | from . import api # bulk patching 2 | from .api import ( 3 | Application as Application, 4 | Viewport as Viewport, 5 | Runtime as Runtime, 6 | Registry as Registry, 7 | ) 8 | from .items import * 9 | -------------------------------------------------------------------------------- /dearpypixl/__main__.py: -------------------------------------------------------------------------------- 1 | import types 2 | import inspect 3 | from inspect import Parameter 4 | from typing import Any, Mapping, get_overloads 5 | from pathlib import Path 6 | from . import _typing, _interface, _mkstub 7 | from ._mkstub import indent 8 | 9 | 10 | 11 | 12 | try: 13 | DPX_PATH = Path(__file__).parent.resolve() 14 | except AttributeError: 15 | DPX_PATH = Path().resolve() 16 | 17 | typing_imports = _mkstub.Imported(_typing, False) 18 | typing_imports.extend(( 19 | _typing.Item, 20 | _typing.ItemAlias, 21 | _typing.ItemUUID, 22 | _typing.ItemCommand, 23 | _typing.Literal, 24 | _typing.Sequence, 25 | _typing.Callable, 26 | _typing.Unpack, 27 | _typing.Property, 28 | _typing.Any, 29 | _typing.overload, 30 | )) 31 | 32 | interface_imports = _mkstub.Imported(_interface) 33 | interface_imports.extend(( 34 | o for o in _mkstub.Exported.fetch(_interface).values() 35 | if isinstance(o, type) 36 | and issubclass(o, _interface.AppItemType) 37 | )) 38 | 39 | 40 | 41 | 42 | def fetch_exports(module: types.ModuleType): 43 | return _mkstub.Exported.fetch(module) 44 | 45 | 46 | def write_stub(filename: Path | str, source: str) -> None: 47 | #mkstub.validate_source(source) 48 | with open(DPX_PATH / filename, 'w') as typestub: 49 | typestub.write(source) 50 | 51 | 52 | 53 | 54 | def _to_source_itp_base(tp: type[_interface.AppItemType]) -> str: 55 | pyi = [_mkstub.to_source_classdef(tp)] 56 | 57 | with indent: 58 | for name, anno in getattr(tp, 'configure').__annotations__.items(): 59 | value = getattr(tp, name, Parameter.empty) 60 | if value is Parameter.empty or not hasattr(value, '__get__'): 61 | continue 62 | anno = _mkstub.object_annotation(anno) 63 | pyi.append( 64 | indent(f"{name}: {_typing.Property.__qualname__}[{anno}] = ...") 65 | ) 66 | 67 | init_params = inspect.signature(tp.__init__).parameters 68 | init_overloads = get_overloads(tp.__init__) 69 | if init_overloads: 70 | for fn in init_overloads: 71 | params = inspect.signature(fn).parameters 72 | pyi.extend(( 73 | indent(f'@overload'), 74 | indent(f"def __init__({_mkstub.to_source_parameters(params)}) -> None: ..."), 75 | )) 76 | else: 77 | pyi.extend(( 78 | indent(f"def __init__({_mkstub.to_source_parameters(init_params)}) -> None: ..."), 79 | )) 80 | 81 | icmd_params = init_params.copy() 82 | del icmd_params['self'] 83 | pyi.extend(( 84 | indent(f"@staticmethod"), 85 | indent(f"def command({_mkstub.to_source_parameters(icmd_params)}) -> Item: ..."), 86 | )) 87 | 88 | configure_params = inspect.signature(tp.configure).parameters 89 | configuration_rt = _mkstub.object_annotation( 90 | inspect.signature(tp.configuration).return_annotation 91 | ) 92 | pyi.extend(( 93 | indent(f"def configure({_mkstub.to_source_parameters(configure_params)}) -> None: ..."), 94 | indent(f"def configuration(self) -> {configuration_rt}: ..."), 95 | )) 96 | return '\n'.join(pyi) 97 | 98 | 99 | def items_pyi(fpath: str | Path): 100 | from . import items 101 | 102 | itp_imports = interface_imports.copy() 103 | itp_imports.append(_interface.mvAll, export=True) 104 | itp_imports.extend(( 105 | o for o in fetch_exports(_interface).values() 106 | if not (isinstance(o, type) and issubclass(o, _interface.AppItemType)) 107 | ), export=True) 108 | 109 | pyi_imp = '\n'.join(( 110 | str(typing_imports), 111 | str(itp_imports), 112 | '\n\n\n\n' 113 | )) 114 | 115 | pyi_src1 = [] 116 | pyi_src2 = [] 117 | exports = fetch_exports(items) 118 | # TODO: use new '__aliased__' member in exports 119 | for name, itp in exports.items(): 120 | if name == itp.__qualname__: 121 | pyi_src1.append(_to_source_itp_base(itp)) 122 | else: 123 | pyi_src2.append(f"{name} = {itp.__qualname__}") 124 | pyi_src2.sort() 125 | 126 | pyi = ''.join(( 127 | pyi_imp, 128 | '\n\n\n'.join(pyi_src1), 129 | "\n\n\n\n", 130 | '\n'.join(pyi_src2), 131 | )) 132 | write_stub(fpath, pyi) 133 | 134 | 135 | def _theme_element_pyi(fpath: str | Path, module: types.ModuleType): 136 | from . import items 137 | 138 | item_imports = _mkstub.Imported(items) 139 | item_imports.extend(( 140 | items.mvThemeColor, 141 | 'ThemeColor', 142 | items.mvThemeStyle, 143 | 'ThemeStyle', 144 | ), export=True) 145 | 146 | pyi_imp = '\n'.join(( 147 | str(typing_imports), 148 | str(interface_imports), 149 | str(item_imports), 150 | )) 151 | 152 | exports = fetch_exports(module) 153 | aliases = exports.aliased 154 | pyi_src1 = "\n\n\n".join( 155 | _mkstub.to_source_function(itp) 156 | for itp in exports.values() 157 | ) 158 | pyi_src2 = '\n'.join( 159 | f"{name} = {obj.__name__}" for name, obj in aliases.items() 160 | ) 161 | pyi = "".join((pyi_imp, "\n\n\n", pyi_src1, '\n\n\n', pyi_src2)) 162 | write_stub(fpath, pyi) 163 | 164 | 165 | def color_pyi(fpath: str | Path): 166 | from . import color 167 | _theme_element_pyi(fpath, color) 168 | 169 | 170 | def style_pyi(fpath: str | Path): 171 | from . import style 172 | _theme_element_pyi(fpath, style) 173 | 174 | 175 | 176 | 177 | items_pyi("items.pyi") 178 | color_pyi("color.pyi") 179 | style_pyi("style.pyi") 180 | -------------------------------------------------------------------------------- /dearpypixl/_errors.py: -------------------------------------------------------------------------------- 1 | """DearPyGui item error utilities.""" 2 | import inspect 3 | import functools 4 | from dearpygui import _dearpygui 5 | from ._typing import Item, ItemCommand, Protocol, ParamSpec 6 | from . import _parsing 7 | 8 | 9 | _P = ParamSpec("_P") 10 | 11 | 12 | class ItemError(Protocol[_P]): 13 | def __call__(self, item: Item, command: ItemCommand[_P, Item]) -> Exception | None: ... 14 | 15 | 16 | # [ HELPER FUNCTIONS ] 17 | 18 | def is_tag_valid(tag: Item) -> bool: 19 | return isinstance(tag, (int, str)) 20 | 21 | 22 | def is_item_interface(item: Item) -> bool: 23 | if type(item) in (str, int): 24 | return False 25 | elif is_tag_valid(item): 26 | return True 27 | return False 28 | 29 | 30 | def does_item_exist(tag: Item) -> bool: 31 | if isinstance(tag, str): 32 | tag = _dearpygui.get_alias_id(tag) 33 | return tag in _dearpygui.get_all_items() 34 | 35 | 36 | def command_signature(command: ItemCommand): 37 | variadics = (inspect.Parameter.VAR_KEYWORD, inspect.Parameter.VAR_POSITIONAL) 38 | signature = inspect.signature(command) 39 | return signature.replace( 40 | parameters=tuple( 41 | p for p in signature.parameters.values() 42 | if p.kind not in variadics 43 | ) 44 | ) 45 | 46 | 47 | def command_itp_name(command: ItemCommand) -> str: 48 | from . import api 49 | 50 | for tp_def in _parsing.item_definitions().values(): 51 | try: 52 | if command == tp_def.command1 or command == tp_def.command2: # type: ignore 53 | return tp_def.name # type: ignore 54 | except AttributeError: 55 | pass 56 | return '' 57 | 58 | 59 | 60 | 61 | # [ ERROR FUNCTIONS ] 62 | 63 | def err_tag_invalid(item: Item, command: ItemCommand, *args, **kwargs): 64 | if not is_tag_valid(item): 65 | return TypeError(f'`tag` expected as int or str, got {type(item)!r}.') 66 | 67 | 68 | def err_item_nonexistant(item: Item, command: ItemCommand, *args, **kwargs): 69 | exists = does_item_exist(item) 70 | if exists: 71 | return 72 | 73 | invalid_id = err_tag_invalid(item, command, *args, **kwargs) 74 | if invalid_id: 75 | return invalid_id 76 | 77 | if isinstance(item, int): 78 | item = int(item) 79 | id_type = ' uuid' 80 | else: 81 | item = str(item) 82 | id_type = 'n alias' 83 | 84 | return ValueError( 85 | f"{item!r} is not currently in-use as a{id_type} for any " 86 | "existing item - perhaps the item using it was deleted?" 87 | ) 88 | 89 | 90 | def err_arg_unexpected(item: Item, command: ItemCommand, *args, **kwargs): 91 | signature = command_signature(command) 92 | # Python will automatically throw a TypeError for unexpected 93 | # positional args since `dearpygui.dearpygui` is valid code. 94 | # All item commands accept variadic keywords though, and 95 | # `mvPythonParser` will throw vague errors for them. 96 | bad_kwds = ', '.join(repr(k) for k in kwargs if k not in signature.parameters) 97 | if not bad_kwds: 98 | return 99 | if not is_tag_valid(item): 100 | return err_tag_invalid(item, command, *args, **kwargs) 101 | elif is_item_interface(item): 102 | callee = f'{type(item).__qualname__}()' 103 | else: 104 | callee = f'{command.__qualname__}()' # type: ignore 105 | 106 | return TypeError(f'{callee!r} received unexpected keyword argument(s) {bad_kwds}.') 107 | 108 | 109 | def err_arg_invalid(item: Item, command: ItemCommand, *args, **kwargs): 110 | # TODO 111 | ... 112 | 113 | 114 | 115 | 116 | 117 | def _invld_p_err(parent_tp: str, parent: int | str, child_tp: str, *tp_list: str) -> ValueError: 118 | msg = f"incompatible parent {parent_tp}(tag={parent!r}) for child {child_tp!r} item{{}}" 119 | if tp_list: 120 | if isinstance(tp_list, str): 121 | tp_list = ''.join(tp_list), 122 | msg = msg.format(f", try {', '.join(repr(s) for s in tp_list)}.") 123 | return ValueError(msg) 124 | 125 | def err_parent_invalid(item: Item, command: ItemCommand, *args, **kwargs): 126 | if "parent" not in command_signature(command).parameters: 127 | return 128 | 129 | cstack = _dearpygui.top_container_stack() 130 | parent = kwargs.get("parent", 0) 131 | 132 | # CASE 1: `parent` is not `int` nor `str` (also prevents error in CASE 2) 133 | if not is_tag_valid(parent): 134 | return ValueError(f"`parent` expected as int or str, got {type(parent)!r}") 135 | 136 | if parent: 137 | # CASE 2: the included parent does not exist 138 | if not does_item_exist(parent): 139 | return ValueError(f"`parent` item {parent!r} does not exist.") 140 | # CASE 3: is it even a container? 141 | if _dearpygui.get_item_info(parent)['container'] is False: 142 | return ValueError(f"`parent` item {parent!r} is not a container and cannot parent items") 143 | 144 | # CASE 4: did not include parent while the container stack is empty 145 | elif not parent and not cstack: 146 | return TypeError("no parent item available (container stack is empty).") 147 | 148 | 149 | parent = parent or cstack 150 | 151 | # CASE 5: a parent is available but maybe incompatible 152 | p_tp_name = _dearpygui.get_item_info( 153 | parent 154 | )["type"].removeprefix("mvAppItemType::") 155 | c_tp_name = command_itp_name(command).removeprefix('mvAppItemType::') 156 | 157 | err = functools.partial(_invld_p_err, p_tp_name, parent, c_tp_name) 158 | 159 | # these are supposed to be "universal" parents, but there are some exceptions 160 | if p_tp_name in ("mvStage", "mvTemplateRegistry"): 161 | return err() 162 | 163 | if c_tp_name.startswith("mvDraw") and "Drawlist" not in c_tp_name and p_tp_name not in ('mvDrawlist', 'mvViewportDrawlist', 'mvDrawLayer'): 164 | return err('mvDrawlist', 'mvViewportDrawlist', 'mvDrawLayer') 165 | if c_tp_name.startswith("mvThemeComponent") and p_tp_name not in ("mvTheme",): 166 | return err('mvTheme',) 167 | if c_tp_name in ("mvThemeColor", 'mvThemeStyle') and p_tp_name not in ("mvThemeComponent",): 168 | return err("mvThemeComponent",) 169 | if "Handler" in c_tp_name: 170 | if p_tp_name == "mvHandlerRegistry": 171 | return err("mvItemHandlerRegistry",) 172 | if p_tp_name == "mvItemHandlerRegistry": 173 | return err("mvItemHandlerRegistry",) 174 | if c_tp_name.endswith("Series") and p_tp_name != 'mvPlotAxis': 175 | return err('mvPlotAxis',) 176 | if c_tp_name in ('mvAnnotation', 'mvPlotLegend', 'mvPlotAxis') and p_tp_name != "mvPlot": 177 | return err("mvPlot",) 178 | if c_tp_name.startswith('mvNode'): 179 | if c_tp_name == 'mvNodeAttribute' and p_tp_name != 'mvNode': 180 | return err('mvNode',) 181 | if c_tp_name == 'mvNode' and p_tp_name != 'mvNodeEditor': 182 | return err('mvNodeEditor',) 183 | if c_tp_name in ('mvTableRow', 'mvTableCell') and p_tp_name != 'mvTable': 184 | return err('mvTable',) 185 | if c_tp_name == 'mvTableCell' and p_tp_name != 'mvTableRow': 186 | return err('mvTableRow',) 187 | 188 | 189 | -------------------------------------------------------------------------------- /dearpypixl/_mkstub.py: -------------------------------------------------------------------------------- 1 | import re 2 | import ast 3 | import sys 4 | import types 5 | import inspect 6 | import itertools # type: ignore 7 | import dataclasses 8 | from inspect import Parameter 9 | from types import ModuleType 10 | from typing import ( 11 | MutableMapping, 12 | TypeVar, 13 | Iterable, 14 | Sequence, 15 | Mapping, 16 | Any, 17 | Callable, 18 | Self, 19 | get_overloads, 20 | TYPE_CHECKING, 21 | ) 22 | if TYPE_CHECKING: 23 | from ._interface import AppItemType 24 | 25 | 26 | 27 | _T = TypeVar('_T') 28 | 29 | 30 | # [ STRING FORMATTING ] 31 | 32 | class _IndentedStrType(type): 33 | __slots__ = () 34 | 35 | def indent(self) -> None: 36 | self.level += 1 37 | 38 | def dedent(self) -> None: 39 | self.level -= 1 40 | if self.level < 0: 41 | self.level = 0 42 | 43 | def __enter__(self): 44 | self.indent() 45 | 46 | def __exit__(self, *args): 47 | self.dedent() 48 | 49 | class indent(str, metaclass=_IndentedStrType): 50 | __slots__ = () 51 | 52 | __indent: str = ' ' * 4 53 | 54 | level: int = 0 55 | 56 | def __new__(cls, s: str, level: int | None = None) -> str: 57 | return cls.__indent * (level or cls.level) + s or '' 58 | 59 | @classmethod 60 | def lines(cls, s: str, level: int, *, sep: str = '\n') -> str: 61 | indent = cls.__indent * (level or cls.level) 62 | return indent + indent.join(s.split(sep)) 63 | 64 | 65 | # >>> re.findall('vector: collections.abc.Sequence[int | typing.Any]') 66 | # ['collections.abc.S', 'typing.A'] 67 | _RE_PTRN_TPLOOKUP = re.compile(r'(?:(?::\s?)|(?:\[)|(?:\s*\|\s*)\s?)((?:(?:\w*\.)(?:\w))+)') 68 | 69 | def _rm_source_anno_namespaces(s: str) -> str: 70 | """Removes dotted namespace lookup prefixes from signatures and 71 | annotations of Python source code. 72 | 73 | >>> clean_source_annotations('vector: collections.abc.Sequence[int | typing.Any]') 74 | 'vector: Sequence[int | Any]' 75 | 76 | """ 77 | # This should not remove namespace lookup strings elseware, but 78 | # needs more testing. Unused for now. 79 | for hit in set(re.findall(_RE_PTRN_TPLOOKUP, s)): 80 | try: 81 | # the pattern isn't perfect -- matches also include 82 | # the char after the last dot 83 | s = s.replace(hit[:-1], "") 84 | except IndexError: 85 | pass 86 | return s 87 | 88 | 89 | _RE_PTRN_NSPFX = r'((?:(?:\w*\.))+)' 90 | 91 | def trim_lookup_trail(s: str) -> str: 92 | """Removes dotted namespace lookup prefixes in a 93 | Python source code string.""" 94 | # Unlike the *other* one, this one is rather...indescriminate 95 | # when it comes to matches. 96 | for hit in set(re.findall(_RE_PTRN_NSPFX, s)): 97 | s = s.replace(hit, "") 98 | return s 99 | 100 | 101 | def uneval(tp: type | str | None) -> str: 102 | """Return a string that would evaluate to the passed type or 103 | annotation of that type when compiled as source code.""" 104 | return inspect.formatannotation(tp) 105 | 106 | 107 | def object_annotation(o: Any) -> str: 108 | """Return a string that would evaluate to the passed type or 109 | annotation of that type when compiled as source code if 110 | available in the would-be namespace.""" 111 | return trim_lookup_trail(uneval(o)) 112 | 113 | 114 | 115 | 116 | # [ PARSERS / WRITERS ] 117 | 118 | def _get_module(module: str | types.ModuleType) -> types.ModuleType: 119 | if isinstance(module, str): 120 | try: 121 | return sys.modules[module] 122 | except KeyError: 123 | raise ValueError( 124 | f"{module!r} is not the name of an imported module." 125 | ) from None 126 | assert isinstance(module, types.ModuleType) 127 | return module 128 | 129 | 130 | @dataclasses.dataclass(init=False) 131 | class Exported(MutableMapping): 132 | """A mapping containing names and objects to export to stub files. 133 | 134 | Flagging objects for export: 135 | >>> exported = Exported(__name__) # `ModuleType` object also works 136 | ... 137 | >>> @exported # uses `PublicClass.__qualname__` as the key 138 | >>> class PublicClass: ... 139 | ... 140 | >>> @exported # uses `public_func.__name__` as the key 141 | >>> def public_func(): ... 142 | ... 143 | >>> # passing an explicit name 144 | >>> exported(public_func, "alias_of_public_func") 145 | 146 | Getting exports from a module: 147 | >>> # the module must have been imported prior 148 | >>> exports = Exported.fetch(__name__) # `ModuleType` object also works 149 | 150 | """ 151 | 152 | module: str | types.ModuleType 153 | 154 | __slots__ = ("_module", "_exported", "aliased") 155 | 156 | EXPORTED = '__exported__' 157 | ALIASED = '__aliased__' 158 | 159 | def __init__(self, module: str | types.ModuleType): 160 | self._exported = {} 161 | self.aliased = {} 162 | self._module = _get_module(module) 163 | setattr(self._module, self.EXPORTED, self) 164 | 165 | def __getitem__(self, key: str): 166 | return self._exported[key] 167 | 168 | def __setitem__(self, key: str, value: Any): 169 | self._exported[key] = value 170 | 171 | def __delitem__(self, key: str): 172 | del self._exported[key] 173 | 174 | def __iter__(self): 175 | return iter(self._exported) 176 | 177 | def __len__(self): 178 | return len(self._exported) 179 | 180 | def export(self, o: _T | None = None, /, name: str = '') -> _T: 181 | def capture_object(o: _T) -> _T: 182 | nonlocal name 183 | if not name: # type: ignore 184 | try: 185 | name = o.__qualname__ # type: ignore 186 | except AttributeError: 187 | name = o.__name__ # type: ignore 188 | self._exported[name] = o # type: ignore 189 | return o 190 | if o is None: 191 | return capture_object # type: ignore 192 | return capture_object(o) 193 | 194 | __call__ = export 195 | 196 | def export_alias(self, name: str, o: _T | None = None) -> _T: 197 | def capture_object(o: _T) -> _T: 198 | self.aliased[name] = o # type: ignore 199 | return o 200 | if o is None: 201 | return capture_object # type: ignore 202 | return capture_object(o) 203 | 204 | @classmethod 205 | def fetch(cls, module: types.ModuleType) -> Self: 206 | """Return a mapping of a module's exports.""" 207 | module = _get_module(module) 208 | return getattr(module, cls.EXPORTED, None) or cls(module) 209 | 210 | 211 | 212 | 213 | 214 | @dataclasses.dataclass(slots=True) 215 | class Imported(Sequence): 216 | module : str | ModuleType 217 | sorted : bool = True 218 | absolute: bool = False 219 | relative: bool = True 220 | 221 | _name_map: dict[Any, str] = dataclasses.field(init=False, repr=False) 222 | _imports : list[Any] = dataclasses.field(init=False, repr=False) 223 | _exports : list[Any] = dataclasses.field(init=False, repr=False) 224 | 225 | def __post_init__(self): 226 | self.module = _get_module(self.module) 227 | self._imports = [] 228 | self._exports = [] # "exported" imports need to be followed up with an alias "obj as obj" 229 | self._name_map = {} 230 | self._build_name_map() 231 | 232 | def _build_name_map(self): 233 | self._name_map.clear() 234 | for k, v in self.module.__dict__.items(): 235 | try: 236 | if v not in self._name_map: 237 | self._name_map[v] = k 238 | except TypeError: 239 | continue 240 | 241 | def __getitem__(self, key: int) -> Any: 242 | return NotImplemented 243 | 244 | def __len__(self): 245 | return len(self._imports) + len(self._exports) 246 | 247 | def __iter__(self): 248 | yield from itertools.chain(self._imports, self._exports) 249 | 250 | def __str__(self) -> str: 251 | stmt = [] 252 | src = self.module.__name__ # type: ignore 253 | if self.absolute and self.relative: 254 | if not self._imports: 255 | return '' 256 | stmt.append(f"from {src} import (") 257 | elif self.relative: 258 | if not self._imports and not self._exports: 259 | return '' 260 | stmt.append(f'from .{src.split(".", maxsplit=1)[-1]} import (') 261 | else: 262 | return f'import {src}' 263 | 264 | imports = [] 265 | for o in self._imports: 266 | imports.append(f" {self._name_map[o]},") 267 | for o in self._exports: 268 | try: 269 | o_name = self._name_map[o] 270 | except KeyError: 271 | if isinstance(o, str) and hasattr(self.module, o): 272 | o_name = o 273 | else: 274 | raise 275 | imports.append(f" {o_name} as {o_name},") 276 | 277 | if self.sorted: 278 | imports.sort() 279 | 280 | stmt.extend(imports) 281 | stmt.append(")") 282 | return '\n'.join(stmt) 283 | 284 | @property 285 | def imports(self) -> tuple: 286 | return tuple(self._imports) 287 | 288 | @property 289 | def exports(self) -> tuple: 290 | return tuple(self._exports) 291 | 292 | def copy(self) -> Self: 293 | copy = type(self)(self.module, self.sorted, self.absolute, self.relative) 294 | copy._imports = self._imports.copy() 295 | return copy 296 | 297 | def append(self, value: Any, *, export: bool = False): 298 | if export: 299 | self._exports.append(value) 300 | if value in self._imports: 301 | self._imports.remove(value) 302 | else: 303 | self._imports.append(value) 304 | if value in self._exports: 305 | self._exports.remove(value) 306 | 307 | def extend(self, iterable: Iterable[Any], *, export: bool = False): 308 | for v in iterable: 309 | self.append(v, export=export) 310 | 311 | 312 | 313 | 314 | def validate_source(s: str) -> str: 315 | """Validate a Python source code string. 316 | 317 | Returns the string unchanged if the source is valid. Throws 318 | various errors otherwise. 319 | """ 320 | ast.parse(s) 321 | return s 322 | 323 | 324 | def to_source_classdef(tp: type) -> str: 325 | """Return a source code string containing the preliminary 326 | class definition of a type. 327 | """ 328 | src = [ 329 | f"class {tp.__qualname__}({', '.join(b.__qualname__ for b in tp.__bases__)}):" 330 | ] 331 | with indent: 332 | if tp.__doc__: 333 | src.append(indent('"""') + tp.__doc__ + '\n """') 334 | 335 | if '__slots__' in tp.__dict__: 336 | slots = tp.__slots__ # type: ignore 337 | if slots: 338 | src.append(indent('__slots__: tuple[str, ...] = (')) 339 | with indent: 340 | mbrs = ''.join( 341 | ",\n".join(indent(repr(m)) for m in slots) 342 | ) 343 | src.append(mbrs) 344 | src.append(indent(")")) 345 | else: 346 | src.append(indent('__slots__: tuple[str, ...] = ()')) 347 | return '\n'.join(src) 348 | 349 | 350 | def to_source_parameters(parameters: Mapping[str, Parameter]) -> str: 351 | """Return a source code string containing the argument 352 | signature of a callable. 353 | """ 354 | src = [] 355 | 356 | mk_pos_only = False 357 | mk_kwd_only = False 358 | for p in parameters.values(): 359 | if p.annotation is p.empty: 360 | src_string = p.name 361 | else: 362 | src_string = f"{p.name}: {trim_lookup_trail(uneval(p.annotation))}" 363 | if p.default is not p.empty: 364 | src_string = f"{src_string} = ..." 365 | 366 | kind = p.kind 367 | 368 | if kind == p.POSITIONAL_ONLY: 369 | mk_pos_only = True 370 | src.append(src_string) 371 | continue 372 | elif mk_pos_only: 373 | mk_pos_only = False 374 | src_string = f"/, {src_string}" 375 | 376 | if kind == p.VAR_POSITIONAL: 377 | # Arguments following VAR_POSITIONAL are made KEYWORD_ONLY 378 | # by way of Python syntax. In this case, however, the lone 379 | # "*" marker is not needed. 380 | mk_kwd_only = True 381 | elif kind == p.KEYWORD_ONLY and not mk_kwd_only: 382 | src_string = f"*, {src_string}" 383 | mk_kwd_only = True 384 | elif kind == p.VAR_KEYWORD: 385 | src_string = f"**{src_string}" 386 | 387 | src.append(src_string) 388 | 389 | return ', '.join(src).replace("*, /,", "/, *,") 390 | 391 | 392 | def to_source_function(fn: Callable): 393 | pyi = [] 394 | 395 | overloads = get_overloads(fn) 396 | if overloads: 397 | for ovrld in overloads: 398 | sig = inspect.signature(ovrld) 399 | params = to_source_parameters(sig.parameters) 400 | return_tp = object_annotation(sig.return_annotation) 401 | pyi.extend(( 402 | f"@overload", 403 | f"def {fn.__name__}({params}) -> {return_tp}: ...", 404 | )) 405 | 406 | else: 407 | sig = inspect.signature(fn) 408 | params = to_source_parameters(sig.parameters) 409 | return_tp = object_annotation(sig.return_annotation) 410 | pyi.append(f"def {fn.__name__}({params}) -> {return_tp}: ...") 411 | 412 | return '\n'.join(pyi) 413 | -------------------------------------------------------------------------------- /dearpypixl/_parsing.py: -------------------------------------------------------------------------------- 1 | """Utilities for inspecting Dear PyGui's API.""" 2 | import re 3 | import types 4 | import array 5 | import typing 6 | import inspect 7 | import textwrap 8 | import threading 9 | import dataclasses 10 | from inspect import Parameter 11 | from dearpygui import dearpygui 12 | from . import constants, _typing, _tools 13 | from ._typing import ( 14 | Any, 15 | Item, 16 | Callable, 17 | ItemCommand, 18 | Sequence, 19 | Mapping, 20 | Literal, 21 | ) 22 | 23 | 24 | 25 | 26 | # [ API Parsers ] 27 | 28 | def is_contextmanager(o: Any, /) -> bool: 29 | """Return True if a function is wrapped by `contextlib.contextmanager`. 30 | 31 | Args: 32 | * o: The object to inspect. 33 | 34 | 35 | >>> from dearpygui import dearpygui 36 | ... 37 | >>> is_contextmanager(dearpygui.window) 38 | True 39 | >>> is_contextmanager(dearpygui.add_window) 40 | False 41 | >>> is_contextmanager(dearpygui.child_window) 42 | True 43 | >>> is_contextmanager(dearpygui.add_child_window) 44 | False 45 | >>> is_contextmanager(dearpygui.mutex) 46 | True 47 | """ 48 | # Works 99% of the time. In theory, it could return false-positives. 49 | is_ctx_manager = all(( 50 | callable(o), 51 | o.__globals__["__name__"] == "contextlib", 52 | o.__module__ != "contextlib", 53 | isinstance(getattr(o, "__wrapped__", None), types.FunctionType), 54 | )) 55 | return is_ctx_manager or False 56 | 57 | 58 | def is_item_command(o: Any, /) -> bool: 59 | """Return True if an object is a callable defined in DearPyGui that creates 60 | and yields/returns an item identifier. 61 | 62 | Only returns True for item commands located in the `dearpygui.dearpygui`, 63 | not `dearpygui._dearpygui`. Functions in the latter are considered "built- 64 | in" and have no signature, in which this function returns False. 65 | 66 | Args: 67 | * o: The object to inspect. 68 | 69 | 70 | >>> from dearpygui import dearpygui, _dearpygui 71 | ... 72 | >>> is_item_command(dearpygui.window) 73 | True 74 | >>> is_item_command(dearpygui.add_window) 75 | True 76 | >>> # parsed as a signature-less "built-in" 77 | >>> is_item_command(_dearpygui.add_window) 78 | False 79 | >>> # does not return or accept *tag* 80 | >>> is_item_command(dearpygui.configure_item) 81 | False 82 | >>> def randfunc(*args, tag: int | str = 0) -> int | str: ... 83 | >>> # not from `dearpygui.dearpygui` 84 | >>> is_item_command(randfunc) 85 | False 86 | """ 87 | # This only works reliably when the function is in `dearpygui` 88 | # public module because those in the private `_dearpygui` module 89 | # can't be accurately inspected (no signature). 90 | try: 91 | signature = inspect.signature(o) 92 | except: 93 | return False # not callable or is "built-in" 94 | 95 | try: 96 | if dearpygui.__name__ not in o.__module__: 97 | return False 98 | except AttributeError: 99 | return False 100 | 101 | if signature.return_annotation != int | str: 102 | return False 103 | 104 | if all(kwd not in signature.parameters for kwd in ("tag", "id")): 105 | return False 106 | # weeds out false positives 107 | elif signature.parameters['tag'].kind != Parameter.KEYWORD_ONLY: 108 | return False 109 | 110 | # minor optimization for future calls to `signature` (there will be) 111 | if not hasattr(o, "__signature__"): 112 | o.__signature__ = signature 113 | return True 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | @dataclasses.dataclass(slots=True, frozen=True) 123 | class ItemDefinition: 124 | name : str 125 | enum : int 126 | command1 : ItemCommand 127 | command2 : ItemCommand | None 128 | init_params : Mapping[str, Parameter] = dataclasses.field(repr=False) 129 | rconfig_params: Mapping[str, Parameter] = dataclasses.field(repr=False) 130 | wconfig_params: Mapping[str, Parameter] = dataclasses.field(repr=False) 131 | 132 | is_container: bool = dataclasses.field(init=False, repr=False) 133 | 134 | def __post_init__(self): 135 | _setattr = object.__setattr__.__get__(self) 136 | _setattr('is_container', bool(self.command2)) 137 | 138 | 139 | @_tools.cache_once 140 | def item_definitions() -> Mapping[str, ItemDefinition]: 141 | """Parse Dear PyGui's API and return compiled item type definitions. 142 | 143 | The result of this function is only built once and is cached 144 | for future calls. 145 | """ 146 | def set_item_types(): 147 | nonlocal name_to_enum 148 | dearpygui.create_context() 149 | name_to_enum = dearpygui.get_item_types() 150 | dearpygui.destroy_context() 151 | 152 | name_to_enum = None 153 | # This needs to be done in a separate thread so the GPU 154 | # context isn't created too early or burned for the user. 155 | _t = threading.Thread(target=set_item_types, daemon=True) 156 | _t.start() 157 | _t.join() 158 | 159 | name_to_enum = _typing.cast(dict[str, int], name_to_enum) 160 | name_to_def = {} 161 | commands_to_names = { 162 | dearpygui.add_2d_histogram_series: "mv2dHistogramSeries", 163 | dearpygui.add_3d_slider : "mvSlider3D", 164 | dearpygui.add_hline_series : "mvHLineSeries", 165 | dearpygui.add_vline_series : "mvVLineSeries", 166 | dearpygui.add_plot_annotation : "mvAnnotation", 167 | dearpygui.add_text_point : "mvLabelSeries", 168 | dearpygui.draw_image : 'mvDrawImage', 169 | dearpygui.draw_text : 'mvDrawText', 170 | dearpygui.draw_rectangle : "mvDrawRect", 171 | dearpygui.add_subplots : 'mvSubPlots', 172 | dearpygui.add_window : "mvWindowAppItem", 173 | } 174 | command_cache = set() 175 | cpfx_add = 'add_' 176 | cpfx_draw = 'draw_' 177 | for name, obj in dearpygui.__dict__.items(): 178 | if ( 179 | not callable(obj) 180 | or not is_item_command(obj) 181 | or "popup" in name 182 | or obj in command_cache 183 | ): 184 | continue 185 | 186 | # find item commands and raw type name 187 | if is_contextmanager(obj): 188 | tp_cmd1 = ( 189 | getattr(dearpygui, f"{cpfx_add}{name}", None) or 190 | getattr(dearpygui, f"{cpfx_draw}{name}") 191 | ) 192 | tp_cmd2 = obj 193 | _tp_name = name 194 | else: 195 | tp_cmd1 = obj 196 | _tp_name = name 197 | # chaining `.removeprefix(...)` could remove more than intended 198 | if _tp_name.startswith(cpfx_draw): 199 | _tp_name = _tp_name.removeprefix(cpfx_draw) 200 | elif _tp_name.startswith(cpfx_add): 201 | _tp_name = _tp_name.removeprefix(cpfx_add) 202 | tp_cmd2 = getattr(dearpygui, _tp_name, None) 203 | 204 | command_cache.add(tp_cmd1) 205 | if tp_cmd2 is not None: 206 | command_cache.add(tp_cmd2) 207 | 208 | # build final itemtype name 209 | if tp_cmd1 in commands_to_names: 210 | tp_name = commands_to_names[tp_cmd1] 211 | tp_enum = name_to_enum[tp_name] 212 | else: 213 | if _tp_name.startswith("colormap"): 214 | _tp_name = _tp_name.replace("colormap", "color_map", 1) # capwords 215 | tp_name = f'mv{_tp_name.title().replace("_", "")}' 216 | try: 217 | tp_enum = name_to_enum[tp_name] 218 | except KeyError: # `tp_name` is wrong. Try to "fix" it case-by-case. 219 | # drawing item? (the `.removeprefix` calls from earlier causes this) 220 | if cpfx_draw in tp_cmd1.__name__ and not tp_name.startswith("mvDraw"): 221 | tp_name = f'mvDraw{tp_name.removeprefix("mv")}' 222 | tp_enum = name_to_enum[tp_name] 223 | # item handler? (global handlers shouldn't fail above) 224 | elif all(s in tp_cmd1.__name__ for s in ("item_", "_handler")): 225 | tp_name = tp_name.replace("Item", "", 1) 226 | tp_enum = name_to_enum[tp_name] 227 | # x4 input item? 228 | elif tp_cmd1.__name__[-1] == "x": 229 | tp_name = f"{tp_name[:-1]}Multi" 230 | tp_enum = name_to_enum[tp_name] 231 | else: 232 | raise 233 | 234 | # build method parameters 235 | init_params = upd_param_annotations( 236 | inspect.signature(tp_cmd1).parameters 237 | ) 238 | 239 | # Ideally, `.configure` should accept what `.configuration` returns 240 | # as keyword args. But not everything is writable. At the very least, 241 | # `type(itf)(**.itf.configuration())` MUST be possible so that the 242 | # interface can be accurately copied/reconstructed. 243 | # `get_config_parameters` handles a lot of this, but some detail work 244 | # may need to be done per type. 245 | rconfig_params = get_config_parameters(init_params) 246 | if tp_name in ("mvThemeColor", "mvThemeStyle"): 247 | # `.configure` : -`target`, -`category` 248 | # `.configuration`: +`target`, +`category` 249 | wconfig_params = rconfig_params.copy() 250 | wconfig_params.pop('category', None) 251 | rconfig_params['target'] = init_params['target'].replace( 252 | kind=Parameter.KEYWORD_ONLY 253 | ) 254 | else: 255 | wconfig_params = rconfig_params.copy() 256 | # `pos` is writable as configuration, but is considered a state... 257 | rconfig_params.pop('pos', '') 258 | 259 | name_to_def[tp_name] = ItemDefinition( 260 | name=tp_name, 261 | enum=tp_enum, 262 | command1=tp_cmd1, 263 | command2=tp_cmd2, 264 | init_params=types.MappingProxyType(init_params), 265 | rconfig_params=types.MappingProxyType(rconfig_params), 266 | wconfig_params=types.MappingProxyType(wconfig_params), 267 | ) 268 | 269 | return types.MappingProxyType({n:name_to_def[n] for n in sorted(name_to_def)}) # type: ignore 270 | 271 | 272 | 273 | 274 | @dataclasses.dataclass(slots=True, frozen=True) 275 | class ElementDefinition: 276 | name1 : str 277 | name2 : str 278 | type : Literal['mvThemeColor', 'mvThemeStyle'] 279 | constant: str 280 | target : int 281 | category: constants.ThemeCategory 282 | 283 | 284 | 285 | @_tools.cache_once 286 | def element_definitions() -> Mapping[str, ElementDefinition]: 287 | const_pfxs = ( 288 | "mvThemeCol", 289 | "mvPlotCol", 290 | "mvNodeCol", 291 | "mvNodesCol", 292 | "mvStyleVar", 293 | "mvPlotStyleVar", 294 | "mvNodeStyleVar", 295 | "mvNodesStyleVar", 296 | ) 297 | 298 | CORE = constants.ThemeCategory(dearpygui.mvThemeCat_Core) 299 | PLOT = constants.ThemeCategory(dearpygui.mvThemeCat_Plots) 300 | NODE = constants.ThemeCategory(dearpygui.mvThemeCat_Nodes) 301 | const_to_elem: dict[constants.ThemeCategory, dict[str, ElementDefinition]] = { 302 | CORE: {}, 303 | PLOT: {}, 304 | NODE: {}, 305 | } 306 | 307 | RE_CAPS = re.compile(r'([A-Z])') 308 | 309 | for constant, target in dearpygui.__dict__.items(): 310 | pfx, *name1 = constant.split("_", maxsplit=1) 311 | if pfx not in const_pfxs: 312 | continue 313 | name1 = name1[0] \ 314 | .replace("_", "") \ 315 | .replace("Background", "Bg") \ 316 | .replace("bg", "Bg") 317 | name2 = '_'.join(re.sub(RE_CAPS, r' \1', name1).split()).lower() 318 | 319 | kind = 'mvThemeColor' if pfx.endswith("Col") else 'mvThemeStyle' 320 | 321 | if pfx.startswith('mvPlot'): 322 | category = PLOT 323 | if not name1.startswith('Plot'): 324 | name1 = f'Plot{name1}' 325 | name2 = f'plot_{name2}' 326 | elif pfx.startswith('mvNode'): 327 | category = NODE 328 | if not name1.startswith('Node'): 329 | name1 = f'Node{name1}' 330 | name2 = f'node_{name2}' 331 | else: 332 | category = CORE 333 | 334 | element = ElementDefinition( 335 | name1=name1, 336 | name2=name2, 337 | type=kind, 338 | constant=constant, 339 | target=target, 340 | category=category, 341 | ) 342 | const_to_elem[category][constant] = element 343 | 344 | # Using the names formated above can cause plot and node 345 | # elements to shadow core elements. Core elements should 346 | # keep the more simple and intuitive name(s). 347 | #for cat in (PLOT, NODE): 348 | # name_map = name_to_elem[cat] 349 | # for name1, element in name_map.items(): 350 | # if name1 in name_to_elem[CORE]: 351 | # name1 = f'{cat.name.title()}{name1}' 352 | # assert name1 not in name_to_elem[CORE] 353 | # assert name1 not in name_map 354 | # object.__setattr__(element, 'name1', name1) 355 | # object.__setattr__(element, 'name2', f'{cat.name.lower()}_{element.name2}') 356 | 357 | for cat in constants.ThemeCategory: 358 | const_to_elem[cat] = dict( 359 | sorted(const_to_elem[cat].items(), key=lambda x: x[1].name1) # type: ignore 360 | ) 361 | 362 | return types.MappingProxyType( 363 | const_to_elem[CORE] | const_to_elem[PLOT] | const_to_elem[NODE] 364 | ) 365 | 366 | @_tools.cache_once 367 | def color_definitions() -> Mapping[str, ElementDefinition]: 368 | return types.MappingProxyType({ 369 | k:v for k,v in element_definitions().items() 370 | if v.type == "mvThemeColor" 371 | }) 372 | 373 | @_tools.cache_once 374 | def style_definitions() -> Mapping[str, ElementDefinition]: 375 | return types.MappingProxyType({ 376 | k:v for k,v in element_definitions().items() 377 | if v.type == "mvThemeStyle" 378 | }) 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | # [ Signature Parsing, Formatting ] 388 | 389 | def get_config_parameters(parameters: Mapping[str, Parameter]): 390 | """Return a mapping of item command parameters containing only 391 | configuration-related parameters.""" 392 | excluded_names = ( 393 | "tag", "id", # read-only 394 | "parent", # read-only (ish) 395 | "before", # not config 396 | "default_value", # exposed via `item.g/set_value` 397 | "delay_search", # can be set if supported, but never incl in `get_item_configuration` 398 | ) 399 | excluded_parts = ("default",) 400 | return { 401 | p.name:p for p in parameters.values() 402 | if p.kind == Parameter.KEYWORD_ONLY 403 | and p.name not in excluded_names 404 | and all(npart not in p.name for npart in excluded_parts) 405 | } 406 | 407 | 408 | def upd_param_annotations(parameters: Mapping[str, Parameter]) -> dict[str, Parameter]: 409 | """Return item command parameters with updated annotations.""" 410 | upd_params = {} 411 | for p in parameters.values(): 412 | if p.kind == Parameter.VAR_KEYWORD: 413 | continue 414 | # This specifically targets old Python 3.6 annotations and replaces 415 | # them with concrete type annotations. Several others are changed 416 | # for better accuracy. 417 | name = p.name 418 | anno = p.annotation 419 | 420 | # case-specific formatting 421 | if anno == Item: 422 | anno = 'Item' # type alias 423 | elif name == 'label': 424 | anno = str | None 425 | elif name == 'pos': 426 | anno = tuple[int, int] | list[int] 427 | elif 'callback' in name or name == 'on_close': 428 | anno = Callable | None 429 | elif ( 430 | 'color' in name 431 | or (p.default and isinstance(p.default, tuple)) 432 | and len(p.default) == 4 433 | and anno == typing.List[int] | typing.Tuple[int, ...] 434 | ): 435 | anno = tuple[int, int, int] | tuple[int, int, int, int] | list[int] 436 | elif anno == typing.List[typing.List[float]]: 437 | anno = Sequence[Sequence[float]] 438 | # general-case formatting 439 | else: 440 | for tp in str, float, int: 441 | if anno != ((typing.List[tp] | typing.Tuple[tp, ...])): 442 | continue 443 | anno = Sequence[tp] 444 | break 445 | upd_params[name] = Parameter( 446 | name, 447 | p.kind, 448 | annotation=anno, 449 | default=p.default, 450 | ) 451 | return upd_params 452 | 453 | 454 | 455 | _item_ref = "A reference to an existing item (as a `int` uuid, `str` alias, or `int` item interface)" 456 | _empty_ref = "None or 0 (default)" 457 | 458 | @_tools.frozen_namespace 459 | class _DocParseConst: 460 | RE_ARGS = re.compile( 461 | r'^\s*Args[;:]$', re.MULTILINE 462 | ) 463 | RE_PARAM = re.compile( 464 | r'^\s+(?P\w*)\s?\((?P[^)]+)\):\s*(?P.*)', 465 | re.MULTILINE, 466 | ) 467 | # XXX: This doesn't do the job. Kept as a reference for later (TODO). 468 | RE_UNION = re.compile( 469 | r'Union\[\s*(?:[^\s]+]?)(?:(?P,)\s*[^\s]+)+(?P\])' 470 | ) 471 | 472 | # "large-scope" replacements 473 | # TODO: regex 474 | TYPE_REPLACEMENTS_1 = ( 475 | ("Union[int, str]" , "int | str" ), 476 | ("Union[List[int], Tuple[int, ...]]" , f"Sequence[int]" ), 477 | ("Union[List[float], Tuple[float, ...]]", f"Sequence[float]" ), 478 | ("Union[List[str], Tuple[str, ...]]" , f"Sequence[str]" ), 479 | ("List[List[float]]" , f"Sequence[Sequence[float]]"), 480 | ("List[List[int]]" , f"Sequence[Sequence[int]]" ), 481 | ) 482 | # "small-scope" replacements 483 | TYPE_REPLACEMENTS_2 = ( 484 | ("Dict" , "dict" ), 485 | ("List" , "list" ), 486 | ("Set" , "set" ), 487 | ("Tuple", "tuple") 488 | ) 489 | 490 | HEAD_SPECIFIC_REPLACEMENTS: dict[str, str] = {} 491 | 492 | PARAM_GENERAL_REPLACEMENTS: dict[str, str] = { 493 | 'before': ( 494 | f"{_item_ref} that is parented by the container that will soon parent this one. If specified, this item will " 495 | f"be inserted into the parent's child slot preceding the referenced item. If this value is None or 0 (default), " 496 | f"the item will be added to the end of the slot." 497 | ), 498 | 'callback': ( 499 | 'Called by Dear PyGui when the item is interacted with (usually clicked). Can be None, or any callable with ' 500 | 'a `__code__` attribute. Dear PyGui will send up to three positional arguments to the callback; ' 501 | '`sender` (`tag` of the item *callback* is registered to), `app_data` (None, or an updated value associated ' 502 | 'with `sender`), and `user_data` (registered *user_data* value of `sender`). However, the callback is not ' 503 | 'required to accept all or any of these arguments.' 504 | ), 505 | 'enabled': ( 506 | "Sets the item's operational state. Setting to False disables the item's functionality. An item's operational " 507 | "state also affects which elements/components of a theme are applied to it (based off the *enabled_state* value " 508 | "of each theme component). This setting does not affect the item's visibility state." 509 | ), 510 | 'filter_key': ( 511 | 'The text value of the item that will be used when applying filters.' 512 | ), 513 | 'height': ( 514 | 'Vertical size of the item in pixels. Setting this value may override auto-scaling options for some items; this can be ' 515 | 'reversed by setting this back to 0. Note that the value is interpreted as unsigned.' 516 | ), 517 | 'indent': ( 518 | 'Horizontal padding added to the left of the widget. Multiplicative with the `mvStyleVar_IndentSpacing` ' 519 | 'theme style element.' 520 | ), 521 | 'label': ( 522 | 'An informal name for the item. Items that are visible in the user interface may also use this as their display ' 523 | 'name. The `"##"` delimiter can be used within the label text. If present, only text preceding the first ' 524 | 'use of this delimiter will be shown when displaying the label. You can prevent this behavior by escaping the ' 525 | 'second hash character with a carriage return (`#\\\\r#`).' 526 | ), 527 | 'parent': ( 528 | f"{_item_ref} that will become this item's parent. If {_empty_ref}, Dear PyGui will default to " 529 | f"using whatever item that is on top of the container stack." 530 | ), 531 | 'show': ( 532 | 'Setting to True (default) will render the item and allow it to be seen, while False does the opposite. ' 533 | 'For items that cannot be visible in the user interface (handlers, registries, etc), the behavior of ' 534 | '*show* is similar to that of *enabled* where False disables their functionality.' 535 | ), 536 | 'source': ( 537 | f"{_item_ref}. If specified, getting or setting this item's value will instead get or set the value of the " 538 | f"referenced item. If the item normally displays its' value, it will instead display the referenced item's " 539 | f"value." 540 | ), 541 | 'tag': ( 542 | 'An item reference (as a `int` uuid, `str` alias, or `int` item interface) to bind to the interface. If ' 543 | f'{_empty_ref}, or if the integer/string value of the reference is new or unused by another item, ' 544 | "the interface creates a new item of its' type during initialization. If the value is not a new binding " 545 | "reference, the interface will (try to) operate on the item with an identifier matching that value." 546 | ), 547 | 'track_offset': ( 548 | 'A numeric value between 0 and 1 that affects item tracking while scrolling when *tracked* is True. Common values are' 549 | ' 0.0 (top), 0.5 (center, default), and 1.0 (bottom).' 550 | ), 551 | 'tracked': ( 552 | "Enables (True, default) or disables (False) the item's ability to be tracked while scrolling." 553 | ), 554 | 'use_internal_label': ( 555 | "True (default) will append `'##'` to the item's *label* value." 556 | ), 557 | 'user_data': ( 558 | 'Can be set as any value or reference. For items with callback-related configuration options, Dear PyGui will send this ' 559 | 'as the third positional argument when invoking the callback(s).' 560 | ), 561 | 'width': ( 562 | f'Horizontal size of the item in pixels. Setting this value may override auto-scaling options for some items; this can be ' 563 | 'reversed by setting this back to 0. Note that the value is interpreted as unsigned.' 564 | ), 565 | } 566 | 567 | PARAM_SPECIFIC_REPLACEMENTS: dict[str, str] = {} 568 | 569 | 570 | def upd_command_doc(command: ItemCommand, line_width: int = 92) -> str: 571 | """Return a formatted docstring parsed from a Dear PyGui item 572 | command.""" 573 | s = command.__doc__ or '' 574 | s = s \ 575 | .replace('\t', ' ') \ 576 | .split(' Returns:')[0] \ 577 | .split(" Yields:")[0] \ 578 | 579 | key = _DocParseConst.RE_ARGS.search(s) 580 | if not key: 581 | return s 582 | 583 | header, body = s.split(key.group(0), maxsplit=1) 584 | header = textwrap.fill( 585 | f"{header.strip().rstrip('.')}.", 586 | line_width, 587 | subsequent_indent=" " 588 | ) 589 | 590 | # TODO: Build an accurate regex for unions. Harder than it seems -- like 591 | # reading alphabet soup while drunk. 592 | for replacement in _DocParseConst.TYPE_REPLACEMENTS_1: 593 | body = body.replace(*replacement) 594 | 595 | params = array.array('u') 596 | for param in _DocParseConst.RE_PARAM.finditer(body): 597 | name, tp, doc = param.groups() 598 | if '(deprecated)' in doc: 599 | continue 600 | if name in _DocParseConst.PARAM_GENERAL_REPLACEMENTS: 601 | doc = _DocParseConst.PARAM_GENERAL_REPLACEMENTS[name] 602 | else: 603 | doc = doc.rstrip().rstrip('.') 604 | if doc: 605 | doc = f"{doc}." 606 | for replacement in _DocParseConst.TYPE_REPLACEMENTS_2: 607 | tp = tp.replace(*replacement) 608 | params.fromunicode(textwrap.fill( 609 | f"* {name} ({tp}): {doc}", 610 | line_width, 611 | initial_indent=" ", 612 | subsequent_indent=" ", 613 | )) 614 | params.fromunicode('\n\n') 615 | 616 | return ''.join(( 617 | header, 618 | "\n\n Args:\n", 619 | params.tounicode(), 620 | f" Command: `dearpygui.{command.__name__}`" 621 | )) 622 | -------------------------------------------------------------------------------- /dearpypixl/_tools.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import abc 3 | import time 4 | import enum 5 | import types 6 | import ctypes 7 | import typing 8 | import inspect 9 | import itertools 10 | import functools 11 | import threading 12 | from typing import overload, Any, Mapping, Iterable, Callable, Sequence, TypeVar, ParamSpec 13 | 14 | 15 | P = ParamSpec("P") 16 | T = TypeVar("T") 17 | KT = TypeVar("KT") 18 | VT = TypeVar("VT") 19 | O = TypeVar("O") 20 | 21 | 22 | _SENTINEL = object() 23 | 24 | 25 | 26 | 27 | 28 | # [Dynamic Code Generation] 29 | 30 | def create_module( 31 | name : str, 32 | body : Mapping[str, Any] | Iterable[tuple[str, Any]] = (), 33 | attrs: Mapping[str, Any] | Iterable[tuple[str, Any]] = (), 34 | ) -> types.ModuleType: 35 | """Create a new module object. 36 | 37 | Args: 38 | - name: Name for the new module. 39 | 40 | - body: Used to update the module's contents/dictionary. 41 | 42 | - attrs: Used to update the module's attributes. 43 | """ 44 | m = types.ModuleType(name, None) 45 | for k, v in tuple(attrs): 46 | setattr(m, k, v) 47 | m.__dict__.update(body) 48 | 49 | return m 50 | 51 | 52 | @overload 53 | def create_function(name: str, args: Sequence[str], body: Sequence[str], return_type: T = Any, module: str = '', *, globals: dict[str, Any] | None = None, locals: Mapping[str, Any] | Iterable[tuple[str, Any]] = ()) -> Callable[..., T]: ... # type: ignore 54 | def create_function(name: str, args: Sequence[str], body: Sequence[str], return_type: T = Any, module: str = '', *, globals: dict[str, Any] | None = None, locals: Mapping[str, Any] | Iterable[tuple[str, Any]] = (), __default_module=create_module('')) -> Callable[..., T]: 55 | """Compile a new function from source. 56 | 57 | Args: 58 | * name: Name for the new function. 59 | 60 | * args: A sequence containing argument signatures (as strings) 61 | for the new function. Each value in *args* should be limited to 62 | a single argument signature. 63 | 64 | * body: A sequence containing the source that will be executed when 65 | the when the new function is called. Each value in the sequence should 66 | be limited to one line of code. 67 | 68 | * return_type: Object to use as the function's return annotation. 69 | 70 | * module: Used to update the function's `__module__` attribute 71 | and to fetch the appropriate global mapping when *globals* is 72 | None. 73 | 74 | * globals: The global scope for the new function. 75 | 76 | * locals: The (non)local scope for the new function. 77 | 78 | 79 | When *globals* is None, this function will attempt to look up *module* 80 | in `sys.modules`, and will use the returned module's dictionary as 81 | *globals* if found. If the module could not be found or both *module* 82 | and *globals* are unspecified, *globals* defaults to the dictionary of 83 | a dedicated internal dummy module. 84 | 85 | Note that, when including *locals*, the created function's local scope 86 | will not be *locals*, but a new mapping created from its' contents. 87 | However, *globals* is used as-is. 88 | 89 | """ 90 | assert not isinstance(body, str) 91 | body = '\n'.join(f' {line}' for line in body) 92 | 93 | locals = dict(locals) 94 | locals["_return_type"] = return_type 95 | 96 | if globals is None: 97 | if module in sys.modules: 98 | globals = sys.modules[module].__dict__ 99 | else: 100 | globals = __default_module.__dict__ 101 | 102 | closure = ( 103 | f"def __create_function__({', '.join(locals)}):\n" 104 | f" def {name}({', '.join(args)}) -> _return_type:\n{body}\n" 105 | f" return {name}" 106 | ) 107 | 108 | scope = {} 109 | exec(closure, globals, scope) 110 | 111 | fn = scope["__create_function__"](**locals) 112 | fn.__module__ = module or __default_module.__name__ 113 | fn.__qualname__ = fn.__name__ = name 114 | 115 | return fn 116 | 117 | 118 | @typing.final 119 | class _MarkerType(typing.Protocol): 120 | def __repr__(self) -> str: ... 121 | def __str__(self) -> str: ... 122 | def __eq__(self, other: Any) -> bool: ... 123 | def __hash__(self) -> int: ... 124 | def __delattr__(self, name: str) -> TypeError: ... 125 | def __setattr__(self, name: str, value: Any) -> TypeError: ... 126 | 127 | 128 | def create_marker_type(name: str, module: str, *, body: Mapping[str, Any] | Sequence[tuple[str, Any]] = ()) -> type[_MarkerType]: 129 | """Dynamically create and return a new marker type as if it were defined 130 | in the module this function is called from. 131 | 132 | Markers cannot be instantiated or subclassed, and are read-only by default. 133 | 134 | Args: 135 | * name: Class name of the marker. 136 | 137 | * body: A mapping to use as the namespace/dict of the marker's 138 | metaclass. 139 | 140 | 141 | This function creates both the marker class as well as the marker's metaclass. 142 | By default, the metaclass implements the `__repr__`, `__eq__`, `__hash__`, 143 | `__setattr__`, and `__delattr__` methods. User implementations included in 144 | *body* will be used instead, if present. 145 | 146 | Note that the metaclass also implements `__new__` and `__call__`. User 147 | implementations of these methods will be used when creating the marker class 148 | if available. However, they are deleted from the metaclass' dict before 149 | returning the marker class. 150 | """ 151 | body = dict(body) 152 | module = sys.modules[module] # type: ignore 153 | 154 | namespace = { 155 | '__module__': module.__name__, # type: ignore 156 | '__repr__': create_function( 157 | '__repr__', 158 | ('cls',), 159 | (f'return ""',), 160 | globals=module.__dict__ 161 | ), 162 | '__eq__': create_function( 163 | '__eq__', 164 | ('cls', 'other'), 165 | (f'return other is cls',), 166 | globals=module.__dict__ 167 | ), 168 | '__hash__': create_function( 169 | '__hash__', 170 | ('cls',), 171 | (f'return {hash(object())}',), 172 | globals=module.__dict__, 173 | ), 174 | '__setattr__': create_function( 175 | '__setattr__', 176 | ('cls', 'name = None', 'value = None'), 177 | (f"raise TypeError(\"cannot set attributes on read-only class {name!r}.\")",), 178 | globals=module.__dict__, 179 | ), 180 | '__delattr__': create_function( 181 | '__delattr__', 182 | ('cls', 'name = None'), 183 | (f"raise TypeError(\"cannot delete attributes on read-only class {name!r}.\")",), 184 | globals=module.__dict__, 185 | ), 186 | } 187 | namespace.update(body) 188 | cls: Any = type(f'{name.title()}Type', (type,), namespace)( 189 | name, (), {'__module__': module.__name__} # type:ignore 190 | ) 191 | type(cls).__call__ = create_function( 192 | '__call__', 193 | ('*args', '**kwargs'), 194 | (f"raise TypeError(\"{name!r} class cannot be instantiated.\")",), 195 | globals=module.__dict__, 196 | ) 197 | type(cls).__new__ = create_function( # type: ignore 198 | '__new__', 199 | ('*args', '**kwargs'), 200 | (f"raise TypeError(\"one or more bases cannot be subclassed.\")",), 201 | globals=module.__dict__, 202 | ) 203 | return typing.final(cls) 204 | 205 | 206 | def frozen_namespace(cls: type[T]) -> type[T]: 207 | contents = tuple(m for m in dir(cls) if not m.startswith('_')) 208 | return create_marker_type( # type: ignore 209 | cls.__qualname__, 210 | cls.__module__, 211 | body={ 212 | '__annotations__': cls.__annotations__, 213 | "__iter__": create_function( 214 | '__iter__', 215 | ('self',), 216 | ('return iter({' + ', '.join(f"'{m}': self.{m}" for m in contents) + '}.items())',), 217 | globals=sys.modules[cls.__module__].__dict__, 218 | ), 219 | **{m: getattr(cls, m) for m in contents} 220 | } 221 | ) 222 | 223 | 224 | 225 | # [Inspection Tools] 226 | 227 | class PyTypeFlag(enum.IntFlag): 228 | """Python type bit masks (`type.__flags__`, `PyTypeObject.tp_flags`). 229 | 230 | A type's flag bit mask is created when the object is defined -- 231 | changing it from Python does nothing helpful. 232 | """ 233 | STATIC_BUILTIN = (1 << 1) # (undocumented, internal) 234 | MANAGED_WEAKREF = (1 << 3) 235 | MANAGED_DICT = (1 << 4) 236 | PREHEADER = (MANAGED_WEAKREF | MANAGED_DICT) 237 | SEQUENCE = (1 << 5) 238 | MAPPING = (1 << 6) 239 | DISALLOW_INSTANTIATION = (1 << 7) # `tp_new == NULL` 240 | IMMUTABLETYPE = (1 << 8) 241 | HEAPTYPE = (1 << 9) 242 | BASETYPE = (1 << 10) # allows subclassing 243 | HAVE_VECTORCALL = (1 << 11) 244 | READY = (1 << 12) # fully constructed type 245 | READYING = (1 << 13) # type is under construction 246 | HAVE_GC = (1 << 14) # allow garbage collection 247 | HAVE_STACKLESS_EXTENSION = (3 << 15) # Stackless Python 248 | METHOD_DESCRIPTOR = (1 << 17) # behaves like unbound methods 249 | VALID_VERSION_TAG = (1 << 19) # has up-to-date type attribute cache 250 | ABSTRACT = (1 << 20) # `ABCMeta.__new__` 251 | MATCH_SELF = (1 << 22) # "builtin" class pattern-matting behavior (undocumented, internal) 252 | ITEMS_AT_END = (1 << 23) # items at tail end of instance memory 253 | LONG_SUBCLASS = (1 << 24) # |- used for `Py_Check`, `isinstance`, `issubclass` 254 | LIST_SUBCLASS = (1 << 25) # | 255 | TUPLE_SUBCLASS = (1 << 26) # | 256 | BYTES_SUBCLASS = (1 << 27) # | 257 | UNICODE_SUBCLASS = (1 << 28) # | 258 | DICT_SUBCLASS = (1 << 29) # | 259 | BASE_EXC_SUBCLASS = (1 << 30) # | 260 | TYPE_SUBCLASS = (1 << 31) # | 261 | 262 | 263 | def has_feature(cls: type[Any], flag: int | PyTypeFlag): 264 | """Python implementation of CPython's `PyType_HasFeature` macro.""" 265 | return bool(cls.__flags__ & flag) 266 | 267 | 268 | def managed_dict_type(o: Any) -> bool: 269 | """Check if an instance, or future instances of a class, support 270 | the dynamic assignment of new members/variables. 271 | 272 | Args: 273 | - o: Class or instance object to inspect. 274 | 275 | 276 | If *o* is an instance object, this function returns True if it has 277 | a `__dict__` attribute. For class objects; return True if instances 278 | of that class are expected to have a `__dict__` attribute, instead. 279 | 280 | This function will return False on "slotted" classes if any of its' 281 | non-builtin bases did not declare `__slots__` at the time of their 282 | creation. 283 | """ 284 | if not isinstance(o, type): 285 | o = type(o) 286 | return has_feature(o, PyTypeFlag.MANAGED_DICT) 287 | 288 | 289 | def is_cclass(o: Any): 290 | """Return True if an object is a class implemented in C.""" 291 | return isinstance(o, type) and not has_feature(o, PyTypeFlag.HEAPTYPE) 292 | 293 | 294 | def is_cfunction(o: Any) -> bool: 295 | """Return True if an object is a function implemented in C. This 296 | includes built-in functions like `len` and `isinstance`, as well 297 | as those exported in C-extensions. 298 | """ 299 | return isinstance(o, types.BuiltinFunctionType) 300 | 301 | 302 | @overload 303 | def is_builtin(o: Any) -> bool: ... # type: ignore 304 | def is_builtin(o: Any, *, __module=type.__module__) -> bool: 305 | """Return True if an object is a built-in function or class. 306 | 307 | Unlike `inspect.isbuiltin`, the result of this function is not 308 | falsified by C-extension objects, and can also identify built-in 309 | classes. 310 | 311 | Args: 312 | - o: Object to test. 313 | """ 314 | try: 315 | return o.__module__ == __module 316 | except: 317 | return False 318 | 319 | 320 | @overload 321 | def is_mapping(o: Any) -> bool: ... # type: ignore 322 | def is_mapping(o: Any, *, __key=object()): 323 | """Return True if an object implements the basic behavior of a 324 | mapping. 325 | 326 | Useful to validate an object based on protocol rather than type; 327 | objects need not be derived from `Mapping` to be considered a 328 | mapping or mapping-like. 329 | 330 | Args: 331 | - o: Object to test. 332 | 333 | 334 | This function returns True if `KeyError` is raised when attempting 335 | to "key" the object with one it does not contain. The object 336 | must also implement or inherit the `__len__`, `__iter__`, and 337 | `__contains__` methods. 338 | 339 | Note that this function only tests/inspects behavior methods, 340 | and may return True even if an object does implement high-level 341 | `Mapping` methods such as `.get`. 342 | """ 343 | # Could also throw it a non-hashable and check for `TypeError`, but 344 | # it feels more ambiguous than `KeyError` in this context. 345 | try: 346 | o[__key] 347 | except KeyError: pass 348 | except: 349 | return False 350 | return ( 351 | hasattr(o, '__len__') 352 | and 353 | hasattr(o, '__contains__') 354 | and 355 | hasattr(o, '__iter__') 356 | ) 357 | 358 | 359 | def is_sequence(o: Any) -> bool: 360 | """Return True if an object implements the basic behavior of a 361 | sequence. 362 | 363 | Useful to validate an object based on protocol rather than type; 364 | objects need not be derived from `Sequence` to be considered a 365 | sequence or sequence-like. 366 | 367 | Args: 368 | - o: Object to test. 369 | 370 | 371 | This function returns True if `IndexError` is raised when attempting 372 | to "key" the object with an index outside of its' range. The object 373 | must also implement or inherit the `__len__`, `__iter__`, and 374 | `__contains__` methods. 375 | 376 | Note that this function only tests/inspects behavior methods, 377 | and may return True even if an object does implement high-level 378 | `Sequence` methods such as `.count` and `.index`. 379 | """ 380 | try: 381 | o[len(o)] 382 | except IndexError: pass 383 | except: 384 | return False 385 | return hasattr(o, '__contains__') and hasattr(o, '__iter__') 386 | 387 | 388 | def compare_versions(lo: str, comp: typing.Literal['<', '<=', '==', '!=', '>=', '>'], ro: str) -> bool: 389 | """Simple software version comparison tool. 390 | 391 | Does not follow PEP standards. 392 | 393 | Args: 394 | * lo: Left operand version string. 395 | 396 | * comp: Rich comparison symbol(s). 397 | 398 | * ro: Right operand version string. 399 | """ 400 | l_vers = tuple(int(v.strip()) for v in lo.split('.') if v.isdigit()) 401 | r_vers = tuple(int(v.strip()) for v in ro.split('.') if v.isdigit()) 402 | return eval(f'{l_vers} {comp} {r_vers}') 403 | 404 | 405 | 406 | 407 | # [ Descriptors ] 408 | 409 | class _property(typing.Generic[T], abc.ABC): 410 | __slots__ = ('__wrapped__',) 411 | 412 | def __init__(self, fget: Callable[..., T]): 413 | self.__wrapped__ = fget 414 | 415 | def __set_name__(self, cls: type[Any], name: str): 416 | set_name = getattr(self.__wrapped__, "__set_name__", None) 417 | if set_name: 418 | set_name(cls, name) 419 | 420 | @abc.abstractmethod 421 | def __get__(self, instance: Any, cls: type | None = None) -> T | typing.Self: ... 422 | 423 | 424 | class classproperty(_property, typing.Generic[T]): 425 | """A descriptor for emulating class-bound virtual attributes. Can 426 | optionally be used as a decorator. 427 | 428 | The functionality of this descriptor is similar to a `classmethod` and 429 | `property` descriptor chain in Python 3.9 which, as of 3.11, has been 430 | deprecated. 431 | 432 | Note that, due to Python's lookup behavior, the mutability of the 433 | property can be misleading; it is read-only when an assignment is made 434 | on an instance, but not on the class. This weird behavior is one of the 435 | reasons for the feature's deprecation. 436 | """ 437 | __slots__ = () 438 | 439 | def __get__(self, instance: Any, cls: type[Any]) -> T: 440 | return self.__wrapped__(cls) 441 | 442 | 443 | class staticproperty(_property, typing.Generic[T]): 444 | """A descriptor for emulating unbound virtual attributes. Can optionally 445 | be used as a decorator. 446 | 447 | The functionality of this descriptor is similar to a `staticmethod` and 448 | `property` descriptor chain in Python 3.9 which, as of 3.11, has been 449 | deprecated. 450 | 451 | Note that, due to Python's lookup behavior, the mutability of the 452 | property can be misleading; it is read-only when an assignment is made 453 | on an instance, but not on the class. This weird behavior is one of the 454 | reasons for the feature's deprecation. 455 | """ 456 | __slots__ = () 457 | 458 | def __get__(self, instance: Any, cls: type[Any]) -> T: 459 | return self.__wrapped__() 460 | 461 | 462 | class simpleproperty(_property, typing.Generic[T]): 463 | """A data descriptor for emulating read-only virtual attributes. Can 464 | optionally be used as a decorator. 465 | >>> class MyClass: 466 | ... foo = simpleproperty() 467 | ... 468 | 469 | 470 | The behavior of `foo = simpleproperty()` within a class body is roughly 471 | outlined in the below example using a `property` descriptor: 472 | >>> class MyClass: 473 | ... @property 474 | ... def foo(self): 475 | ... return self._foo 476 | ... 477 | ... @foo.setter 478 | ... def foo(self, value): 479 | ... if hasattr(self, '_foo'): 480 | ... raise AttributeError 481 | ... self._foo = value 482 | 483 | When accessed, the descriptor returns the value of a similarly- 484 | named private member on the instance -- That is, a member whose name 485 | matches the name the descriptor is assigned to, but prefixed with a 486 | single underscore. Writes to the "public" member are allowed, but 487 | only if the "private" member is missing. It is otherwise read-only, 488 | and any attempt to write to the public member are met with 489 | `AttributeError`: 490 | >>> my_instance = MyClass() 491 | >>> my_instance.foo = 'bar' # WRITABLE 492 | >>> my_instance.foo 493 | 'bar' 494 | >>> my_instance.foo = 'BAR' # READ-ONLY 495 | Traceback (most recent call last): 496 | ... 497 | AttributeError: 'MyClass' object attribute 'foo' is read-only. 498 | 499 | The default setter implementation may not work for all cases, as it 500 | is usually desirable to transform the value before setting it. The 501 | descriptor's constructor accepts a custom setter as the first argument, 502 | allowing it to be used as a decorator: 503 | >>> class MyClass: 504 | ... @simpleproperty 505 | ... def foo(self, value): 506 | ... self._foo = str(value).lower() 507 | ... 508 | >>> my_instance = MyClass() 509 | >>> my_instance.foo = 'BAR' 510 | >>> my_instance.foo 511 | 'bar' 512 | 513 | Unlike the outlined `property` simplification above, the logic that 514 | determines when to perform the set is actually decoupled from the setter 515 | itself. The descriptor determines *when* to invoke the setter, and the 516 | setter determines *how* to set the value. That is, if the descriptor 517 | determines the public member is read-only (i.e. `my_instance._foo` exists), 518 | it won't call the setter. 519 | 520 | When accessing the public member, the "getter" used by the descriptor 521 | first checks the instance dictionary for the private member. If it is not 522 | found, the result of calling `getattr(instance, '_member')` is returned. 523 | 524 | The "getter" used by the descriptor is static; it cannot be configured nor 525 | can a new one be set. If your use-case extends beyond the capacity of this 526 | simple descriptor, it is best to use a `property` instead. 527 | 528 | When setting `simpleproperty` descriptors on an existing class, you must 529 | manually call the `.__set_name__` method to fully initialize the descriptor 530 | (Python does this automatically DURING class creation). Otherwise, 531 | `RuntimeError` may be thrown when attempting to access the target member. 532 | 533 | The descriptor works for slotted classes. However, the class must define, 534 | inherit, or slot the "private" target member. 535 | """ 536 | __slots__ = ('name',) 537 | 538 | def __init__(self, fset: Callable[[Any, Any], T] | None = None): 539 | self.__wrapped__ = fset 540 | self.name = '' 541 | 542 | def __set_name__(self, cls: type, name: str): 543 | super().__set_name__(cls, name) 544 | name = f"_{name}" 545 | if '__slots__' in cls.__dict__: 546 | slots = set(itertools.chain(getattr(b, '__slots__', ()) for b in cls.mro())) 547 | if name not in slots and getattr(cls, name, _SENTINEL) is _SENTINEL: 548 | raise ValueError( 549 | f"slotted class {cls.__qualname__!r} does not include, inherit, " 550 | f"or define non-public member {name!r}." 551 | ) 552 | self.name = name 553 | 554 | def __get__(self, instance: Any, cls: type | None = None) -> T: 555 | try: 556 | return instance.__dict__[self.name] 557 | except AttributeError: 558 | if instance is None: 559 | return self # type: ignore 560 | elif not self.name: 561 | raise RuntimeError( 562 | f'{type(self).__qualname__!r} descriptor not fully initialized ' 563 | f'-- did you forget to call `__set_name__`?' 564 | ) 565 | return getattr(instance, self.name) 566 | except KeyError: 567 | if self.name: 568 | raise AttributeError( 569 | f'{type(instance).__qualname__!r} object has no attribute ' 570 | f'{self.name[1:]!r}.' 571 | ) from None 572 | raise 573 | 574 | def __set__(self, instance: Any, value: Any) -> None: 575 | if hasattr(instance, self.name): 576 | raise AttributeError( 577 | f'{type(instance).__qualname__!r} object attribute {self.name[1:]!r} is read-only.' 578 | ) 579 | if self.__wrapped__ is None: 580 | setattr(instance, self.name, value) 581 | else: 582 | self.__wrapped__(instance, value) 583 | 584 | def __copy__(self) -> typing.Self: 585 | copy = type(self)(self.__wrapped__) 586 | copy.name = self.name 587 | return copy 588 | 589 | def setter(self, fset: Callable[[Any, Any], Any] | None) -> typing.Self: 590 | copy = self.__copy__() 591 | copy.__wrapped__ = fset 592 | return copy 593 | 594 | 595 | 596 | 597 | # [ Misc ] 598 | 599 | Lock = threading.Lock if not sys.gettrace() else threading.RLock 600 | 601 | 602 | class Locker(typing.Generic[T]): 603 | """A mutex-like object that encapsulates a value that is to be managed 604 | only between select callables. 605 | """ 606 | __slots__ = ( 607 | 'value', 608 | '__lock', 609 | ) 610 | 611 | def __init__(self, default_value: T): 612 | self.value = default_value 613 | self.__lock = Lock() 614 | 615 | def __enter__(self): 616 | self.__lock.acquire() 617 | 618 | def __exit__(self, *args): 619 | self.__lock.release() 620 | 621 | def aquire(self): 622 | self.__lock.acquire() 623 | 624 | def release(self): 625 | self.__lock.release() 626 | 627 | def bind(self, fn: Callable[typing.Concatenate[typing.Self, P], O]) -> Callable[P, O]: 628 | """Return a callable as a method bound to this object. Can be used 629 | as a decorator.""" 630 | return typing.cast(Callable[P, O], types.MethodType(fn, self)) 631 | 632 | __call__ = bind 633 | 634 | 635 | def cfunction(pfunction: 'ctypes._NamedFuncPointer', argtypes: tuple[Any, ...] = (), restype: Any = None): 636 | """Cast a foreign C function pointer as the decorated function. 637 | 638 | *argtypes* and *restypes* will be assigned to *pfunction*. 639 | """ 640 | def func_prototype(fn_signature: Callable[P, T]) -> Callable[P, T]: 641 | pfunction.argtypes = argtypes 642 | pfunction.restype = restype 643 | functools.update_wrapper(pfunction, fn_signature, updated='') 644 | return pfunction # type: ignore 645 | return func_prototype 646 | 647 | 648 | # used to cork c-extension memory leaks 649 | @cfunction(ctypes.pythonapi.Py_DecRef, (ctypes.py_object,)) 650 | def Py_DECREF(_object: Any) -> None: 651 | """Reduce the reference count of a Python object by one. 652 | 653 | An object may be deconstructed (garbage collected) when its 654 | reference count reaches zero. 655 | """ 656 | 657 | 658 | def timed(fn: Callable[P, T]) -> Callable[P, T]: 659 | """Decorator that times the execution of a function.""" 660 | @functools.wraps(fn) 661 | def wrap(*args: P.args, **kwds: P.kwargs) -> T: 662 | ts = timer() 663 | result = fn(*args, **kwds) 664 | te = timer() 665 | sig = ', '.join(( 666 | *(repr(v) for v in args), 667 | *(f"{k}={v!r}" for k,v in kwds.items()), 668 | )) 669 | print(f'`{fn.__name__}({sig}) -> ...` took {te-ts} second(s)') 670 | return result 671 | timer = time.time 672 | return wrap 673 | 674 | 675 | def cache_once(fn: Callable[P, T], /) -> Callable[P, T]: 676 | """Similar to `functools.cached_property` but for functions. Returns the 677 | same result regardless of the arguments passed to the decorated function. 678 | """ 679 | @functools.wraps(fn) 680 | def cached_result(*args: P.args, **kwargs: P.kwargs) -> T: 681 | nonlocal cache 682 | try: 683 | return cache 684 | except NameError: 685 | cache = fn(*args, **kwargs) 686 | return cache 687 | cache: T 688 | return cached_result 689 | 690 | 691 | @functools.lru_cache(128) 692 | def parameters(c: Callable) -> tuple[inspect.Parameter, ...]: 693 | """Return the parameters of the given callable. The result 694 | is cached for future calls. 695 | 696 | Args: 697 | * c: Callable for the query. 698 | """ 699 | return tuple(inspect.signature(c).parameters.values()) -------------------------------------------------------------------------------- /dearpypixl/_typing.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import abc 3 | import datetime 4 | from numbers import Number 5 | from types import ( 6 | CodeType, 7 | CodeType as Code, 8 | EllipsisType, 9 | GenericAlias, 10 | MappingProxyType, 11 | MappingProxyType as MappingProxy, 12 | MethodType, 13 | MethodType as Method, 14 | ) 15 | from collections.abc import ( 16 | Callable, 17 | Collection, 18 | Generator, 19 | Iterable, 20 | Iterator, 21 | Sequence, 22 | MutableSequence, 23 | Mapping, 24 | MutableMapping, 25 | KeysView, 26 | ValuesView, 27 | ItemsView, 28 | ) 29 | from typing import ( 30 | Any, 31 | Required, 32 | NotRequired, 33 | 34 | SupportsIndex, 35 | SupportsAbs, 36 | SupportsBytes, 37 | SupportsComplex, 38 | SupportsFloat, 39 | SupportsInt, 40 | SupportsRound, 41 | 42 | Self, 43 | Literal, 44 | Generic, 45 | Protocol, 46 | TypedDict, 47 | 48 | Annotated, 49 | Concatenate, 50 | ClassVar, 51 | TypeAlias, 52 | TypeVar, 53 | TypeVarTuple, 54 | ParamSpec, 55 | Unpack, 56 | Union, 57 | 58 | NamedTuple, 59 | 60 | cast, 61 | final, 62 | get_overloads, 63 | overload, 64 | runtime_checkable, 65 | 66 | TYPE_CHECKING, 67 | ) 68 | if sys.version_info < (3, 12): 69 | try: 70 | from typing_extensions import override, TypeAliasType 71 | except ImportError: 72 | raise ImportError("Python < 3.12 requires `typing-extensions` module.") from None 73 | else: 74 | from typing import override 75 | from . import _tools 76 | 77 | if TYPE_CHECKING: 78 | from ._interface import AppItemType 79 | 80 | 81 | # Super-primitive types and protocols are declared here. 82 | # Parts of the lib rely on runtime type annotations, so 83 | # `from __future__ import annotations` is rather destru- 84 | # ctive. 85 | 86 | 87 | _T = TypeVar("_T") 88 | _N = TypeVar("_N", bound=float | int) 89 | _T_co = TypeVar("_T_co", covariant=True) 90 | _P = ParamSpec("_P") 91 | 92 | 93 | 94 | 95 | # [ GENERAL ] 96 | 97 | Vec = tuple[_N, ...] | Sequence[_N] 98 | Vec2 = tuple[_N, _N] | Sequence[_N] 99 | Vec3 = tuple[_N, _N, _N] | Sequence[_N] 100 | Vec4 = tuple[_N, _N, _N, _N] | Sequence[_N] 101 | 102 | Color = Vec3[int] | Vec4[int] 103 | Point = Vec2[int] 104 | 105 | 106 | class Property(Protocol[_T_co]): 107 | def __get__(self, instance: Any, cls: type[Any] | None = None) -> _T_co: ... 108 | def __set__(self, instance: Any, value: Any): ... 109 | 110 | 111 | 112 | 113 | # [ ITEM STUFF ] 114 | 115 | # XXX: "uuid", "alias", and "item" are thrown around a lot in 116 | # DPG. They are given specific meaning in the context of this 117 | # project: 118 | # * uuid : A compatible integer id for an item, in-use or 119 | # otherwise. 120 | # * alias: A compatible string id for an item, in-use or 121 | # otherwise. 122 | # * item : A uuid or alias tied to an active widget in the 123 | # registry. 124 | ItemAlias = str 125 | ItemUUID = int 126 | Item = ItemUUID | ItemAlias 127 | 128 | Interface = TypeVar("Interface", bound='AppItemType') 129 | ItemType = TypeVar("ItemType", bound=type['AppItemType']) 130 | 131 | 132 | _Item_co = TypeVar("_Item_co", covariant=True, bound=int | str) 133 | 134 | class ItemCommand(Protocol[_P, _Item_co]): 135 | __name__: str 136 | def __call__(self, *, tag: Item = 0, **kwargs) -> _Item_co: ... 137 | 138 | 139 | class ItemCallback(Protocol): 140 | __code__: CodeType 141 | def __call__(self, *args: Unpack[tuple[Item, Any, Any]]) -> Any: ... 142 | 143 | 144 | 145 | 146 | class _ItemProperty(Generic[_T]): 147 | __slots__ = ("_key",) 148 | 149 | def __init__(self, key: str = '', /): 150 | self._key = key 151 | 152 | def __set_name__(self, cls: type['ItemInterface'], name: str): 153 | self._key = self._key or name 154 | 155 | def __set__(self, instance: 'ItemInterface', value: Any): 156 | raise AttributeError( 157 | f'{type(instance).__qualname__!r} object attribute {self._key!r} is read-only.' 158 | ) 159 | 160 | 161 | class ItemConfig(_ItemProperty[_T]): 162 | """Item interface data descriptor. Uses the interface's `.configuration` 163 | and `.configure` hooks. 164 | """ 165 | __slots__ = ('__set__',) 166 | 167 | __set__: Callable[..., None] 168 | 169 | def __set_name__(self, cls: type['ItemInterface'], name: str): 170 | self._key = self._key or name 171 | # Dynamically generated for performance. 5-million calls 172 | # ~ 1 second w/potato CPU -- it doesn't get any faster. 173 | # XXX: Unfortunately, `__get__` can't have the same treatment. 174 | # Python will find the `__get__` member descriptor and, 175 | # since it's a dunder method, will call the class-level 176 | # member i.e. `member_descriptor.__call__`, which doesn't 177 | # exist. Any other dunder would work fine, just not `__get__`. 178 | __set__ = _tools.create_function( 179 | '__set__', 180 | ('self', 'instance', 'value'), 181 | (f"instance.configure({self._key}=value)",) 182 | ) 183 | self.__set__ = MethodType(__set__, self) 184 | 185 | def __get__(self, instance: 'ItemInterface', cls: type['ItemInterface'] | None = None) -> _T: 186 | if instance is None: 187 | return self # type: ignore 188 | return instance.configuration()[self._key] 189 | 190 | 191 | 192 | class ItemInfo(_ItemProperty[_T]): 193 | """Item interface descriptor. Uses the interface's `.information` hook. 194 | """ 195 | __slots__ = () 196 | 197 | def __get__(self, instance: 'ItemInterface', cls: type['ItemInterface'] | None = None) -> _T: 198 | if instance is None: 199 | return self # type: ignore 200 | return instance.information()[self._key] 201 | 202 | 203 | class ItemState(_ItemProperty[_T]): 204 | """Item interface descriptor. Uses the interface's `.state` hook. 205 | """ 206 | __slots__ = () 207 | 208 | def __get__(self, instance: 'ItemInterface', cls: type['ItemInterface'] | None = None) -> _T | None: 209 | if instance is None: 210 | return self # type: ignore 211 | return instance.state().get(self._key, None) 212 | 213 | 214 | 215 | 216 | null_itemtype = 0, "mvAppItemType::mvAppItem" # 0 == dearpygui.mvAll 217 | 218 | def null_command(*args, tag: Item = 0, **kwargs) -> Item: 219 | """Default 'command' value for item interface types.""" 220 | return tag 221 | 222 | 223 | class ItemInterfaceMeta(type(Protocol), abc.ABCMeta): 224 | __slots__ = () 225 | 226 | identity: tuple[int, str] 227 | command : ItemCommand 228 | 229 | def __new__(__mcls: type[Self], __name: str, __bases: tuple[type, ...], __namespace: dict[str, Any], **kwargs: Any) -> Self: 230 | cls = super().__new__(__mcls, __name, __bases, __namespace, **kwargs) 231 | if not hasattr(cls, 'command'): 232 | setattr(cls, 'command', staticmethod(null_command)) 233 | if not hasattr(cls, 'identity'): 234 | setattr(cls, 'identity', (-hash(cls), cls.__qualname__)) 235 | return cls 236 | 237 | def __int__(self) -> int: 238 | return self.identity[0] 239 | 240 | def __str__(self) -> str: 241 | return self.identity[1] 242 | 243 | def __repr__(self) -> str: 244 | return f"" 245 | 246 | def register(self, cls: type[_T]) -> type[_T]: 247 | # This should only be used internally, and only a couple of 248 | # times (max). 249 | assert isinstance(cls, ItemInterface) 250 | return super().register(cls) 251 | 252 | 253 | @runtime_checkable 254 | class ItemInterface(Protocol, metaclass=ItemInterfaceMeta): 255 | """Minimal item API for non-item types.""" 256 | __slots__ = () 257 | 258 | def __repr__(self): 259 | config = ', '.join(f"{k}={str(v)!r}" for k, v in self.configuration().items()) 260 | return f"{type(self).__qualname__}({config})" 261 | 262 | @property 263 | def tag(self) -> ItemUUID: 264 | return -hash(self) # signed -- avoids DPG uuid conflicts 265 | 266 | @property 267 | def alias(self) -> ItemAlias: 268 | return str(self.tag) 269 | 270 | parent: Item | None = None 271 | 272 | @abc.abstractmethod 273 | def configure(self, **kwargs) -> None: ... 274 | 275 | @abc.abstractmethod 276 | def configuration(self) -> Mapping[str, Any]: ... 277 | 278 | def information(self) -> Mapping[str, Any]: 279 | info = dict(ITEM_INFO_TEMPLATE) 280 | info['type'] = self.identity[1] # type:ignore 281 | return info 282 | 283 | def state(self) -> Mapping[str, Any]: 284 | return dict(ITEM_STATE_TEMPLATE) 285 | 286 | # Only virtual subclasses (`AppItemType`) need to define these, as 287 | # the metaclass ensures that subclasses have these attributes. I 288 | # don't want pyright complaining about it all the friggin' time. 289 | ItemInterface.__annotations__.update( 290 | identity=tuple[int, str], 291 | command=ItemCommand, 292 | ) 293 | 294 | 295 | 296 | class ItemInfoDict(TypedDict): 297 | children : Mapping[Literal[0, 1, 2, 3], list[Item]] 298 | type : str 299 | target : int 300 | parent : Item | None 301 | theme : Item | None 302 | handlers : Item | None 303 | font : Item | None 304 | container : bool 305 | hover_handler_applicable : bool 306 | active_handler_applicable : bool 307 | focus_handler_applicable : bool 308 | clicked_handler_applicable : bool 309 | visible_handler_applicable : bool 310 | edited_handler_applicable : bool 311 | activated_handler_applicable : bool 312 | deactivated_handler_applicable : bool 313 | deactivatedae_handler_applicable: bool 314 | toggled_open_handler_applicable : bool 315 | resized_handler_applicable : bool 316 | 317 | ITEM_INFO_TEMPLATE: ItemInfoDict = MappingProxyType( 318 | ItemInfoDict( 319 | children=MappingProxyType(dict.fromkeys(range(4), ())), # type:ignore 320 | type="", 321 | target=1, 322 | parent=None, 323 | theme=None, 324 | handlers=None, 325 | font=None, 326 | container=False, 327 | hover_handler_applicable=False, 328 | active_handler_applicable=False, 329 | focus_handler_applicable=False, 330 | clicked_handler_applicable=False, 331 | visible_handler_applicable=False, 332 | edited_handler_applicable=False, 333 | activated_handler_applicable=False, 334 | deactivated_handler_applicable=False, 335 | deactivatedae_handler_applicable=False, 336 | toggled_open_handler_applicable=False, 337 | resized_handler_applicable=False, 338 | ) 339 | ) 340 | 341 | 342 | class ItemStateDict(TypedDict): 343 | ok : Required[bool] 344 | pos : Vec2[int] | None 345 | hovered : bool | None 346 | active : bool | None 347 | focused : bool | None 348 | clicked : bool | None 349 | left_clicked : bool | None 350 | right_clicked : bool | None 351 | middle_clicked : bool | None 352 | visible : bool | None 353 | edited : bool | None 354 | activated : bool | None 355 | deactivated : bool | None 356 | resized : bool | None 357 | rect_min : Vec2[int] | None 358 | rect_max : Vec2[int] | None 359 | rect_size : Vec2[int] | None 360 | content_region_avail : Vec2[int] | None 361 | 362 | ITEM_STATE_TEMPLATE: ItemStateDict = MappingProxyType( # type:ignore 363 | ItemStateDict( 364 | ok=True, 365 | pos=None, 366 | hovered=None, 367 | active=None, 368 | focused=None, 369 | clicked=None, 370 | left_clicked=None, 371 | right_clicked=None, 372 | middle_clicked=None, 373 | visible=None, 374 | edited=None, 375 | activated=None, 376 | deactivated=None, 377 | resized=None, 378 | rect_min=None, 379 | rect_max=None, 380 | rect_size=None, 381 | content_region_avail=None, 382 | ) 383 | ) 384 | 385 | 386 | 387 | 388 | 389 | 390 | # Dear PyGui buffer types 391 | 392 | # NOTE: The proper *index* argument type of the below `__get/setitem__` 393 | # methods is `SupportsIndex`. Normal Python arrays will throw 394 | # `IndexError` when out-of-range, however, DPG's buffers will NOT. 395 | # Since `mvVec4` and `mvMat4` are s 396 | 397 | class mvBuffer(Protocol): 398 | """Protocol for Dear PyGui's `mvBuffer` type.""" 399 | def __init__(self, length: int = 0) -> None: ... 400 | def __str__(self) -> str: ... 401 | def __len__(self) -> int: ... 402 | def __getitem__(self, __i: SupportsIndex, /) -> float: ... 403 | def __setitem__(self, __i: SupportsIndex, __value: float, /) -> None: ... 404 | def get_width(self) -> int: ... 405 | def get_height(self) -> int: ... 406 | def clear_value(self, __initial_value: float, /) -> None: ... 407 | 408 | 409 | class mvVec4(Protocol): 410 | """Protocol for Dear PyGui's 4-dimensional vector type `mvVec4`. 411 | 412 | >NOTE: `IndexError` is NOT thrown when the index is out-of-range. 413 | """ 414 | def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0, w: float = 0.0) -> None: ... 415 | def __str__(self) -> str: ... 416 | def __len__(self) -> Literal[4]: ... 417 | def __getitem__(self, __i: SupportsIndex, /) -> float: ... 418 | def __setitem__(self, __i: SupportsIndex, __value: float, /) -> None: ... 419 | def __add__(self, __other: Self, /) -> Self: ... 420 | def __radd__(self, __other: Self, /) -> Self: ... 421 | def __sub__(self, __other: Self, /) -> Self: ... 422 | def __rsub__(self, __other: Self, /) -> Self: ... 423 | def __mul__(self, __other: Self | float, /) -> Self: ... 424 | def __rmul__(self, __other: Self | float, /) -> Self: ... 425 | 426 | 427 | class mvMat4(Protocol): 428 | """Protocol for Dear PyGui's 4x4 matrix type `mvMat4`. 429 | 430 | >NOTE: `IndexError` is NOT thrown when the index is out-of-range. 431 | """ 432 | def __init__(self, m00: float = 0.0, m01: float = 0.0, m02: float = 0.0, m03: float = 0.0, m10: float = 0.0, m11: float = 0.0, m12: float = 0.0, m13: float = 0.0, m20: float = 0.0, m21: float = 0.0, m22: float = 0.0, m23: float = 0.0, m30: float = 0.0, m31: float = 0.0, m32: float = 0.0, m33: float = 0.0) -> None: ... 433 | def __str__(self) -> str: ... 434 | def __len__(self) -> Literal[16]: ... 435 | def __getitem__(self, __i: SupportsIndex, /) -> float: ... 436 | def __setitem__(self, __i: SupportsIndex, __value: float, /) -> None: ... 437 | def __add__(self, __other: Self, /) -> Self: ... 438 | def __radd__(self, __other: Self, /) -> Self: ... 439 | def __sub__(self, __other: Self, /) -> Self: ... 440 | def __rsub__(self, __other: Self, /) -> Self: ... 441 | def __mul__(self, __other: Self | float, /) -> Self: ... 442 | def __rmul__(self, __other: Self | float, /) -> Self: ... 443 | -------------------------------------------------------------------------------- /dearpypixl/color.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from . import _typing, _parsing, _tools, _mkstub 3 | from ._typing import overload, Item, Color, Any 4 | from .items import mvThemeColor, ThemeColor, mvThemeStyle, ThemeStyle 5 | 6 | 7 | def _init_module(): 8 | overloads = _typing.get_overloads(_add_theme_element) 9 | 10 | p_kwargs = inspect.Parameter('kwargs', inspect.Parameter.VAR_KEYWORD) 11 | 12 | for ovld in overloads: 13 | try: 14 | ovld_sig = ovld.__signature__ 15 | except AttributeError: 16 | ovld_sig = inspect.signature(ovld) 17 | try: 18 | kwargs_sig = _ITP_BASE.configure.__signature__ 19 | except AttributeError: 20 | kwargs_sig = _ITP_BASE.configure.__signature__ = inspect.signature( 21 | _ITP_BASE.configure 22 | ) 23 | 24 | ovld_args = { 25 | n:p for n, p in ovld_sig.parameters.items() 26 | if p.kind is p.POSITIONAL_ONLY 27 | } 28 | ovld_kwds = { 29 | n:p for n, p in kwargs_sig.parameters.items() 30 | if n not in ovld_args 31 | and p.kind in (p.POSITIONAL_OR_KEYWORD, p.KEYWORD_ONLY)} 32 | ovld_kwds['kwargs'] = p_kwargs 33 | ovld_kwds.pop('self', None) 34 | ovld_kwds.pop('cls', None) 35 | ovld.__signature__ = ovld_sig.replace( 36 | parameters=list((ovld_args | ovld_kwds).values()) 37 | ) 38 | 39 | exported = _mkstub.Exported(__name__) 40 | 41 | ns = {} 42 | for elem_def in _parsing.color_definitions().values(): 43 | fn = _tools.create_function( 44 | elem_def.name2, _DFN_ARGS, 45 | _DFN_BODY, 46 | _ITP_BASE, 47 | globals=globals(), 48 | locals={ 49 | 'itp' : _ITP_BASE, 50 | 'category' : int(elem_def.category), 51 | 'target' : elem_def.target, 52 | } 53 | ) 54 | fn.__module__ = __name__ 55 | 56 | # HACK: force register existing overloads 57 | for ovld in overloads: 58 | ovld.__name__ = fn.__name__ 59 | ovld.__qualname__ = fn.__qualname__ 60 | overload(ovld) 61 | 62 | exported[fn.__name__] = fn 63 | exported.aliased[elem_def.name1] = fn 64 | ns[elem_def.name2] = ns[elem_def.name1] = fn 65 | 66 | globals().update(ns) 67 | 68 | 69 | 70 | 71 | _ITP_BASE = mvThemeColor 72 | _DFN_ARGS = ('value: Any, /', '*args: float', '**kwargs') 73 | _DFN_BODY = ( 74 | "kwargs.update(", 75 | " value=value if not args else (value, *args),", 76 | " target=target, category=category,", 77 | ")", 78 | "return itp(**kwargs)" 79 | ) 80 | 81 | @overload 82 | def _add_theme_element(value: Color, /, **kwargs) -> _ITP_BASE: ... 83 | @overload 84 | def _add_theme_element(r: float, g: float, b: float, a: float = ..., /, **kwargs) -> _ITP_BASE: ... 85 | def _add_theme_element(*args, **kwargs) -> _ITP_BASE: ... 86 | 87 | _init_module() 88 | -------------------------------------------------------------------------------- /dearpypixl/console.py: -------------------------------------------------------------------------------- 1 | """Items for emulating consoles and other streams.""" 2 | import sys # type: ignore 3 | import types 4 | import codeop 5 | import traceback 6 | import functools 7 | import threading 8 | import itertools # type: ignore 9 | import contextlib 10 | import types 11 | from dearpygui import dearpygui 12 | from ._typing import Item, Color 13 | from . import items, color, style 14 | from . import api, _interface, _tools 15 | from .api import Item as ItemAPI 16 | from ._typing import ( 17 | Item, 18 | Any, 19 | Sequence, 20 | ItemCommand, 21 | Callable, 22 | Self, 23 | Literal, 24 | SupportsIndex, 25 | Generator, 26 | TypeVar, 27 | ParamSpec, 28 | Method, 29 | override, 30 | ) 31 | 32 | 33 | _T = TypeVar("_T") 34 | _P = ParamSpec("_P") 35 | 36 | 37 | 38 | 39 | 40 | def _null_item_processing(self: 'FileStream', obj: _T) -> _T: 41 | return obj 42 | 43 | def _null_value_processing(self: 'FileStream', obj: _T, value: Any) -> _T: 44 | return value 45 | 46 | def _item_generator(item_factory: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs) -> Generator[_T, None, None]: 47 | while True: 48 | yield item_factory(*args, **kwargs) 49 | 50 | 51 | class FileStream(_interface.SupportsValueArray[str], items.mvValueRegistry): 52 | """A value registry that acts like pseudo file stream or buffer. It 53 | represents itself as a living multiline string split at newlines. 54 | >>> f = FileStream() 55 | >>> f.value = "foo\\nbar" 56 | >>> f.value 57 | ['f', 'o', 'o', '\\n', 'b', 'a', 'r'] 58 | >>> f[0] = 'b' 59 | >>> f.value 60 | ['b', 'o', 'o', '\\n', 'b', 'a', 'r'] 61 | >>> f[-1] = "FOO" 62 | >>> f.value 63 | ['b', 'o', 'o', '\\n', 'b', 'a', 'FOO'] 64 | >>> f.value = ["foobar"] 65 | >>> f.value 66 | ["foobar"] 67 | 68 | Writing to the stream appends the value written: 69 | >>> f.write("helloworld") 70 | >>> f.value 71 | ['foobar', 'helloworld'] 72 | 73 | The stream will always use the string representation of any object used 74 | as a value or values. When setting its' value as a whole: 75 | >>> f.value = ['a', 'b', None, 'd'] 76 | >>> f.value 77 | ['a', 'b', 'None', 'd'] 78 | 79 | Setting part of the value at a specific line number/index: 80 | >>> f[1] = None 81 | >>> f.value 82 | ['a', 'None', 'None', 'd'] 83 | 84 | The string representation of the stream is the assembled value as if 85 | joined by newlines: 86 | >>> str(f) 87 | 'a\\nNone\\nNone\\nd\\n' 88 | 89 | 90 | Individual 'mvStringValue' items are created to store each value added to 91 | the registry. These items can be used as a source for other items. Iterating 92 | through the stream will yield the identifier of the 'mvStringValue' and its 93 | value. Alternatively, you can get the identifier of a value item at a specific 94 | line number by indexing the stream's children (slot 1). 95 | 96 | The stream accepts three optional callbacks during initialization. This 97 | makes it easier to interface with the stream when it performs notable 98 | operations. For example, when making a primitive file display: 99 | >>> with dpg.window() as file_window: ... 100 | >>> 101 | >>> def add_line(buffer: FileStream, value_item: int | str): 102 | ... # Add a new line to the display when a value is added to 103 | ... # the stream. If the value is updated at this line, the 104 | ... # text will display accordingly. 105 | ... # Also, store the text item id within the value item for 106 | ... # `del_line`. 107 | ... text_item = dpg.add_text(parent=file_window, source=value_item) 108 | >>> 109 | >>> def del_line(buffer: FileStream, value_item: int | str): 110 | ... # Destroy the associated text item when the value item is 111 | ... # deleted. 112 | ... dpg.delete_item(dpg.get_item_configuration(value_item)["user_data"]) 113 | >>> 114 | >>> f = FileStream( 115 | ... ["f", "o", "o", "bar"], 116 | ... add_item_callback=add_line, 117 | ... del_item_callback=del_line, 118 | ... ) 119 | In the above example, writing to `f` will display content written in 120 | `file_window`. There's a lot of room for creativity here; you can use `f` 121 | as a logging handler for a log display. Want a more interactive display? 122 | Pass `dpg.add_input_text` as the value to the *text_factory* keyword argument 123 | when initializing the stream. 124 | """ 125 | 126 | def __init__( 127 | self, 128 | default_value: Sequence[str] | str = '', 129 | max_len : int | None = None, 130 | *, 131 | add_item_callback : Callable[[Self, _T], Any] | None = None, 132 | del_item_callback : Callable[[Self, _T], Any] | None = None, 133 | set_value_callback: Callable[[Self, _T, Any], Any] | None = None, 134 | item_factory : ItemCommand[_P, Item] = dearpygui.add_string_value, 135 | **kwargs 136 | ): 137 | """Args: 138 | * default_value: Starting value for the item. 139 | 140 | * max_len: The total number of lines the buffer will store. Once at 141 | capacity, lines are deleted in FIFO order. If unspecified or is None 142 | (default), the buffer can grow to an arbitrary length. 143 | 144 | * item_factory: Callable that returns a DearPyGui item identifier. 145 | 146 | * add_item_callback: Callable that accepts this item and the new child value 147 | item as positional arguments. It will be called immediately following the 148 | value item's creation. 149 | 150 | * del_item_callback: Callable that accepts this item and the child value item 151 | scheduled for destruction as item as positional arguments. It will be called 152 | before destroying the value item. 153 | 154 | * set_value_callback: Callable that accepts this item, the child value item 155 | who's value will be set, and the value to set, while returning the value to 156 | set. It will be called before updating the value of a value item. 157 | 158 | """ 159 | super().__init__(**kwargs) 160 | self._max_len = max_len 161 | 162 | self._add_value_item = Method( 163 | _tools.create_function( 164 | '_new_value_item', 165 | ('self', "*args", "**kwargs"), 166 | ( 167 | 'kwargs["parent"] = self', 168 | 'vitem = item_factory(*args, **kwargs)', 169 | 'callback(self, vitem)', 170 | 'return vitem', 171 | ), 172 | Item, 173 | locals={ 174 | 'item_factory': item_factory, 175 | 'callback': add_item_callback or _null_item_processing, 176 | } 177 | ), 178 | self, 179 | ) 180 | self._del_value_item = Method( 181 | _tools.create_function( 182 | '_del_value_item', 183 | ('self', 'item: int | str', '/'), 184 | ('callback(self, item)', 'delete_item(item)'), 185 | Item, 186 | locals={ 187 | 'callback': del_item_callback or _null_item_processing, 188 | 'delete_item': api.Registry.delete_item 189 | } 190 | ), 191 | self, 192 | ) 193 | self._set_value_item = Method( 194 | _tools.create_function( 195 | '_set_value_item', 196 | ('self', 'item: int | str', 'value: str', '/'), 197 | ('set_value(item, callback(self, item, value))',), 198 | locals={ 199 | 'set_value': api.Item.set_value, 200 | 'callback': set_value_callback or _null_value_processing, 201 | } 202 | ), 203 | self, 204 | ) 205 | 206 | self._generate_items = functools.partial(_item_generator, self._add_value_item) 207 | 208 | if default_value: 209 | self.set_value(default_value) 210 | 211 | def __str__(self): 212 | return '\n'.join(self.get_value()) 213 | 214 | def __iter__(self): 215 | vitems = self.children(1) 216 | yield from zip(vitems, ItemAPI.get_values(vitems)) 217 | 218 | def __setitem__(self, index: SupportsIndex, value: Any): 219 | ItemAPI.set_value(self.children(1)[index], value) 220 | 221 | def _add_value_item(self, *args, **kwargs) -> Item: ... 222 | 223 | def _del_value_item(self, item: Item, /) -> None: ... 224 | 225 | def _set_value_item(self, item: Item, value: str, /) -> None: ... 226 | 227 | def _generate_items(self, *args, **kwargs) -> Generator[Item, None, None]: ... 228 | 229 | def _truncate_lines(self): 230 | if self._max_len is not None: 231 | for vitem in self.children(1)[self._max_len:]: 232 | self._del_value_item(vitem) 233 | 234 | @property 235 | def max_len(self) -> int | None: 236 | return self._max_len 237 | @max_len.setter 238 | def max_len(self, value: int | None): 239 | if value is not None: 240 | value = int(value) 241 | if value < 0: 242 | raise ValueError("`max_len` cannot be less than zero.") 243 | self._max_len = value 244 | 245 | @override 246 | def get_value(self) -> list[str]: 247 | return ItemAPI.get_values(self.children(1)) 248 | 249 | @override 250 | def set_value(self, value: Sequence[Any]): 251 | vitems = self.children(1) 252 | for v, item in zip(value, itertools.chain(vitems, self._generate_items())): 253 | self._set_value_item(item, v) 254 | # delete leftover children if any 255 | for item in vitems[len(value):]: 256 | self._del_value_item(item) 257 | self._truncate_lines() 258 | 259 | @override 260 | def delete(self, *, children_only: bool = False, slot: Literal[-1, 0, 1, 2, 3] = -1) -> None: 261 | self.clear() 262 | super().delete(children_only=children_only, slot=slot) 263 | 264 | # list-like methods 265 | 266 | @override 267 | def pop(self, index: SupportsIndex) -> tuple[Item, str]: 268 | """Remove a line from the buffer at *index*, returning both the value 269 | item identifier and value.""" 270 | vitem = self.children(1)[index] 271 | value = ItemAPI.get_value(vitem) 272 | self._del_value_item(vitem) 273 | return value 274 | 275 | @override 276 | def remove(self, value: str) -> None: 277 | """At the first occurence of *value*, remove the line and value.""" 278 | index = self.get_value().index(value) 279 | self._del_value_item(self.children(1)[index]) 280 | 281 | @override 282 | def insert(self, index: SupportsIndex, value: Any) -> None: 283 | """Insert *value* at *index*, offsetting the position of trailing values by 1. 284 | >>> f = FileStream(["one", "two", "three"]) 285 | >>> f.insert(1, "four") 286 | >>> arr 287 | ["one", "four", "two", "three"] 288 | """ 289 | # Unlike most child items, value items don't have a "before" option. 290 | # The new item must be added first, then rearrange the children. 291 | vitems = self.children(1) 292 | vitem = self._add_value_item() 293 | self._set_value_item(vitem, value) 294 | vitems.insert(index, vitem) 295 | self.reorder(1, vitems) 296 | self._truncate_lines() 297 | 298 | @override 299 | def extend(self, value: Sequence[Any]): 300 | v = self.get_value() 301 | v.extend(value) 302 | self.set_value(v) 303 | 304 | @override 305 | def append(self, value: Any): 306 | self._set_value_item(self._add_value_item(), value) 307 | self._truncate_lines() 308 | 309 | def clear(self, *, use_del_item_callback: bool = False): 310 | if not use_del_item_callback: 311 | ItemAPI.delete(self, children_only=True, slot=1) 312 | else: 313 | self.set_value('') 314 | 315 | # misc 316 | 317 | def strip(self): 318 | """Remove all empty lines from the start and end of the buffer. 319 | >>> f = FileStream(["", "", "foo", "bar", ""]) 320 | >>> f.strip() 321 | >>> arr 322 | ["foo", "bar"] 323 | """ 324 | for item, value in self: 325 | if value: 326 | break 327 | self._del_value_item(item) 328 | for item, value in self[::-1]: 329 | if value: 330 | break 331 | self._del_value_item(item) 332 | 333 | def write(self, s: str): 334 | self.append(s) 335 | 336 | 337 | 338 | 339 | class ConsoleWindow(items.mvChildWindow): 340 | """A child window for displaying text logs. Writing to the item updates 341 | the display with the written text. 342 | 343 | The window does not push to and pop itself from the container stack when 344 | used as a context manager. Instead, any write to stdout and/or stderr 345 | will be written to the window instead. This behavior can be altered by 346 | setting the boolean `.ctx_redirect_stdout` and `.ctx_redirect_stderr` 347 | attributes on the class or instance. 348 | 349 | Note that when this interface is initialized, a resize handler is 350 | added to the handler registry of the root parent. The handler registry 351 | will be created if necessary. 352 | 353 | """ 354 | ctx_redirect_stdout: bool = True 355 | ctx_redirect_stderr: bool = False 356 | 357 | def __init__( 358 | self, 359 | max_len: int | None = None, 360 | *, 361 | redirect_stdout: bool | None = None, 362 | redirect_stderr: bool | None = None, 363 | auto_scroll : bool = True, 364 | wrap_text : bool = True, 365 | text_factory : ItemCommand[_P, Item] = dearpygui.add_text, 366 | buffer_factory : type[FileStream] = FileStream, 367 | **kwargs, 368 | ): 369 | """Args: 370 | * max_len: The total number of records to display before deleting 371 | older records in FIFO order. 372 | 373 | * redirect_stdout: If True (class-level default), all writes to stdout 374 | will instead write to this window when it is used as a context manager. 375 | If None, the `.ctx_redirect_stdout` attribute will not be set at the 376 | instance-level. 377 | 378 | * redirect_stderr: If True, all writes to stderr will instead write to 379 | this window when it is used as a context manager. If None, the 380 | `.ctx_redirect_stderr` attribute will not be set at the instance-level. 381 | 382 | * auto_scroll: If True (default), the scroll position is set to view the newest 383 | record when it is added. 384 | 385 | * wrap_text: If True, displayed text will be wrapped when the root 386 | window parent item is resized. Ignored when *text_factory* does produce 387 | 'mvText' items. 388 | 389 | * text_factory: Creates the text items that the window displays. Supports 390 | callables that create 'mvText' and 'mvInputText' items. 391 | 392 | """ 393 | super().__init__(**kwargs) 394 | self.wrap_text = wrap_text 395 | self.text_factory = text_factory 396 | self.auto_scroll = auto_scroll 397 | self.value_buffer = buffer_factory( 398 | max_len=max_len, 399 | add_item_callback=self._cb_add_record, 400 | del_item_callback=self._cb_del_record, 401 | set_value_callback=self._cb_set_record, 402 | ) 403 | self.__ctx_lock = threading.RLock() 404 | self.__ctx_counter = 0 405 | self.__ctx_stdout = contextlib.redirect_stdout(self) # type: ignore 406 | self.__ctx_stderr = contextlib.redirect_stderr(self) # type: ignore 407 | if redirect_stdout is not None: 408 | self.ctx_redirect_stdout = redirect_stdout 409 | if redirect_stderr is not None: 410 | self.ctx_redirect_stderr = redirect_stderr 411 | self._attach_resize_handler() 412 | 413 | 414 | # ResizeHandler management 415 | 416 | _resize_handler_uuid = 0 417 | 418 | def _destroy_resize_handler(self): 419 | try: 420 | ItemAPI.delete(self._resize_handler_uuid) 421 | except SystemError: 422 | pass 423 | 424 | def _attach_resize_handler(self): 425 | # XXX: this needs to be called whenever the console is re-parented! 426 | if self._resize_handler_uuid: 427 | self._destroy_resize_handler() 428 | else: 429 | # this uuid is recycled and should not change for this object 430 | self._resize_handler_uuid = api.Registry.create_uuid() 431 | root_parent = self.root_parent 432 | root_handlers = ItemAPI.information(root_parent)['handlers'] 433 | if root_handlers is None: 434 | root_handlers = items.ItemHandlerRegistry(label='') 435 | ItemAPI.set_handlers(root_parent, root_handlers) 436 | items.ResizeHandler( 437 | label='', 438 | parent=root_handlers, 439 | callback=self._cb_resize, 440 | tag=self._resize_handler_uuid, 441 | ) 442 | 443 | # buffer callbacks 444 | 445 | def _cb_add_record(self, b: FileStream, item: Item): 446 | text_item = self.text_factory( 447 | parent=self, 448 | source=item, 449 | ) 450 | if self.wrap_text and 'wrap' in ItemAPI.configuration(text_item): 451 | ItemAPI.configure(text_item, wrap=self.content_region_avail[0]) # type: ignore 452 | # The 'del_item' callback will receive the source value item but 453 | # not the text item. Keep a reference to the text item in the value 454 | # item so it can be easily deleted later. 455 | ItemAPI.configure(item, user_data=text_item) 456 | if self.auto_scroll: 457 | self.y_scroll_pos = -1.0 458 | 459 | def _cb_del_record(self, b: FileStream, item: Item): 460 | ItemAPI.delete(ItemAPI.configuration(item)['user_data']) 461 | 462 | def _cb_set_record(self, b: FileStream, item: Item, value: str) -> str: 463 | # XXX: This is unchanged from the default behavior and can be overridden 464 | return value 465 | 466 | def _cb_resize(self, sender: Item, *args): 467 | if self.wrap_text: 468 | width = self.content_region_avail[0] # type: ignore 469 | configuraton = ItemAPI.configuration 470 | for text in self.children(1): 471 | if 'wrap' in configuraton(text): 472 | try: 473 | ItemAPI.configure(text, wrap=width) 474 | except SystemError: 475 | pass 476 | 477 | # context usage 478 | 479 | __ctx_no_redirect = contextlib.nullcontext() 480 | __stdout_redirect = __ctx_no_redirect 481 | __stderr_redirect = __ctx_no_redirect 482 | 483 | def __enter__(self) -> Self: 484 | self.redirect() 485 | return self 486 | 487 | def __exit__(self, *args): 488 | self.restore(*args) 489 | 490 | def redirect(self): 491 | with self.__ctx_lock: 492 | if self.__ctx_counter > 1: 493 | self.__ctx_counter += 1 494 | return 495 | self.__ctx_counter = 1 496 | if self.ctx_redirect_stdout: 497 | self.__stdout_redirect = self.__ctx_stdout 498 | self.__stdout_redirect.__enter__() 499 | if self.ctx_redirect_stderr: 500 | self.__stderr_redirect = self.__ctx_stderr 501 | self.__stderr_redirect.__enter__() 502 | 503 | def restore(self, *args): 504 | with self.__ctx_lock: 505 | if self.__ctx_counter > 1: 506 | self.__ctx_counter -= 1 507 | return 508 | self.__stdout_redirect.__exit__(*args) 509 | self.__stdout_redirect = self.__ctx_no_redirect 510 | self.__stderr_redirect.__exit__(*args) 511 | self.__stderr_redirect = self.__ctx_no_redirect 512 | self.__ctx_counter = 0 513 | 514 | # the rest 515 | 516 | @property 517 | def max_len(self): 518 | return self.value_buffer.max_len 519 | @max_len.setter 520 | def max_len(self, value: Any): 521 | self.value_buffer.max_len = value 522 | 523 | @override 524 | def move(self, *, parent: Item = 0, before: Item = 0) -> None: 525 | super().move(parent=parent, before=before) 526 | if parent: 527 | self._attach_resize_handler() 528 | 529 | @override 530 | def unstage(self): 531 | super().unstage() 532 | self._attach_resize_handler() 533 | 534 | @override 535 | def delete(self, *, children_only: bool = False, slot: Literal[-1, 0, 1, 2, 3] = -1) -> None: 536 | if not children_only: 537 | self._destroy_resize_handler() 538 | self.value_buffer.delete(children_only=children_only, slot=slot) 539 | super().delete(children_only=children_only, slot=slot) 540 | 541 | @override 542 | def destroy(self): 543 | try: self._destroy_resize_handler() 544 | except: pass 545 | try: self.value_buffer.destroy() 546 | except: pass 547 | super().destroy() 548 | 549 | def clear(self, **kwargs): 550 | self.value_buffer.clear(use_del_item_callback=True) 551 | 552 | def write(self, s: str): 553 | return self.value_buffer.write(s) 554 | 555 | 556 | 557 | class PythonConsole(ConsoleWindow): 558 | """A console window for displaying the output of executed Python 559 | code. 560 | 561 | See the `.push` and `.compile` methods for more information. 562 | """ 563 | def __init__( 564 | self, 565 | locals : dict[str, Any] | None = None, 566 | max_len: int | None = None, 567 | **kwargs 568 | ): 569 | super().__init__( 570 | max_len, 571 | redirect_stderr=True, 572 | redirect_stdout=True, 573 | auto_scroll=True, 574 | wrap_text=True, 575 | text_factory=dearpygui.add_text, 576 | **kwargs 577 | ) 578 | self._compiler = codeop.CommandCompiler() 579 | 580 | self.locals = locals if locals is not None else { 581 | "__name__": "__console__", 582 | "__doc__" : None, 583 | } 584 | 585 | with items.mvTheme(label='') as self.theme: 586 | with items.mvThemeComponent(int(items.mvChildWindow), label=''): 587 | self.console_color = color.ChildBg(25, 25, 25) 588 | style.WindowPadding(8, 0) 589 | 590 | with items.mvTheme() as self._stdout_theme: 591 | with items.mvThemeComponent(label=''): 592 | self.stdout_color = color.Text(255, 255, 255) 593 | color.FrameBg(0, 0, 0, 0) 594 | 595 | with items.mvTheme() as self._stderr_theme: 596 | with items.mvThemeComponent(label=''): 597 | self.stderr_color = color.Text(255, 20, 20) 598 | color.FrameBg(0, 0, 0, 0) 599 | 600 | def _cb_set_record(self, b: FileStream, item: Item, value: str) -> str: 601 | # set the theme of the text 602 | text_item = ItemAPI.configuration(item)['user_data'] 603 | if value.startswith("Traceback (") or '\nSyntaxError:' in value or '\nIndentationError:' in value: 604 | theme = self._stderr_theme 605 | else: 606 | theme = self._stdout_theme 607 | ItemAPI.set_theme(text_item, theme) 608 | return value 609 | 610 | @property 611 | def filename(self): 612 | return self.locals.get('__name__', '') 613 | 614 | def _write_syntax_err(self): 615 | """Format and write the most recent syntax error set and 616 | write it to the console. 617 | """ 618 | exc_tp, exc_val, tb = sys.exc_info() 619 | sys.last_type = exc_tp 620 | sys.last_value = exc_val 621 | sys.last_traceback = tb 622 | 623 | # SyntaxErrors don't actually include the traceback in 624 | # stderr. However, the traceback lines are formatted 625 | # the same regardless. Remove the indent(s). 626 | lines = traceback.format_exception_only(exc_tp, exc_val) 627 | indent = "" 628 | for char in lines[0]: 629 | if not char.isspace(): 630 | break 631 | indent += char 632 | for idx, ln in enumerate(lines): 633 | lines[idx] = ln.removeprefix(indent) 634 | 635 | self.value_buffer.write(''.join(lines)) 636 | 637 | def _write_traceback(self): 638 | """Format the most recent exception set and write it to the 639 | console. 640 | """ 641 | sys.last_type, sys.last_value, last_tb = ei = sys.exc_info() 642 | sys.last_traceback = last_tb 643 | try: 644 | # use the next traceback because the first is from *our* code 645 | lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next) # type: ignore 646 | self.value_buffer.write(''.join(lines)) 647 | finally: 648 | last_tb = ei = None 649 | 650 | def compile(self, s: str, filename: str = '', mode: str = 'single'): 651 | """Compile a string as source code. 652 | 653 | The return of this method varies based on the outcome of the compilation: 654 | * CodeType: The code compiled sucessfully (as the returned object). 655 | 656 | * False: The code was compiled successfully but could not be executed 657 | because it is incomplete. 658 | 659 | * None: `SyntaxError` was thrown while compiling the code. 660 | 661 | All exceptions that occur from compiling the code are displayed in the 662 | console and are not raised. 663 | """ 664 | filename = filename or self.filename 665 | try: 666 | code = self._compiler(s, filename, mode) 667 | except (OverflowError, SyntaxError, ValueError): 668 | self._write_syntax_err() 669 | return None # invalid source code 670 | if code is None: # incomplete source code 671 | return False 672 | return code 673 | 674 | def push(self, c: types.CodeType | str): 675 | """Compile a string as source code, execute it, and display the output 676 | in the console. 677 | 678 | The return of this method varies based on the outcome of the compilation: 679 | * True: The code compiled sucessfully or was not needed, and was 680 | executed. 681 | 682 | * False: The code was compiled successfully but could not be executed 683 | because it is incomplete. 684 | 685 | * None: `SyntaxError` was thrown while compiling the code. 686 | 687 | All exceptions that occur from compiling or executing the code are 688 | displayed in the console and are not raised. 689 | 690 | Accepts a string or `CodeType` object as an argument. This method will 691 | execute a `CodeType` object immediately and will always return True. 692 | """ 693 | if isinstance(c, types.CodeType): 694 | code = c 695 | else: 696 | code = self.compile(c, self.filename) 697 | if not code: 698 | return code 699 | with self: 700 | try: 701 | exec(code, self.locals) 702 | except: 703 | self._write_traceback() 704 | return True 705 | 706 | 707 | 708 | 709 | @_interface.auto_parent(functools.partial( # type: ignore 710 | dearpygui.add_window, 711 | width=600, 712 | height=400, 713 | no_scrollbar=True 714 | )) 715 | class InteractivePython(items.mvChildWindow): 716 | """Execute Python code from an emulated interactive session. 717 | 718 | This item will create a root parent window for itself if a parent is 719 | unspecified or unavailable. 720 | 721 | When this interface is initialized, a resize handler is added to the 722 | handler registry of the root parent. The handler registry will be 723 | created if necessary. 724 | """ 725 | 726 | _PROMPT_1 = ">>>" 727 | _PROMPT_2 = "..." 728 | 729 | def __init__( 730 | self, 731 | locals: dict[str, Any] | None = None, 732 | echo : bool = True, 733 | **kwargs 734 | ): 735 | """Args: 736 | * locals: Namespace used for lookups. 737 | 738 | * echo: If True, text from the input will be displayed in the 739 | console when the 'enter' key is pressed. 740 | """ 741 | if not 'label' in kwargs: 742 | kwargs['label'] = f'[{type(self).__qualname__}]' 743 | super().__init__(**kwargs) 744 | if not self.parent.label: 745 | self.parent.label = f'[{type(self).__qualname__}]' 746 | 747 | self._input_pending = [] 748 | 749 | self.echo = echo 750 | 751 | with items.mvTheme() as self.theme: 752 | with items.mvThemeComponent(): 753 | style.WindowPadding(0, 0) 754 | style.FramePadding(4, 4) 755 | style.ItemSpacing(8, 0) 756 | color.FrameBg(0, 0, 0, 0) 757 | 758 | with items.mvTheme() as self._echo_theme: 759 | with items.mvThemeComponent(): 760 | self.echo_color = color.Text(50, 210, 230, 150) # cyan 761 | 762 | with self: 763 | # The initial height needs to be very small so the input parent can 764 | # have room to stretch its' legs for the initial render. Otherwise, 765 | # the console will squish it and its y_scroll_max will be 0. 766 | with PythonConsole(locals=locals, height=1) as self.console: 767 | ... 768 | with items.mvChildWindow(no_scrollbar=True, border=False, height=24) as self._input_parent: 769 | self._input_parent.theme = self.theme 770 | with items.mvGroup(horizontal=True, horizontal_spacing=0): 771 | self._input_prompt = items.mvText(self._PROMPT_1, indent=8) # indent == WindowPadding[0] 772 | self.input = items.mvInputText( 773 | on_enter=True, 774 | callback=self._cb_enter, 775 | hint="(enter a partial or complete Python expression/statement)" 776 | ) 777 | 778 | self._attach_resize_handler() 779 | 780 | self.console.write("Python {} on {}\n{}\n".format( 781 | sys.version, 782 | sys.platform, 783 | 'Type "help", "copyright", "credits" or "license" for more information.\n', 784 | )) 785 | 786 | def _cb_enter(self, sender: Item, app_data: str, *args): 787 | self._input_pending.append(app_data) 788 | self.input.value = '' 789 | if self.echo and app_data: 790 | self.console.write(f"{self._input_prompt.value} {app_data}") 791 | ItemAPI.set_theme(self.console.children(1)[-1], self._echo_theme) 792 | code = self.console.compile('\n'.join(self._input_pending)) 793 | if code: 794 | self.console.push(code) 795 | self._input_prompt.value = self._PROMPT_1 796 | self._input_pending.clear() 797 | elif code is None: # SyntaxError, etc. 798 | self._input_prompt.value = self._PROMPT_1 799 | self._input_pending.clear() 800 | else: # pending 801 | self._input_prompt.value = self._PROMPT_2 802 | self.input.focus() 803 | 804 | def _cb_resize(self): 805 | # XXX: ALL of the input window must be "visible" for the first 806 | # render so its' y_scroll_max gets set before `_cb_resize` reads 807 | # it. If it's only viewable off-screen (in the parent's scroll 808 | # area, etc) y_scroll_max will be initially set to 0. This can 809 | # be worked around by setting the initial height of the console 810 | # to *smol-er* to keep it from being a fatass. 811 | text_wt, text_ht = self._input_prompt.rect_size # type: ignore 812 | wt_avail, ht_avail = self.content_region_avail # type: ignore 813 | # Resize the input so that it's just tall enough for the 814 | # rendered font. It could be larger or smaller than before; 815 | # set the height to *smol* and wait a frame (at least), then 816 | # set the height based off the upd. max scroll position. 817 | self._input_parent.height = 5 818 | dearpygui.split_frame(delay=16) 819 | input_ht = int(self._input_parent.y_scroll_max) + 8 # account for `FramePadding[1]` 820 | self._input_parent.height = input_ht 821 | 822 | self.input.configure(width=wt_avail - text_wt - 8) # account for `WindowPadding[0]` 823 | 824 | self.console.height = ht_avail - input_ht 825 | 826 | _resize_handler_uuid = 0 827 | 828 | def _destroy_resize_handler(self): 829 | try: 830 | ItemAPI.delete(self._resize_handler_uuid) 831 | except SystemError: 832 | pass 833 | 834 | def _attach_resize_handler(self): 835 | # XXX: this needs to be called whenever the console is re-parented! 836 | if self._resize_handler_uuid: 837 | self._destroy_resize_handler() 838 | else: 839 | # this uuid is recycled and should not change for this object 840 | self._resize_handler_uuid = api.Registry.create_uuid() 841 | root_parent = self.root_parent 842 | root_handlers = ItemAPI.information(root_parent)['handlers'] 843 | if root_handlers is None: 844 | root_handlers = items.ItemHandlerRegistry(label='') 845 | ItemAPI.set_handlers(root_parent, root_handlers) 846 | items.ResizeHandler( 847 | label='', 848 | parent=root_handlers, 849 | callback=self._cb_resize, 850 | tag=self._resize_handler_uuid, 851 | ) 852 | 853 | @override 854 | def move(self, *, parent: Item = 0, before: Item = 0) -> None: 855 | super().move(parent=parent, before=before) 856 | if parent: 857 | self._attach_resize_handler() 858 | 859 | @override 860 | def unstage(self): 861 | super().unstage() 862 | self._attach_resize_handler() 863 | 864 | @override 865 | def delete(self, *, children_only: bool = False, slot: Literal[-1, 0, 1, 2, 3] = -1) -> None: 866 | if not children_only: 867 | self._destroy_resize_handler() 868 | self.console.delete(children_only=children_only, slot=slot) 869 | super().delete(children_only=children_only, slot=slot) 870 | 871 | @override 872 | def destroy(self): 873 | try: self._destroy_resize_handler() 874 | except: pass 875 | try: self.console.destroy() 876 | except: pass 877 | super().destroy() 878 | 879 | def clear_input(self): 880 | """Empty the input buffer.""" 881 | self._input_pending.clear() 882 | 883 | 884 | -------------------------------------------------------------------------------- /dearpypixl/constants.py: -------------------------------------------------------------------------------- 1 | import enum 2 | from dearpygui import dearpygui 3 | 4 | 5 | class Platform(enum.IntEnum): 6 | WINDOWS = dearpygui.mvPlatform_Windows # 0 7 | MAC = dearpygui.mvPlatform_Apple # 1 8 | LINUX = dearpygui.mvPlatform_Linux # 2 9 | OTHER = -1 10 | 11 | @classmethod 12 | def _missing_(cls, value: object): 13 | if isinstance(value, int): 14 | return cls.OTHER 15 | return None 16 | 17 | 18 | class MouseInput(enum.IntEnum): 19 | ANY = -1 20 | LEFT = L = dearpygui.mvMouseButton_Left # 0 21 | RIGHT = R = dearpygui.mvMouseButton_Right # 1 22 | MIDDLE = M = dearpygui.mvMouseButton_Middle # 2 23 | X1 = dearpygui.mvMouseButton_X1 # 3 24 | X2 = dearpygui.mvMouseButton_X2 # 4 25 | 26 | 27 | class KeyInput(enum.IntEnum): 28 | ANY = -1 29 | BREAK = 3 30 | BACKSPACE = 8 31 | TAB = 9 32 | CLEAR = 12 33 | RETURN = ENTER = 13 34 | SHIFT = 16 35 | CTRL = 17 36 | ALT = 18 37 | PAUSE = 19 38 | CAPS_LOCK = 20 39 | ESC = 27 40 | SPACEBAR = SPACE = 32 41 | PAGE_UP = PG_UP = 33 42 | PAGE_DN = PG_DN = 34 43 | END = 35 44 | HOME = 36 45 | ARROW_LEFT = 37 46 | ARROW_UP = 38 47 | ARROW_RIGHT = 39 48 | ARROW_DN = 40 49 | SELECT = 41 50 | PRINT = 42 51 | EXEC = 43 52 | PRINTSCREEN = PRTSCR = 44 53 | INSERT = INS = 45 54 | DELETE = DEL = 46 55 | HELP = 47 56 | DIGIT_0 = 48 57 | DIGIT_1 = 49 58 | DIGIT_2 = 50 59 | DIGIT_3 = 51 60 | DIGIT_4 = 52 61 | DIGIT_5 = 53 62 | DIGIT_6 = 54 63 | DIGIT_7 = 55 64 | DIGIT_8 = 56 65 | DIGIT_9 = 57 66 | A = 65 67 | B = 66 68 | C = 67 69 | D = 68 70 | E = 69 71 | F = 70 72 | G = 71 73 | H = 72 74 | I = 73 75 | J = 74 76 | K = 75 77 | L = 76 78 | M = 77 79 | N = 78 80 | O = 79 81 | P = 80 82 | Q = 81 83 | R = 82 84 | S = 83 85 | T = 84 86 | U = 85 87 | V = 86 88 | W = 87 89 | X = 88 90 | Y = 89 91 | Z = 90 92 | L_META = L_WIN = 91 93 | R_META = R_WIN = 92 94 | APPS = 93 95 | SLEEP = 95 96 | NUMPAD_0 = 96 97 | NUMPAD_1 = 97 98 | NUMPAD_2 = 98 99 | NUMPAD_3 = 99 100 | NUMPAD_4 = 100 101 | NUMPAD_5 = 101 102 | NUMPAD_6 = 102 103 | NUMPAD_7 = 103 104 | NUMPAD_8 = 104 105 | NUMPAD_9 = 105 106 | NUMPAD_MUL = 106 107 | NUMPAD_ADD = 107 108 | NUMPAD_SEP = 108 109 | NUMPAD_SUB = 109 110 | NUMPAD_DEC = 110 111 | NUMPAD_DIV = 111 112 | F1 = 112 113 | F2 = 113 114 | F3 = 114 115 | F4 = 115 116 | F5 = 116 117 | F6 = 117 118 | F7 = 118 119 | F8 = 119 120 | F9 = 120 121 | F10 = 121 122 | F11 = 122 123 | F12 = 123 124 | F13 = 124 125 | F14 = 125 126 | F15 = 126 127 | F16 = 127 128 | F17 = 128 129 | F18 = 129 130 | F19 = 130 131 | F20 = 131 132 | F21 = 132 133 | F22 = 133 134 | F23 = 134 135 | F24 = 135 136 | NUM_LOCK = 144 137 | SCR_LOCK = 145 138 | L_SHIFT = 160 139 | R_SHIFT = 161 140 | L_CTRL = 162 141 | R_CTRL = 163 142 | L_MENU = 164 143 | R_MENU = 165 144 | BROWSER_BACK = 166 145 | BROWSER_FORWARD = 167 146 | BROWSER_REFRESH = 168 147 | BROWSER_STOP = 169 148 | BROWSER_SEARCH = 170 149 | BROWSER_FAVS = 171 150 | BROWSER_HOME = 172 151 | VOL_MUTE = 173 152 | VOL_DN = 174 153 | VOL_UP = 175 154 | MEDIA_TRACK_NEXT = 176 155 | MEDIA_TRACK_PREV = 177 156 | MEDIA_STOP = 178 157 | MEDIA_PLAY = MEDIA_PAUSE = 179 158 | LAUNCH_MAIL = 180 159 | MEDIA_SELECT = 181 160 | LAUNCH_APP1 = 182 161 | LAUNCH_APP2 = 183 162 | SEMICOLON = 186 163 | PLUS = 187 164 | COMMA = 188 165 | MINUS = 189 166 | PERIOD = 190 167 | FORWARD_SLASH = 191 168 | TILDE = 192 # ~ 169 | BRACKET_OPEN = 219 # [ 170 | BACKSLASH = 220 # 171 | BRACKET_CLOSE = 221 172 | QUOTE = 222 173 | INTL_BACKSLASH = 226 # \ 174 | UNIDENTIFIED = 255 175 | 176 | 177 | class ThemeCategory(enum.IntEnum): 178 | CORE = dearpygui.mvThemeCat_Core # 0 179 | PLOT = dearpygui.mvThemeCat_Plots # 1 180 | NODE = dearpygui.mvThemeCat_Nodes # 2 181 | 182 | 183 | class NodeLink(enum.IntEnum): 184 | INPUT = dearpygui.mvNode_Attr_Input 185 | OUTPUT = dearpygui.mvNode_Attr_Output 186 | STATIC = dearpygui.mvNode_Attr_Static 187 | 188 | 189 | class NodeMiniMapLoc(enum.IntEnum): 190 | BOTTOM_LEFT = BL = dearpygui.mvNodeMiniMap_Location_BottomLeft 191 | BOTTOM_RIGHT = BR = dearpygui.mvNodeMiniMap_Location_BottomRight 192 | TOP_RIGHT = TR = dearpygui.mvNodeMiniMap_Location_TopRight 193 | TOP_LEFT = TL = dearpygui.mvNodeMiniMap_Location_TopLeft 194 | 195 | 196 | class PlotAxis(enum.IntEnum): 197 | X = dearpygui.mvXAxis 198 | Y = dearpygui.mvYAxis 199 | 200 | 201 | class PlotLocation(enum.IntEnum): 202 | CENTER = C = dearpygui.mvPlot_Location_Center 203 | NORTH = N = dearpygui.mvPlot_Location_North 204 | NORTHEAST = NE = dearpygui.mvPlot_Location_NorthEast 205 | EAST = E = dearpygui.mvPlot_Location_East 206 | SOUTHEAST = SE = dearpygui.mvPlot_Location_SouthEast 207 | SOUTH = S = dearpygui.mvPlot_Location_South 208 | SOUTHWEST = SW = dearpygui.mvPlot_Location_SouthWest 209 | WEST = W = dearpygui.mvPlot_Location_West 210 | NORTHWEST = NW = dearpygui.mvPlot_Location_NorthWest 211 | 212 | 213 | class TablePolicy(enum.IntEnum): 214 | FIXED_FIT = dearpygui.mvTable_SizingFixedFit 215 | FIXED_SAME = dearpygui.mvTable_SizingFixedSame 216 | STRETCH_PROP = dearpygui.mvTable_SizingStretchProp 217 | STRETCH_SAME = dearpygui.mvTable_SizingStretchSame 218 | 219 | 220 | class ColorFormat(enum.IntEnum): 221 | RGBA = dearpygui.mvFormat_Float_rgba 222 | RGB = dearpygui.mvFormat_Float_rgb 223 | 224 | 225 | class PlotMarker(enum.IntEnum): 226 | NONE = dearpygui.mvPlotMarker_None 227 | CIRCLE = dearpygui.mvPlotMarker_Circle 228 | SQUARE = dearpygui.mvPlotMarker_Square 229 | DIAMOND = dearpygui.mvPlotMarker_Diamond 230 | UP = dearpygui.mvPlotMarker_Up 231 | DOWN = dearpygui.mvPlotMarker_Down 232 | LEFT = dearpygui.mvPlotMarker_Left 233 | RIGHT = dearpygui.mvPlotMarker_Right 234 | CROSS = dearpygui.mvPlotMarker_Cross 235 | PLUS = dearpygui.mvPlotMarker_Plus 236 | ASTERISK = dearpygui.mvPlotMarker_Asterisk 237 | 238 | 239 | class PlotBin(enum.IntEnum): 240 | SQRT = dearpygui.mvPlotBin_Sqrt 241 | STURGES = dearpygui.mvPlotBin_Sturges 242 | RICE = dearpygui.mvPlotBin_Rice 243 | SCOTT = dearpygui.mvPlotBin_Scott 244 | 245 | 246 | class ColorEdit(enum.IntEnum): 247 | ALPHA_PREVIEW_NONE = dearpygui.mvColorEdit_AlphaPreviewNone 248 | ALPHA_PREVIEW = dearpygui.mvColorEdit_AlphaPreview 249 | ALPHA_PREVIEW_HALF = dearpygui.mvColorEdit_AlphaPreviewHalf 250 | UINT8 = dearpygui.mvColorEdit_uint8 251 | FLOAT = dearpygui.mvColorEdit_float 252 | RGB = dearpygui.mvColorEdit_rgb 253 | HSV = dearpygui.mvColorEdit_hsv 254 | HEX = dearpygui.mvColorEdit_hex 255 | INPUT_RGB = dearpygui.mvColorEdit_input_rgb 256 | INPUT_HSV = dearpygui.mvColorEdit_input_hsv 257 | 258 | 259 | class PlotColorMap(enum.IntEnum): 260 | DEFAULT = dearpygui.mvPlotColormap_Default 261 | DEEP = dearpygui.mvPlotColormap_Deep 262 | DARK = dearpygui.mvPlotColormap_Dark 263 | PASTEL = dearpygui.mvPlotColormap_Pastel 264 | PAIRED = dearpygui.mvPlotColormap_Paired 265 | VIRIDIS = dearpygui.mvPlotColormap_Viridis 266 | PLASMA = dearpygui.mvPlotColormap_Plasma 267 | HOT = dearpygui.mvPlotColormap_Hot 268 | COOL = dearpygui.mvPlotColormap_Cool 269 | PINK = dearpygui.mvPlotColormap_Pink 270 | JET = dearpygui.mvPlotColormap_Jet 271 | TWILIGHT = dearpygui.mvPlotColormap_Twilight 272 | RDBU = dearpygui.mvPlotColormap_RdBu 273 | BRBG = dearpygui.mvPlotColormap_BrBG 274 | PIYG = dearpygui.mvPlotColormap_PiYG 275 | SPECTRAL = dearpygui.mvPlotColormap_Spectral 276 | GREYS = dearpygui.mvPlotColormap_Greys 277 | 278 | 279 | class ColorPickerDisplay(enum.IntEnum): 280 | BAR = dearpygui.mvColorPicker_bar 281 | WHEEL = dearpygui.mvColorPicker_wheel 282 | 283 | 284 | class TabOrder(enum.IntEnum): 285 | REORDERABLE = dearpygui.mvTabOrder_Reorderable 286 | FIXED = dearpygui.mvTabOrder_Fixed 287 | LEADING = dearpygui.mvTabOrder_Leading 288 | TRAILING = dearpygui.mvTabOrder_Trailing 289 | 290 | 291 | class TimeUnit(enum.IntEnum): 292 | MICROSECOND = uS = dearpygui.mvTimeUnit_Us 293 | MILLISECOND = mS = dearpygui.mvTimeUnit_Ms 294 | SECOND = S = dearpygui.mvTimeUnit_S 295 | MINUTE = M = dearpygui.mvTimeUnit_Min 296 | HOUR = HR = dearpygui.mvTimeUnit_Hr 297 | DAY = dearpygui.mvTimeUnit_Day 298 | MONTH = dearpygui.mvTimeUnit_Mo 299 | YEAR = dearpygui.mvTimeUnit_Yr 300 | 301 | 302 | class DatePickerLevel(enum.IntEnum): 303 | DAY = dearpygui.mvDatePickerLevel_Day 304 | MONTH = dearpygui.mvDatePickerLevel_Month 305 | YEAR = dearpygui.mvDatePickerLevel_Year 306 | 307 | 308 | class CullMode(enum.IntEnum): 309 | NONE = dearpygui.mvCullMode_None 310 | BACK = dearpygui.mvCullMode_Back 311 | FRONT = dearpygui.mvCullMode_Front 312 | 313 | 314 | class FontRangeHint(enum.IntEnum): 315 | DEFAULT = dearpygui.mvFontRangeHint_Default 316 | JAPANESE = dearpygui.mvFontRangeHint_Japanese 317 | KOREAN = dearpygui.mvFontRangeHint_Korean 318 | CHINESE_FULL = dearpygui.mvFontRangeHint_Chinese_Full 319 | CHINESE_SIMPLIFIED_COMMON = dearpygui.mvFontRangeHint_Chinese_Simplified_Common 320 | CYRILLIC = dearpygui.mvFontRangeHint_Cyrillic 321 | THAI = dearpygui.mvFontRangeHint_Thai 322 | VIETNAMESE = dearpygui.mvFontRangeHint_Vietnamese 323 | 324 | 325 | class NodePinShape(enum.IntEnum): 326 | CIRCLE = dearpygui.mvNode_PinShape_Circle 327 | CIRCLE_FILLED = dearpygui.mvNode_PinShape_CircleFilled 328 | TRIANGLE = dearpygui.mvNode_PinShape_Triangle 329 | TRIANGLE_FILLED = dearpygui.mvNode_PinShape_TriangleFilled 330 | QUAD = dearpygui.mvNode_PinShape_Quad 331 | QUAD_FILLED = dearpygui.mvNode_PinShape_QuadFilled 332 | 333 | 334 | class Direction(enum.IntEnum): 335 | NONE = dearpygui.mvDir_None 336 | LEFT = dearpygui.mvDir_Left 337 | RIGHT = dearpygui.mvDir_Right 338 | UP = dearpygui.mvDir_Up 339 | DOWN = dearpygui.mvDir_Down 340 | 341 | 342 | class ComboHeight(enum.IntEnum): 343 | SMALL = dearpygui.mvComboHeight_Small 344 | REGULAR = dearpygui.mvComboHeight_Regular 345 | LARGE = dearpygui.mvComboHeight_Large 346 | LARGEST = dearpygui.mvComboHeight_Largest 347 | 348 | 349 | class GraphicsBackend(enum.IntEnum): 350 | D3D11 = DX11 = dearpygui.mvGraphicsBackend_D3D11 351 | D3D12 = DX12 = dearpygui.mvGraphicsBackend_D3D12 352 | VULKAN = dearpygui.mvGraphicsBackend_VULKAN 353 | METAL = dearpygui.mvGraphicsBackend_METAL 354 | OPENGL = dearpygui.mvGraphicsBackend_OPENGL 355 | -------------------------------------------------------------------------------- /dearpypixl/items.py: -------------------------------------------------------------------------------- 1 | 2 | def _fill_namespace(): 3 | from . import _interface, _parsing, _mkstub 4 | 5 | exported = _mkstub.Exported(__name__) 6 | 7 | namespace = dict( 8 | exp for exp in _mkstub.Exported.fetch(_interface).items() 9 | ) 10 | for tp_def in _parsing.item_definitions().values(): 11 | if not tp_def: 12 | continue 13 | 14 | itp = exported(_interface.create_itemtype(tp_def)) 15 | itp.__module__ = __name__ 16 | namespace[itp.__qualname__] = itp 17 | # set an alias 18 | if itp.__qualname__ == "mvWindowAppItem": 19 | namespace['mvWindow'] = namespace['Window'] = itp 20 | exported(itp, 'mvWindow') 21 | exported(itp, 'Window') 22 | elif itp.__qualname__ == 'mvAnnotation': 23 | namespace['PlotAnnotation'] = itp 24 | exported(itp, 'PlotAnnotation') 25 | else: 26 | alias = itp.__qualname__.removeprefix('mv') 27 | if alias[0].isdigit(): 28 | alias = f'{alias[2:]}{alias[0]}{alias[1].upper()}' 29 | assert alias not in namespace 30 | namespace[alias] = itp 31 | exported(itp, alias) 32 | 33 | globals().update(namespace) 34 | 35 | 36 | _fill_namespace() 37 | -------------------------------------------------------------------------------- /dearpypixl/py.typed: -------------------------------------------------------------------------------- 1 | partial 2 | -------------------------------------------------------------------------------- /dearpypixl/style.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from . import _typing, _parsing, _tools, _mkstub 3 | from ._typing import overload, Item, Color, Any, Vec2 4 | from .items import mvThemeColor, ThemeColor, mvThemeStyle, ThemeStyle 5 | 6 | 7 | def _init_module(): 8 | overloads = _typing.get_overloads(_add_theme_element) 9 | 10 | p_kwargs = inspect.Parameter('kwargs', inspect.Parameter.VAR_KEYWORD) 11 | 12 | for ovld in overloads: 13 | try: 14 | ovld_sig = ovld.__signature__ 15 | except AttributeError: 16 | ovld_sig = inspect.signature(ovld) 17 | try: 18 | kwargs_sig = _ITP_BASE.configure.__signature__ 19 | except AttributeError: 20 | kwargs_sig = _ITP_BASE.configure.__signature__ = inspect.signature( 21 | _ITP_BASE.configure 22 | ) 23 | 24 | ovld_args = { 25 | n:p for n, p in ovld_sig.parameters.items() 26 | if p.kind is p.POSITIONAL_ONLY 27 | } 28 | ovld_kwds = { 29 | n:p for n, p in kwargs_sig.parameters.items() 30 | if n not in ovld_args 31 | and p.kind in (p.POSITIONAL_OR_KEYWORD, p.KEYWORD_ONLY)} 32 | ovld_kwds['kwargs'] = p_kwargs 33 | ovld_kwds.pop('self', None) 34 | ovld_kwds.pop('cls', None) 35 | ovld.__signature__ = ovld_sig.replace( 36 | parameters=list((ovld_args | ovld_kwds).values()) 37 | ) 38 | 39 | exported = _mkstub.Exported(__name__) 40 | 41 | ns = {} 42 | for elem_def in _parsing.style_definitions().values(): 43 | fn = _tools.create_function( 44 | elem_def.name2, _DFN_ARGS, 45 | _DFN_BODY, 46 | _ITP_BASE, 47 | globals=globals(), 48 | locals={ 49 | 'itp' : _ITP_BASE, 50 | 'category' : int(elem_def.category), 51 | 'target' : elem_def.target, 52 | } 53 | ) 54 | fn.__module__ = __name__ 55 | 56 | # HACK: force register existing overloads 57 | for ovld in overloads: 58 | ovld.__name__ = fn.__name__ 59 | ovld.__qualname__ = fn.__qualname__ 60 | overload(ovld) 61 | 62 | exported[fn.__name__] = fn 63 | exported.aliased[elem_def.name1] = fn 64 | ns[elem_def.name2] = ns[elem_def.name1] = fn 65 | 66 | globals().update(ns) 67 | 68 | 69 | 70 | 71 | _ITP_BASE = mvThemeStyle 72 | _DFN_ARGS = ('value: Any = 1', 'y: Any = -1', '/', '**kwargs') 73 | _DFN_BODY = ( 74 | "if isinstance(value, (int, float)):", 75 | " x = value", 76 | "else:", 77 | " x, y = value", 78 | "kwargs.update(", 79 | " target=target,", 80 | " x=x,", 81 | " y=y,", 82 | " category=category,", 83 | ")", 84 | "return itp(**kwargs)", 85 | ) 86 | 87 | 88 | @overload 89 | def _add_theme_element(value: Vec2[float] = ..., /, **kwargs) -> _ITP_BASE: ... 90 | @overload 91 | def _add_theme_element(x: float = ..., y: float = ..., /, **kwargs) -> _ITP_BASE: ... 92 | def _add_theme_element(value: Any = 1, y: Any = -1, /, **kwargs) -> _ITP_BASE: ... 93 | 94 | _init_module() 95 | -------------------------------------------------------------------------------- /dearpypixl/style.pyi: -------------------------------------------------------------------------------- 1 | from ._typing import ( 2 | Item, 3 | ItemAlias, 4 | ItemUUID, 5 | ItemCommand, 6 | Literal, 7 | Sequence, 8 | Callable, 9 | Unpack, 10 | Property, 11 | Any, 12 | overload, 13 | ) 14 | from ._interface import ( 15 | BasicType, 16 | ContainerType, 17 | DrawNodeType, 18 | DrawingType, 19 | FontType, 20 | HandlerType, 21 | NodeEditorType, 22 | NodeType, 23 | PlotAxisType, 24 | PlotType, 25 | PlottingType, 26 | RegistryType, 27 | RootType, 28 | SupportsCallback, 29 | SupportsSized, 30 | SupportsValueArray, 31 | TableItemType, 32 | TableType, 33 | ThemeElementType, 34 | ThemeType, 35 | WindowType, 36 | mvAll, 37 | ) 38 | from .items import ( 39 | ThemeColor as ThemeColor, 40 | ThemeStyle as ThemeStyle, 41 | mvThemeColor as mvThemeColor, 42 | mvThemeStyle as mvThemeStyle, 43 | ) 44 | 45 | 46 | @overload 47 | def alpha(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 48 | @overload 49 | def alpha(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 50 | 51 | 52 | @overload 53 | def button_text_align(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 54 | @overload 55 | def button_text_align(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 56 | 57 | 58 | @overload 59 | def cell_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 60 | @overload 61 | def cell_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 62 | 63 | 64 | @overload 65 | def child_border_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 66 | @overload 67 | def child_border_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 68 | 69 | 70 | @overload 71 | def child_rounding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 72 | @overload 73 | def child_rounding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 74 | 75 | 76 | @overload 77 | def frame_border_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 78 | @overload 79 | def frame_border_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 80 | 81 | 82 | @overload 83 | def frame_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 84 | @overload 85 | def frame_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 86 | 87 | 88 | @overload 89 | def frame_rounding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 90 | @overload 91 | def frame_rounding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 92 | 93 | 94 | @overload 95 | def grab_min_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 96 | @overload 97 | def grab_min_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 98 | 99 | 100 | @overload 101 | def grab_rounding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 102 | @overload 103 | def grab_rounding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 104 | 105 | 106 | @overload 107 | def indent_spacing(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 108 | @overload 109 | def indent_spacing(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 110 | 111 | 112 | @overload 113 | def item_inner_spacing(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 114 | @overload 115 | def item_inner_spacing(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 116 | 117 | 118 | @overload 119 | def item_spacing(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 120 | @overload 121 | def item_spacing(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 122 | 123 | 124 | @overload 125 | def popup_border_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 126 | @overload 127 | def popup_border_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 128 | 129 | 130 | @overload 131 | def popup_rounding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 132 | @overload 133 | def popup_rounding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 134 | 135 | 136 | @overload 137 | def scrollbar_rounding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 138 | @overload 139 | def scrollbar_rounding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 140 | 141 | 142 | @overload 143 | def scrollbar_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 144 | @overload 145 | def scrollbar_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 146 | 147 | 148 | @overload 149 | def selectable_text_align(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 150 | @overload 151 | def selectable_text_align(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 152 | 153 | 154 | @overload 155 | def tab_rounding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 156 | @overload 157 | def tab_rounding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 158 | 159 | 160 | @overload 161 | def window_border_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 162 | @overload 163 | def window_border_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 164 | 165 | 166 | @overload 167 | def window_min_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 168 | @overload 169 | def window_min_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 170 | 171 | 172 | @overload 173 | def window_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 174 | @overload 175 | def window_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 176 | 177 | 178 | @overload 179 | def window_rounding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 180 | @overload 181 | def window_rounding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 182 | 183 | 184 | @overload 185 | def window_title_align(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 186 | @overload 187 | def window_title_align(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 188 | 189 | 190 | @overload 191 | def plot_annotation_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 192 | @overload 193 | def plot_annotation_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 194 | 195 | 196 | @overload 197 | def plot_border_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 198 | @overload 199 | def plot_border_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 200 | 201 | 202 | @overload 203 | def plot_default_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 204 | @overload 205 | def plot_default_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 206 | 207 | 208 | @overload 209 | def plot_digital_bit_gap(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 210 | @overload 211 | def plot_digital_bit_gap(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 212 | 213 | 214 | @overload 215 | def plot_digital_bit_height(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 216 | @overload 217 | def plot_digital_bit_height(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 218 | 219 | 220 | @overload 221 | def plot_error_bar_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 222 | @overload 223 | def plot_error_bar_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 224 | 225 | 226 | @overload 227 | def plot_error_bar_weight(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 228 | @overload 229 | def plot_error_bar_weight(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 230 | 231 | 232 | @overload 233 | def plot_fill_alpha(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 234 | @overload 235 | def plot_fill_alpha(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 236 | 237 | 238 | @overload 239 | def plot_fit_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 240 | @overload 241 | def plot_fit_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 242 | 243 | 244 | @overload 245 | def plot_label_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 246 | @overload 247 | def plot_label_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 248 | 249 | 250 | @overload 251 | def plot_legend_inner_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 252 | @overload 253 | def plot_legend_inner_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 254 | 255 | 256 | @overload 257 | def plot_legend_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 258 | @overload 259 | def plot_legend_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 260 | 261 | 262 | @overload 263 | def plot_legend_spacing(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 264 | @overload 265 | def plot_legend_spacing(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 266 | 267 | 268 | @overload 269 | def plot_line_weight(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 270 | @overload 271 | def plot_line_weight(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 272 | 273 | 274 | @overload 275 | def plot_major_grid_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 276 | @overload 277 | def plot_major_grid_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 278 | 279 | 280 | @overload 281 | def plot_major_tick_len(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 282 | @overload 283 | def plot_major_tick_len(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 284 | 285 | 286 | @overload 287 | def plot_major_tick_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 288 | @overload 289 | def plot_major_tick_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 290 | 291 | 292 | @overload 293 | def plot_marker(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 294 | @overload 295 | def plot_marker(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 296 | 297 | 298 | @overload 299 | def plot_marker_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 300 | @overload 301 | def plot_marker_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 302 | 303 | 304 | @overload 305 | def plot_marker_weight(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 306 | @overload 307 | def plot_marker_weight(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 308 | 309 | 310 | @overload 311 | def plot_min_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 312 | @overload 313 | def plot_min_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 314 | 315 | 316 | @overload 317 | def plot_minor_alpha(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 318 | @overload 319 | def plot_minor_alpha(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 320 | 321 | 322 | @overload 323 | def plot_minor_grid_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 324 | @overload 325 | def plot_minor_grid_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 326 | 327 | 328 | @overload 329 | def plot_minor_tick_len(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 330 | @overload 331 | def plot_minor_tick_len(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 332 | 333 | 334 | @overload 335 | def plot_minor_tick_size(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 336 | @overload 337 | def plot_minor_tick_size(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 338 | 339 | 340 | @overload 341 | def plot_mouse_pos_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 342 | @overload 343 | def plot_mouse_pos_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 344 | 345 | 346 | @overload 347 | def plot_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 348 | @overload 349 | def plot_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 350 | 351 | 352 | @overload 353 | def node_border_thickness(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 354 | @overload 355 | def node_border_thickness(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 356 | 357 | 358 | @overload 359 | def node_corner_rounding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 360 | @overload 361 | def node_corner_rounding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 362 | 363 | 364 | @overload 365 | def node_grid_spacing(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 366 | @overload 367 | def node_grid_spacing(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 368 | 369 | 370 | @overload 371 | def node_link_hover_distance(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 372 | @overload 373 | def node_link_hover_distance(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 374 | 375 | 376 | @overload 377 | def node_link_line_segments_per_length(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 378 | @overload 379 | def node_link_line_segments_per_length(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 380 | 381 | 382 | @overload 383 | def node_link_thickness(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 384 | @overload 385 | def node_link_thickness(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 386 | 387 | 388 | @overload 389 | def node_mini_map_offset(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 390 | @overload 391 | def node_mini_map_offset(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 392 | 393 | 394 | @overload 395 | def node_mini_map_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 396 | @overload 397 | def node_mini_map_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 398 | 399 | 400 | @overload 401 | def node_padding(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 402 | @overload 403 | def node_padding(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 404 | 405 | 406 | @overload 407 | def node_pin_circle_radius(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 408 | @overload 409 | def node_pin_circle_radius(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 410 | 411 | 412 | @overload 413 | def node_pin_hover_radius(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 414 | @overload 415 | def node_pin_hover_radius(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 416 | 417 | 418 | @overload 419 | def node_pin_line_thickness(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 420 | @overload 421 | def node_pin_line_thickness(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 422 | 423 | 424 | @overload 425 | def node_pin_offset(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 426 | @overload 427 | def node_pin_offset(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 428 | 429 | 430 | @overload 431 | def node_pin_quad_side_length(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 432 | @overload 433 | def node_pin_quad_side_length(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 434 | 435 | 436 | @overload 437 | def node_pin_triangle_side_length(value: tuple[float, float] | Sequence[float] = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 438 | @overload 439 | def node_pin_triangle_side_length(x: float = ..., y: float = ..., /, *, label: str | None = ..., user_data: Any = ..., use_internal_label: bool = ..., **kwargs) -> mvThemeStyle: ... 440 | 441 | 442 | Alpha = alpha 443 | ButtonTextAlign = button_text_align 444 | CellPadding = cell_padding 445 | ChildBorderSize = child_border_size 446 | ChildRounding = child_rounding 447 | FrameBorderSize = frame_border_size 448 | FramePadding = frame_padding 449 | FrameRounding = frame_rounding 450 | GrabMinSize = grab_min_size 451 | GrabRounding = grab_rounding 452 | IndentSpacing = indent_spacing 453 | ItemInnerSpacing = item_inner_spacing 454 | ItemSpacing = item_spacing 455 | PopupBorderSize = popup_border_size 456 | PopupRounding = popup_rounding 457 | ScrollbarRounding = scrollbar_rounding 458 | ScrollbarSize = scrollbar_size 459 | SelectableTextAlign = selectable_text_align 460 | TabRounding = tab_rounding 461 | WindowBorderSize = window_border_size 462 | WindowMinSize = window_min_size 463 | WindowPadding = window_padding 464 | WindowRounding = window_rounding 465 | WindowTitleAlign = window_title_align 466 | PlotAnnotationPadding = plot_annotation_padding 467 | PlotBorderSize = plot_border_size 468 | PlotDefaultSize = plot_default_size 469 | PlotDigitalBitGap = plot_digital_bit_gap 470 | PlotDigitalBitHeight = plot_digital_bit_height 471 | PlotErrorBarSize = plot_error_bar_size 472 | PlotErrorBarWeight = plot_error_bar_weight 473 | PlotFillAlpha = plot_fill_alpha 474 | PlotFitPadding = plot_fit_padding 475 | PlotLabelPadding = plot_label_padding 476 | PlotLegendInnerPadding = plot_legend_inner_padding 477 | PlotLegendPadding = plot_legend_padding 478 | PlotLegendSpacing = plot_legend_spacing 479 | PlotLineWeight = plot_line_weight 480 | PlotMajorGridSize = plot_major_grid_size 481 | PlotMajorTickLen = plot_major_tick_len 482 | PlotMajorTickSize = plot_major_tick_size 483 | PlotMarker = plot_marker 484 | PlotMarkerSize = plot_marker_size 485 | PlotMarkerWeight = plot_marker_weight 486 | PlotMinSize = plot_min_size 487 | PlotMinorAlpha = plot_minor_alpha 488 | PlotMinorGridSize = plot_minor_grid_size 489 | PlotMinorTickLen = plot_minor_tick_len 490 | PlotMinorTickSize = plot_minor_tick_size 491 | PlotMousePosPadding = plot_mouse_pos_padding 492 | PlotPadding = plot_padding 493 | NodeBorderThickness = node_border_thickness 494 | NodeCornerRounding = node_corner_rounding 495 | NodeGridSpacing = node_grid_spacing 496 | NodeLinkHoverDistance = node_link_hover_distance 497 | NodeLinkLineSegmentsPerLength = node_link_line_segments_per_length 498 | NodeLinkThickness = node_link_thickness 499 | NodeMiniMapOffset = node_mini_map_offset 500 | NodeMiniMapPadding = node_mini_map_padding 501 | NodePadding = node_padding 502 | NodePinCircleRadius = node_pin_circle_radius 503 | NodePinHoverRadius = node_pin_hover_radius 504 | NodePinLineThickness = node_pin_line_thickness 505 | NodePinOffset = node_pin_offset 506 | NodePinQuadSideLength = node_pin_quad_side_length 507 | NodePinTriangleSideLength = node_pin_triangle_side_length -------------------------------------------------------------------------------- /dearpypixl/themes.py: -------------------------------------------------------------------------------- 1 | """Contains functions that return preset themes. 2 | 3 | A preset is created only if it doesn't exist. Otherwise, 4 | the existing preset(s) are returned. Each preset is 5 | assigned an alias that remains constant throughout 6 | sessions. The collection of available Dear PyPixl presets 7 | can be obtained by calling the `load_presets` function. 8 | 9 | Presets can be cloned by calling their `.__copy__` method. 10 | """ 11 | import functools 12 | import contextlib 13 | from . import _typing, api 14 | from . import color, style 15 | from .items import ( 16 | mvTheme, 17 | mvThemeComponent, 18 | ) 19 | 20 | 21 | 22 | 23 | def _onetime_setup(): 24 | if not api.Application.state()['ok']: 25 | api.Application() 26 | global _onetime_setup 27 | _onetime_setup = lambda: None 28 | 29 | 30 | _preset_fns: tuple[_typing.Callable[[], mvTheme], ...] = () 31 | 32 | def _dearpypixl_preset(preset_name: str): 33 | def capture_fn(fn: _typing.Callable[[], mvTheme]): 34 | @functools.wraps(fn) 35 | def get_theme_preset(): 36 | _onetime_setup() 37 | if api.Registry.alias_exists(preset_name): 38 | return mvTheme.new(preset_name) 39 | return fn() 40 | global _preset_fns 41 | _preset_fns += (fn,) 42 | return get_theme_preset 43 | preset_name = f'[{mvTheme.__name__}] {preset_name}' 44 | return capture_fn 45 | 46 | 47 | @contextlib.contextmanager 48 | def _theme_preset(name: str): 49 | with mvTheme.aliased(tag=f'[mvTheme] {name}', label=f'[mvTheme] {name}') as theme: 50 | color_c = mvThemeComponent.aliased( 51 | tag=f'[mvThemeComponent] color ({name})', 52 | label=f'[mvThemeComponent] color ({name})', 53 | ) 54 | style_c = mvThemeComponent.aliased( 55 | tag=f'[mvThemeComponent] style ({name})', 56 | label=f'[mvThemeComponent] style ({name})', 57 | ) 58 | try: 59 | yield theme 60 | finally: 61 | id_element = api.ThemeElement.identify 62 | for elem_tp, c in (('mvThemeColor', color_c), ('mvThemeStyle', style_c)): 63 | label_pfx = elem_tp.removeprefix('mvTheme').lower() 64 | for e in c.children(1): 65 | alias = f'[{elem_tp}] {id_element(e)} ({name}.{label_pfx})' 66 | api.Item.set_alias( 67 | e, 68 | alias, 69 | ) 70 | api.Item.configure(e, label=alias) 71 | 72 | 73 | 74 | 75 | def load_presets() -> tuple[mvTheme, ...]: 76 | """Return all Dear PyPixl presets, loading them if they do 77 | not exist. 78 | """ 79 | return tuple(fn() for fn in _preset_fns) 80 | 81 | 82 | 83 | 84 | # [ THEMES ] 85 | 86 | # "Complete" themes use most but not all elements. The 87 | # functions that make the unused elements are simply left 88 | # uncalled; this makes it easier to tell which elements are 89 | # missing at a glance. 90 | 91 | 92 | @_dearpypixl_preset('Default') 93 | def default(): 94 | """Return the preset that emulates Dear PyGui's internal 95 | default theme. 96 | """ 97 | with _theme_preset('Default') as theme: 98 | with mvThemeComponent.new(theme[1][0]): 99 | color.border(78, 78, 78, 255) 100 | color.border_shadow(78, 78, 78, 255) 101 | color.button(51, 51, 55, 255) 102 | color.button_active(0, 119, 200, 153) 103 | color.button_hovered(29, 151, 236, 103) 104 | color.check_mark(0, 119, 200, 153) 105 | color.child_bg(37, 37, 38, 255) 106 | color.docking_empty_bg(51, 51, 51, 255) 107 | color.docking_preview(0, 119, 200, 153) 108 | color.drag_drop_target(255, 255, 0, 179) 109 | color.frame_bg(51, 51, 55, 255) 110 | color.frame_bg_active(0, 119, 200, 153) 111 | color.frame_bg_hovered(29, 151, 236, 103) 112 | color.header(51, 51, 55, 255) 113 | color.header_active(0, 119, 200, 153) 114 | color.header_hovered(29, 151, 236, 103) 115 | color.menu_bar_bg(51, 51, 55, 255) 116 | color.modal_window_dim_bg(37, 37, 38, 150) 117 | color.nav_highlight(37, 37, 38, 255) 118 | color.nav_windowing_dim_bg(204, 204, 204, 51) 119 | color.nav_windowing_highlight(255, 255, 255, 179) 120 | color.popup_bg(37, 37, 38, 255) 121 | color.resize_grip(37, 37, 38, 255) 122 | color.resize_grip_hovered(51, 51, 55, 255) 123 | color.resize_grip_active(51, 51, 55, 255) 124 | color.scrollbar_bg(51, 51, 55, 255) 125 | color.scrollbar_grab(82, 82, 85, 255) 126 | color.scrollbar_grab_active(90, 90, 95, 255) 127 | color.scrollbar_grab_hovered(90, 90, 95, 255) 128 | color.separator(78, 78, 78, 255) 129 | color.separator_active(78, 78, 78, 255) 130 | color.separator_hovered(78, 78, 78, 255) 131 | color.slider_grab(29, 151, 236, 103) 132 | color.slider_grab_active(0, 119, 200, 153) 133 | color.tab(51, 51, 55, 255) 134 | color.tab_active(0, 119, 200, 153) 135 | color.tab_hovered(29, 151, 236, 103) 136 | color.tab_unfocused(51, 51, 55, 255) 137 | color.tab_unfocused_active(0, 119, 200, 153) 138 | color.table_border_light(59, 59, 64, 255) 139 | color.table_border_strong(79, 79, 89, 255) 140 | color.table_header_bg(48, 48, 51, 255) 141 | color.table_row_bg(0, 0, 0, 0) 142 | color.table_row_bg_alt(255, 255, 255, 15) 143 | color.text(255, 255, 255, 255) 144 | color.text_disabled(151, 151, 151, 255) 145 | color.text_selected_bg(0, 119, 200, 153) 146 | color.title_bg(37, 37, 38, 255) 147 | color.title_bg_active(15, 86, 135, 255) 148 | color.title_bg_collapsed(37, 37, 38, 255) 149 | color.window_bg(37, 37, 38, 255) 150 | color.plot_bg 151 | color.plot_border 152 | color.plot_crosshairs 153 | color.plot_error_bar 154 | color.plot_fill 155 | color.plot_frame_bg 156 | color.plot_histogram(0, 119, 200, 153) 157 | color.plot_histogram_hovered(29, 151, 236, 103) 158 | color.plot_inlay_text 159 | color.plot_legend_bg 160 | color.plot_legend_border 161 | color.plot_legend_text 162 | color.plot_line 163 | color.plot_lines(0, 119, 200, 153) 164 | color.plot_lines_hovered(29, 151, 236, 103) 165 | color.plot_marker_fill 166 | color.plot_marker_outline 167 | color.plot_query 168 | color.plot_selection 169 | color.plot_title_text 170 | color.plot_x_axis 171 | color.plot_x_axis_grid 172 | color.plot_y_axis 173 | color.plot_y_axis2 174 | color.plot_y_axis3 175 | color.plot_y_axis_grid 176 | color.plot_y_axis_grid2 177 | color.plot_y_axis_grid3 178 | color.node_bg(62, 62, 62, 255) 179 | color.node_bg_hovered(75, 75, 75, 255) 180 | color.node_bg_selected(75, 75, 75, 255) 181 | color.node_box_selector(61, 133, 224, 30) 182 | color.node_box_selector_outline(61, 133, 224, 150) 183 | color.node_grid_bg(35, 35, 35, 255) 184 | color.node_grid_line(0, 0, 0, 255) 185 | color.node_grid_line_primary(240, 240, 240, 60) 186 | color.node_link(255, 255, 255, 200) 187 | color.node_link_hovered(66, 150, 250, 255) 188 | color.node_link_selected(66, 150, 250, 255) 189 | color.node_mini_map_bg(25, 25, 25, 150) 190 | color.node_mini_map_bg_hovered(150, 150, 150, 200) 191 | color.node_mini_map_canvas(200, 200, 200, 25) 192 | color.node_mini_map_canvas_outline(200, 200, 200, 200) 193 | color.node_mini_map_link(61, 133, 224, 200) 194 | color.node_mini_map_link_selected(66, 150, 250, 255) 195 | color.node_mini_map_node_bg(200, 200, 200, 100) 196 | color.node_mini_map_node_bg_hovered(200, 200, 200, 255) 197 | color.node_mini_map_node_bg_selected(200, 200, 200, 255) 198 | color.node_mini_map_node_outline(200, 200, 200, 100) 199 | color.node_mini_map_outline(150, 150, 150, 100) 200 | color.node_mini_map_outline_hovered(150, 150, 150, 200) 201 | color.node_outline(100, 100, 100, 255) 202 | color.node_pin(199, 199, 41, 255) 203 | color.node_pin_hovered(255, 255, 50, 255) 204 | color.node_title_bar(37, 37, 38, 255) 205 | color.node_title_bar_hovered(15, 86, 135, 255) 206 | color.node_title_bar_selected(0, 119, 200, 153) 207 | with mvThemeComponent.new(theme[1][1]): 208 | style.alpha 209 | style.button_text_align(0.5, 0.5) 210 | style.cell_padding(4.0, 2.0) 211 | style.child_border_size(1) 212 | style.child_rounding(0) 213 | style.frame_border_size(0) 214 | style.frame_padding(4.0, 3.0) 215 | style.frame_rounding(0) 216 | style.grab_min_size(10.0) 217 | style.grab_rounding(0) 218 | style.indent_spacing(21.0) 219 | style.item_inner_spacing(4, 4) 220 | style.item_spacing(8, 4) 221 | style.popup_border_size(1) 222 | style.popup_rounding(0) 223 | style.scrollbar_rounding(9) 224 | style.scrollbar_size(14) 225 | style.selectable_text_align(0.0, 0.5) 226 | style.tab_rounding(4) 227 | style.window_border_size(1) 228 | style.window_min_size(100, 100) 229 | style.window_padding(8, 8) 230 | style.window_rounding(0) 231 | style.window_title_align(0.0, 0.5) 232 | style.plot_annotation_padding(2.0, 2.0) 233 | style.plot_border_size(1) 234 | style.plot_default_size(400, 300) 235 | style.plot_digital_bit_gap(8.0) 236 | style.plot_digital_bit_height(4.0) 237 | style.plot_error_bar_size(5.0) 238 | style.plot_error_bar_weight(1.0) 239 | style.plot_fill_alpha(1.0) 240 | style.plot_fit_padding(0.0, 0.0) 241 | style.plot_label_padding(5, 5) 242 | style.plot_legend_inner_padding(5, 5) 243 | style.plot_legend_padding(10, 10) 244 | style.plot_legend_spacing(5, 0) 245 | style.plot_line_weight(1.0) 246 | style.plot_major_grid_size(1.0, 1.0) 247 | style.plot_major_tick_len(10, 10) 248 | style.plot_major_tick_size(1.0, 1.0) 249 | style.plot_marker(4.0) 250 | style.plot_marker_size(4.0) 251 | style.plot_marker_weight(1.0) 252 | style.plot_min_size(200, 150) 253 | style.plot_minor_alpha(0.25) 254 | style.plot_minor_grid_size(1.0, 1.0) 255 | style.plot_minor_tick_len(5, 5) 256 | style.plot_minor_tick_size(1.0, 1.0) 257 | style.plot_mouse_pos_padding(10, 10) 258 | style.plot_padding(10, 10) 259 | style.node_border_thickness(1) 260 | style.node_corner_rounding(4) 261 | style.node_grid_spacing(24) 262 | style.node_link_hover_distance(10) 263 | style.node_link_line_segments_per_length(0) 264 | style.node_link_thickness(3) 265 | #style.node_mini_map_offset(4, 0) DPG BUG? 266 | style.node_mini_map_padding(8, 8) 267 | style.node_padding(8, 8) 268 | style.node_pin_circle_radius(4) 269 | style.node_pin_hover_radius(10) 270 | style.node_pin_line_thickness(1) 271 | style.node_pin_offset(0) 272 | style.node_pin_quad_side_length(7) 273 | style.node_pin_triangle_side_length(10) 274 | return theme 275 | 276 | 277 | @_dearpypixl_preset('ImGui Dark') 278 | def imgui_dark(): 279 | """Return the preset emulating `dearpygui_ext`s "imgui dark" 280 | theme. 281 | """ 282 | with _theme_preset('ImGui Dark') as theme: 283 | with mvThemeComponent.new(theme[1][0]): 284 | color.border(110, 110, 128, 128) 285 | color.border_shadow(0, 0, 0, 0) 286 | color.button(66, 150, 250, 102) 287 | color.button_active(15, 135, 250, 255) 288 | color.button_hovered(66, 150, 250, 255) 289 | color.check_mark(66, 150, 250, 255) 290 | color.child_bg(0, 0, 0, 0) 291 | color.docking_empty_bg(51, 51, 51, 255) 292 | color.docking_preview(66, 150, 250, 178) 293 | color.drag_drop_target(255, 255, 0, 230) 294 | color.frame_bg(41, 74, 122, 138) 295 | color.frame_bg_active(66, 150, 250, 171) 296 | color.frame_bg_hovered(66, 150, 250, 102) 297 | color.header(66, 150, 250, 79) 298 | color.header_active(66, 150, 250, 255) 299 | color.header_hovered(66, 150, 250, 204) 300 | color.menu_bar_bg(36, 36, 36, 255) 301 | color.modal_window_dim_bg(204, 204, 204, 89) 302 | color.nav_highlight(66, 150, 250, 255) 303 | color.nav_windowing_dim_bg(204, 204, 204, 51) 304 | color.nav_windowing_highlight(255, 255, 255, 178) 305 | color.popup_bg(20, 20, 20, 240) 306 | color.resize_grip(66, 150, 250, 51) 307 | color.resize_grip_active(66, 150, 250, 242) 308 | color.resize_grip_hovered(66, 150, 250, 171) 309 | color.scrollbar_bg(5, 5, 5, 135) 310 | color.scrollbar_grab(79, 79, 79, 255) 311 | color.scrollbar_grab_active(130, 130, 130, 255) 312 | color.scrollbar_grab_hovered(105, 105, 105, 255) 313 | color.separator(110, 110, 128, 128) 314 | color.separator_active(26, 102, 191, 255) 315 | color.separator_hovered(26, 102, 191, 199) 316 | color.slider_grab(61, 133, 224, 255) 317 | color.slider_grab_active(66, 150, 250, 255) 318 | color.tab(46, 89, 148, 219) 319 | color.tab_active(51, 105, 173, 255) 320 | color.tab_hovered(66, 150, 250, 204) 321 | color.tab_unfocused(18, 26, 38, 247) 322 | color.tab_unfocused_active(36, 66, 107, 255) 323 | color.table_border_light(59, 59, 64, 255) 324 | color.table_border_strong(79, 79, 89, 255) 325 | color.table_header_bg(48, 48, 51, 255) 326 | color.table_row_bg(0, 0, 0, 0) 327 | color.table_row_bg_alt(255, 255, 255, 15) 328 | color.text(255, 255, 255, 255) 329 | color.text_disabled(128, 128, 128, 255) 330 | color.text_selected_bg(66, 150, 250, 89) 331 | color.title_bg(10, 10, 10, 255) 332 | color.title_bg_active(41, 74, 122, 255) 333 | color.title_bg_collapsed(0, 0, 0, 130) 334 | color.window_bg(15, 15, 15, 240) 335 | color.plot_bg(0, 0, 0, 128) 336 | color.plot_border(110, 110, 128, 128) 337 | color.plot_crosshairs(255, 255, 255, 128) 338 | color.plot_error_bar 339 | color.plot_fill 340 | color.plot_frame_bg(255, 255, 255, 18) 341 | color.plot_histogram(230, 178, 0, 255) 342 | color.plot_histogram_hovered(255, 153, 0, 255) 343 | color.plot_inlay_text(255, 255, 255, 255) 344 | color.plot_legend_bg(20, 20, 20, 240) 345 | color.plot_legend_border(110, 110, 128, 128) 346 | color.plot_legend_text(255, 255, 255, 255) 347 | color.plot_line 348 | color.plot_lines(156, 156, 156, 255) 349 | color.plot_lines_hovered(255, 110, 89, 255) 350 | color.plot_marker_fill 351 | color.plot_marker_outline 352 | color.plot_query(0, 255, 112, 255) 353 | color.plot_selection(255, 153, 0, 255) 354 | color.plot_title_text(255, 255, 255, 255) 355 | color.plot_x_axis(255, 255, 255, 255) 356 | color.plot_x_axis_grid(255, 255, 255, 64) 357 | color.plot_y_axis(255, 255, 255, 255) 358 | color.plot_y_axis2(255, 255, 255, 255) 359 | color.plot_y_axis3(255, 255, 255, 255) 360 | color.plot_y_axis_grid(255, 255, 255, 64) 361 | color.plot_y_axis_grid2(255, 255, 255, 64) 362 | color.plot_y_axis_grid3(255, 255, 255, 64) 363 | color.node_bg(50, 50, 50, 255) 364 | color.node_bg_hovered(75, 75, 75, 255) 365 | color.node_bg_selected(75, 75, 75, 255) 366 | color.node_box_selector(61, 133, 224, 30) 367 | color.node_box_selector_outline(61, 133, 224, 150) 368 | color.node_grid_bg(40, 40, 50, 200) 369 | color.node_grid_line(200, 200, 200, 40) 370 | color.node_grid_line_primary 371 | color.node_link(61, 133, 224, 200) 372 | color.node_link_hovered(66, 150, 250, 255) 373 | color.node_link_selected(66, 150, 250, 255) 374 | color.node_mini_map_bg 375 | color.node_mini_map_bg_hovered 376 | color.node_mini_map_canvas 377 | color.node_mini_map_canvas_outline 378 | color.node_mini_map_link 379 | color.node_mini_map_link_selected 380 | color.node_mini_map_node_bg 381 | color.node_mini_map_node_bg_hovered 382 | color.node_mini_map_node_bg_selected 383 | color.node_mini_map_node_outline 384 | color.node_mini_map_outline 385 | color.node_mini_map_outline_hovered 386 | color.node_outline(100, 100, 100, 255) 387 | color.node_pin(53, 150, 250, 180) 388 | color.node_pin_hovered(53, 150, 250, 255) 389 | color.node_title_bar(41, 74, 122, 255) 390 | color.node_title_bar_hovered(66, 150, 250, 255) 391 | color.node_title_bar_selected(66, 150, 250, 255) 392 | with mvThemeComponent.new(theme[1][1]): 393 | style.alpha 394 | style.button_text_align(0.5, 0.5) 395 | style.cell_padding(4.0, 2.0) 396 | style.child_border_size(1) 397 | style.child_rounding(0) 398 | style.frame_border_size(0) 399 | style.frame_padding(4.0, 3.0) 400 | style.frame_rounding(0) 401 | style.grab_min_size(10.0) 402 | style.grab_rounding(0) 403 | style.indent_spacing(21.0) 404 | style.item_inner_spacing(4, 4) 405 | style.item_spacing(8, 4) 406 | style.popup_border_size(1) 407 | style.popup_rounding(0) 408 | style.scrollbar_rounding(9) 409 | style.scrollbar_size(14) 410 | style.selectable_text_align(0.0, 0.5) 411 | style.tab_rounding(4) 412 | style.window_border_size(1) 413 | style.window_min_size(100, 100) 414 | style.window_padding(8, 8) 415 | style.window_rounding(0) 416 | style.window_title_align(0.0, 0.5) 417 | style.plot_annotation_padding(2.0, 2.0) 418 | style.plot_border_size(1) 419 | style.plot_default_size(400, 300) 420 | style.plot_digital_bit_gap(8.0) 421 | style.plot_digital_bit_height(4.0) 422 | style.plot_error_bar_size(5.0) 423 | style.plot_error_bar_weight(1.0) 424 | style.plot_fill_alpha(1.0) 425 | style.plot_fit_padding(0.0, 0.0) 426 | style.plot_label_padding(5, 5) 427 | style.plot_legend_inner_padding(5, 5) 428 | style.plot_legend_padding(10, 10) 429 | style.plot_legend_spacing(5, 0) 430 | style.plot_line_weight(1.0) 431 | style.plot_major_grid_size(1.0, 1.0) 432 | style.plot_major_tick_len(10, 10) 433 | style.plot_major_tick_size(1.0, 1.0) 434 | style.plot_marker(4.0) 435 | style.plot_marker_size(4.0) 436 | style.plot_marker_weight(1.0) 437 | style.plot_min_size(200, 150) 438 | style.plot_minor_alpha(0.25) 439 | style.plot_minor_grid_size(1.0, 1.0) 440 | style.plot_minor_tick_len(5, 5) 441 | style.plot_minor_tick_size(1.0, 1.0) 442 | style.plot_mouse_pos_padding(10, 10) 443 | style.plot_padding(10, 10) 444 | style.node_border_thickness(1) 445 | style.node_corner_rounding(4) 446 | style.node_grid_spacing(24) 447 | style.node_link_hover_distance(10) 448 | style.node_link_line_segments_per_length(0) 449 | style.node_link_thickness(3) 450 | #style.node_mini_map_offset(4, 0) DPG BUG? 451 | style.node_mini_map_padding(8, 8) 452 | style.node_padding(8, 8) 453 | style.node_pin_circle_radius(4) 454 | style.node_pin_hover_radius(10) 455 | style.node_pin_line_thickness(1) 456 | style.node_pin_offset(0) 457 | style.node_pin_quad_side_length(7) 458 | style.node_pin_triangle_side_length(10) 459 | return theme 460 | 461 | 462 | # TODO 463 | # @_dearpypixl_preset('ImGui Light') 464 | def _imgui_light(): 465 | """Return the preset emulating `dearpygui_ext`s "imgui light" 466 | theme. 467 | """ 468 | with _theme_preset('ImGui Light') as theme: 469 | with mvThemeComponent.new(theme[1][0]): 470 | color.border 471 | color.border_shadow 472 | color.button 473 | color.button_active 474 | color.button_hovered 475 | color.check_mark 476 | color.child_bg 477 | color.docking_empty_bg 478 | color.docking_preview 479 | color.drag_drop_target 480 | color.frame_bg 481 | color.frame_bg_active 482 | color.frame_bg_hovered 483 | color.header 484 | color.header_active 485 | color.header_hovered 486 | color.menu_bar_bg 487 | color.modal_window_dim_bg 488 | color.nav_highlight 489 | color.nav_windowing_dim_bg 490 | color.nav_windowing_highlight 491 | color.popup_bg 492 | color.resize_grip 493 | color.resize_grip_active 494 | color.resize_grip_hovered 495 | color.scrollbar_bg 496 | color.scrollbar_grab 497 | color.scrollbar_grab_active 498 | color.scrollbar_grab_hovered 499 | color.separator 500 | color.separator_active 501 | color.separator_hovered 502 | color.slider_grab 503 | color.slider_grab_active 504 | color.tab 505 | color.tab_active 506 | color.tab_hovered 507 | color.tab_unfocused 508 | color.tab_unfocused_active 509 | color.table_border_light 510 | color.table_border_strong 511 | color.table_header_bg 512 | color.table_row_bg 513 | color.table_row_bg_alt 514 | color.text 515 | color.text_disabled 516 | color.text_selected_bg 517 | color.title_bg 518 | color.title_bg_active 519 | color.title_bg_collapsed 520 | color.window_bg 521 | color.plot_bg 522 | color.plot_border 523 | color.plot_crosshairs 524 | color.plot_error_bar 525 | color.plot_fill 526 | color.plot_frame_bg 527 | color.plot_histogram 528 | color.plot_histogram_hovered 529 | color.plot_inlay_text 530 | color.plot_legend_bg 531 | color.plot_legend_border 532 | color.plot_legend_text 533 | color.plot_line 534 | color.plot_lines 535 | color.plot_lines_hovered 536 | color.plot_marker_fill 537 | color.plot_marker_outline 538 | color.plot_query 539 | color.plot_selection 540 | color.plot_title_text 541 | color.plot_x_axis 542 | color.plot_x_axis_grid 543 | color.plot_y_axis 544 | color.plot_y_axis2 545 | color.plot_y_axis3 546 | color.plot_y_axis_grid 547 | color.plot_y_axis_grid2 548 | color.plot_y_axis_grid3 549 | color.node_bg 550 | color.node_bg_hovered 551 | color.node_bg_selected 552 | color.node_box_selector 553 | color.node_box_selector_outline 554 | color.node_grid_bg 555 | color.node_grid_line 556 | color.node_grid_line_primary 557 | color.node_link 558 | color.node_link_hovered 559 | color.node_link_selected 560 | color.node_mini_map_bg 561 | color.node_mini_map_bg_hovered 562 | color.node_mini_map_canvas 563 | color.node_mini_map_canvas_outline 564 | color.node_mini_map_link 565 | color.node_mini_map_link_selected 566 | color.node_mini_map_node_bg 567 | color.node_mini_map_node_bg_hovered 568 | color.node_mini_map_node_bg_selected 569 | color.node_mini_map_node_outline 570 | color.node_mini_map_outline 571 | color.node_mini_map_outline_hovered 572 | color.node_outline 573 | color.node_pin 574 | color.node_pin_hovered 575 | color.node_title_bar 576 | color.node_title_bar_hovered 577 | color.node_title_bar_selected 578 | with mvThemeComponent.new(theme[1][1]): 579 | style.alpha 580 | style.button_text_align(0.5, 0.5) 581 | style.cell_padding(4.0, 2.0) 582 | style.child_border_size(1) 583 | style.child_rounding(0) 584 | style.frame_border_size(0) 585 | style.frame_padding(4.0, 3.0) 586 | style.frame_rounding(0) 587 | style.grab_min_size(10.0) 588 | style.grab_rounding(0) 589 | style.indent_spacing(21.0) 590 | style.item_inner_spacing(4, 4) 591 | style.item_spacing(8, 4) 592 | style.popup_border_size(1) 593 | style.popup_rounding(0) 594 | style.scrollbar_rounding(9) 595 | style.scrollbar_size(14) 596 | style.selectable_text_align(0.0, 0.5) 597 | style.tab_rounding(4) 598 | style.window_border_size(1) 599 | style.window_min_size(100, 100) 600 | style.window_padding(8, 8) 601 | style.window_rounding(0) 602 | style.window_title_align(0.0, 0.5) 603 | style.plot_annotation_padding(2.0, 2.0) 604 | style.plot_border_size(1) 605 | style.plot_default_size(400, 300) 606 | style.plot_digital_bit_gap(8.0) 607 | style.plot_digital_bit_height(4.0) 608 | style.plot_error_bar_size(5.0) 609 | style.plot_error_bar_weight(1.0) 610 | style.plot_fill_alpha(1.0) 611 | style.plot_fit_padding(0.0, 0.0) 612 | style.plot_label_padding(5, 5) 613 | style.plot_legend_inner_padding(5, 5) 614 | style.plot_legend_padding(10, 10) 615 | style.plot_legend_spacing(5, 0) 616 | style.plot_line_weight(1.0) 617 | style.plot_major_grid_size(1.0, 1.0) 618 | style.plot_major_tick_len(10, 10) 619 | style.plot_major_tick_size(1.0, 1.0) 620 | style.plot_marker(4.0) 621 | style.plot_marker_size(4.0) 622 | style.plot_marker_weight(1.0) 623 | style.plot_min_size(200, 150) 624 | style.plot_minor_alpha(0.25) 625 | style.plot_minor_grid_size(1.0, 1.0) 626 | style.plot_minor_tick_len(5, 5) 627 | style.plot_minor_tick_size(1.0, 1.0) 628 | style.plot_mouse_pos_padding(10, 10) 629 | style.plot_padding(10, 10) 630 | style.node_border_thickness(1) 631 | style.node_corner_rounding(4) 632 | style.node_grid_spacing(24) 633 | style.node_link_hover_distance(10) 634 | style.node_link_line_segments_per_length(0) 635 | style.node_link_thickness(3) 636 | #style.node_mini_map_offset(4, 0) DPG BUG? 637 | style.node_mini_map_padding(8, 8) 638 | style.node_padding(8, 8) 639 | style.node_pin_circle_radius(4) 640 | style.node_pin_hover_radius(10) 641 | style.node_pin_line_thickness(1) 642 | style.node_pin_offset(0) 643 | style.node_pin_quad_side_length(7) 644 | style.node_pin_triangle_side_length(10) 645 | return theme 646 | 647 | 648 | @_dearpypixl_preset("Padless") 649 | def padless(): 650 | with _theme_preset("Padless") as theme: 651 | with mvThemeComponent.new(theme[1][1]): 652 | style.cell_padding(0, 0) 653 | style.item_spacing(0, 0) 654 | style.window_padding(0, 0) 655 | return theme 656 | -------------------------------------------------------------------------------- /dearpypixl/typing.py: -------------------------------------------------------------------------------- 1 | from dearpygui import dearpygui 2 | from ._typing import ( 3 | Item as Item, 4 | ItemUUID as ItemUUID, 5 | ItemAlias as ItemAlias, 6 | Interface as Interface, 7 | ItemCallback as ItemCallback, 8 | ItemCommand as ItemCommand, 9 | ItemInfoDict as ItemInfoDict, 10 | ItemStateDict as ItemStateDict, 11 | mvBuffer as mvBuffer, 12 | mvMat4 as mvMat4, 13 | mvVec4 as mvVec4, 14 | 15 | cast, 16 | ) 17 | from ._interface import ( 18 | AppItemMeta as AppItemMeta, 19 | AppItemType as AppItemType, 20 | ABCAppItemMeta as ABCAppItemMeta, 21 | ABCAppItemType as ABCAppItemType, 22 | 23 | BasicType as BasicType, 24 | ContainerType as ContainerType, 25 | RootType as RootType, 26 | RegistryType as RegistryType, 27 | HandlerType as HandlerType, 28 | NodeType as NodeType, 29 | NodeEditorType as NodeEditorType, 30 | ThemeType as ThemeType, 31 | ThemeElementType as ThemeElementType, 32 | FontType as FontType, 33 | DrawingType as DrawingType, 34 | DrawNodeType as DrawNodeType, 35 | PlottingType as PlottingType, 36 | PlotType as PlotType, 37 | PlotAxisType as PlotAxisType, 38 | TableType as TableType, 39 | TableItemType as TableItemType, 40 | WindowType as WindowType, 41 | 42 | SupportsCallback as SupportsCallback, 43 | SupportsSized as SupportsSized, 44 | SupportsValueArray as SupportsValueArray, 45 | ) 46 | from .api import ( 47 | AppConfigDict as AppConfigDict, 48 | ViewportConfigDict as ViewportConfigDict, 49 | RuntimeConfigDict as RuntimeConfigDict, 50 | ) 51 | 52 | 53 | mvBuffer = cast(type[mvBuffer], dearpygui.mvBuffer) 54 | mvMat4 = cast(type[mvMat4], dearpygui.mvMat4) 55 | mvVec4 = cast(type[mvVec4], dearpygui.mvVec4) 56 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "dearpypixl" 3 | version = "1.2.13" 4 | description = "An object-oriented, lightweight, modular framework and toolkit for Dear PyGui." 5 | authors = [{name="Anthony Doupe", email="atlamillias@outlook.com"}] 6 | keywords = ["dearpygui", "gui", "ui", "user-interface"] 7 | urls = {Homepage="https://github.com/Atlamillias/DearPyPixl"} 8 | readme = "README.md" 9 | classifiers = [ 10 | "Programming Language :: Python :: Implementation :: CPython", 11 | "Programming Language :: Python :: 3 :: Only", 12 | "Programming Language :: Python :: 3.10", 13 | 14 | "Operating System :: Microsoft :: Windows :: Windows 8.1", 15 | "Operating System :: Microsoft :: Windows :: Windows 10", 16 | "Operating System :: Microsoft :: Windows :: Windows 11", 17 | "Operating System :: MacOS", 18 | "Operating System :: POSIX", 19 | "Operating System :: Unix", 20 | 21 | "License :: OSI Approved :: MIT License", 22 | ] 23 | requires-python = ">=3.10" 24 | dependencies = [ 25 | "dearpygui >= 1.9.0", 26 | "typing-extensions; python_version < '3.12'", 27 | ] 28 | 29 | 30 | [build-system] 31 | requires = ["setuptools ~= 65.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | 34 | 35 | [tool.setuptools] 36 | packages = [ 37 | "dearpypixl", 38 | ] 39 | include-package-data = true 40 | 41 | [tool.setuptools.package-data] 42 | dearpypixl = ["*.typed", "*.pyi"] 43 | 44 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # `pip` & `build` use the deps listed in `pyproject.toml`, 2 | # not here. This is for development. 3 | dearpygui 4 | typing-extensions ; python_version < "3.12" 5 | build --------------------------------------------------------------------------------