├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── README.md ├── dearpygui_obj.sublime-project ├── dearpygui_obj ├── __init__.py ├── basic.py ├── colors.py ├── containers.py ├── data.py ├── devtools.py ├── drawing.py ├── input.py ├── layout.py ├── node.py ├── plots │ ├── __init__.py │ └── dataseries.py ├── tables.py ├── userwidget.py ├── widgets.py ├── window.py └── wrapper │ ├── __init__.py │ ├── dataseries.py │ ├── drawing.py │ └── widget.py ├── demos └── widget_status_queries_demo.py ├── docs ├── Makefile ├── api_reference │ ├── basic.rst │ ├── containers.rst │ ├── core.rst │ ├── data.rst │ ├── devtools.rst │ ├── drawing.rst │ ├── index.rst │ ├── input.rst │ ├── layout.rst │ ├── nodes.rst │ ├── plots.rst │ ├── tables.rst │ ├── userwidgets.rst │ ├── windows.rst │ └── wrapper.rst ├── conf.py ├── example.png ├── index.rst ├── make.bat ├── requirements.txt └── widget_list.rst ├── pyproject.toml └── setup.cfg /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | # PyCharm 4 | 5 | /.idea/ 6 | 7 | # Sublime Text 8 | *.sublime-workspace 9 | 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | wheels/ 32 | pip-wheel-metadata/ 33 | share/python-wheels/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | MANIFEST 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .nox/ 53 | .coverage 54 | .coverage.* 55 | .cache 56 | nosetests.xml 57 | coverage.xml 58 | *.cover 59 | *.py,cover 60 | .hypothesis/ 61 | .pytest_cache/ 62 | 63 | # Translations 64 | *.mo 65 | *.pot 66 | 67 | # Django stuff: 68 | *.log 69 | local_settings.py 70 | db.sqlite3 71 | db.sqlite3-journal 72 | 73 | # Flask stuff: 74 | instance/ 75 | .webassets-cache 76 | 77 | # Scrapy stuff: 78 | .scrapy 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # PyBuilder 84 | target/ 85 | 86 | # Jupyter Notebook 87 | .ipynb_checkpoints 88 | 89 | # IPython 90 | profile_default/ 91 | ipython_config.py 92 | 93 | # pyenv 94 | .python-version 95 | 96 | # pipenv 97 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 98 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 99 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 100 | # install all needed dependencies. 101 | #Pipfile.lock 102 | 103 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 104 | __pypackages__/ 105 | 106 | # Celery stuff 107 | celerybeat-schedule 108 | celerybeat.pid 109 | 110 | # SageMath parsed files 111 | *.sage.py 112 | 113 | # Environments 114 | .env 115 | .venv 116 | env/ 117 | venv/ 118 | ENV/ 119 | env.bak/ 120 | venv.bak/ 121 | 122 | # Spyder project settings 123 | .spyderproject 124 | .spyproject 125 | 126 | # Rope project settings 127 | .ropeproject 128 | 129 | # mkdocs documentation 130 | /site 131 | 132 | # mypy 133 | .mypy_cache/ 134 | .dmypy.json 135 | dmypy.json 136 | 137 | # Pyre type checker 138 | .pyre/ 139 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | # Optionally build your docs in additional formats such as PDF 13 | formats: all 14 | 15 | # Optionally set the version of Python and requirements required to build your docs 16 | python: 17 | version: 3.8 18 | install: 19 | - requirements: docs/requirements.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Michael Daniel Werezak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DearPyGui-Obj 2 | An object-oriented interface for [Dear PyGui](https://github.com/hoffstadt/DearPyGui). 3 | 4 | *Dear PyGui* is an excellent Python GUI framework built on top of the [Dear ImGui](https://github.com/ocornut/imgui) immediate-mode lightweight graphical interface library for C++. Dear PyGui itself is mostly a C++/CPython library with a thin scripting layer as it's primary interface. 5 | 6 | This project aims to implement a pure-Python interface to Dear PyGui that takes full advantage of the Python language to provide a concise and ergonomic API. 7 | 8 | ## Documentation 9 | Documentation (on ReadTheDocs) can be found [here](https://dearpygui-obj.readthedocs.io/en/latest/index.html). 10 | 11 | ## Features and Examples 12 | DearPyGui-Obj makes using DPG more concise and intuitive by allowing you to get and set widget properties using attributes. Setting the callback for a 13 | widget is easy using the `callback` decorator. 14 | 15 | #### Basic Usage 16 | If you've used Dear PyGui, using the object library should be familiar. 17 | 18 | ``` python 19 | from dearpygui_obj import start_gui 20 | from dearpygui_obj import colors 21 | from dearpygui_obj.widgets import * 22 | 23 | with Window('Example'): 24 | text = Text('Edit me using the controls below!', color=colors.salmon) 25 | 26 | Separator() 27 | 28 | text_input = InputText('text content', text.value) 29 | text_color = ColorEdit('text color', text.color) 30 | 31 | @text_input.callback 32 | def callback(): 33 | text.value = text_input.value 34 | 35 | @text_color.callback 36 | def callback(): 37 | text.color = text_color.value 38 | 39 | start_gui() 40 | ``` 41 | 42 | #### Plots and Data Series 43 | 44 | ``` python 45 | from dearpygui_obj import start_gui 46 | from dearpygui_obj.widgets import * 47 | from dearpygui_obj.plots.dataseries import * 48 | 49 | with Window('Example') as win: 50 | data = [ (-1, -9), (1, -4), (3, 11), (4, 5), (9, 7) ] 51 | lineseries = LineSeries('example', data) 52 | 53 | ## plot data series support indexing and all other MutableSequence methods 54 | p1, p2 = lineseries[-2], lineseries[-1] 55 | print('slope:', (p2.y - p1.y)/(p2.x - p1.x)) # elements are named tuples 56 | lineseries.append((10, 2)) # but methods will accept any compatible sequence 57 | 58 | ## you can also access and modify data as individual 1D sequences, 59 | ## as long as the length does not change 60 | print(*lineseries.y[0:3]) # prints -9 -4 11 61 | lineseries.y[3] += 1 62 | lineseries.y[0:3] = (-4, 7, -2) 63 | lineseries.x[:] = [1, 2, 3, 4, 5, 6] 64 | #lineseries.x = [1, 2, 3] # TypeError: cannot change length of individual DataSeries field 65 | 66 | plot = Plot() 67 | plot.add_dataseries(lineseries) 68 | 69 | start_gui() 70 | ``` 71 | 72 | #### Manipulate Tables using Slices 73 | ``` python 74 | from dearpygui_obj import start_gui 75 | from dearpygui_obj.widgets import * 76 | 77 | with Window('Example') as win: 78 | table = Table(['col 1', 'col 2', 'col 3', 'col 4']) 79 | table[:, :] = [ 80 | ['a', 'b', 'c', 'd'], 81 | ['e', 'f', 'g', 'h'], 82 | ['i', 'j', 'k', 'l'], 83 | ['m', 'n', 'o', 'p'], 84 | ] 85 | 86 | btn = Button('Select Checkerboard') 87 | @btn.callback 88 | def callback(): 89 | table.selected[::2, ::2] = True 90 | table.selected[1::2, 1::2] = True 91 | 92 | start_gui() 93 | ``` 94 | 95 | #### Defining Custom Widgets 96 | ``` python 97 | from dataclasses import dataclass 98 | from dearpygui_obj import start_gui 99 | from dearpygui_obj.widgets import * 100 | 101 | @dataclass 102 | class Person: 103 | firstname: str 104 | lastname: str 105 | 106 | class PersonInfo(UserWidget): 107 | def __setup_content__(self, person: Person): 108 | Separator() 109 | with group_horizontal(): 110 | self.selected_chk = Checkbox() 111 | Button(arrow=ButtonArrow.Up, callback=self.move_up) 112 | Button(arrow=ButtonArrow.Down, callback=self.move_down) 113 | Text(f'First name: {person.firstname}') 114 | Text(f'Last name: {person.lastname}') 115 | 116 | @property 117 | def selected(self) -> bool: 118 | return self.selected_chk.value 119 | 120 | with Window('Person List Example'): 121 | with Group() as container: 122 | pass 123 | 124 | Separator() 125 | 126 | remove_btn = Button('Remove Selected') 127 | add_btn = Button('Add Person') 128 | fname_inp = InputText('First name') 129 | lname_inp = InputText('Last name') 130 | 131 | @remove_btn.callback 132 | def callback(): 133 | for child in container.iter_children(): 134 | if child.selected: 135 | child.delete() 136 | 137 | @add_btn.callback 138 | def callback(): 139 | person = Person(fname_inp.value, lname_inp.value) 140 | PersonInfo.add_to(container, person) 141 | fname_inp.value = '' 142 | lname_inp.value = '' 143 | 144 | start_gui() 145 | ``` 146 | 147 | #### Drawing API 148 | This is the same dynamic drawing example given in the DPG Wiki. You can compare 149 | it with the [original code](https://github.com/hoffstadt/DearPyGui/wiki/Drawing-API#modification). 150 | 151 | ``` python 152 | from dearpygui_obj import start_gui 153 | from dearpygui_obj import colors 154 | from dearpygui_obj.widgets import * 155 | 156 | counter = 0 157 | modifier = 2 158 | 159 | with Window("Tutorial", size=(800, 800)): 160 | canvas = Drawing(size=(700, 700)) 161 | circle = canvas.draw_circle((0, 0), 5, colors.from_rgba8(255, 255, 255)) 162 | 163 | @dearpygui_obj.set_render_callback 164 | def on_render(): 165 | global counter, modifier 166 | 167 | counter += 1 168 | if counter < 300: 169 | modifier += 1 170 | elif counter < 600: 171 | modifier -= 1 172 | else: 173 | counter = 0 174 | modifier = 2 175 | 176 | circle.center = (15 + modifier*1.25, 15 + modifier*1.25) 177 | circle.color = colors.from_rgba8( 178 | 255 - modifier*.8, 255 - modifier*.8, 255 - modifier*.3, 179 | ) 180 | circle.radius = 15 + modifier/2 181 | circle.segments = round(35-modifier/10) 182 | 183 | start_gui() 184 | ``` 185 | 186 | #### Other Features 187 | ``` python 188 | from dearpygui_obj import start_gui 189 | from dearpygui_obj import colors 190 | from dearpygui_obj.widgets import * 191 | 192 | with Window('Example') as win: 193 | ## See what config properties a widget has in the REPL 194 | Button.get_config_properties() # Returns ['arrow', 'enabled', 'height', ...] 195 | 196 | ## There are many small ergonomic improvements to the API of various widgets 197 | ## For example, setting arrow buttons is just an Enum instead of two 198 | ## separate properties 199 | btn = Button(arrow=ButtonArrow.Right) 200 | 201 | @btn.callback 202 | def callback(): 203 | if btn.arrow: 204 | btn.arrow = None 205 | btn.label = 'Not an arrow button anymore!' 206 | 207 | ## Colors 208 | red = Text('This text is red.', color=colors.red) # preset HTML colors 209 | green = Text('This text is green.', color=colors.from_hex('#00FF00')) 210 | 211 | ## Radio buttons, combo boxes, and list widgets are mutable sequences 212 | radio = RadioButtons(['Apple', 'Orange']) 213 | radio[0] = 'Banana' 214 | radio.remove('Orange') 215 | radio.extend(['Pear', 'Grape']) 216 | del radio[-1] 217 | 218 | ## You can add widgets after creating the GUI using methods instead of keywords 219 | add_text = Button.add_to(win, 'Add Label') # add to the end of a container 220 | 221 | @add_text.callback 222 | def callback(): 223 | Text.insert_before(add_text, 'Insert before.') # insert before a widget 224 | 225 | start_gui() 226 | ``` 227 | 228 | #### Using DearPyGui-Obj With Existing Dear PyGui Code 229 | DearPyGui-Obj aims to be fully *backwards compatible* with Dear PyGui. This means that you can freely mix code that uses both DearPyGui and DearPyGui-Obj without issues. Wherever possible, widget classes are designed to draw all of their state from DPG so that there is no possibility of invalidation. You can even create object instances for existing widgets that were created in vanilla DearPyGui. 230 | 231 | ## Installation 232 | This project is currently in the early implementation stage, and a lot of features still need to be implemented. Even the current name for the project is provisional and may change. 233 | 234 | **Requirements** 235 | - Python 3.8 64-bit 236 | - dearpygui 0.6.x 237 | 238 | You can install from [TestPyPI](https://test.pypi.org/project/dearpygui-obj/): 239 | ``` 240 | pip install -i https://test.pypi.org/simple/ dearpygui-obj 241 | ``` 242 | 243 | Or you can simply copy the `dearpygui_obj` package somewhere where Python can find it. 244 | DearPyGui-Obj will be available on PyPI proper once it has reached a fuller level of feature-completeness. 245 | 246 | ## License 247 | 248 | *DearPyGui-Obj* is licensed under the [MIT License](https://github.com/mwerezak/DearPyGui-Obj/blob/master/LICENSE). 249 | -------------------------------------------------------------------------------- /dearpygui_obj.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "." 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /dearpygui_obj/__init__.py: -------------------------------------------------------------------------------- 1 | """An object-oriented Wrapper around DearPyGui 0.6""" 2 | 3 | from __future__ import annotations 4 | 5 | from warnings import warn 6 | from inspect import signature, Parameter 7 | from typing import TYPE_CHECKING 8 | 9 | from dearpygui import dearpygui as dpgcore 10 | 11 | if TYPE_CHECKING: 12 | from typing import Dict, Iterable, Optional, Callable, Any, Union 13 | from dearpygui_obj.wrapper.widget import Widget 14 | from dearpygui_obj.window import Window 15 | 16 | ## Type Aliases 17 | PyGuiCallback = Union[ 18 | # accept any of these signatures for callbacks 19 | Callable[[Widget, Any, Any], None], 20 | Callable[[Widget, Any], None], 21 | Callable[[Widget], None], 22 | Callable[[], None], 23 | ] 24 | 25 | # signature used by DPG 26 | _DPGCallback = Callable[[int, Any, Any], None] 27 | 28 | # DearPyGui's widget ID scope is global, so I guess it's okay that this is too. 29 | _ITEM_LOOKUP: Dict[int, Widget] = {} 30 | 31 | # Used to construct the correct type when getting an item 32 | # that was created outside the object wrapper library 33 | _ITEM_TYPES: Dict[str, Callable[..., Widget]] = {} 34 | 35 | # Fallback constructor used when getting a type that isn't registered in _ITEM_TYPES 36 | _default_ctor: Optional[Callable[..., Widget]] = None 37 | 38 | 39 | def get_item_by_id(widget_id: int) -> Widget: 40 | """Retrieve an item using its unique name. 41 | 42 | If the item was created by instantiating a :class:`.Widget` object, this will return that 43 | object. Otherwise, a new wrapper object will be created for that item and returned. Future calls 44 | for the same ID will return the same object. 45 | 46 | Raises: 47 | KeyError: if name refers to an item that is invalid (deleted) or does not exist. 48 | """ 49 | if not dpgcore.does_item_exist(widget_id): 50 | raise KeyError(f"widget with id={widget_id} does not exist") 51 | 52 | item = _ITEM_LOOKUP.get(widget_id) 53 | if item is not None: 54 | return item 55 | 56 | item_type = dpgcore.get_item_type(widget_id) ## WARNING: this will segfault if name does not exist 57 | return _create_item_wrapper(widget_id, item_type) 58 | 59 | def try_get_item_by_id(widget_id: int) -> Optional[Widget]: 60 | """Retrieve an item using its unique name or ``None``. 61 | 62 | Similar to :func:`.get_item_by_id`, but returns ``None`` if the wrapper object could not be retrieved.""" 63 | if not dpgcore.does_item_exist(widget_id): 64 | return None 65 | 66 | item = _ITEM_LOOKUP.get(widget_id) 67 | if item is not None: 68 | return item 69 | 70 | item_type = dpgcore.get_item_type(widget_id) ## WARNING: this will segfault if name does not exist 71 | return _create_item_wrapper(widget_id, item_type) 72 | 73 | def _create_item_wrapper(widget_id: int, item_type: str) -> Widget: 74 | ctor = _ITEM_TYPES.get(item_type, _default_ctor) 75 | if ctor is None: 76 | raise ValueError(f"could not create wrapper for widget with id={widget_id}: no constructor for item type '{item_type}'") 77 | return ctor(id=widget_id) 78 | 79 | def iter_all_items() -> Iterable[Widget]: 80 | """Iterate all items and yield their wrapper objects.""" 81 | for widget_id in dpgcore.get_all_items(): 82 | yield get_item_by_id(widget_id) 83 | 84 | def iter_all_windows() -> Iterable[Widget]: 85 | """Iterate all windows and yield their wrapper objects.""" 86 | for window_id in dpgcore.get_windows(): 87 | yield get_item_by_id(window_id) 88 | 89 | def get_active_window() -> Widget: 90 | """Get the active window.""" 91 | active_id = dpgcore.get_active_window() 92 | return get_item_by_id(active_id) 93 | 94 | def _register_item(instance: Widget) -> None: 95 | item_id = instance.id 96 | if item_id in _ITEM_LOOKUP: 97 | warn(f"item with id='{item_id}' already exists in global item registry, overwriting") 98 | _ITEM_LOOKUP[item_id] = instance 99 | 100 | def _unregister_item(widget_id: int, unregister_children: bool = True) -> None: 101 | _ITEM_LOOKUP.pop(widget_id, None) 102 | if unregister_children: 103 | children = dpgcore.get_item_children(widget_id) 104 | if children is not None: 105 | for child_id in children: 106 | _unregister_item(child_id, True) 107 | 108 | def _register_item_type(item_type: str) -> Callable: 109 | """Associate a :class:`.Widget` class or constructor with a DearPyGui item type. 110 | 111 | This will let :func:`.get_item_by_id` know what constructor to use when getting 112 | an item that was not created by the object library.""" 113 | def decorator(ctor: Callable[..., Widget]): 114 | if item_type in _ITEM_TYPES: 115 | raise ValueError(f"'{item_type}' is already registered to {_ITEM_TYPES[item_type]!r}") 116 | _ITEM_TYPES[item_type] = ctor 117 | return ctor 118 | return decorator 119 | 120 | def _set_default_ctor(default_ctor: Callable[..., Widget]) -> None: 121 | global _default_ctor 122 | if _default_ctor is not None: 123 | raise ValueError(f"default ctor is already registered to {_default_ctor!r}") 124 | _default_ctor = default_ctor 125 | 126 | _IDGEN_SEQ = 0 127 | def _generate_id(o: Any) -> str: 128 | global _IDGEN_SEQ 129 | 130 | clsname = o.__class__.__name__ 131 | while dpgcore.does_item_exist(name := clsname + '##' + str(_IDGEN_SEQ)): 132 | _IDGEN_SEQ += 1 133 | _IDGEN_SEQ += 1 134 | return name 135 | 136 | ## Start/Stop DearPyGui 137 | 138 | def start_gui(*, primary_window: Window = None) -> None: 139 | """Start the GUI engine and show the main window.""" 140 | if primary_window is not None: 141 | dpgcore.start_dearpygui(primary_window=primary_window.id) 142 | else: 143 | dpgcore.start_dearpygui() 144 | 145 | def stop_gui() -> None: 146 | """Stop the GUI engine and exit the main window.""" 147 | dpgcore.stop_dearpygui() 148 | 149 | def is_running() -> bool: 150 | """Get the status of the GUI engine.""" 151 | return dpgcore.is_dearpygui_running() 152 | 153 | def set_start_callback(callback: Callable) -> None: 154 | """Fires when the main window is started.""" 155 | dpgcore.set_start_callback(callback) # not wrapped because sender will not be a widget anyways 156 | 157 | def set_exit_callback(callback: Callable) -> None: 158 | """Fires when the main window is exited.""" 159 | dpgcore.set_exit_callback(callback) # not wrapped because sender will not be a widget anyways 160 | 161 | def set_render_callback(callback: Callable) -> None: 162 | """Fires after rendering each frame.""" 163 | dpgcore.set_render_callback(callback) # not wrapped because sender will not be a widget anyways 164 | 165 | def get_delta_time() -> float: 166 | """Get the time elapsed since the last frame.""" 167 | return dpgcore.get_delta_time() 168 | 169 | def get_total_time() -> float: 170 | """Get the time elapsed since the application started.""" 171 | return dpgcore.get_total_time() 172 | 173 | def enable_vsync(enabled: bool) -> None: 174 | """Enable or disable vsync""" 175 | return dpgcore.set_vsync(enabled) 176 | 177 | ## Value Storage System 178 | 179 | def create_value(init_value: Any) -> DataValue: 180 | """Create a data value 181 | 182 | This can be handy if you need to refer to a value before the widgets that supply the value 183 | have been added. For example: 184 | 185 | .. code-block:: python 186 | 187 | linked_text = create_value('') 188 | with Window('Data Example'): 189 | ## using created value 190 | TextInput('Text1', data_source = linked_text) 191 | TextInput('Text2', data_source = linked_text) 192 | 193 | ## directly assign a widget as data source 194 | text3 = TextInput('Text3', data_source = linked_text) 195 | TextInput('Text4', data_source = text3) 196 | 197 | """ 198 | proxy = DataValue(None) 199 | proxy.id = _generate_id(proxy) 200 | dpgcore.add_value(proxy.id, init_value) 201 | return proxy 202 | 203 | 204 | class DataValue: 205 | """Proxy object for working with Dear PyGui's Value Storage System""" 206 | 207 | id: str 208 | 209 | def __init__(self, data_source: Any): 210 | self.id = str(data_source) 211 | 212 | def __repr__(self) -> str: 213 | return f'<{self.__class__.__qualname__}({self.id!r})>' 214 | 215 | def __str__(self) -> str: 216 | return self.id 217 | 218 | @property 219 | def value(self) -> Any: 220 | value = dpgcore.get_value(self.id) 221 | # need to return an immutable value since modifying the list wont actually change the value in DPG 222 | # if isinstance(value, list): 223 | # value = tuple(value) 224 | return value 225 | 226 | @value.setter 227 | def value(self, value: Any) -> None: 228 | # if isinstance(value, Sequence): 229 | # value = list(value) # DPG only accepts lists for sequence values 230 | dpgcore.set_value(self.id, value) 231 | 232 | ## Callbacks 233 | 234 | def wrap_callback(callback: PyGuiCallback) -> _DPGCallback: 235 | """Wraps callbacks that expect ``sender`` to be an object. 236 | 237 | DPG expects callbacks' sender argument to take the sender ID as a string. 238 | However it is convenient to write callbacks where the sender is an object. 239 | 240 | This can be used to wrap such callbacks, ensuring that the ID is resolved 241 | into an object before the wrapped callback is invoked. 242 | 243 | Note: 244 | DearPyGui-Obj will typically wrap callbacks for you so this function 245 | should only be needed if you are calling DPG functions yourself directly. 246 | """ 247 | 248 | ## This is a workaround for the fact that DPG cannot use Callables as callbacks. 249 | wrapper_obj = CallbackWrapper(callback) 250 | def invoke_wrapper(sender, data): 251 | wrapper_obj(sender, data) 252 | invoke_wrapper.wrapped = wrapper_obj.wrapped 253 | return invoke_wrapper 254 | 255 | def unwrap_callback(callback: Callable) -> Callable: 256 | """If the callback was wrapped with :func:`.wrap_callback`, this will unwrap it. 257 | 258 | Otherwise, the callback will just be returned unchanged. 259 | """ 260 | return getattr(callback, 'wrapped', callback) 261 | 262 | class CallbackWrapper: 263 | """Wraps callbacks that expect ``sender`` to be an object. 264 | 265 | DPG expects callbacks' sender argument to take the sender ID as a string. 266 | However it is convenient to write callbacks where the sender is an object. 267 | 268 | This can be used to wrap such callbacks, ensuring that the ID is resolved 269 | into an object before the wrapped callback is invoked. 270 | 271 | Parameters: 272 | callback: The callback to wrap. 273 | """ 274 | 275 | def __init__(self, callback: PyGuiCallback): 276 | self.wrapped = callback 277 | 278 | positional = (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD) 279 | sig = signature(callback) 280 | arg_count = sum(1 for param in sig.parameters.values() if param.kind in positional) 281 | if arg_count == 0: 282 | self._invoke = self._call_noargs 283 | elif arg_count == 1: 284 | self._invoke = self._call_sender_only 285 | else: 286 | self._invoke = self._call_sender_data 287 | 288 | def __call__(self, sender: str, data: Any) -> None: 289 | """Invoke the callback. 290 | 291 | Parameters: 292 | sender: The sender, typically given by DPG. 293 | data: The callback data, typically given by DPG. 294 | """ 295 | self._invoke(sender, data) 296 | 297 | @staticmethod 298 | def _resolve_sender(sender: str) -> Any: 299 | if dpgcore.does_item_exist(sender): 300 | if sender in _ITEM_LOOKUP: 301 | return _ITEM_LOOKUP[sender] 302 | 303 | # warning, this will segfault if sender does not exist! 304 | sender_type = dpgcore.get_item_type(sender) 305 | return _create_item_wrapper(sender, sender_type) 306 | 307 | def _call_sender_data(self, sender: Any, data: Any) -> None: 308 | self.wrapped(self._resolve_sender(sender), data) 309 | 310 | def _call_sender_only(self, sender: Any, data: Any) -> None: 311 | self.wrapped(self._resolve_sender(sender)) 312 | 313 | def _call_noargs(self, sender: Any, data: Any) -> None: 314 | self.wrapped() # no need to resolve sender either! 315 | 316 | __all__ = [ 317 | 'get_item_by_id', 318 | 'try_get_item_by_id', 319 | 'iter_all_items', 320 | 'iter_all_windows', 321 | 'get_active_window', 322 | 'start_gui', 323 | 'stop_gui', 324 | 'is_running', 325 | 'set_start_callback', 326 | 'set_exit_callback', 327 | 'set_render_callback', 328 | 'get_delta_time', 329 | 'get_total_time', 330 | 'enable_vsync', 331 | 'create_value', 332 | 'DataValue', 333 | 'wrap_callback', 334 | 'unwrap_callback', 335 | 'CallbackWrapper', 336 | ] -------------------------------------------------------------------------------- /dearpygui_obj/basic.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from warnings import warn 4 | from enum import Enum 5 | from typing import TYPE_CHECKING, Sequence, MutableSequence, overload 6 | 7 | from dearpygui import dearpygui as dpgcore 8 | from dearpygui_obj import _register_item_type 9 | from dearpygui_obj.data import ColorRGBA, ConfigPropertyColorRGBA 10 | from dearpygui_obj.wrapper.widget import Widget, ItemWidgetMx, ValueWidgetMx, ConfigProperty 11 | 12 | if TYPE_CHECKING: 13 | from typing import Optional, Iterable, Sequence, List 14 | from dearpygui_obj.wrapper.widget import ItemConfigData 15 | 16 | ## Basic Content 17 | 18 | @_register_item_type('mvAppItemType::Text') 19 | class Text(Widget, ItemWidgetMx, ValueWidgetMx[str]): 20 | """A basic element that displays some text.""" 21 | 22 | value: str #: The text to display. 23 | 24 | #: Wrap after this many characters. Set to -1 to disable. 25 | wrap: int = ConfigProperty() 26 | 27 | #: Display a bullet point with the text. 28 | bullet: bool = ConfigProperty() 29 | 30 | color: ColorRGBA = ConfigPropertyColorRGBA() 31 | 32 | def __init__(self, value: str = '', **config): 33 | super().__init__(default_value=value, **config) 34 | 35 | def __setup_add_widget__(self, dpg_args) -> None: 36 | dpgcore.add_text(self.id, **dpg_args) 37 | 38 | 39 | @_register_item_type('mvAppItemType::LabelText') 40 | class LabelText(Widget, ItemWidgetMx, ValueWidgetMx[str]): 41 | """Display text with a label. 42 | 43 | Useful for output values when used with a :attr:`~.Widget.data_source`. 44 | The text is linked to the data source, while the label remains unchanged.""" 45 | 46 | value: str #: The text to display (separate from the :attr:`label`). 47 | 48 | label: str = ConfigProperty() 49 | color: ColorRGBA = ConfigPropertyColorRGBA() 50 | 51 | def __init__(self, label: str = None, value: str = '', **config): 52 | super().__init__(label=label, default_value=value, **config) 53 | 54 | def __setup_add_widget__(self, dpg_args) -> None: 55 | dpgcore.add_label_text(self.id, **dpg_args) 56 | 57 | 58 | @_register_item_type('mvAppItemType::Separator') 59 | class Separator(Widget, ItemWidgetMx): 60 | """Adds a horizontal line.""" 61 | def __init__(self, **config): 62 | super().__init__(**config) 63 | 64 | def __setup_add_widget__(self, dpg_args) -> None: 65 | dpgcore.add_separator(name=self.id, **dpg_args) 66 | 67 | ## Buttons 68 | 69 | class ButtonArrow(Enum): 70 | """Specifies direction for arrow buttons.""" 71 | Left = 0 72 | Right = 1 73 | Up = 2 74 | Down = 3 75 | 76 | @_register_item_type('mvAppItemType::Button') 77 | class Button(Widget, ItemWidgetMx): 78 | """A simple button.""" 79 | 80 | label: str = ConfigProperty() 81 | 82 | #: If ``True``, makes the button a small button. Useful for embedding in text. 83 | small: bool = ConfigProperty() 84 | 85 | arrow: Optional[ButtonArrow] 86 | @ConfigProperty() 87 | def arrow(self) -> Optional[ButtonArrow]: 88 | """Configure the button as an arrow button. 89 | 90 | If the button is an arrow button, the value will be the arrow direction. 91 | Otherwise the value will be ``None``. 92 | 93 | Assigning to this property will enable/disable the arrow and/or set the direction.""" 94 | config = self.get_config() 95 | if not config['arrow']: 96 | return None 97 | return ButtonArrow(config['direction']) 98 | 99 | @arrow.getconfig 100 | def arrow(self, adir: Optional[ButtonArrow]): 101 | if adir is None: 102 | return {'arrow': False} 103 | return {'arrow': True, 'direction': adir.value} 104 | 105 | def __init__(self, label: str = None, **config): 106 | super().__init__(label=label, **config) 107 | 108 | def __setup_add_widget__(self, dpg_args) -> None: 109 | dpgcore.add_button(self.id, **dpg_args) 110 | 111 | 112 | @_register_item_type('mvAppItemType::Checkbox') 113 | class Checkbox(Widget, ItemWidgetMx, ValueWidgetMx[bool]): 114 | """Simple checkbox widget.""" 115 | 116 | value: bool #: ``True`` if the checkbox is checked, otherwise ``False``. 117 | 118 | label: str = ConfigProperty() 119 | 120 | def __init__(self, label: str = None, value: bool = False, **config): 121 | super().__init__(label=label, default_value=value, **config) 122 | 123 | def __setup_add_widget__(self, dpg_args) -> None: 124 | dpgcore.add_checkbox(self.id, **dpg_args) 125 | 126 | @_register_item_type('mvAppItemType::Selectable') 127 | class Selectable(Widget, ItemWidgetMx, ValueWidgetMx[bool]): 128 | """Text that can be selected, functionally similar to a checkbox.""" 129 | 130 | value: bool #: ``True`` if the item is selected, otherwise ``False``. 131 | 132 | label: str = ConfigProperty() 133 | span_columns: bool = ConfigProperty() 134 | 135 | def __init__(self, label: str = None, value: bool = False, **config): 136 | super().__init__(label=label, default_value=value, **config) 137 | 138 | def __setup_add_widget__(self, dpg_args) -> None: 139 | dpgcore.add_selectable(self.id, **dpg_args) 140 | 141 | 142 | 143 | @_register_item_type('mvAppItemType::RadioButtons') 144 | class RadioButtons(Widget, ItemWidgetMx, ValueWidgetMx[int], MutableSequence[str]): 145 | """A set of radio buttons. 146 | 147 | This widget can be used as a mutable sequence of labels. Changing the sequence will 148 | change the radio buttons in the group and their labels.""" 149 | 150 | value: int #: The **index** of the selected item. 151 | 152 | horizontal: bool = ConfigProperty() 153 | 154 | items: Sequence[str] 155 | @ConfigProperty() 156 | def items(self) -> Sequence[str]: 157 | """Get or set this widget's items as a sequence.""" 158 | return tuple(self._get_items()) 159 | 160 | @items.getconfig 161 | def items(self, items: Sequence[str]): 162 | return {'items':list(items)} 163 | 164 | def __init__(self, items: Iterable[str], value: int = 0, **config): 165 | super().__init__(items=items, default_value=value, **config) 166 | 167 | def __setup_add_widget__(self, dpg_args) -> None: 168 | dpgcore.add_radio_button(self.id, **dpg_args) 169 | 170 | def _get_items(self) -> List[str]: 171 | return self.get_config()['items'] 172 | 173 | def __len__(self) -> int: 174 | return len(self._get_items()) 175 | 176 | @overload 177 | def __getitem__(self, idx: int) -> str: ... 178 | 179 | @overload 180 | def __getitem__(self, idx: slice) -> Iterable[str]: ... 181 | 182 | def __getitem__(self, idx): 183 | return self._get_items()[idx] 184 | 185 | @overload 186 | def __setitem__(self, idx: int, label: str) -> None: ... 187 | 188 | @overload 189 | def __setitem__(self, idx: slice, label: Iterable[str]) -> None: ... 190 | 191 | def __setitem__(self, idx, label) -> None: 192 | items = self._get_items() 193 | items[idx] = label 194 | self.set_config(items=items) 195 | 196 | @overload 197 | def __delitem__(self, idx: int) -> None: ... 198 | 199 | @overload 200 | def __delitem__(self, idx: slice) -> None: ... 201 | 202 | def __delitem__(self, idx): 203 | items = self._get_items() 204 | del items[idx] 205 | self.set_config(items=items) 206 | 207 | def insert(self, idx: int, label: str) -> None: 208 | items = self._get_items() 209 | items.insert(idx, label) 210 | self.set_config(items=items) 211 | 212 | 213 | class ComboHeightMode(Enum): 214 | """Specifies the height of a combo box.""" 215 | Small = 'height_small' #: Max ~4 items visible. 216 | Regular = 'height_regular' #: Max ~8 items visible. 217 | Large = 'height_large' #: Max ~20 items visible. 218 | Largest = 'height_largest' #: As many items visible as possible. 219 | 220 | @_register_item_type('mvAppItemType::Combo') 221 | class Combo(Widget, ItemWidgetMx, ValueWidgetMx[str], MutableSequence[str]): 222 | """A combo box (drop down). 223 | 224 | Unlike :class:`.RadioButtons`, the :attr:`value` of a Combo is one of the item strings, 225 | not the index. 226 | 227 | Unless specified, none of the items are initially selected and :attr:`value` is an empty string. 228 | """ 229 | value: str #: The string **value** of the selected item. 230 | 231 | label: str = ConfigProperty() 232 | popup_align_left: bool = ConfigProperty() 233 | no_arrow_button: bool = ConfigProperty() #: Don't display the arrow button. 234 | no_preview: bool = ConfigProperty() #: Don't display the preview box showing the selected item. 235 | 236 | items: Sequence[str] 237 | @ConfigProperty() 238 | def items(self) -> Sequence[str]: 239 | """Get or set this widget's items as a sequence.""" 240 | return tuple(self._get_items()) 241 | 242 | @items.getconfig 243 | def items(self, items: Sequence[str]): 244 | return {'items':list(items)} 245 | 246 | height_mode: ComboHeightMode 247 | @ConfigProperty(key='height') 248 | def height_mode(self) -> ComboHeightMode: 249 | config = self.get_config() 250 | for mode in ComboHeightMode: 251 | if config.get(mode.value): 252 | return mode 253 | warn('could not determine height_mode') 254 | return ComboHeightMode.Regular # its supposedly the default? 255 | 256 | @height_mode.getconfig 257 | def height_mode(self, value: ComboHeightMode) -> ItemConfigData: 258 | return { 259 | mode.value : (mode == value) for mode in ComboHeightMode 260 | } 261 | 262 | def __init__(self, label: str = None, items: Iterable[str] = (), value: str = '', **config): 263 | super().__init__(label=label, items=items, default_value=value, **config) 264 | 265 | def __setup_add_widget__(self, dpg_args) -> None: 266 | dpgcore.add_combo(self.id, **dpg_args) 267 | 268 | def _get_items(self) -> List[str]: 269 | return self.get_config()['items'] 270 | 271 | def __len__(self) -> int: 272 | return len(self._get_items()) 273 | 274 | @overload 275 | def __getitem__(self, idx: int) -> str: ... 276 | 277 | @overload 278 | def __getitem__(self, idx: slice) -> Iterable[str]: ... 279 | 280 | def __getitem__(self, idx): 281 | return self._get_items()[idx] 282 | 283 | @overload 284 | def __setitem__(self, idx: int, label: str) -> None: ... 285 | 286 | @overload 287 | def __setitem__(self, idx: slice, label: Iterable[str]) -> None: ... 288 | 289 | def __setitem__(self, idx, label): 290 | items = self._get_items() 291 | items[idx] = label 292 | self.set_config(items=items) 293 | 294 | @overload 295 | def __delitem__(self, idx: int) -> None: ... 296 | 297 | @overload 298 | def __delitem__(self, idx: slice) -> None: ... 299 | 300 | def __delitem__(self, idx): 301 | items = self._get_items() 302 | del items[idx] 303 | self.set_config(items=items) 304 | 305 | def insert(self, idx: int, label: str) -> None: 306 | items = self._get_items() 307 | items.insert(idx, label) 308 | self.set_config(items=items) 309 | 310 | @_register_item_type('mvAppItemType::Listbox') 311 | class ListBox(Widget, ItemWidgetMx, ValueWidgetMx[int], MutableSequence[str]): 312 | """A scrollable box containing a selection of items.""" 313 | 314 | value: int #: The **index** of the selected item. 315 | 316 | label: str = ConfigProperty() 317 | num_visible: int = ConfigProperty(key='num_items') #: The number of items to show. 318 | 319 | items: Sequence[str] 320 | @ConfigProperty() 321 | def items(self) -> Sequence[str]: 322 | """Get or set this widget's items as a sequence.""" 323 | return tuple(self._get_items()) 324 | 325 | @items.getconfig 326 | def items(self, items: Sequence[str]): 327 | return {'items':list(items)} 328 | 329 | def __init__(self, label: str = None, items: Iterable[str] = (), value: int = 0, **config): 330 | super().__init__(label=label, items=items, default_value=value, **config) 331 | 332 | def __setup_add_widget__(self, dpg_args) -> None: 333 | dpgcore.add_listbox(self.id, **dpg_args) 334 | 335 | def _get_items(self) -> List[str]: 336 | return self.get_config()['items'] 337 | 338 | def __len__(self) -> int: 339 | return len(self._get_items()) 340 | 341 | @overload 342 | def __getitem__(self, idx: int) -> str: ... 343 | 344 | @overload 345 | def __getitem__(self, idx: slice) -> Iterable[str]: ... 346 | 347 | def __getitem__(self, idx): 348 | return self._get_items()[idx] 349 | 350 | @overload 351 | def __setitem__(self, idx: int, label: str) -> None: ... 352 | 353 | @overload 354 | def __setitem__(self, idx: slice, label: Iterable[str]) -> None: ... 355 | 356 | def __setitem__(self, idx, label): 357 | items = self._get_items() 358 | items[idx] = label 359 | self.set_config(items=items) 360 | 361 | @overload 362 | def __delitem__(self, idx: int) -> None: ... 363 | 364 | @overload 365 | def __delitem__(self, idx: slice) -> None: ... 366 | 367 | def __delitem__(self, idx): 368 | items = self._get_items() 369 | del items[idx] 370 | self.set_config(items=items) 371 | 372 | def insert(self, idx: int, label: str) -> None: 373 | items = self._get_items() 374 | items.insert(idx, label) 375 | self.set_config(items=items) 376 | 377 | 378 | @_register_item_type('mvAppItemType::ProgressBar') 379 | class ProgressBar(Widget, ItemWidgetMx, ValueWidgetMx[float]): 380 | """A progress bar.""" 381 | 382 | value: float #: The progress to display, between ``0.0`` and ``1.0``. 383 | 384 | overlay_text: str = ConfigProperty(key='overlay') #: Overlayed text. 385 | 386 | def __init__(self, value: float = 0.0, **config): 387 | super().__init__(default_value=value, **config) 388 | 389 | def __setup_add_widget__(self, dpg_args) -> None: 390 | dpgcore.add_progress_bar(self.id, **dpg_args) 391 | 392 | 393 | @_register_item_type('mvAppItemType::SimplePlot') 394 | class SimplePlot(Widget, ItemWidgetMx, ValueWidgetMx[Sequence[float]]): 395 | """A simple plot to visualize a sequence of float values.""" 396 | 397 | label: str = ConfigProperty() 398 | 399 | #: Overlays text (similar to a plot title). 400 | title: str = ConfigProperty(key='overlay') 401 | 402 | minscale: float = ConfigProperty() 403 | maxscale: float = ConfigProperty() 404 | histogram: bool = ConfigProperty() 405 | 406 | def __init__(self, label: str = None, **config): 407 | super().__init__(label=label, **config) 408 | 409 | def __setup_add_widget__(self, dpg_args) -> None: 410 | dpgcore.add_simple_plot(self.id, **dpg_args) 411 | 412 | 413 | __all__ = [ 414 | 'Text', 415 | 'LabelText', 416 | 'Separator', 417 | 'ButtonArrow', 418 | 'Button', 419 | 'Checkbox', 420 | 'Selectable', 421 | 'RadioButtons', 422 | 'ComboHeightMode', 423 | 'Combo', 424 | 'ListBox', 425 | 'ProgressBar', 426 | 'SimplePlot', 427 | ] 428 | -------------------------------------------------------------------------------- /dearpygui_obj/colors.py: -------------------------------------------------------------------------------- 1 | """Predefined color values. 2 | 3 | Source: `Wikipedia `_""" 4 | 5 | from __future__ import annotations 6 | 7 | from typing import TYPE_CHECKING 8 | 9 | if TYPE_CHECKING: 10 | pass 11 | 12 | from dearpygui_obj.data import ( 13 | color_from_float as from_float, 14 | color_from_rgba8 as from_rgba8, 15 | color_from_hex as from_hex, 16 | ) 17 | 18 | ## Red Colors 19 | dark_red = from_hex('#8B0000') 20 | red = from_hex('#FF0000') 21 | firebrick = from_hex('#B22222') 22 | crimson = from_hex('#DC143C') 23 | indian_red = from_hex('#CD5C5C') 24 | light_coral = from_hex('#F08080') 25 | salmon = from_hex('#FA8072') 26 | dark_salmon = from_hex('#E9967A') 27 | light_salmon = from_hex('#FFA07A') 28 | 29 | ## Orange Colors 30 | orange_red = from_hex('#FF4500') 31 | tomato = from_hex('#FF6347') 32 | dark_orange = from_hex('#FF8C00') 33 | coral = from_hex('#FF7F50') 34 | orange = from_hex('#FFA500') 35 | 36 | ## Yellow Colors 37 | dark_khaki = from_hex('#BDB76B') 38 | gold = from_hex('#FFD700') 39 | khaki = from_hex('#F0E68C') 40 | peach_puff = from_hex('#FFDAB9') 41 | yellow = from_hex('#FFFF00') 42 | pale_goldenrod = from_hex('#EEE8AA') 43 | moccasin = from_hex('#FFE4B5') 44 | papaya_whip = from_hex('#FFEFD5') 45 | light_goldenrod_yellow = from_hex('#FAFAD2') 46 | lemon_chiffon = from_hex('#FFFACD') 47 | light_yellow = from_hex('#FFFFE0') 48 | 49 | ## Brown Colors 50 | maroon = from_hex('#800000') 51 | brown = from_hex('#A52A2A') 52 | saddle_brown = from_hex('#8B4513') 53 | sienna = from_hex('#A0522D') 54 | chocolate = from_hex('#D2691E') 55 | dark_goldenrod = from_hex('#B8860B') 56 | peru = from_hex('#CD853F') 57 | rosy_brown = from_hex('#BC8F8F') 58 | goldenrod = from_hex('#DAA520') 59 | sandy_brown = from_hex('#F4A460') 60 | tan = from_hex('#D2B48C') 61 | burlywood = from_hex('#DEB887') 62 | wheat = from_hex('#F5DEB3') 63 | navajo_white = from_hex('#FFDEAD') 64 | bisque = from_hex('#FFE4C4') 65 | blanched_almond = from_hex('#FFEBCD') 66 | cornsilk = from_hex('#FFF8DC') 67 | 68 | ## Green Colors 69 | dark_green = from_hex('#006400') 70 | green = from_hex('#008000') 71 | dark_olive_green = from_hex('#556B2F') 72 | forest_green = from_hex('#228B22') 73 | sea_green = from_hex('#2E8B57') 74 | olive = from_hex('#808000') 75 | olive_drab = from_hex('#6B8E23') 76 | medium_sea_green = from_hex('#3CB371') 77 | lime_green = from_hex('#32CD32') 78 | lime = from_hex('#00FF00') 79 | spring_green = from_hex('#00FF7F') 80 | medium_spring_green = from_hex('#00FA9A') 81 | dark_sea_green = from_hex('#8FBC8F') 82 | medium_aquamarine = from_hex('#66CDAA') 83 | yellow_green = from_hex('#9ACD32') 84 | lawn_green = from_hex('#7CFC00') 85 | chartreuse = from_hex('#7FFF00') 86 | light_green = from_hex('#90EE90') 87 | green_yellow = from_hex('#ADFF2F') 88 | pale_green = from_hex('#98FB98') 89 | 90 | ## Cyan Colors 91 | teal = from_hex('#008080') 92 | dark_cyan = from_hex('#008B8B') 93 | lightsea_green = from_hex('#20B2AA') 94 | cadet_blue = from_hex('#5F9EA0') 95 | dark_turquoise = from_hex('#00CED1') 96 | medium_turquoise = from_hex('#48D1CC') 97 | turquoise = from_hex('#40E0D0') 98 | aqua = from_hex('#00FFFF') 99 | cyan = from_hex('#00FFFF') 100 | aquamarine = from_hex('#7FFFD4') 101 | pale_turquoise = from_hex('#AFEEEE') 102 | light_cyan = from_hex('#E0FFFF') 103 | 104 | ## Blue Colors 105 | navy = from_hex('#000080') 106 | dark_blue = from_hex('#00008B') 107 | medium_blue = from_hex('#0000CD') 108 | blue = from_hex('#0000FF') 109 | midnight_blue = from_hex('#191970') 110 | royal_blue = from_hex('#4169E1') 111 | steel_blue = from_hex('#4682B4') 112 | dodger_blue = from_hex('#1E90FF') 113 | deep_sky_blue = from_hex('#00BFFF') 114 | cornflower_blue = from_hex('#6495ED') 115 | sky_blue = from_hex('#87CEEB') 116 | light_sky_blue = from_hex('#87CEFA') 117 | light_steel_blue = from_hex('#B0C4DE') 118 | light_blue = from_hex('#ADD8E6') 119 | powder_blue = from_hex('#B0E0E6') 120 | 121 | ## Magenta Colors 122 | indigo = from_hex('#4B0082') 123 | purple = from_hex('#800080') 124 | dark_magenta = from_hex('#8B008B') 125 | dark_violet = from_hex('#9400D3') 126 | dark_slate_blue = from_hex('#483D8B') 127 | blue_violet = from_hex('#8A2BE2') 128 | dark_orchid = from_hex('#9932CC') 129 | fuchsia = from_hex('#FF00FF') 130 | magenta = from_hex('#FF00FF') 131 | slate_blue = from_hex('#6A5ACD') 132 | medium_slate_blue = from_hex('#7B68EE') 133 | medium_orchid = from_hex('#BA55D3') 134 | medium_purple = from_hex('#9370DB') 135 | orchid = from_hex('#DA70D6') 136 | violet = from_hex('#EE82EE') 137 | plum = from_hex('#DDA0DD') 138 | thistle = from_hex('#D8BFD8') 139 | lavender = from_hex('#E6E6FA') 140 | 141 | ## Pink Colors 142 | medium_violet_red = from_hex('#C71585') 143 | deep_pink = from_hex('#FF1493') 144 | pale_violet_red = from_hex('#DB7093') 145 | hot_pink = from_hex('#FF69B4') 146 | light_pink = from_hex('#FFB6C1') 147 | pink = from_hex('#FFC0CB') 148 | 149 | ## White Colors 150 | misty_rose = from_hex('#FFE4E1') 151 | antique_white = from_hex('#FAEBD7') 152 | linen = from_hex('#FAF0E6') 153 | beige = from_hex('#F5F5DC') 154 | white_smoke = from_hex('#F5F5F5') 155 | lavender_blush = from_hex('#FFF0F5') 156 | old_lace = from_hex('#FDF5E6') 157 | alice_blue = from_hex('#F0F8FF') 158 | seashell = from_hex('#FFF5EE') 159 | ghost_white = from_hex('#F8F8FF') 160 | honeydew = from_hex('#F0FFF0') 161 | floral_white = from_hex('#FFFAF0') 162 | azure = from_hex('#F0FFFF') 163 | mint_cream = from_hex('#F5FFFA') 164 | snow = from_hex('#FFFAFA') 165 | ivory = from_hex('#FFFFF0') 166 | white = from_hex('#FFFFFF') 167 | 168 | ## Black Colors 169 | black = from_hex('#000000') 170 | dark_slate_gray = from_hex('#2F4F4F') 171 | dim_gray = from_hex('#696969') 172 | slate_gray = from_hex('#708090') 173 | gray = from_hex('#808080') 174 | light_slate_gray = from_hex('#778899') 175 | dark_gray = from_hex('#A9A9A9') 176 | silver = from_hex('#C0C0C0') 177 | light_gray = from_hex('#D3D3D3') 178 | gainsboro = from_hex('#DCDCDC') 179 | 180 | 181 | __all__ = [ 182 | 'from_float', 183 | 'from_rgba8', 184 | 'from_hex', 185 | 186 | 'dark_red', 187 | 'red', 188 | 'firebrick', 189 | 'crimson', 190 | 'indian_red', 191 | 'light_coral', 192 | 'salmon', 193 | 'dark_salmon', 194 | 'light_salmon', 195 | 196 | 'orange_red', 197 | 'tomato', 198 | 'dark_orange', 199 | 'coral', 200 | 'orange', 201 | 202 | 'dark_khaki', 203 | 'gold', 204 | 'khaki', 205 | 'peach_puff', 206 | 'yellow', 207 | 'pale_goldenrod', 208 | 'moccasin', 209 | 'papaya_whip', 210 | 'light_goldenrod_yellow', 211 | 'lemon_chiffon', 212 | 'light_yellow', 213 | 214 | 'maroon', 215 | 'brown', 216 | 'saddle_brown', 217 | 'sienna', 218 | 'chocolate', 219 | 'dark_goldenrod', 220 | 'peru', 221 | 'rosy_brown', 222 | 'goldenrod', 223 | 'sandy_brown', 224 | 'tan', 225 | 'burlywood', 226 | 'wheat', 227 | 'navajo_white', 228 | 'bisque', 229 | 'blanched_almond', 230 | 'cornsilk', 231 | 232 | 'dark_green', 233 | 'green', 234 | 'dark_olive_green', 235 | 'forest_green', 236 | 'sea_green', 237 | 'olive', 238 | 'olive_drab', 239 | 'medium_sea_green', 240 | 'lime_green', 241 | 'lime', 242 | 'spring_green', 243 | 'medium_spring_green', 244 | 'dark_sea_green', 245 | 'medium_aquamarine', 246 | 'yellow_green', 247 | 'lawn_green', 248 | 'chartreuse', 249 | 'light_green', 250 | 'green_yellow', 251 | 'pale_green', 252 | 253 | 'teal', 254 | 'dark_cyan', 255 | 'lightsea_green', 256 | 'cadet_blue', 257 | 'dark_turquoise', 258 | 'medium_turquoise', 259 | 'turquoise', 260 | 'aqua', 261 | 'cyan', 262 | 'aquamarine', 263 | 'pale_turquoise', 264 | 'light_cyan', 265 | 266 | 'navy', 267 | 'dark_blue', 268 | 'medium_blue', 269 | 'blue', 270 | 'midnight_blue', 271 | 'royal_blue', 272 | 'steel_blue', 273 | 'dodger_blue', 274 | 'deep_sky_blue', 275 | 'cornflower_blue', 276 | 'sky_blue', 277 | 'light_sky_blue', 278 | 'light_steel_blue', 279 | 'light_blue', 280 | 'powder_blue', 281 | 282 | 'indigo', 283 | 'purple', 284 | 'dark_magenta', 285 | 'dark_violet', 286 | 'dark_slate_blue', 287 | 'blue_violet', 288 | 'dark_orchid', 289 | 'fuchsia', 290 | 'magenta', 291 | 'slate_blue', 292 | 'medium_slate_blue', 293 | 'medium_orchid', 294 | 'medium_purple', 295 | 'orchid', 296 | 'violet', 297 | 'plum', 298 | 'thistle', 299 | 'lavender', 300 | 301 | 'medium_violet_red', 302 | 'deep_pink', 303 | 'pale_violet_red', 304 | 'hot_pink', 305 | 'light_pink', 306 | 'pink', 307 | 308 | 'misty_rose', 309 | 'antique_white', 310 | 'linen', 311 | 'beige', 312 | 'white_smoke', 313 | 'lavender_blush', 314 | 'old_lace', 315 | 'alice_blue', 316 | 'seashell', 317 | 'ghost_white', 318 | 'honeydew', 319 | 'floral_white', 320 | 'azure', 321 | 'mint_cream', 322 | 'snow', 323 | 'ivory', 324 | 'white', 325 | 326 | 'black', 327 | 'dark_slate_gray', 328 | 'dim_gray', 329 | 'slate_gray', 330 | 'gray', 331 | 'light_slate_gray', 332 | 'dark_gray', 333 | 'silver', 334 | 'light_gray', 335 | 'gainsboro', 336 | ] -------------------------------------------------------------------------------- /dearpygui_obj/containers.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum 4 | from typing import TYPE_CHECKING 5 | 6 | from dearpygui import dearpygui as dpgcore 7 | from dearpygui_obj import _register_item_type 8 | from dearpygui_obj.wrapper.widget import Widget, ItemWidgetMx, ContainerWidgetMx, ValueWidgetMx, ConfigProperty 9 | 10 | if TYPE_CHECKING: 11 | pass 12 | 13 | ## Tree Nodes 14 | 15 | @_register_item_type('mvAppItemType::TreeNode') 16 | class TreeNode(Widget, ItemWidgetMx, ContainerWidgetMx['TreeNode'], ValueWidgetMx[bool]): 17 | """A collapsing container with a label.""" 18 | 19 | value: bool #: ``True`` if the header is uncollapsed, otherwise ``False``. 20 | 21 | label: str = ConfigProperty() 22 | closable: bool = ConfigProperty() 23 | default_open: bool = ConfigProperty() 24 | bullet: bool = ConfigProperty() #: Display a bullet instead of arrow. 25 | 26 | #: If ``True``, a double click is needed to toggle 27 | open_on_double_click: bool = ConfigProperty() 28 | 29 | #: If ``True``, the user must click on the arrow/bullet to toggle 30 | open_on_arrow: bool = ConfigProperty() 31 | 32 | #: If ``True``, the header is always open and cannot be collapsed, 33 | #: and the arrow/bullet not shown (use as a convenience for leaf nodes). 34 | is_leaf: bool = ConfigProperty(key='leaf') 35 | 36 | def __init__(self, label: str = None, **config): 37 | config.setdefault('show', True) # workaround for DPG 0.6 38 | super().__init__(label=label, **config) 39 | 40 | def __setup_add_widget__(self, dpg_args) -> None: 41 | dpgcore.add_tree_node(self.id, **dpg_args) 42 | 43 | 44 | 45 | @_register_item_type('mvAppItemType::CollapsingHeader') 46 | class TreeNodeHeader(TreeNode, ContainerWidgetMx['TreeNodeHeader']): 47 | """Similar to :class:`.TreeNode`, but the label is visually emphasized.""" 48 | 49 | def __setup_add_widget__(self, dpg_args) -> None: 50 | dpgcore.add_collapsing_header(self.id, **dpg_args) 51 | 52 | ## Tab Container 53 | 54 | @_register_item_type('mvAppItemType::TabBar') 55 | class TabBar(Widget, ItemWidgetMx, ContainerWidgetMx['TabBar']): 56 | """A container that allows switching between different tabs. 57 | 58 | Note: 59 | This container should only contain :class:`.TabItem` or :class:`.TabButton` elements. 60 | """ 61 | 62 | reorderable: bool = ConfigProperty() 63 | 64 | def __init__(self, **config): 65 | super().__init__(**config) 66 | 67 | def __setup_add_widget__(self, dpg_args) -> None: 68 | dpgcore.add_tab_bar(self.id, **dpg_args) 69 | 70 | 71 | class TabOrderMode(Enum): 72 | """Specifies the ordering behavior of a tab items.""" 73 | Reorderable = None #: Default 74 | Fixed = 'no_reorder' #: Disable reordering this tab or having another tab cross over this tab 75 | Leading = 'leading' #: Enforce the tab position to the left of the tab bar (after the tab list popup button) 76 | Trailing = 'trailing' #: Enforce the tab position to the right of the tab bar (before the scrolling buttons) 77 | 78 | @_register_item_type('mvAppItemType::TabItem') 79 | class TabItem(Widget, ItemWidgetMx, ContainerWidgetMx['TabItem']): 80 | """A container whose contents will be displayed when selected in a :class:`.TabBar`. 81 | 82 | Note: 83 | This widget must be placed inside a :class:`.TabBar` to be visible. 84 | """ 85 | 86 | label: str = ConfigProperty() 87 | 88 | #: Create a button on the tab that can hide the tab. 89 | closable: bool = ConfigProperty() 90 | 91 | order_mode: TabOrderMode 92 | @ConfigProperty() 93 | def order_mode(self) -> TabOrderMode: 94 | config = self.get_config() 95 | if config.get('leading'): 96 | return TabOrderMode.Leading 97 | if config.get('trailing'): 98 | return TabOrderMode.Trailing 99 | if config.get('no_reorder'): 100 | return TabOrderMode.Fixed 101 | return TabOrderMode.Reorderable 102 | 103 | @order_mode.getconfig 104 | def order_mode(self, value: TabOrderMode): 105 | return { 106 | mode.value : (mode == value) for mode in TabOrderMode if mode.value is not None 107 | } 108 | 109 | #: Disable tooltip 110 | no_tooltip: bool = ConfigProperty() 111 | 112 | def __init__(self, label: str = None, **config): 113 | super().__init__(label=label, **config) 114 | 115 | def __setup_add_widget__(self, dpg_args) -> None: 116 | dpgcore.add_tab(self.id, **dpg_args) 117 | 118 | 119 | @_register_item_type('mvAppItemType::TabButton') 120 | class TabButton(Widget, ItemWidgetMx): 121 | """A button that can be added to a :class:`TabBar`. 122 | 123 | Note: 124 | This widget must be placed inside a :class:`.TabBar` to be visible. 125 | """ 126 | 127 | label: str = ConfigProperty() 128 | 129 | #: Create a button on the tab that can hide the tab. 130 | closable: bool = ConfigProperty() 131 | 132 | order_mode: TabOrderMode 133 | @ConfigProperty() 134 | def order_mode(self) -> TabOrderMode: 135 | config = self.get_config() 136 | if config.get('leading'): 137 | return TabOrderMode.Leading 138 | if config.get('trailing'): 139 | return TabOrderMode.Trailing 140 | if config.get('no_reorder'): 141 | return TabOrderMode.Fixed 142 | return TabOrderMode.Reorderable 143 | 144 | @order_mode.getconfig 145 | def order_mode(self, value: TabOrderMode): 146 | return { 147 | mode.value : (mode == value) for mode in TabOrderMode if mode.value is not None 148 | } 149 | 150 | #: Disable tooltip 151 | no_tooltip: bool = ConfigProperty() 152 | 153 | def __init__(self, label: str = None, **config): 154 | super().__init__(label=label, **config) 155 | 156 | def __setup_add_widget__(self, dpg_args) -> None: 157 | dpgcore.add_tab_button(self.id, **dpg_args) 158 | 159 | 160 | ## Menus and Menu Items 161 | 162 | @_register_item_type('mvAppItemType::Menu') 163 | class Menu(Widget, ItemWidgetMx, ContainerWidgetMx['Menu']): 164 | """A menu containing :class:`.MenuItem` objects. 165 | 166 | While they are often found inside a :class:`.MenuBar`, they are actually a general container 167 | that can be added anywhere and contain other kinds of widgets (e.g. buttons and text), 168 | even if it is unusual.""" 169 | 170 | label: str = ConfigProperty() 171 | 172 | def __init__(self, label: str = None, **config): 173 | super().__init__(label=label, **config) 174 | 175 | def __setup_add_widget__(self, dpg_args) -> None: 176 | dpgcore.add_menu(self.id, **dpg_args) 177 | 178 | 179 | @_register_item_type('mvAppItemType::MenuItem') 180 | class MenuItem(Widget, ItemWidgetMx): 181 | """An item for a :class:`.Menu`.""" 182 | 183 | label: str = ConfigProperty() 184 | 185 | #: Keyboard shortcut, e.g. `'CTRL+M'`. 186 | shortcut: str = ConfigProperty() 187 | 188 | #: If ``True``, a checkmark is shown if the item's :attr:`value` is ``True``. 189 | enable_check: bool = ConfigProperty(key='check') 190 | 191 | def __init__(self, label: str = None, value: bool = None, **config): 192 | super().__init__(label=label, **config) 193 | if value is not None: 194 | self.value = value 195 | 196 | def __setup_add_widget__(self, dpg_args) -> None: 197 | dpgcore.add_menu_item(self.id, **dpg_args) 198 | 199 | 200 | ## Popups 201 | 202 | class PopupInteraction(Enum): 203 | """Specifies the trigger for a :class:`.Popup`.""" 204 | MouseLeft = 0 205 | MouseRight = 1 206 | MouseMiddle = 2 207 | MouseX1 = 3 208 | MouseX2 = 4 209 | 210 | @_register_item_type('mvAppItemType::Popup') 211 | class Popup(Widget, ContainerWidgetMx['Popup']): 212 | """A container that appears when a :class:`.Widget` is interacted with.""" 213 | 214 | trigger: PopupInteraction #: The interaction that will trigger the popup. 215 | @ConfigProperty(key='mousebutton') 216 | def trigger(self) -> PopupInteraction: 217 | config = self.get_config() 218 | return PopupInteraction(config['mousebutton']) 219 | 220 | @trigger.getconfig 221 | def trigger(self, trigger: PopupInteraction): 222 | return {'mousebutton' : trigger.value} 223 | 224 | #: Prevent the user from interacting with other windows until the popup is closed. 225 | modal: bool = ConfigProperty() 226 | 227 | def __init__(self, parent: Widget, **config): 228 | self._popup_parent = parent 229 | super().__init__(**config) 230 | 231 | def __setup_add_widget__(self, dpg_args) -> None: 232 | dpgcore.add_popup(self._popup_parent.id, self.id, **dpg_args) 233 | 234 | parent: Widget 235 | @property 236 | def parent(self) -> Widget: 237 | """The :class:`.ItemWidgetMx` that the popup is attached to. Cannot be changed.""" 238 | return self._popup_parent 239 | 240 | def close(self) -> None: 241 | """Closes the popup. 242 | 243 | Node: 244 | Modal popups cannot be closed except by using this method. 245 | """ 246 | dpgcore.close_popup(self.id) 247 | 248 | 249 | __all__ = [ 250 | 'TreeNode', 251 | 'TreeNodeHeader', 252 | 'TabBar', 253 | 'TabItem', 254 | 'TabButton', 255 | 'TabOrderMode', 256 | 'Menu', 257 | 'MenuItem', 258 | 'Popup', 259 | 'PopupInteraction', 260 | ] -------------------------------------------------------------------------------- /dearpygui_obj/data.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import string 4 | import datetime 5 | import calendar 6 | from datetime import date, time 7 | from typing import TYPE_CHECKING, NamedTuple 8 | 9 | from dearpygui_obj.wrapper.widget import ConfigProperty 10 | 11 | if TYPE_CHECKING: 12 | from typing import Any, List, Iterable, Union, Mapping 13 | from dearpygui_obj.wrapper.widget import Widget, ItemConfigData 14 | 15 | 16 | number = Union[int, float] 17 | 18 | ## Colors 19 | 20 | class ColorRGBA(NamedTuple): 21 | """RGBA color data. 22 | 23 | Values should be expressed in the range between 0 and 255 (since that's what DPG uses). 24 | The alpha value is optional. 25 | 26 | Using the :func:`.color_from_float`, :func:`.color_from_rgba8`, and :func:`.color_from_hex` 27 | constructor functions should be preferred over instantiating ColorRGBA tuples directly, as 28 | the internal representation may be subject to change.""" 29 | r: number #: red channel 30 | g: number #: green channel 31 | b: number #: blue channel 32 | a: number = 255 #: alpha channel 33 | 34 | def color_from_float(r: float, g: float, b: float, a: float = 1.0) -> ColorRGBA: 35 | # noinspection PyArgumentList 36 | return ColorRGBA(r*255.0, g*255.0, b*255.0, a*255.0) 37 | 38 | def color_from_rgba8(r: number, g: number, b: number, a: number = 255) -> ColorRGBA: 39 | """Create a :class:`.ColorRGBA` from 0-255 channel values.""" 40 | # noinspection PyArgumentList 41 | return ColorRGBA(r, g, b, a) 42 | 43 | def color_from_hex(color: str) -> ColorRGBA: 44 | """Create a :class:`.ColorRGBA` from a hex color string. 45 | 46 | Supported formats (The "#" and alpha channel are optional): 47 | 48 | - "[#]RGB[A]" (hex shorthand format) 49 | - "[#]RRGGBB[AA]" 50 | """ 51 | 52 | # strip all non-hex characters from input 53 | hexstr = ''.join(c for c in color if c in string.hexdigits) 54 | hexlen = len(hexstr) 55 | if hexlen == 3 or hexlen == 4: 56 | values = (c*2 for c in hexstr) # hex shorthand format 57 | elif hexlen == 6 or hexlen == 8: 58 | values = (hexstr[i:i+2] for i in range(0, hexlen, 2)) 59 | else: 60 | raise ValueError("unsupported hex color format") 61 | 62 | return color_from_rgba8(*(int(value, 16) for value in values)) 63 | 64 | def import_color_from_dpg(colorlist: List[number]) -> ColorRGBA: 65 | """Create a ColorRGBA from DPG color data.""" 66 | return ColorRGBA(*(min(max(0, value), 255) for value in colorlist)) 67 | 68 | def export_color_to_dpg(color: Iterable[number]) -> List[number]: 69 | """Convert a :class:`ColorRGBA`-like iterable into DPG color data (list of floats 0-255)""" 70 | return [min(max(0, value), 255) for value in color] 71 | 72 | class ConfigPropertyColorRGBA(ConfigProperty): 73 | def fvalue(self, instance: Widget) -> Any: 74 | return import_color_from_dpg(instance.get_config()[self.key]) 75 | def fconfig(self, instance: Widget, value: ColorRGBA) -> ItemConfigData: 76 | return {self.key : export_color_to_dpg(value)} 77 | 78 | ## Date/Time 79 | 80 | MINYEAR = 1970 #: the smallest year number supported by DPG. 81 | MAXYEAR = 2999 #: the largest year number supported by DPG. 82 | 83 | def _clamp(value, min_val, max_val): 84 | return min(max(min_val, value), max_val) 85 | 86 | def import_date_from_dpg(date_data: Mapping[str, int]) -> date: 87 | """Convert date data used by DPG into a :class:`~datetime.date` object.""" 88 | year = _clamp(date_data.get('year', MINYEAR), datetime.MINYEAR, datetime.MAXYEAR) 89 | month = _clamp(date_data.get('month', 1), 1, 12) 90 | 91 | _, max_day = calendar.monthrange(year, month) 92 | day = _clamp(date_data.get('month_day', 1), 1, max_day) 93 | return date(year, month, day) 94 | 95 | def export_date_to_dpg(date_val: date) -> Mapping: 96 | """Convert a :class:`date` into date data used by DPG. 97 | 98 | Unfortunately the range of year numbers supported by DPG is smaller than that of python. 99 | See the :data:`.MINYEAR` and :data:`.MAXYEAR` constants. 100 | 101 | If a date outside of the supported range is given, this function will still return a value, 102 | however that value may not produce desired results when supplied to DPG's date widgets 103 | (should get clamped).""" 104 | return { 105 | 'month_day': date_val.day, 106 | 'month': date_val.month, 107 | 'year': date_val.year - 1900 108 | } 109 | 110 | def import_time_from_dpg(time_data: Mapping[str, int]) -> time: 111 | return time(hour = time_data['hour'], minute = time_data['min'], second = time_data['sec']) 112 | 113 | def export_time_to_dpg(tm: time) -> Mapping[str, int]: 114 | return dict(hour = tm.hour, min = tm.minute, sec = tm.second) 115 | 116 | 117 | ## Textures 118 | 119 | # class TextureFormat(Enum): 120 | # """Texture format useds by DPG. 121 | # 122 | # Values come from DPG's "mvTEX_XXXX_XXXXX" constants. 123 | # """ 124 | # RGBA_INT = 0 125 | # RGBA_FLOAT = 1 126 | # RGB_FLOAT = 2 127 | # RGB_INT = 3 128 | # 129 | __all__ = [ 130 | 'ColorRGBA', 131 | 'color_from_float', 132 | 'color_from_rgba8', 133 | 'color_from_hex', 134 | 'import_color_from_dpg', 135 | 'export_color_to_dpg', 136 | 'ConfigPropertyColorRGBA', 137 | 138 | 'MINYEAR', 139 | 'MAXYEAR', 140 | 'import_date_from_dpg', 141 | 'export_date_to_dpg', 142 | 'import_time_from_dpg', 143 | 'export_time_to_dpg', 144 | ] 145 | 146 | 147 | -------------------------------------------------------------------------------- /dearpygui_obj/devtools.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING 3 | 4 | from dearpygui import dearpygui as dpgcore 5 | from dearpygui_obj import _register_item_type, get_item_by_id 6 | from dearpygui_obj.window import Window 7 | 8 | if TYPE_CHECKING: 9 | pass 10 | 11 | 12 | @_register_item_type('mvAppItemType::DebugWindow') 13 | class DebugWindow(Window): 14 | 15 | def __init__(self, **config): 16 | super().__init__(**config) 17 | 18 | """Developer tool, creates a window containing handy GUI debugging info.""" 19 | def __setup_add_widget__(self, dpg_args) -> None: 20 | dpgcore.add_debug_window(self.id, **dpg_args) 21 | dpgcore.end() 22 | 23 | @classmethod 24 | def get_instance(cls): 25 | """Get the standard instance that is automatically created by DPG.""" 26 | return get_item_by_id('debug##standard') 27 | 28 | @classmethod 29 | def show_debug(cls) -> None: 30 | """Show the standard instance that is automatically created by DPG.""" 31 | cls.get_instance().show = True 32 | 33 | 34 | @_register_item_type('mvAppItemType::MetricsWindow') 35 | class MetricsWindow(Window): 36 | """Developer tool, creates a metrics window.""" 37 | 38 | def __init__(self, **config): 39 | super().__init__(**config) 40 | 41 | def __setup_add_widget__(self, dpg_args) -> None: 42 | dpgcore.add_metrics_window(self.id, **dpg_args) 43 | dpgcore.end() 44 | 45 | @classmethod 46 | def get_instance(cls): 47 | """Get the standard instance that is automatically created by DPG.""" 48 | return get_item_by_id('metrics##standard') 49 | 50 | @classmethod 51 | def show_metrics(cls) -> None: 52 | """Show the standard instance that is automatically created by DPG.""" 53 | cls.get_instance().show = True 54 | 55 | @_register_item_type('mvAppItemType::StyleWindow') 56 | class StyleEditorWindow(Window): 57 | """Developer tool, creates a window containing a GUI style editor..""" 58 | 59 | def __init__(self, **config): 60 | super().__init__(**config) 61 | 62 | def __setup_add_widget__(self, dpg_args) -> None: 63 | dpgcore.add_style_window(self.id, **dpg_args) 64 | dpgcore.end() 65 | 66 | @classmethod 67 | def get_instance(cls): 68 | """Get the standard instance that is automatically created by DPG.""" 69 | return get_item_by_id('style##standard') 70 | 71 | @classmethod 72 | def show_style_editor(cls) -> None: 73 | """Show the standard instance that is automatically created by DPG.""" 74 | cls.get_instance().show = True 75 | 76 | @_register_item_type('mvAppItemType::DocWindow') 77 | class DocumentationWindow(Window): 78 | """Developer tool, creates a window showing DearPyGui documentation.""" 79 | 80 | def __init__(self, **config): 81 | super().__init__(**config) 82 | 83 | def __setup_add_widget__(self, dpg_args) -> None: 84 | dpgcore.add_doc_window(self.id, **dpg_args) 85 | dpgcore.end() 86 | 87 | @classmethod 88 | def get_instance(cls): 89 | """Get the standard instance that is automatically created by DPG.""" 90 | return get_item_by_id('documentation##standard') 91 | 92 | @classmethod 93 | def show_documentation(cls) -> None: 94 | """Show the standard instance that is automatically created by DPG.""" 95 | cls.get_instance().show = True 96 | 97 | @_register_item_type('mvAppItemType::AboutWindow') 98 | class AboutWindow(Window): 99 | """Developer tool, creates window containing information about DearPyGui.""" 100 | 101 | def __init__(self, **config): 102 | super().__init__(**config) 103 | 104 | def __setup_add_widget__(self, dpg_args) -> None: 105 | dpgcore.add_about_window(self.id, **dpg_args) 106 | dpgcore.end() 107 | 108 | @classmethod 109 | def get_instance(cls): 110 | """Get the standard instance that is automatically created by DPG.""" 111 | return get_item_by_id('about##standard') 112 | 113 | @classmethod 114 | def show_about(cls) -> None: 115 | """Show the standard instance that is automatically created by DPG.""" 116 | cls.get_instance().show = True 117 | 118 | 119 | __all__ = [ 120 | 'DebugWindow', 121 | 'MetricsWindow', 122 | 'StyleEditorWindow', 123 | 'DocumentationWindow', 124 | 'AboutWindow', 125 | ] -------------------------------------------------------------------------------- /dearpygui_obj/drawing.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import TYPE_CHECKING, NamedTuple 5 | 6 | from dearpygui import dearpygui as dpgcore 7 | from dearpygui_obj import _register_item_type 8 | from dearpygui_obj.data import export_color_to_dpg, import_color_from_dpg 9 | from dearpygui_obj.wrapper.widget import Widget, ItemWidgetMx 10 | from dearpygui_obj.wrapper.drawing import DrawCommand, DrawProperty 11 | 12 | if TYPE_CHECKING: 13 | from typing import Any, Optional, Tuple, Sequence 14 | from dearpygui_obj.data import ColorRGBA 15 | from dearpygui_obj.window import Window 16 | from dearpygui_obj.wrapper.drawing import DrawConfigData 17 | 18 | 19 | class DrawingCanvas(ABC): 20 | """Abstract base class for drawing.""" 21 | 22 | @property 23 | @abstractmethod 24 | def id(self) -> str: 25 | ... 26 | 27 | def clear(self) -> None: 28 | """Clears the drawing. 29 | 30 | Warning: 31 | Any :class:`.DrawCommand` objects created using this canvas must not be used after this 32 | method is called. 33 | 34 | This includes reading or writing to any properties of :class:`DrawCommand` objects. 35 | """ 36 | dpgcore.clear_drawing(self.id) 37 | 38 | def draw_line(self, p1: Tuple[float, float], p2: Tuple[float, float], color: ColorRGBA, thickness: int) -> DrawLine: 39 | """See :class:`.DrawLine`""" 40 | return DrawLine(self, p1, p2, color, thickness) 41 | 42 | def draw_rectangle(self, pmin: Tuple[float, float], pmax: Tuple[float, float], color: ColorRGBA, **kwargs: Any) -> DrawRectangle: 43 | """See :class:`.DrawRectangle` for keyword arguments.""" 44 | return DrawRectangle(self, pmin, pmax, color, **kwargs) 45 | 46 | def draw_circle(self, center: Tuple[float, float], radius: float, color: ColorRGBA, **kwargs: Any) -> DrawCircle: 47 | """See :class:`.DrawCircle` for keyword arguments.""" 48 | return DrawCircle(self, center, radius, color, **kwargs) 49 | 50 | def draw_text(self, pos: Tuple[float, float], text: str, **kwargs) -> DrawText: 51 | """See :class:`.DrawText` for keyword arguments.""" 52 | return DrawText(self, pos, text, **kwargs) 53 | 54 | def draw_arrow(self, p1: Tuple[float, float], p2: Tuple[float, float], color: ColorRGBA, thickness: int, arrow_size: int) -> DrawArrow: 55 | """See :class:`.DrawArrow` for keyword arguments.""" 56 | return DrawArrow(self, p1, p2, color, thickness, arrow_size) 57 | 58 | def draw_polyline(self, points: Sequence[Tuple[float, float]], color: ColorRGBA, **kwargs: Any) -> DrawPolyLine: 59 | """See :class:`.DrawPolyLine` for keyword arguments.""" 60 | return DrawPolyLine(self, points, color, **kwargs) 61 | 62 | def draw_triangle(self, p1: Tuple[float, float], p2: Tuple[float, float], p3: Tuple[float, float], color: ColorRGBA, **kwargs: Any) -> DrawTriangle: 63 | """See :class:`.DrawTriangle` for keyword arguments.""" 64 | return DrawTriangle(self, p1, p2, p3, color, **kwargs) 65 | 66 | def draw_quad(self, p1: Tuple[float, float], p2: Tuple[float, float], p3: Tuple[float, float], p4: Tuple[float, float], color: ColorRGBA, **kwargs: Any) -> DrawQuad: 67 | """See :class:`.DrawQuod` for keyword arguments.""" 68 | return DrawQuad(self, p1, p2, p3, p4, color, **kwargs) 69 | 70 | def draw_polygon(self, points: Sequence[Tuple[float, float]], color: ColorRGBA, **kwargs) -> DrawPolygon: 71 | """See :class:`.DrawPolygon` for keyword arguments.""" 72 | return DrawPolygon(self, points, color, **kwargs) 73 | 74 | def draw_bezier_curve(self, p1: Tuple[float, float], p2: Tuple[float, float], p3: Tuple[float, float], p4: Tuple[float, float], color: ColorRGBA, **kwargs: Any) -> DrawBezierCurve: 75 | """See :class:`.DrawBezierCurve` for keyword arguments.""" 76 | return DrawBezierCurve(self, p1, p2, p3, p4, color, **kwargs) 77 | 78 | 79 | @_register_item_type('mvAppItemType::Drawing') 80 | class Drawing(Widget, ItemWidgetMx, DrawingCanvas): 81 | """A widget that displays the result of drawing commands.""" 82 | 83 | def __init__(self, size: Tuple[int, int] = (300, 300), **config): 84 | super().__init__(size=size, **config) 85 | 86 | def __setup_add_widget__(self, dpg_args) -> None: 87 | dpgcore.add_drawing(self.id, **dpg_args) 88 | 89 | def get_mouse_pos(self) -> Optional[Tuple[int, int]]: 90 | """Get the mouse position within the drawing, or ``None`` if the drawing is not hovered.""" 91 | if not self.is_hovered(): 92 | return None 93 | return dpgcore.get_drawing_mouse_pos() 94 | 95 | class WindowCanvas(DrawingCanvas): 96 | """A :class:`.DrawingCanvas` that can be used to draw onto a window. 97 | 98 | Can also be obtained from :meth:`.Window.get_canvas`. 99 | 100 | To draw on the foreground or background of the main viewport, see :attr:`.MainWindow.foreground` 101 | and :attr:`.MainWindow.background`.""" 102 | 103 | def __init__(self, window: Window): 104 | self._id = window.id 105 | 106 | @property 107 | def id(self) -> str: 108 | return self._id 109 | 110 | 111 | ## Draw Commands 112 | 113 | class Pos2D(NamedTuple): 114 | """2D position data used for drawing.""" 115 | x: float #: x coordinate 116 | y: float #: y coordinate 117 | 118 | 119 | class DrawPropertyColorRGBA(DrawProperty): 120 | def fvalue(self, instance: Widget) -> Any: 121 | return import_color_from_dpg(instance.get_config()[self.key]) 122 | def fconfig(self, instance: Widget, value: ColorRGBA) -> DrawConfigData: 123 | return {self.key : export_color_to_dpg(value)} 124 | 125 | class DrawPropertyPos2D(DrawProperty): 126 | def fvalue(self, instance: DrawCommand) -> Pos2D: 127 | return Pos2D(*instance.get_config()[self.key]) 128 | def fconfig(self, instance: DrawCommand, value: Tuple[float, float]) -> DrawConfigData: 129 | return {self.key : list(value)} 130 | 131 | 132 | class DrawLine(DrawCommand): 133 | """Draws a line.""" 134 | 135 | p1: Tuple[float, float] = DrawPropertyPos2D() 136 | p2: Tuple[float, float] = DrawPropertyPos2D() 137 | color: ColorRGBA = DrawPropertyColorRGBA() 138 | thickness: int = DrawProperty() 139 | 140 | def __draw_internal__(self, draw_args) -> None: 141 | dpgcore.draw_line(self.canvas.id, tag=self.id, **draw_args) 142 | 143 | class DrawRectangle(DrawCommand): 144 | """Draws a rectangle.""" 145 | 146 | pmin: Tuple[float, float] = DrawPropertyPos2D() 147 | pmax: Tuple[float, float] = DrawPropertyPos2D() 148 | color: ColorRGBA = DrawPropertyColorRGBA() 149 | 150 | fill: ColorRGBA = DrawPropertyColorRGBA() 151 | rounding: float = DrawProperty() 152 | thickness: float = DrawProperty() 153 | 154 | def __draw_internal__(self, draw_args) -> None: 155 | dpgcore.draw_rectangle(self.canvas.id, tag=self.id, **draw_args) 156 | 157 | class DrawCircle(DrawCommand): 158 | """Draws a circle.""" 159 | 160 | center: Tuple[float, float] = DrawPropertyPos2D() 161 | radius: float = DrawProperty() 162 | color: ColorRGBA = DrawPropertyColorRGBA() 163 | 164 | segments: int = DrawProperty() 165 | thickness: float = DrawProperty() 166 | fill: ColorRGBA = DrawPropertyColorRGBA() 167 | 168 | def __draw_internal__(self, draw_args) -> None: 169 | dpgcore.draw_circle(self.canvas.id, tag=self.id, **draw_args) 170 | 171 | class DrawText(DrawCommand): 172 | """Draws text.""" 173 | 174 | pos: Tuple[float, float] = DrawPropertyPos2D() 175 | text: str = DrawProperty() 176 | 177 | color: ColorRGBA = DrawPropertyColorRGBA() 178 | font_size: int = DrawProperty(key='size') 179 | 180 | def __draw_internal__(self, draw_args) -> None: 181 | dpgcore.draw_text(self.canvas.id, tag=self.id, **draw_args) 182 | 183 | class DrawArrow(DrawCommand): 184 | """Draw a line with an arrowhead.""" 185 | 186 | p1: Tuple[float, float] = DrawPropertyPos2D() 187 | p2: Tuple[float, float] = DrawPropertyPos2D() 188 | color: ColorRGBA = DrawPropertyColorRGBA() 189 | thickness: int = DrawProperty() 190 | arrow_size: int = DrawProperty(key='size') 191 | 192 | def __draw_internal__(self, draw_args) -> None: 193 | dpgcore.draw_arrow(self.canvas.id, tag=self.id, **draw_args) 194 | 195 | class DrawPolyLine(DrawCommand): 196 | """Draws connected lines.""" 197 | 198 | @DrawProperty() 199 | def points(self) -> Sequence[Tuple[float, float]]: 200 | return [Pos2D(*p) for p in self.get_config()['points']] 201 | 202 | @points.getconfig 203 | def points(self, value: Sequence[Tuple[float, float]]): 204 | return { 'points' : [ list(p) for p in value ] } 205 | 206 | color: ColorRGBA = DrawPropertyColorRGBA() 207 | 208 | closed: bool = DrawProperty() 209 | thickness: float = DrawProperty() 210 | 211 | def __draw_internal__(self, draw_args) -> None: 212 | dpgcore.draw_polyline(self.canvas.id, tag=self.id, **draw_args) 213 | 214 | class DrawTriangle(DrawCommand): 215 | """Draws a triangle.""" 216 | 217 | p1: Tuple[float, float] = DrawPropertyPos2D() 218 | p2: Tuple[float, float] = DrawPropertyPos2D() 219 | p3: Tuple[float, float] = DrawPropertyPos2D() 220 | color: ColorRGBA = DrawPropertyColorRGBA() 221 | 222 | fill: ColorRGBA = DrawPropertyColorRGBA() 223 | thickness: float = DrawProperty() 224 | 225 | def __draw_internal__(self, draw_args) -> None: 226 | dpgcore.draw_triangle(self.canvas.id, tag=self.id, **draw_args) 227 | 228 | class DrawQuad(DrawCommand): 229 | """Draws a quadrilateral.""" 230 | 231 | p1: Tuple[float, float] = DrawPropertyPos2D() 232 | p2: Tuple[float, float] = DrawPropertyPos2D() 233 | p3: Tuple[float, float] = DrawPropertyPos2D() 234 | p4: Tuple[float, float] = DrawPropertyPos2D() 235 | color: ColorRGBA = DrawPropertyColorRGBA() 236 | 237 | fill: ColorRGBA = DrawPropertyColorRGBA() 238 | thickness: float = DrawProperty() 239 | 240 | def __draw_internal__(self, draw_args) -> None: 241 | dpgcore.draw_quad(self.canvas.id, tag=self.id, **draw_args) 242 | 243 | class DrawPolygon(DrawCommand): 244 | """Draws a polygon.""" 245 | 246 | @DrawProperty() 247 | def points(self) -> Sequence[Tuple[float, float]]: 248 | return [Pos2D(*p) for p in self.get_config()['points']] 249 | 250 | @points.getconfig 251 | def points(self, value: Sequence[Tuple[float, float]]): 252 | return { 'points' : [ list(p) for p in value ] } 253 | 254 | color: ColorRGBA = DrawPropertyColorRGBA() 255 | 256 | fill: ColorRGBA = DrawPropertyColorRGBA() 257 | thickness: float = DrawProperty() 258 | 259 | def __draw_internal__(self, draw_args) -> None: 260 | dpgcore.draw_polygon(self.canvas.id, tag=self.id, **draw_args) 261 | 262 | class DrawBezierCurve(DrawCommand): 263 | """Draws a bezier curve.""" 264 | 265 | p1: Tuple[float, float] = DrawPropertyPos2D() 266 | p2: Tuple[float, float] = DrawPropertyPos2D() 267 | p3: Tuple[float, float] = DrawPropertyPos2D() 268 | p4: Tuple[float, float] = DrawPropertyPos2D() 269 | color: ColorRGBA = DrawPropertyColorRGBA() 270 | 271 | thickness: float = DrawProperty() 272 | segments: int = DrawProperty() 273 | 274 | def __draw_internal__(self, draw_args) -> None: 275 | dpgcore.draw_bezier_curve(self.canvas.id, tag=self.id, **draw_args) 276 | 277 | ## class DrawImage TODO 278 | 279 | __all__ = [ 280 | 'Drawing', 281 | 'WindowCanvas', 282 | 283 | 'DrawLine', 284 | 'DrawRectangle', 285 | 'DrawCircle', 286 | 'DrawText', 287 | 'DrawArrow', 288 | 'DrawPolyLine', 289 | 'DrawTriangle', 290 | 'DrawQuad', 291 | 'DrawPolygon', 292 | 'DrawBezierCurve', 293 | ] 294 | 295 | if TYPE_CHECKING: 296 | from dearpygui_obj.wrapper.drawing import DrawCommand 297 | __all__.extend([ 298 | 'DrawingCanvas', 299 | 'DrawCommand', 300 | ]) -------------------------------------------------------------------------------- /dearpygui_obj/layout.py: -------------------------------------------------------------------------------- 1 | """Widgets for controlling layout.""" 2 | 3 | from __future__ import annotations 4 | from typing import TYPE_CHECKING 5 | 6 | from dearpygui import dearpygui as dpgcore 7 | from dearpygui_obj import _register_item_type 8 | from dearpygui_obj.wrapper.widget import Widget, ItemWidgetMx, ContainerWidgetMx, ConfigProperty 9 | 10 | if TYPE_CHECKING: 11 | from typing import Any, Sequence 12 | 13 | @_register_item_type('mvAppItemType::Spacing') 14 | class VSpacing(Widget, ItemWidgetMx): 15 | """Adds vertical spacing.""" 16 | 17 | space: int = ConfigProperty(key='count') #: The amount of vertical space. 18 | 19 | def __init__(self, **config): 20 | super().__init__(**config) 21 | 22 | def __setup_add_widget__(self, dpg_args) -> None: 23 | dpgcore.add_spacing(name=self.id, **dpg_args) 24 | 25 | 26 | @_register_item_type('mvAppItemType::SameLine') 27 | class HAlignNext(Widget, ItemWidgetMx): 28 | """Places a widget on the same line as the previous widget. 29 | Can also be used for horizontal spacing.""" 30 | 31 | xoffset: float = ConfigProperty() #: offset from containing window 32 | spacing: float = ConfigProperty() #: offset from previous widget 33 | 34 | def __init__(self, **config): 35 | super().__init__(**config) 36 | 37 | def __setup_add_widget__(self, dpg_args) -> None: 38 | dpgcore.add_same_line(name=self.id, **dpg_args) 39 | 40 | def group_horizontal(spacing: float = -1, **config: Any) -> Group: 41 | """Shortcut constructor for ``Group(horizontal=True)``""" 42 | return Group(horizontal=True, horizontal_spacing=spacing, **config) 43 | 44 | @_register_item_type('mvAppItemType::Group') 45 | class Group(Widget, ItemWidgetMx, ContainerWidgetMx['Group']): 46 | """Grouped widgets behave as a single unit when acted on by other layout widgets. 47 | 48 | They can optionally have their contents flow horizontally instead of vertically. 49 | """ 50 | 51 | horizontal: bool = ConfigProperty() 52 | horizontal_spacing: float = ConfigProperty() 53 | 54 | def __init__(self, **config): 55 | super().__init__(**config) 56 | 57 | def __setup_add_widget__(self, dpg_args) -> None: 58 | dpgcore.add_group(self.id, **dpg_args) 59 | 60 | 61 | @_register_item_type('mvAppItemType::Indent') 62 | class IndentLayout(Widget, ItemWidgetMx, ContainerWidgetMx['IndentLayout']): 63 | """Adds an indent to contained items.""" 64 | 65 | offset: float = ConfigProperty() 66 | 67 | def __init__(self, **config): 68 | super().__init__(**config) 69 | 70 | def __setup_add_widget__(self, dpg_args) -> None: 71 | dpgcore.add_indent(name=self.id, **dpg_args) 72 | 73 | def __finalize__(self) -> None: 74 | dpgcore.unindent() # doesn't use dpgcore.end() 75 | 76 | 77 | @_register_item_type('mvAppItemType::ManagedColumns') 78 | class ColumnLayout(Widget, ItemWidgetMx, ContainerWidgetMx['ColumnLayout']): 79 | """Places contents into columns. 80 | 81 | Each new widget added will be placed in the next column, wrapping around to the start.""" 82 | 83 | columns: int = ConfigProperty(no_init=True) #: Number of columns. 84 | border: bool = ConfigProperty() #: Draw a border between columns. 85 | 86 | def __init__(self, columns: int = 2, **config): 87 | super().__init__(columns=columns, **config) 88 | 89 | def __setup_add_widget__(self, dpg_args) -> None: 90 | dpgcore.add_managed_columns(name=self.id, **dpg_args) 91 | 92 | @property 93 | def column_widths(self) -> Sequence[float]: 94 | """Get or set column widths as a sequence of floats.""" 95 | return tuple( 96 | dpgcore.get_managed_column_width(self.id, i) 97 | for i in range(self.columns) 98 | ) 99 | 100 | @column_widths.setter 101 | def column_widths(self, widths: Sequence[float]) -> None: 102 | if len(widths) != self.columns: 103 | raise ValueError('incorrect number of widths') 104 | for i, width in enumerate(widths): 105 | dpgcore.set_managed_column_width(self.id, i, width) 106 | 107 | def get_column_width(self, col_idx: int) -> float: 108 | """Get an individual column width.""" 109 | return dpgcore.get_managed_column_width(self.id, col_idx) 110 | 111 | def set_column_width(self, col_idx: int, width: float) -> None: 112 | """Set an individual column width.""" 113 | dpgcore.set_managed_column_width(self.id, col_idx, width) 114 | 115 | 116 | @_register_item_type('mvAppItemType::Child') 117 | class ChildView(Widget, ItemWidgetMx, ContainerWidgetMx['ChildView']): 118 | """Adds an embedded child window with optional scollbars.""" 119 | 120 | border: bool = ConfigProperty() 121 | autosize_x: bool = ConfigProperty() 122 | autosize_y: bool = ConfigProperty() 123 | menubar: bool = ConfigProperty() 124 | 125 | #: Disable scrollbars (can still scroll with mouse or programmatically). 126 | no_scrollbar: bool = ConfigProperty() 127 | 128 | #: Allow horizontal scrollbar to appear. 129 | horizontal_scrollbar: bool = ConfigProperty() 130 | 131 | def __setup_add_widget__(self, dpg_args) -> None: 132 | dpgcore.add_child(self.id, **dpg_args) 133 | 134 | 135 | @_register_item_type('mvAppItemType::Dummy') 136 | class Dummy(Widget, ItemWidgetMx): 137 | """Adds a spacer or 'dummy' widget.""" 138 | def __init__(self, **config): 139 | super().__init__(**config) 140 | 141 | def __setup_add_widget__(self, dpg_args) -> None: 142 | dpgcore.add_dummy(name=self.id, **dpg_args) 143 | 144 | 145 | __all__ = [ 146 | 'VSpacing', 147 | 'HAlignNext', 148 | 'group_horizontal', 149 | 'Group', 150 | 'IndentLayout', 151 | 'ColumnLayout', 152 | 'ChildView', 153 | 'Dummy', 154 | ] -------------------------------------------------------------------------------- /dearpygui_obj/node.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum 4 | from warnings import warn 5 | from typing import TYPE_CHECKING, NamedTuple, overload 6 | 7 | from dearpygui import dearpygui as dpgcore 8 | 9 | from dearpygui_obj import _register_item_type, try_get_item_by_id, wrap_callback 10 | from dearpygui_obj.wrapper.widget import Widget, ItemWidgetMx, ContainerWidgetMx, ConfigProperty 11 | 12 | if TYPE_CHECKING: 13 | from typing import Optional, Iterable, Callable 14 | from dearpygui_obj import PyGuiCallback 15 | 16 | 17 | class NodeLink(NamedTuple): 18 | """Holds info about a link between two :class:`.NodeAttribute` objects.""" 19 | input: NodeAttribute #: The input end of the link. 20 | output: NodeAttribute #: The output end of the link. 21 | 22 | 23 | ## While I personally think it is better design to raise an exception here than return None, 24 | ## (so that the user can expect they will always have a NodeLink after a successful call to add_link) 25 | ## it doesn't seem appropriate to raise an exception for an operation that does not raise an exception 26 | ## in DPG. So lets generate warnings instead so at least the user can tell what went wrong. 27 | def _get_link(end1: NodeAttribute, end2: NodeAttribute) -> Optional[NodeLink]: 28 | endpoints = end1, end2 29 | input, output = None, None 30 | for end in endpoints: 31 | if end.is_input(): 32 | if input is not None: 33 | warn('attempt to link two node inputs') 34 | return None 35 | input = end 36 | if end.is_output(): 37 | if output is not None: 38 | warn('attempt to link two node outputs') 39 | return None 40 | output = end 41 | if input is None: 42 | warn('did not provide a node input') 43 | return None 44 | if output is None: 45 | warn('did not provide a node output') 46 | return None 47 | 48 | return NodeLink(input=input, output=output) 49 | 50 | def _get_link_from_ids(id1: str, id2: str) -> Optional[NodeLink]: 51 | end1 = try_get_item_by_id(id1) 52 | end2 = try_get_item_by_id(id2) 53 | if not isinstance(end1, NodeAttribute) or not isinstance(end2, NodeAttribute): 54 | warn('item ID does not reference a node attribute') 55 | return None 56 | return _get_link(end1, end2) 57 | 58 | @_register_item_type('mvAppItemType::NodeEditor') 59 | class NodeEditor(Widget, ItemWidgetMx, ContainerWidgetMx['NodeEditor']): 60 | """A canvas specific to graph node workflow. 61 | 62 | Should only contain :class:`.Node` objects. Any other kind of widget will not be displayed. 63 | """ 64 | 65 | def __init__(self, **config): 66 | super().__init__(**config) 67 | 68 | def __setup_add_widget__(self, dpg_args) -> None: 69 | dpgcore.add_node_editor( 70 | self.id, link_callback=self._on_link, delink_callback=self._on_delink, **dpg_args, 71 | ) 72 | 73 | ## Links 74 | 75 | def get_all_links(self) -> Iterable[NodeLink]: 76 | """Get all linkages between any :class:`.NodeAttribute` objects in the editor.""" 77 | for id1, id2 in dpgcore.get_links(self.id): 78 | link = _get_link_from_ids(id1, id2) 79 | if link is None: 80 | warn('dearpygui.core.get_links() produced an invalid link (is there a bug in DPG?)') 81 | else: 82 | yield link 83 | 84 | def add_link(self, end1: NodeAttribute, end2: NodeAttribute) -> Optional[NodeLink]: 85 | """Adds a link between two :class:`.NodeAttribute` objects. 86 | 87 | Returns: 88 | A :class:`.NodeLink` representing the link that was created, or ``None`` 89 | if the link was invalid. 90 | """ 91 | dpgcore.add_node_link(self.id, end1.id, end2.id) 92 | return _get_link(end1, end2) 93 | 94 | @overload 95 | def delete_link(self, link: NodeLink) -> None: 96 | ... 97 | @overload 98 | def delete_link(self, end1: NodeAttribute, end2: NodeAttribute) -> None: 99 | ... 100 | def delete_link(self, end1, end2 = None) -> None: 101 | """Deletes a link between two :class:`.NodeAttribute` objects if a link exists.""" 102 | if end2 is None: 103 | link = end1 104 | dpgcore.delete_node_link(self.id, link.input.id, link.output.id) 105 | else: 106 | dpgcore.delete_node_link(self.id, end1.id, end2.id) 107 | 108 | ## Node and Link Selection 109 | 110 | def get_selected_links(self) -> Iterable[NodeLink]: 111 | """Get all links in the selected state.""" 112 | for id1, id2 in dpgcore.get_selected_links(self.id): 113 | link = _get_link_from_ids(id1, id2) 114 | if link is None: 115 | warn('dearpygui.core.get_selected_links() produced an invalid link (is there a bug in DPG?)') 116 | else: 117 | yield link 118 | 119 | def clear_link_selection(self) -> None: 120 | """Clears all links from being in the selection state.""" 121 | dpgcore.clear_selected_links(self.id) 122 | 123 | def get_selected_nodes(self) -> Iterable[Node]: 124 | """Get all nodes in the selected state.""" 125 | for node_id in dpgcore.get_selected_nodes(self.id): 126 | node = try_get_item_by_id(node_id) 127 | if node is not None: 128 | yield node 129 | 130 | def clear_node_selection(self) -> None: 131 | """Clears all nodes from being in the selection state.""" 132 | dpgcore.clear_selected_nodes(self.id) 133 | 134 | ## Callbacks 135 | 136 | ## workaround for the fact that you can't set the link_callback or delink_callback properties in DPG 137 | _on_link_callback: Optional[Callable] = None 138 | _on_delink_callback: Optional[Callable] = None 139 | 140 | def _on_link(self, sender, data) -> None: 141 | if self._on_link_callback is not None: 142 | self._on_link_callback(sender, data) 143 | 144 | def _on_delink(self, sender, data) -> None: 145 | if self._on_delink_callback is not None: 146 | self._on_delink_callback(sender, data) 147 | 148 | def link_callback(self, callback: Optional[PyGuiCallback]) -> Callable: 149 | """Set the link callback, can be used as a decorator.""" 150 | if callback is not None: 151 | callback = wrap_callback(callback) 152 | self._on_link_callback = callback 153 | return callback 154 | 155 | def delink_callback(self, callback: Optional[PyGuiCallback]) -> Callable: 156 | """Set the delink callback, can be used as a decorator.""" 157 | if callback is not None: 158 | callback = wrap_callback(callback) 159 | self._on_delink_callback = callback 160 | return callback 161 | 162 | 163 | @_register_item_type('mvAppItemType::Node') 164 | class Node(Widget, ItemWidgetMx, ContainerWidgetMx['Node']): 165 | """A :class:`.NodeEditor` node. 166 | 167 | Should only contain :class:`.NodeAttribute` objects, any other kind of widget will not be 168 | displayed. Note that :class:`.NodeAttribute` objects may contain any kind or number of widget 169 | though.""" 170 | 171 | label: str = ConfigProperty() 172 | draggable: bool = ConfigProperty() 173 | 174 | def __init__(self, label: str = None, **config): 175 | super().__init__(label=label, **config) 176 | 177 | def __setup_add_widget__(self, dpg_args) -> None: 178 | dpgcore.add_node(self.id, **dpg_args) 179 | 180 | 181 | class NodeAttributeType(Enum): 182 | """Specifies how a :class:`.NodeAttribute` will link to other nodes.""" 183 | Input = None #: Input nodes may only link to Output nodes. 184 | Output = 'output' #: Output nodes may only link to Input nodes. 185 | Static = 'static' #: Static nodes do not link. They are still useful as containers to place widgets inside a node. 186 | 187 | def input_attribute(*, id: Optional[int] = None) -> NodeAttribute: 188 | """Shortcut constructor for ``NodeAttribute(NodeAttributeType.Input)``""" 189 | return NodeAttribute(NodeAttributeType.Input, id=id) 190 | 191 | def output_attribute(*, id: Optional[int] = None) -> NodeAttribute: 192 | """Shortcut constructor for ``NodeAttribute(NodeAttributeType.Output)``""" 193 | return NodeAttribute(NodeAttributeType.Output, id=id) 194 | 195 | def static_attribute(*, id: Optional[int] = None) -> NodeAttribute: 196 | """Shortcut constructor for ``NodeAttribute(NodeAttributeType.Static)``""" 197 | return NodeAttribute(NodeAttributeType.Static, id=id) 198 | 199 | @_register_item_type('mvAppItemType::NodeAttribute') 200 | class NodeAttribute(Widget, ItemWidgetMx, ContainerWidgetMx['NodeAttribute']): 201 | """An attachment point for a :class:`.Node`.""" 202 | 203 | type: NodeAttributeType 204 | @ConfigProperty() 205 | def type(self) -> NodeAttributeType: 206 | config = self.get_config() 207 | for mode in NodeAttributeType: 208 | if mode.value is not None and config.get(mode.value): 209 | return mode 210 | return NodeAttributeType.Input 211 | 212 | @type.getconfig 213 | def type(self, value: NodeAttributeType): 214 | return { 215 | mode.value : (mode == value) for mode in NodeAttributeType if mode.value is not None 216 | } 217 | 218 | def __init__(self, type: NodeAttributeType = NodeAttributeType.Input, **config): 219 | super().__init__(type=type, **config) 220 | 221 | def __setup_add_widget__(self, dpg_args) -> None: 222 | dpgcore.add_node_attribute(self.id, **dpg_args) 223 | 224 | def is_input(self) -> bool: 225 | """Shortcut for ``self.type == NodeAttributeType.Input``.""" 226 | return self.type == NodeAttributeType.Input 227 | 228 | def is_output(self) -> bool: 229 | """Shortcut for ``self.type == NodeAttributeType.Output``.""" 230 | return self.type == NodeAttributeType.Output 231 | 232 | def is_static(self) -> bool: 233 | """Shortcut for ``self.type == NodeAttributeType.Static``.""" 234 | return self.type == NodeAttributeType.Static 235 | 236 | 237 | __all__ = [ 238 | 'NodeEditor', 239 | 'Node', 240 | 'NodeAttribute', 241 | 'NodeLink', 242 | 'NodeAttributeType', 243 | 'input_attribute', 244 | 'output_attribute', 245 | 'static_attribute', 246 | ] 247 | -------------------------------------------------------------------------------- /dearpygui_obj/plots/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, NamedTuple, cast 4 | 5 | from dearpygui import dearpygui as dpgcore 6 | 7 | from dearpygui_obj import _register_item_type, _generate_id 8 | from dearpygui_obj.data import ColorRGBA, export_color_to_dpg 9 | from dearpygui_obj.wrapper.widget import Widget, ItemWidgetMx, ConfigProperty 10 | 11 | if TYPE_CHECKING: 12 | from typing import Any, Optional, Union, Type, Tuple, Iterable 13 | from dearpygui_obj.plots.dataseries import DataSeries 14 | 15 | TickLabel = Tuple[str, float] 16 | PlotLimits = Tuple[float, float] 17 | YAxis = Union['PlotYAxis', 'PlotYAxisConfig'] 18 | 19 | ## Plot Axis Configuration 20 | 21 | class PlotAxisConfigProperty: 22 | def __set_name__(self, owner: Type[PlotAxisConfig], name: str): 23 | self.key = '_' + name 24 | 25 | def __get__(self, config: Optional[PlotAxisConfig], owner: Type[PlotAxisConfig]) -> Any: 26 | if config is None: 27 | return self 28 | config_key = config.axis.key + self.key 29 | return config.plot.get_config()[config_key] 30 | 31 | def __set__(self, config: PlotAxisConfig, value: Any) -> None: 32 | config_key = config.axis.key + self.key 33 | config.plot.set_config(**{config_key : value}) 34 | 35 | class PlotAxisConfig: 36 | """Container for axis configuration properties. 37 | 38 | This class can be used to modify the configuration of a single axis of a :class:`.Plot`. 39 | """ 40 | no_gridlines: bool = PlotAxisConfigProperty() 41 | no_tick_marks: bool = PlotAxisConfigProperty() 42 | no_tick_labels: bool = PlotAxisConfigProperty() 43 | log_scale: bool = PlotAxisConfigProperty() 44 | invert: bool = PlotAxisConfigProperty() 45 | lock_min: bool = PlotAxisConfigProperty() 46 | lock_max: bool = PlotAxisConfigProperty() 47 | 48 | def __init__(self, plot: Plot, axis: PlotAxis): 49 | self.plot = plot 50 | self.axis = axis 51 | 52 | class PlotXAxisConfig(PlotAxisConfig): 53 | """Configuration container for plot X-axis.""" 54 | axis: PlotXAxis 55 | 56 | time: bool = PlotAxisConfigProperty() 57 | 58 | class PlotYAxisConfig(PlotAxisConfig): 59 | """Configuration container for plot Y-axis.""" 60 | axis: PlotYAxis 61 | 62 | class PlotOptYAxisConfig(PlotYAxisConfig): 63 | """Configuration container for optional Y-axes. 64 | 65 | This class is similar to :class:`.PlotYAxisConfig`. It provides the :attr:`enabled` property 66 | which can be used to enable or disable the optional Y-axes. 67 | """ 68 | axis: PlotYAxis 69 | 70 | @property 71 | def enabled(self) -> bool: 72 | """Enable or disable the optional Y-axis.""" 73 | # noinspection PyUnresolvedReferences 74 | return self.plot.get_config()[self.axis.optkey] 75 | 76 | @enabled.setter 77 | def enabled(self, enable: bool) -> None: 78 | # noinspection PyUnresolvedReferences 79 | self.plot.set_config(**{self.axis.optkey : enable}) 80 | 81 | ## Plot Axis Descriptors 82 | 83 | class PlotAxis: 84 | key: str 85 | 86 | def __set_name__(self, owner: Type[Plot], name: str): 87 | self.key = name 88 | self.config = f'_{name}_config' 89 | 90 | def __get__(self, plot: Optional[Plot], owner: Type[Plot]) -> Union[PlotAxisConfig, PlotAxis]: 91 | if plot is None: 92 | return self 93 | return getattr(plot, self.config) 94 | 95 | class PlotXAxis(PlotAxis): 96 | pass 97 | 98 | class PlotYAxis(PlotAxis): 99 | def __init__(self, index: int, optkey: Optional[str] = None): 100 | self.index = index 101 | self.optkey = optkey 102 | 103 | ## Plot Class 104 | 105 | @_register_item_type('mvAppItemType::Plot') 106 | class Plot(Widget, ItemWidgetMx): 107 | """A rich plot widget.""" 108 | 109 | ## Plot Axes 110 | 111 | xaxis: PlotXAxisConfig = PlotXAxis() #: The X-axis 112 | yaxis: PlotYAxisConfig = PlotYAxis(0) #: The Y-axis 113 | y2axis: PlotOptYAxisConfig = PlotYAxis(1, 'yaxis2') #: Optional Y-axis 2 114 | y3axis: PlotOptYAxisConfig = PlotYAxis(2, 'yaxis3') #: Optional Y-axis 3 115 | 116 | ## Config Properties 117 | 118 | label: str = ConfigProperty() 119 | x_axis_label: str = ConfigProperty(key='x_axis_name') 120 | y_axis_label: str = ConfigProperty(key='y_axis_name') 121 | 122 | show_annotations: bool = ConfigProperty() 123 | show_drag_lines: bool = ConfigProperty() 124 | show_drag_points: bool = ConfigProperty() 125 | show_color_scale: bool = ConfigProperty() 126 | 127 | scale_min: float = ConfigProperty() 128 | scale_max: float = ConfigProperty() 129 | scale_height: int = ConfigProperty() 130 | equal_aspects: bool = ConfigProperty() 131 | 132 | query: bool = ConfigProperty() 133 | crosshairs: bool = ConfigProperty() 134 | no_legend: bool = ConfigProperty() 135 | no_menus: bool = ConfigProperty() 136 | no_box_select: bool = ConfigProperty() 137 | no_mouse_pos: bool = ConfigProperty() 138 | no_highlight: bool = ConfigProperty() 139 | no_child: bool = ConfigProperty() 140 | 141 | anti_aliased: bool = ConfigProperty() 142 | 143 | def __init__(self, **config): 144 | # not super happy that we have to resort to typing.cast() here, but it works 145 | self._xaxis_config = PlotXAxisConfig(self, cast(PlotXAxis, Plot.xaxis)) 146 | self._yaxis_config = PlotYAxisConfig(self, cast(PlotYAxis, Plot.yaxis)) 147 | self._y2axis_config = PlotOptYAxisConfig(self, cast(PlotYAxis, Plot.y2axis)) 148 | self._y3axis_config = PlotOptYAxisConfig(self, cast(PlotYAxis, Plot.y3axis)) 149 | 150 | super().__init__(**config) 151 | 152 | def __setup_add_widget__(self, dpg_args) -> None: 153 | dpgcore.add_plot(self.id, **dpg_args) 154 | 155 | def add_dataseries(self, series: DataSeries, *, update_bounds: bool = True) -> None: 156 | """Add a :class:`.DataSeries` to this plot (or update it). 157 | 158 | Updates the data series if it has already been added.""" 159 | series.update(self, update_bounds) 160 | 161 | def remove_dataseries(self, series: DataSeries) -> None: 162 | """Remove a :class:`.DataSeries` from this plot if it has been added.""" 163 | dpgcore.delete_series(self.id, series.id) 164 | 165 | def clear(self) -> None: 166 | dpgcore.clear_plot(self.id) 167 | 168 | def set_xlimits(self, limits: Optional[PlotLimits]) -> None: 169 | """Set the ``(min, max)`` limits for the x-axis, or pass ``None`` to use automatic limits.""" 170 | if limits is None: 171 | dpgcore.set_plot_xlimits_auto(self.id) 172 | else: 173 | dpgcore.set_plot_xlimits(self.id, *limits) 174 | 175 | def set_ylimits(self, limits: Optional[PlotLimits]) -> None: 176 | """Set the ``(min, max)`` limits for the y-axis, or pass ``None`` to use automatic limits.""" 177 | if limits is None: 178 | dpgcore.set_plot_ylimits_auto(self.id) 179 | else: 180 | dpgcore.set_plot_ylimits(self.id, *limits) 181 | 182 | def set_xticks(self, ticks: Optional[Iterable[TickLabel]]) -> None: 183 | """Set the tick labels for the x-axis, or pass ``None`` to use automatic ticks.""" 184 | if ticks is None: 185 | dpgcore.reset_xticks(self.id) 186 | else: 187 | dpgcore.set_xticks(self.id, ticks) 188 | 189 | def set_yticks(self, ticks: Optional[Iterable[TickLabel]]) -> None: 190 | """Set the tick labels for the y-axis, or pass ``None`` to use automatic ticks.""" 191 | if ticks is None: 192 | dpgcore.reset_yticks(self.id) 193 | else: 194 | dpgcore.set_yticks(self.id, ticks) 195 | 196 | def add_annotation(self, text: str, pos: Tuple[float, float], offset: Tuple[float, float], *, 197 | color: ColorRGBA = None, clamped: bool = True) -> PlotAnnotation: 198 | """Creates a :class:`.PlotAnnotation` and adds it to the plot.""" 199 | return PlotAnnotation(self, text, pos, offset, color=color, clamped=clamped) 200 | 201 | def get_mouse_pos(self) -> Optional[Tuple[float, float]]: 202 | """Returns the ``(x, y)`` mouse position in the plot if it is hovered, or ``None``.""" 203 | if not self.is_hovered(): 204 | return None 205 | return dpgcore.get_plot_mouse_pos() 206 | 207 | def get_selected_query_area(self) -> Optional[PlotQueryArea]: 208 | """Returns a :class:`.PlotQueryArea` for the selected query area if there is one. 209 | 210 | A query area can be selected by the user by holding control and right-click dragging in 211 | a plot. If a query area has not been selected, this will return ``None``.""" 212 | if not dpgcore.is_plot_queried(self.id): 213 | return None 214 | return PlotQueryArea(*dpgcore.get_plot_query_area(self.id)) 215 | 216 | 217 | class PlotQueryArea(NamedTuple): 218 | """The position of a selected :class:`.Plot` query area.""" 219 | x_min: float 220 | x_max: float 221 | y_min: float 222 | y_max: float 223 | 224 | @property 225 | def min_corner(self) -> Tuple[float, float]: 226 | """Returns ``(x_min, y_min)`` as a tuple.""" 227 | return self.x_min, self.y_min 228 | 229 | @property 230 | def max_corner(self) -> Tuple[float, float]: 231 | """Returns ``(x_max, y_max)`` as a tuple.""" 232 | return self.x_max, self.y_max 233 | 234 | class PlotAnnotation: 235 | """Adds a plot annotation. 236 | 237 | Note: 238 | DPG does not support modifying existing plot annoations (other than to delete). 239 | Any methods provided that "mutate" the annotation actually delete and 240 | re-create the annotation in DPG. 241 | """ 242 | 243 | plot: Plot 244 | text: str 245 | color: Optional[ColorRGBA] #: If ``None``, then the annotation will have no callout bubble. 246 | clamped: bool #: If ``True``, the label will be free to shift so that it is not clipped by the plot limits. 247 | 248 | x: float 249 | y: float 250 | offset: Tuple[float, float] 251 | 252 | _tag_id: str = None 253 | def __init__(self, 254 | plot: Plot, 255 | text: str, 256 | pos: Tuple[float, float], 257 | offset: Tuple[float, float], *, 258 | color: ColorRGBA = None, 259 | clamped: bool = True): 260 | 261 | self._tag_id = _generate_id(self) 262 | self._plot = plot 263 | self._text = text 264 | self._pos = pos 265 | self._offset = offset 266 | self._color = color 267 | self._clamped = clamped 268 | self._create_annotation() 269 | 270 | def _create_annotation(self) -> None: 271 | x, y = self._pos 272 | xoff, yoff = self.offset 273 | color = export_color_to_dpg(self._color) if self._color is not None else (0, 0, 0, -1) 274 | dpgcore.add_annotation( 275 | self._plot.id, self._text, x, y, xoff, yoff, 276 | color=color, clamped=self.clamped, 277 | tag=self._tag_id 278 | ) 279 | 280 | def _delete_annotation(self) -> None: 281 | dpgcore.delete_annotation(self._plot.id, self._tag_id) 282 | 283 | def delete(self) -> None: 284 | self._delete_annotation() 285 | del self._tag_id 286 | 287 | @property 288 | def is_valid(self) -> bool: 289 | """``False`` if the annotation has been deleted.""" 290 | return self._tag_id is not None 291 | 292 | @property 293 | def id(self) -> str: return self._tag_id 294 | @property 295 | def plot(self) -> Plot: return self._plot 296 | @property 297 | def text(self) -> str: return self._text 298 | @property 299 | def color(self) -> ColorRGBA: return self._color 300 | @property 301 | def clamped(self) -> bool: return self._clamped 302 | @property 303 | def x(self) -> float: return self._pos[0] 304 | @property 305 | def y(self) -> float: return self._pos[1] 306 | @property 307 | def offset(self) -> Tuple[float, float]: return self._offset 308 | 309 | def set_text(self, text: str) -> None: 310 | self._text = text 311 | self._delete_annotation() 312 | self._create_annotation() 313 | 314 | def set_color(self, color: Optional[ColorRGBA]) -> None: 315 | self._color = color 316 | self._delete_annotation() 317 | self._create_annotation() 318 | 319 | def set_position(self, x: float, y: float) -> None: 320 | self._pos = (x, y) 321 | self._delete_annotation() 322 | self._create_annotation() 323 | 324 | def set_offset(self, xoffset: float, yoffset: float) -> None: 325 | self._offset = (xoffset, yoffset) 326 | self._delete_annotation() 327 | self._create_annotation() 328 | 329 | class PlotText: 330 | """Adds a point with text on a plot. 331 | 332 | Due to the limitations of DPG the each PlotText instance must 333 | have a unique label within each plot that it is added to. 334 | """ 335 | 336 | def __init__(self, 337 | label: str, 338 | pos: Tuple[float, float], *, 339 | vertical: bool = False, 340 | offset: Tuple[int, int] = (0, 0), 341 | axis: YAxis = Plot.yaxis): 342 | 343 | self.axis = axis 344 | self._name_id = label 345 | self.pos = pos 346 | self.offset = offset 347 | self.vertical = vertical 348 | 349 | @property 350 | def id(self) -> str: 351 | return self._name_id 352 | 353 | @property 354 | def axis(self) -> PlotYAxis: 355 | """Set the Y-axis used to display the data series.""" 356 | return self._axis 357 | 358 | @axis.setter 359 | def axis(self, axis: YAxis) -> None: 360 | if hasattr(axis, 'axis'): 361 | self._axis = axis.axis 362 | else: 363 | self._axis = axis 364 | 365 | def update(self, plot: Plot, update_bounds: bool = True) -> None: 366 | x, y = self.pos 367 | xoff, yoff = self.offset 368 | dpgcore.add_text_point( 369 | plot.id, self._name_id, x, y, 370 | vertical=self.vertical, 371 | xoffset=xoff, yoffset=yoff, 372 | update_bounds=update_bounds, 373 | axis=self.axis.index 374 | ) 375 | 376 | __all__ = [ 377 | 'Plot', 378 | ] 379 | -------------------------------------------------------------------------------- /dearpygui_obj/plots/dataseries.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum 4 | from typing import TYPE_CHECKING, NamedTuple 5 | 6 | from dearpygui import dearpygui as dpgcore 7 | from dearpygui_obj.data import import_color_from_dpg, export_color_to_dpg 8 | from dearpygui_obj.wrapper.dataseries import DataSeries, DataSeriesConfig, DataSeriesField 9 | 10 | if TYPE_CHECKING: 11 | from typing import Any, Tuple, Iterable, MutableSequence 12 | from dearpygui_obj.data import ColorRGBA 13 | 14 | 15 | class PlotMarker(Enum): 16 | """Marker shapes that can be used with certain data series.""" 17 | 18 | NoMarker = -1 #: no marker 19 | Circle = 0 #: a circle marker 20 | Square = 1 #: a square maker 21 | Diamond = 2 #: a diamond marker 22 | Up = 3 #: an upward-pointing triangle marker 23 | Down = 4 #: an downward-pointing triangle marker 24 | Left = 5 #: an leftward-pointing triangle marker 25 | Right = 6 #: an rightward-pointing triangle marker 26 | Cross = 7 #: a cross marker (not fillable) 27 | Plus = 8 #: a plus marker (not fillable) 28 | Asterisk = 9 #: a asterisk marker (not fillable) 29 | 30 | class DataSeriesConfigColorRGBA(DataSeriesConfig): 31 | def fvalue(self, config: Any) -> ColorRGBA: 32 | return import_color_from_dpg(config) 33 | def fconfig(self, color: ColorRGBA) -> Any: 34 | return export_color_to_dpg(color) 35 | 36 | class DataSeriesConfigMarker(DataSeriesConfig): 37 | def fvalue(self, config: Any) -> PlotMarker: 38 | return PlotMarker(config) 39 | def fconfig(self, marker: PlotMarker) -> Any: 40 | return marker.value 41 | 42 | class XYData(NamedTuple): 43 | """Common 2D data point used by many data series types.""" 44 | x: float #: X-axis data. 45 | y: float #: Y-axis data. 46 | 47 | 48 | ## Data Series 49 | 50 | class AreaSeries(DataSeries[XYData]): 51 | """Adds an area series to a plot.""" 52 | __update_func__ = dpgcore.add_area_series 53 | __create_record__ = XYData 54 | __data_keywords__ = 'x y' 55 | 56 | x: MutableSequence[float] = DataSeriesField() 57 | y: MutableSequence[float] = DataSeriesField() 58 | 59 | color: ColorRGBA = DataSeriesConfigColorRGBA() 60 | fill: ColorRGBA = DataSeriesConfigColorRGBA() 61 | 62 | weight: float = DataSeriesConfig() 63 | 64 | def __init__(self, label: str, data: Iterable[Any], color: ColorRGBA, fill: ColorRGBA, **config: Any): 65 | super().__init__(label, data, color=color, fill=fill, **config) 66 | 67 | 68 | class BarSeries(DataSeries[XYData]): 69 | """Adds a bar series to a plot.""" 70 | __update_func__ = dpgcore.add_bar_series 71 | __create_record__ = XYData 72 | __data_keywords__ = 'x y' 73 | 74 | x: MutableSequence[float] = DataSeriesField() 75 | y: MutableSequence[float] = DataSeriesField() 76 | 77 | weight: float = DataSeriesConfig() 78 | horizontal: bool = DataSeriesConfig() 79 | 80 | def __init__(self, label: str, data: Iterable[Any], **config: Any): 81 | super().__init__(label, data, **config) 82 | 83 | class CandleSeriesData(NamedTuple): 84 | """Data record type for :class:`.CandleSeries`.""" 85 | date: float #: POSIX timestamp 86 | open: float 87 | high: float 88 | low: float 89 | close: float 90 | 91 | class CandleSeries(DataSeries[CandleSeriesData]): 92 | """Adds a candle series to a plot.""" 93 | __update_func__ = dpgcore.add_candle_series 94 | __create_record__ = CandleSeriesData 95 | __data_keywords__ = 'date opens highs lows closes' 96 | 97 | date: MutableSequence[float] = DataSeriesField() 98 | opens: MutableSequence[float] = DataSeriesField() 99 | highs: MutableSequence[float] = DataSeriesField() 100 | lows: MutableSequence[float] = DataSeriesField() 101 | closes: MutableSequence[float] = DataSeriesField() 102 | 103 | tooltip: bool = DataSeriesConfig() 104 | bull_color: ColorRGBA = DataSeriesConfigColorRGBA() 105 | bear_color: ColorRGBA = DataSeriesConfigColorRGBA() 106 | weight: float = DataSeriesConfig() 107 | 108 | def __init__(self, label: str, data: Iterable[Any], **config: Any): 109 | super().__init__(label, data, **config) 110 | 111 | 112 | class ErrorSeriesData(NamedTuple): 113 | """Data record type for :class:`.ErrorSeries`.""" 114 | x: float 115 | y: float 116 | negative: float 117 | positive: float 118 | 119 | class ErrorSeries(DataSeries[ErrorSeriesData]): 120 | """Adds an error series to a plot.""" 121 | __update_func__ = dpgcore.add_error_series 122 | __create_record__ = ErrorSeriesData 123 | __data_keywords__ = 'x y negative positive' 124 | 125 | 126 | x: MutableSequence[float] = DataSeriesField() 127 | y: MutableSequence[float] = DataSeriesField() 128 | negative: MutableSequence[float] = DataSeriesField() 129 | positive: MutableSequence[float] = DataSeriesField() 130 | 131 | horizontal: bool = DataSeriesConfig() 132 | 133 | def __init__(self, label: str, data: Iterable[Any], **config: Any): 134 | super().__init__(label, data, **config) 135 | 136 | 137 | class HeatSeries(DataSeries[tuple]): 138 | """Adds a heat series to a plot.""" 139 | __update_func__ = dpgcore.add_heat_series 140 | __data_keywords__ = 'values' 141 | 142 | values: MutableSequence[float] = DataSeriesField() 143 | 144 | rows: int = DataSeriesConfig() 145 | columns: int = DataSeriesConfig() 146 | scale_min: float = DataSeriesConfig() 147 | scale_max: float = DataSeriesConfig() 148 | 149 | format: str = DataSeriesConfig() 150 | bounds_min: Tuple[float, float] 151 | bounds_max: Tuple[float, float] 152 | 153 | @DataSeriesConfig(key='bounds_min') 154 | def bounds_min(self, config) -> Tuple[float, float]: 155 | return tuple(config) 156 | 157 | @DataSeriesConfig(key='bounds_max') 158 | def bounds_max(self, config) -> Tuple[float, float]: 159 | return tuple(config) 160 | 161 | def __init__(self, label: str, 162 | values: Iterable[float], 163 | rows: int, columns: int, 164 | scale_min: float, scale_max: float, 165 | **config: Any): 166 | values = ((v,) for v in values) 167 | super().__init__(label, values, rows=rows, columns=columns, scale_min=scale_min, scale_max=scale_max, **config) 168 | 169 | 170 | class HLineSeries(DataSeries[tuple]): 171 | """Adds an infinite horizontal line series to a plot.""" 172 | __update_func__ = dpgcore.add_hline_series 173 | __data_keywords__ = 'x' 174 | 175 | x: MutableSequence[float] = DataSeriesField() 176 | 177 | color: ColorRGBA = DataSeriesConfigColorRGBA() 178 | weight: float = DataSeriesConfig() 179 | 180 | def __init__(self, label: str, x: Iterable[float], **config: Any): 181 | super().__init__(label, ((v,) for v in x), **config) 182 | 183 | 184 | class VLineSeries(DataSeries[tuple]): 185 | """Adds an infinite vertical line series to a plot.""" 186 | __update_func__ = dpgcore.add_vline_series 187 | __data_keywords__ = 'x' 188 | 189 | x: MutableSequence[float] = DataSeriesField() 190 | 191 | color: ColorRGBA = DataSeriesConfigColorRGBA() 192 | weight: float = DataSeriesConfig() 193 | 194 | def __init__(self, label: str, x: Iterable[float], **config: Any): 195 | super().__init__(label, ((v,) for v in x), **config) 196 | 197 | 198 | # TODO 199 | # class ImageSeries(DataSeries): 200 | # """Adds an image series to a plot.""" 201 | 202 | class LineSeries(DataSeries[XYData]): 203 | """Adds a line series to a plot.""" 204 | __update_func__ = dpgcore.add_line_series 205 | __create_record__ = XYData 206 | __data_keywords__ = 'x y' 207 | 208 | x: MutableSequence[float] = DataSeriesField() 209 | y: MutableSequence[float] = DataSeriesField() 210 | 211 | color: ColorRGBA = DataSeriesConfigColorRGBA() 212 | weight: float = DataSeriesConfig() 213 | 214 | def __init__(self, label: str, data: Iterable[Any], **config: Any): 215 | super().__init__(label, data, **config) 216 | 217 | class PieSeriesData(NamedTuple): 218 | """Data record type for :class:`.PieSeries`.""" 219 | value: float 220 | label: str 221 | 222 | class PieSeries(DataSeries[PieSeriesData]): 223 | """Adds a pie chart to a plot.""" 224 | __update_func__ = dpgcore.add_pie_series 225 | __create_record__ = PieSeriesData 226 | __data_keywords__ = 'values labels' 227 | 228 | values: MutableSequence[float] = DataSeriesField() 229 | labels: MutableSequence[str] = DataSeriesField() 230 | 231 | x: float = DataSeriesConfig() 232 | y: float = DataSeriesConfig() 233 | radius: float = DataSeriesConfig() 234 | 235 | normalize: bool = DataSeriesConfig() 236 | angle: bool = DataSeriesConfig() 237 | format: str = DataSeriesConfig() 238 | 239 | def __init__(self, label: str, data: Iterable[Any], 240 | x: float, y: float, radius: float, 241 | **config: Any): 242 | super().__init__(label, data, x=x, y=y, radius=radius, **config) 243 | 244 | class ScatterSeries(DataSeries[XYData]): 245 | """Adds a scatter series to a plot.""" 246 | __update_func__ = dpgcore.add_scatter_series 247 | __create_record__ = XYData 248 | __data_keywords__ = 'x y' 249 | 250 | x: MutableSequence[float] = DataSeriesField() 251 | y: MutableSequence[float] = DataSeriesField() 252 | 253 | marker: PlotMarker = DataSeriesConfigMarker() 254 | size: float = DataSeriesConfig() 255 | weight: float = DataSeriesConfig() 256 | outline: ColorRGBA = DataSeriesConfigColorRGBA() 257 | fill: ColorRGBA = DataSeriesConfigColorRGBA() 258 | xy_data_format: bool = DataSeriesConfig() 259 | 260 | def __init__(self, label: str, data: Iterable[Any], **config: Any): 261 | super().__init__(label, data, **config) 262 | 263 | 264 | class ShadeSeries(DataSeries[XYData]): 265 | """Adds a single-sided shade series to a plot.""" 266 | __update_func__ = dpgcore.add_scatter_series 267 | __create_record__ = XYData 268 | __data_keywords__ = 'x y1' 269 | 270 | x: MutableSequence[float] = DataSeriesField() 271 | y1: MutableSequence[float] = DataSeriesField() 272 | 273 | color: ColorRGBA = DataSeriesConfigColorRGBA() 274 | fill: ColorRGBA = DataSeriesConfigColorRGBA() 275 | weight: float = DataSeriesConfig() 276 | 277 | def __init__(self, label: str, data: Iterable[Any], **config: Any): 278 | super().__init__(label, data, **config) 279 | 280 | 281 | class ShadeRangeData(NamedTuple): 282 | """Data record type for :class:`.ShadeRangeSeries`.""" 283 | x: float 284 | y1: float 285 | y2: float 286 | 287 | class ShadeRangeSeries(DataSeries[ShadeRangeData]): 288 | """Adds a single-sided shade series to a plot.""" 289 | __update_func__ = dpgcore.add_scatter_series 290 | __create_record__ = ShadeRangeData 291 | __data_keywords__ = 'x y1 y2' 292 | 293 | x: MutableSequence[float] = DataSeriesField() 294 | y1: MutableSequence[float] = DataSeriesField() 295 | y2: MutableSequence[float] = DataSeriesField() 296 | 297 | color: ColorRGBA = DataSeriesConfigColorRGBA() 298 | fill: ColorRGBA = DataSeriesConfigColorRGBA() 299 | weight: float = DataSeriesConfig() 300 | 301 | def __init__(self, label: str, data: Iterable[Any], **config: Any): 302 | super().__init__(label, data, **config) 303 | 304 | 305 | class StairSeries(DataSeries[XYData]): 306 | """Add a stair series to a plot.""" 307 | __update_func__ = dpgcore.add_stair_series 308 | __create_record__ = XYData 309 | __data_keywords__ = 'x y' 310 | 311 | x: MutableSequence[float] = DataSeriesField() 312 | y: MutableSequence[float] = DataSeriesField() 313 | 314 | color: ColorRGBA = DataSeriesConfigColorRGBA() 315 | weight: float = DataSeriesConfig() 316 | 317 | def __init__(self, label: str, data: Iterable[Any], **config: Any): 318 | super().__init__(label, data, **config) 319 | 320 | 321 | class StemSeries(DataSeries[XYData]): 322 | """Add a stem series to a plot.""" 323 | __update_func__ = dpgcore.add_stem_series 324 | __create_record__ = XYData 325 | __data_keywords__ = 'x y' 326 | 327 | x: MutableSequence[float] = DataSeriesField() 328 | y: MutableSequence[float] = DataSeriesField() 329 | 330 | marker: PlotMarker = DataSeriesConfigMarker() 331 | size: float = DataSeriesConfig() 332 | weight: float = DataSeriesConfig() 333 | outline: ColorRGBA = DataSeriesConfigColorRGBA() 334 | fill: ColorRGBA = DataSeriesConfigColorRGBA() 335 | 336 | def __init__(self, label: str, data: Iterable[Any], **config: Any): 337 | super().__init__(label, data, **config) 338 | 339 | 340 | __all__ = [ 341 | 'AreaSeries', 342 | 'BarSeries', 343 | 'CandleSeries', 344 | 'CandleSeriesData', 345 | 'ErrorSeries', 346 | 'ErrorSeriesData', 347 | 'HeatSeries', 348 | 'HLineSeries', 349 | 'PieSeries', 350 | 'PieSeriesData', 351 | 'LineSeries', 352 | 'ScatterSeries', 353 | 'ShadeSeries', 354 | 'ShadeRangeSeries', 355 | 'ShadeRangeData', 356 | 'StairSeries', 357 | 'StemSeries', 358 | 'XYData', 359 | 'PlotMarker', 360 | ] 361 | 362 | if TYPE_CHECKING: 363 | from dearpygui_obj.wrapper.dataseries import DataSeries 364 | __all__.append('DataSeries') -------------------------------------------------------------------------------- /dearpygui_obj/tables.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING, overload 3 | 4 | from dearpygui import dearpygui as dpgcore 5 | 6 | from dearpygui_obj import _register_item_type 7 | from dearpygui_obj.wrapper.widget import Widget, ItemWidgetMx, ConfigProperty 8 | 9 | if TYPE_CHECKING: 10 | from typing import Any, Union, Tuple, Iterable, Sequence, List, MutableMapping 11 | 12 | @_register_item_type('mvAppItemType::Table') 13 | class Table(Widget, ItemWidgetMx): 14 | """Adds a simple table that can hold text. 15 | 16 | A Table's data consists of a sequence of rows, each row being a sequence of strings. 17 | 18 | Note that a Table has two different kinds of "columns". A Table will have a number of *data* 19 | columns and a number of *header* columns. 20 | 21 | These won't always match. If you have more data columns than header columns, only a subsection 22 | of the data will actually get shown. This will be the case even if :attr:`hide_headers` is 23 | ``True``. 24 | 25 | Parameters: 26 | headers: can be an iterable of header strings or an integer. If an integer is used, 27 | it will set the number of header columns and the :attr:`hide_headers` property 28 | will be set to ``True``. 29 | 30 | To get/set values in the table, indexing syntax can be used. For example: 31 | 32 | .. code-block:: python 33 | 34 | table[2, 3] = 'cell content' 35 | table[3, :] = ['sets', 'an', 'entire', 'row'] 36 | table[:, 4] = ['sets', 'an', 'entire', 'column'] 37 | table[:, :] = [['first', row'], ['second', 'row'], ['third', 'row]] 38 | 39 | Cell selection state can also be modified in a similar manner. 40 | 41 | .. code-block:: python 42 | 43 | table.selection[1, :] = True # selects the entire second row. 44 | 45 | """ 46 | 47 | hide_headers: bool = ConfigProperty() #: If ``True``, table headers will not be displayed. 48 | 49 | #: A :class:`.TableSelection` instance that can be used to get or modify the table's cell 50 | #: selection state. 51 | selected: TableSelection 52 | 53 | def __init__(self, headers: Union[int, Iterable[str]] = 2, **config: Any): 54 | if isinstance(headers, int): 55 | super().__init__(headers=['' for i in range(headers)], hide_headers=True, **config) 56 | else: 57 | super().__init__(headers=headers, **config) 58 | self.selected = TableSelection(self) 59 | 60 | def __setup_add_widget__(self, dpg_args: MutableMapping[str, Any]) -> None: 61 | dpgcore.add_table(self.id, **dpg_args) 62 | 63 | def set_headers(self, headers: Union[Iterable[str], int]) -> None: 64 | """Set the table headers. 65 | 66 | This determines the number of displayed columns (distinct from the number of data columns!). 67 | If an integer is passed, the headers will be replaced with empty strings and hidden.""" 68 | if isinstance(headers, int): 69 | headers = ['' for i in range(headers)] 70 | self.hide_headers = True 71 | dpgcore.set_headers(self.id, headers) 72 | 73 | def _get_data(self) -> List[List[str]]: 74 | return dpgcore.get_table_data(self.id) 75 | 76 | @property 77 | def rows(self) -> int: 78 | """The number of data rows.""" 79 | return len(self._get_data()) 80 | 81 | @property 82 | def columns(self) -> int: 83 | """The number of data columns.""" 84 | data = self._get_data() 85 | if len(data): 86 | return len(data[0]) 87 | return 0 88 | 89 | @overload 90 | def __getitem__(self, indices: Tuple[int, int]) -> str: 91 | ... 92 | 93 | @overload 94 | def __getitem__(self, indices: Tuple[int, slice]) -> Sequence[str]: 95 | ... 96 | 97 | @overload 98 | def __getitem__(self, indices: Tuple[slice, int]) -> Sequence[str]: 99 | ... 100 | 101 | @overload 102 | def __getitem__(self, indices: Tuple[slice, slice]) -> Sequence[Sequence[str]]: 103 | ... 104 | 105 | def __getitem__(self, indices): 106 | """Get table data using indices or slices.""" 107 | row_idx, col_idx = indices 108 | if isinstance(row_idx, slice) and isinstance(col_idx, slice): 109 | return tuple(tuple(row[col_idx]) for row in self._get_data()[row_idx]) 110 | elif isinstance(row_idx, slice): 111 | return tuple(row[col_idx] for row in self._get_data()[row_idx]) 112 | elif isinstance(col_idx, slice): 113 | return tuple(self._get_data()[row_idx][col_idx]) 114 | else: 115 | return dpgcore.get_table_item(self.id, row_idx, col_idx) 116 | 117 | 118 | @overload 119 | def __setitem__(self, indices: Tuple[int, int], value: str) -> None: 120 | ... 121 | 122 | @overload 123 | def __setitem__(self, indices: Tuple[int, slice], value: Iterable[str]) -> None: 124 | ... 125 | 126 | @overload 127 | def __setitem__(self, indices: Tuple[slice, int], value: Iterable[str]) -> None: 128 | ... 129 | 130 | @overload 131 | def __setitem__(self, indices: Tuple[slice, slice], value: Iterable[Iterable[str]]) -> None: 132 | ... 133 | 134 | def __setitem__(self, indices, value): 135 | """Set table data using indices or slices. 136 | The shape of the **value** argument must match the provided indices/slices.""" 137 | row_idx, col_idx = indices 138 | 139 | ## both row_idx and col_idx are slices. value is an iterable of iterables 140 | if isinstance(row_idx, slice) and isinstance(col_idx, slice): 141 | if row_idx == slice(None) and col_idx == slice(None): 142 | data = value # overwrite entire table data 143 | else: 144 | data = self._get_data() # overwrite just sliced rows and columns 145 | for data_row, set_row in zip(data[row_idx], value): 146 | data_row[col_idx] = set_row 147 | dpgcore.set_table_data(self.id, data) 148 | 149 | ## just row_idx is a slice. value is an iterable 150 | elif isinstance(row_idx, slice): 151 | data = self._get_data() 152 | for row, s in zip(data[row_idx], value): 153 | row[col_idx] = s 154 | dpgcore.set_table_data(self.id, data) 155 | 156 | ## just col_idx is a slice. value is an iterable 157 | elif isinstance(col_idx, slice): 158 | data = self._get_data() 159 | data[row_idx][col_idx] = value 160 | dpgcore.set_table_data(self.id, data) 161 | 162 | ## neither are slices 163 | else: 164 | dpgcore.set_table_item(self.id, row_idx, col_idx, value) 165 | 166 | def clear(self) -> None: 167 | """Clear the table. 168 | 169 | This will remove all rows from the table. 170 | It does NOT change the table headers and therefore the number of visible columns.""" 171 | dpgcore.clear_table(self.id) 172 | 173 | def append_row(self, row: Iterable[str]) -> None: 174 | dpgcore.add_row(self.id, list(row)) 175 | 176 | def insert_row(self, row_idx: int, row: Iterable[str]) -> None: 177 | dpgcore.insert_row(self.id, row_idx, list(row)) 178 | 179 | def remove_row(self, row_idx: int) -> None: 180 | dpgcore.delete_row(self.id, row_idx) 181 | 182 | def append_column(self, header: str, column: Iterable[str]) -> None: 183 | dpgcore.add_column(self.id, header, list(column)) 184 | 185 | def insert_column(self, col_idx: int, header: str, column: Iterable[str]) -> None: 186 | dpgcore.insert_column(self.id, col_idx, header, list(column)) 187 | 188 | def remove_column(self, col_idx: int) -> None: 189 | dpgcore.delete_column(self.id, col_idx) 190 | 191 | 192 | class TableSelection: 193 | """Get/set which cells are selected in a :class:`.Table`.""" 194 | def __init__(self, table: Table): 195 | self.table = table 196 | 197 | def __iter__(self) -> Iterable[Tuple[int, int]]: 198 | """Iterate the selected cells as ``(row, col)`` pairs.""" 199 | for pair in dpgcore.get_table_selections(self.table.id): 200 | yield tuple(pair) 201 | 202 | def __setitem__(self, indices: Tuple[Union[int, slice], Union[int, slice]], selected: bool) -> None: 203 | """Modify the cell selection state. 204 | 205 | Uses the same syntax as table indexing, allowing the selection of multiple cells to be 206 | modified at once using slices.""" 207 | row_idx, col_idx = indices 208 | 209 | if isinstance(row_idx, slice) and isinstance(col_idx, slice): 210 | ## both row_idx and col_idx are slices 211 | for i in range(*row_idx.indices(self.table.rows)): 212 | for j in range(*col_idx.indices(self.table.columns)): 213 | dpgcore.set_table_selection(self.table.id, i, j, selected) 214 | 215 | elif isinstance(row_idx, slice): 216 | ## just row_idx is a slice. value is a sequence 217 | for i in range(*row_idx.indices(self.table.rows)): 218 | dpgcore.set_table_selection(self.table.id, i, col_idx, selected) 219 | 220 | ## just col_idx is a slice. value is a sequence 221 | elif isinstance(col_idx, slice): 222 | for j in range(*col_idx.indices(self.table.columns)): 223 | dpgcore.set_table_selection(self.table.id, row_idx, j, selected) 224 | 225 | else: 226 | ## neither are slices 227 | dpgcore.set_table_selection(self.table.id, row_idx, col_idx, selected) 228 | 229 | 230 | __all__ = [ 231 | 'Table', 232 | ] -------------------------------------------------------------------------------- /dearpygui_obj/userwidget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import TYPE_CHECKING, Any 5 | 6 | from dearpygui import dearpygui as dpgcore 7 | from dearpygui_obj.wrapper.widget import Widget, ItemWidgetMx, ContainerWidgetMx 8 | 9 | class UserWidget(Widget, ItemWidgetMx, ABC): 10 | """An abstract base class that is used to create custom widgets. 11 | 12 | This class provides a way to create custom widgets that are composed of other widgets. 13 | This can be useful for creating custom complex controls that you want to use like a single widget. 14 | 15 | Note that while the user widget is actually a container (it has to be, to hold the user's custom 16 | content), it not meant to be used as a :class:`.ContainerWidgetMx`. It does not have any of the 17 | container widget methods like :meth:`.ContainerWidgetMx.add_child` and cannot be used as a context 18 | manager. 19 | 20 | This makes it ideal for custom controls whose contents are 'closed' and not meant to have 21 | abitrary additional widgets added to them as children. 22 | For custom 'open' containers, see :class:`.UserContainer` (not implemented yet). 23 | 24 | By default, any positional arguments passed to ``__init__()`` and any keyword arguments 25 | that are not reserved by :class:`Widget` or :class:`ItemWidgetMx` will be passed to the 26 | :meth:`__setup_content__` method. 27 | 28 | This method should be overriden in subclasses to actually create the contents of the custom 29 | widget. Furthermore, it is a good practice that subclasses define their own signature for 30 | :meth:`__setup_widget__` that narrows down the 31 | arguments to the ones they actually need.""" 32 | 33 | def __init__(self, *args: Any, parent: str = 'None', before: str = None, **kwargs: Any): 34 | super().__init__(parent=parent, before=before) 35 | self.__setup_content__(*args, **kwargs) 36 | dpgcore.end() 37 | 38 | def __setup_add_widget__(self, dpg_args) -> None: 39 | for kw in ('before', 'parent'): 40 | if dpg_args[kw] is None: 41 | del dpg_args[kw] 42 | 43 | dpgcore.add_group(self.id, **dpg_args) 44 | 45 | @abstractmethod 46 | def __setup_content__(self, *args, **kwargs) -> None: 47 | """The contents of the UserWidget should be added here.""" 48 | ... 49 | 50 | 51 | class UserContainer(Widget, ItemWidgetMx, ContainerWidgetMx[Any], ABC): 52 | """An abstract base class that is used to create custom containers. 53 | 54 | This class is not yet implemented, as it requires adding to the parent stack which 55 | is not yet available in DPG 0.6.""" 56 | 57 | def __new__(cls, *args, **kwargs): 58 | raise NotImplementedError 59 | 60 | __all__ = [ 61 | 'UserWidget', 62 | ] -------------------------------------------------------------------------------- /dearpygui_obj/widgets.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING 3 | 4 | if TYPE_CHECKING: 5 | pass 6 | 7 | from dearpygui_obj.window import * 8 | from dearpygui_obj.basic import * 9 | from dearpygui_obj.tables import * 10 | from dearpygui_obj.input import * 11 | from dearpygui_obj.containers import * 12 | from dearpygui_obj.layout import * 13 | from dearpygui_obj.userwidget import * 14 | from dearpygui_obj.plots import * 15 | from dearpygui_obj.drawing import * 16 | from dearpygui_obj.node import * 17 | from dearpygui_obj.devtools import * 18 | 19 | __all__ = [ 20 | 'MainWindow', 21 | 'Window', 22 | 'MenuBar', 23 | 24 | 'Text', 25 | 'LabelText', 26 | 'Separator', 27 | 'ButtonArrow', 28 | 'Button', 29 | 'Checkbox', 30 | 'Selectable', 31 | 'RadioButtons', 32 | 'ComboHeightMode', 33 | 'Combo', 34 | 'ListBox', 35 | 'ProgressBar', 36 | 37 | 'Table', 38 | 39 | 'InputText', 40 | 'InputFloat', 41 | 'InputFloat2', 42 | 'InputFloat3', 43 | 'InputFloat4', 44 | 'InputInt', 45 | 'InputInt2', 46 | 'InputInt3', 47 | 'InputInt4', 48 | 'SliderFloat', 49 | 'SliderFloat2', 50 | 'SliderFloat3', 51 | 'SliderFloat4', 52 | 'SliderInt', 53 | 'SliderInt2', 54 | 'SliderInt3', 55 | 'SliderInt4', 56 | 'DragFloat', 57 | 'DragFloat2', 58 | 'DragFloat3', 59 | 'DragFloat4', 60 | 'DragInt', 61 | 'DragInt2', 62 | 'DragInt3', 63 | 'DragInt4', 64 | 'ColorButton', 65 | 'ColorFormatMode', 66 | 'ColorEdit', 67 | 'ColorPicker', 68 | 'DatePickerMode', 69 | 'DatePicker', 70 | 71 | 'TreeNode', 72 | 'TreeNodeHeader', 73 | 'TabBar', 74 | 'TabItem', 75 | 'TabButton', 76 | 'TabOrderMode', 77 | 'Menu', 78 | 'MenuItem', 79 | 'Popup', 80 | 'PopupInteraction', 81 | 82 | 'VSpacing', 83 | 'HAlignNext', 84 | 'group_horizontal', 85 | 'Group', 86 | 'IndentLayout', 87 | 'ColumnLayout', 88 | 'ChildView', 89 | 'Dummy', 90 | 91 | 'UserWidget', 92 | 93 | 'SimplePlot', 94 | 'Plot', 95 | 96 | 'Drawing', 97 | 98 | 'NodeEditor', 99 | 'Node', 100 | 'NodeAttribute', 101 | 'NodeLink', 102 | 'NodeAttributeType', 103 | 'input_attribute', 104 | 'output_attribute', 105 | 'static_attribute', 106 | 107 | 'DebugWindow', 108 | 'MetricsWindow', 109 | 'StyleEditorWindow', 110 | 'DocumentationWindow', 111 | 'AboutWindow', 112 | ] 113 | 114 | if TYPE_CHECKING: 115 | from dearpygui_obj.wrapper.widget import Widget, ItemWidgetMx, ValueWidgetMx 116 | __all__.extend([ 117 | 'Widget', 118 | 'ItemWidgetMx', 119 | 'ValueWidgetMx', 120 | ]) -------------------------------------------------------------------------------- /dearpygui_obj/window.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from dearpygui import dearpygui as dpgcore 6 | from dearpygui_obj import _register_item_type, wrap_callback 7 | from dearpygui_obj.wrapper.widget import Widget, ContainerWidgetMx, ItemWidgetMx, ConfigProperty 8 | from dearpygui_obj.drawing import DrawingCanvas, WindowCanvas 9 | 10 | if TYPE_CHECKING: 11 | from typing import Optional, Tuple, Callable 12 | from dearpygui_obj import PyGuiCallback 13 | from dearpygui_obj.wrapper.widget import ItemConfigData 14 | 15 | class MainWindow: 16 | """Container for static functions used to manipulate the main window. 17 | 18 | Attempting to instantiate this class will raise a :class:`TypeError`. 19 | 20 | Warning: 21 | The viewport only exists when the GUI engine is running 22 | (see :func:`dearpygui_obj.is_running`). Some of the methods 23 | contained in this class may fail if called when the engine 24 | is not running. 25 | """ 26 | 27 | def __new__(cls, *args, **kwargs): 28 | raise TypeError('this class may not be instantiated') 29 | 30 | @staticmethod 31 | def set_title(title: str) -> None: 32 | dpgcore.set_main_window_title(title) 33 | 34 | @staticmethod 35 | def set_pos(x: int, y: int) -> None: 36 | dpgcore.set_main_window_pos(x, y) 37 | 38 | @staticmethod 39 | def allow_resize(enabled: bool): 40 | dpgcore.set_main_window_resizable(enabled) 41 | 42 | @staticmethod 43 | def set_size(width: int, height: int): 44 | dpgcore.set_main_window_size(width, height) 45 | 46 | @staticmethod 47 | def get_size() -> Tuple[int, int]: 48 | return tuple(dpgcore.get_main_window_size()) 49 | 50 | @staticmethod 51 | def set_primary_window(window: Optional[Window]) -> None: 52 | """Set a window as the primary window, or remove the primary window. 53 | 54 | When a window is set as the primary window it will fill the entire viewport. 55 | 56 | If any other window was already set as the primary window, it will be unset. 57 | """ 58 | if window is not None: 59 | dpgcore.set_primary_window(window.id, True) 60 | else: 61 | dpgcore.set_primary_window('', False) 62 | 63 | @staticmethod 64 | def set_resize_callback(callback: Callable): 65 | """Set a callback for when the main viewport is resized.""" 66 | dpgcore.set_resize_callback(callback, handler='') 67 | 68 | @staticmethod 69 | def enable_docking(**kwargs): 70 | """Enable docking and set docking options. 71 | 72 | Note: 73 | Once docking is enabled, it cannot be disabled. 74 | 75 | Keyword Arguments: 76 | shift_only: if ``True``, hold down shift for docking. 77 | If ``False``, dock by dragging window titlebars. 78 | dock_space: if ``True``, windows will be able to dock 79 | with the main window viewport. 80 | """ 81 | dpgcore.enable_docking(**kwargs) 82 | 83 | class _ForegroundCanvas(DrawingCanvas): 84 | id = '##FOREGROUND' 85 | 86 | class _BackgroundCanvas(DrawingCanvas): 87 | id = '##BACKGROUND' 88 | 89 | #: A special :class:`.DrawingCanvas` that can be used to draw on the foreground of the viewport. 90 | foreground = _ForegroundCanvas() 91 | 92 | #: A special :class:`.DrawingCanvas` that can be used to draw on the background of the viewport. 93 | background = _BackgroundCanvas() 94 | 95 | 96 | @_register_item_type('mvAppItemType::Window') 97 | class Window(Widget, ContainerWidgetMx['Window']): 98 | """Creates a new window.""" 99 | 100 | label: str = ConfigProperty() 101 | x_pos: int = ConfigProperty() 102 | y_pos: int = ConfigProperty() 103 | autosize: bool = ConfigProperty() 104 | 105 | no_resize: bool = ConfigProperty() 106 | no_title_bar: bool = ConfigProperty() 107 | no_move: bool = ConfigProperty() 108 | no_collapse: bool = ConfigProperty() 109 | no_focus_on_appearing: bool = ConfigProperty() 110 | no_bring_to_front_on_focus: bool = ConfigProperty() 111 | no_close: bool = ConfigProperty() 112 | no_background: bool = ConfigProperty() 113 | 114 | show_menubar: bool = ConfigProperty(key='menubar') 115 | 116 | #: Disable scrollbars (can still scroll with mouse or programmatically). 117 | no_scrollbar: bool = ConfigProperty() 118 | 119 | #: Allow horizontal scrollbar to appear. 120 | horizontal_scrollbar: bool = ConfigProperty() 121 | 122 | pos: Tuple[int, int] 123 | @ConfigProperty() 124 | def pos(self) -> Tuple[int, int]: 125 | """Get or set (x_pos, y_pos) as a tuple.""" 126 | config = self.get_config() 127 | return config['x_pos'], config['y_pos'] 128 | 129 | @pos.getconfig 130 | def pos(self, value: Tuple[int, int]) -> ItemConfigData: 131 | width, height = value 132 | return {'x_pos': width, 'y_pos' : height} 133 | 134 | def __init__(self, label: str = None, **config): 135 | """ 136 | Parameters: 137 | label: window label. 138 | """ 139 | super().__init__(label=label, **config) 140 | 141 | def __setup_add_widget__(self, dpg_args) -> None: 142 | dpgcore.add_window(self.id, on_close=self._on_close, **dpg_args) 143 | 144 | ## workaround for the fact that you can't set the on_close callback in DPG 145 | _on_close_callback: Optional[Callable] = None 146 | def _on_close(self, sender, data) -> None: 147 | if self._on_close_callback is not None: 148 | self._on_close_callback(sender, data) 149 | 150 | def on_close(self, callback: Optional[PyGuiCallback]) -> Callable: 151 | """Set on_close callback, can be used as a decorator.""" 152 | if callback is not None: 153 | callback = wrap_callback(callback) 154 | self._on_close_callback = callback 155 | return callback 156 | 157 | def resized(self, callback: PyGuiCallback) -> Callable: 158 | """Set resized callback, can be used as a decorator.""" 159 | dpgcore.set_resize_callback(wrap_callback(callback), handler=self.id) 160 | return callback 161 | 162 | def get_canvas(self) -> WindowCanvas: 163 | """Obtain a :class:`.WindowCanvas` that can be used to draw on the window using the drawing API.""" 164 | return WindowCanvas(self) 165 | 166 | 167 | ## Menu Bars and Menus 168 | 169 | @_register_item_type('mvAppItemType::MenuBar') 170 | class MenuBar(Widget, ItemWidgetMx, ContainerWidgetMx['MenuBar']): 171 | """A menu bar that can be added to a :class:`.Window`.""" 172 | 173 | def __init__(self, **config): 174 | super().__init__(**config) 175 | 176 | def __setup_add_widget__(self, dpg_args) -> None: 177 | dpgcore.add_menu_bar(self.id, **dpg_args) 178 | 179 | 180 | __all__ = [ 181 | 'MainWindow', 182 | 'Window', 183 | 'MenuBar', 184 | ] -------------------------------------------------------------------------------- /dearpygui_obj/wrapper/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import TYPE_CHECKING 3 | 4 | if TYPE_CHECKING: 5 | pass 6 | -------------------------------------------------------------------------------- /dearpygui_obj/wrapper/dataseries.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import TYPE_CHECKING, TypeVar, Sequence, MutableSequence, overload 5 | 6 | from dearpygui_obj import _generate_id 7 | from dearpygui_obj.plots import Plot 8 | if TYPE_CHECKING: 9 | from typing import Any, Optional, Type, Callable, Mapping, Iterable, NoReturn 10 | from dearpygui_obj.plots import PlotYAxis, YAxis 11 | 12 | ConvertFunc = Callable[[Any], Any] 13 | 14 | 15 | class DataSeriesConfig: 16 | """Descriptor used to get/set non-data config properties for :class:`DataSeries` objects.""" 17 | 18 | def __init__(self, key: str = None): 19 | self.key = key 20 | 21 | def __set_name__(self, owner: Type[DataSeries], name: str): 22 | if self.key is None: 23 | self.key = name 24 | 25 | def __get__(self, instance: Optional[DataSeries], owner: Type[DataSeries]) -> Any: 26 | if instance is None: 27 | return self 28 | return self.fvalue(instance.get_config(self.key)) 29 | 30 | def __set__(self, instance: DataSeries, value: Any) -> None: 31 | instance.set_config(self.key, self.fconfig(value)) 32 | 33 | def __call__(self, fvalue: ConvertFunc): 34 | """Allows the DataSeriesConfig itself to be used as a decorator equivalent to :attr:`getvalue`.""" 35 | return self.getvalue(fvalue) 36 | 37 | def getvalue(self, fvalue: ConvertFunc): 38 | self.fvalue = fvalue 39 | self.__doc__ = fvalue.__doc__ # use the docstring of the getter, the same way property() works 40 | return self 41 | 42 | def getconfig(self, fconfig: ConvertFunc): 43 | self.fconfig = fconfig 44 | return self 45 | 46 | ## default implementations 47 | fvalue: ConvertFunc 48 | fconfig: ConvertFunc 49 | 50 | def fvalue(self, config: Any) -> Any: 51 | return config 52 | 53 | def fconfig(self, value: Any) -> Any: 54 | return value 55 | 56 | 57 | TValue = TypeVar('TValue') 58 | class DataSeriesCollection(MutableSequence[TValue]): 59 | """Collection type that allows set/get of individual data fields of a data series. 60 | 61 | Individual data fields are read-write. Appending, inserting, or deleting individual fields 62 | is not permitted however. Any operations that change the length of the sequence will raise a 63 | :class:`TypeError`.""" 64 | 65 | def __init__(self, series: DataSeries, key: int): 66 | self.series = series 67 | self.key = key 68 | 69 | def __len__(self) -> int: 70 | return len(self.series._data[self.key]) 71 | 72 | def __iter__(self) -> Iterable[TValue]: 73 | return iter(self.series._data[self.key]) 74 | 75 | @overload 76 | def __getitem__(self, index: int) -> TValue: 77 | ... 78 | 79 | @overload 80 | def __getitem__(self, index: slice) -> Iterable[TValue]: 81 | ... 82 | 83 | def __getitem__(self, index): 84 | """Get values for a particular data field using index or slice.""" 85 | return self.series._data[self.key][index] 86 | 87 | @overload 88 | def __setitem__(self, index: int, value: TValue) -> None: 89 | ... 90 | 91 | @overload 92 | def __setitem__(self, index: slice, value: Iterable[TValue]) -> None: 93 | ... 94 | 95 | def __setitem__(self, index, value): 96 | """Set values for a particular data field using index or slice. 97 | 98 | Raises: 99 | TypeError: if the slice assignment would change the length of the sequence.""" 100 | if isinstance(index, slice): 101 | self._set_slice(index, value) 102 | else: 103 | self.series._data[self.key][index] = value 104 | 105 | # raises a TypeError if the slice will change the length of the sequence 106 | def _set_slice(self, s: slice, value: Iterable) -> None: 107 | if not hasattr(value, '__len__'): 108 | value = list(value) 109 | if len(range(*s.indices(len(self)))) != len(value): 110 | raise TypeError('cannot change length of individual DataSeries field') 111 | self.series._data[self.key][s] = value 112 | 113 | def __delitem__(self, index: Any) -> NoReturn: 114 | """Always raises :class:`.TypeError`.""" 115 | raise TypeError('cannot change length of individual DataSeries field') 116 | 117 | def insert(self, index: int, value: TValue) -> NoReturn: 118 | """Always raises :class:`.TypeError`.""" 119 | raise TypeError('cannot change length of individual DataSeries field') 120 | 121 | class DataSeriesField: 122 | """Supports assignment to a DataSeries' data field attributes.""" 123 | def __set_name__(self, owner: Type[DataSeries], name: str): 124 | self.name = '_' + name 125 | self.__doc__ = f"Access '{name}' data as a linear sequence." 126 | 127 | def __get__(self, instance: DataSeries, owner: Type[DataSeries] = None) -> Any: 128 | if instance is None: 129 | return self 130 | return getattr(instance, self.name) 131 | 132 | def __set__(self, instance: DataSeries, value: Any) -> None: 133 | raise AttributeError('can\'t set attribute') 134 | 135 | TRecord = TypeVar('TRecord', bound=Sequence) 136 | class DataSeries(ABC, MutableSequence[TRecord]): 137 | """Abstract base class for plot data series.""" 138 | 139 | @property 140 | @abstractmethod 141 | def __update_func__(self) -> Callable: 142 | """The DPG function used to add/update the data series.""" 143 | ... 144 | 145 | @staticmethod 146 | def __create_record__(*values: Any) -> TRecord: 147 | """Factory function used to create records when retrieving individual data points.""" 148 | return tuple(values) 149 | 150 | #: The keywords used to give the data to the DPG ``add_*_series()`` function. 151 | #: The order of keywords is used when creating records using :meth:`__create_record__`. 152 | __data_keywords__: Sequence[str] 153 | 154 | @classmethod 155 | def _get_data_keywords(cls) -> Iterable[str]: 156 | if cls.__data_keywords__ is None: 157 | # noinspection PyUnresolvedReferences 158 | return cls._record_type._fields 159 | if isinstance(cls.__data_keywords__, str): 160 | cls.__data_keywords__ = cls.__data_keywords__.split() 161 | return cls.__data_keywords__ 162 | 163 | @classmethod 164 | def _get_config_properties(cls) -> Mapping[str, DataSeriesConfig]: 165 | config_properties = cls.__dict__.get('_config_properties') 166 | if config_properties is None: 167 | config_properties = {} 168 | for name in dir(cls): 169 | value = getattr(cls, name) 170 | if isinstance(value, DataSeriesConfig): 171 | config_properties[name] = value 172 | setattr(cls, '_config_properties', config_properties) 173 | return config_properties 174 | 175 | def __init__(self, label: str, data: Iterable, *, axis: YAxis = Plot.yaxis, **config: Any): 176 | self.axis = axis 177 | self._name_id = label + '##' + _generate_id(self) 178 | self._data = [] 179 | self._config = {} 180 | 181 | ## create data fields from record type 182 | for index, name in enumerate(self._get_data_keywords()): 183 | self._data.append([]) 184 | field = DataSeriesCollection(self, index) 185 | setattr(self, '_' + name, field) 186 | 187 | ## non-data config properties 188 | props = self._get_config_properties() 189 | for prop_name, value in config.items(): 190 | prop = props.get(prop_name) 191 | if prop is None: 192 | raise AttributeError(f"no config property named '{prop_name}'") 193 | prop.__set__(self, value) 194 | 195 | self.extend(data) 196 | 197 | @property 198 | def id(self) -> str: 199 | return self._name_id 200 | 201 | @property 202 | def axis(self) -> PlotYAxis: 203 | """Set the Y-axis used to display the data series.""" 204 | return self._axis 205 | 206 | @axis.setter 207 | def axis(self, axis: YAxis) -> None: 208 | if hasattr(axis, 'axis'): 209 | self._axis = axis.axis 210 | else: 211 | self._axis = axis 212 | 213 | def get_config(self, key: str) -> Any: 214 | """Get a config value.""" 215 | return self._config[key] 216 | 217 | def set_config(self, key: str, value: Any) -> None: 218 | """Set a config value.""" 219 | self._config[key] = value 220 | 221 | def update(self, plot: Plot, update_bounds: bool = True) -> None: 222 | """Updates a plot with this DataSeries. 223 | 224 | If this DataSeries has not been added to the plot before, this method will add it. 225 | 226 | Any changes made to a DataSeries's properties will only take effect when this method is called. 227 | 228 | Parameters: 229 | plot: the :class:`.Plot` to update. 230 | update_bounds: also update plot bounds if ``True``. 231 | """ 232 | self.__update_func__( 233 | plot.id, self.id, axis=self._axis.index, update_bounds=update_bounds, 234 | **self._config, **dict(zip(self._get_data_keywords(), self._data)) 235 | ) 236 | 237 | ## Mutable Sequence Implementation 238 | 239 | def __len__(self) -> int: 240 | """Get the size of the data series.""" 241 | return len(self._data[0]) # they should all be the same length 242 | 243 | def __iter__(self) -> Iterable[TRecord]: 244 | """Iterate the data series.""" 245 | for values in zip(*self._data): 246 | yield self.__create_record__(*values) 247 | 248 | # def _iter_slice(self, iterable: Iterable, s: slice) -> Iterable: 249 | # return itertools.islice(iterable, s.start or 0, ) 250 | 251 | @overload 252 | def __getitem__(self, index: int) -> TRecord: 253 | ... 254 | 255 | @overload 256 | def __getitem__(self, index: slice) -> Iterable[TRecord]: 257 | ... 258 | 259 | def __getitem__(self, index): 260 | """Get data from the dataseries using an index or slice.""" 261 | if isinstance(index, slice): 262 | return ( 263 | self.__create_record__(*values) 264 | for values in zip(*(field[index] for field in self._data)) 265 | ) 266 | else: 267 | return self.__create_record__(*(field[index] for field in self._data)) 268 | 269 | @overload 270 | def __setitem__(self, index: int, item: TRecord) -> None: 271 | ... 272 | 273 | @overload 274 | def __setitem__(self, index: slice, item: Iterable[TRecord]) -> None: 275 | ... 276 | 277 | def __setitem__(self, index, item) -> None: 278 | """Modify the data series using an index or slice.""" 279 | if isinstance(index, slice): 280 | item = zip(*item) 281 | for field_idx, value in enumerate(item): 282 | self._data[field_idx][index] = value 283 | 284 | @overload 285 | def __delitem__(self, index: int) -> None: ... 286 | 287 | @overload 288 | def __delitem__(self, index: slice) -> None: ... 289 | 290 | def __delitem__(self, index): 291 | for seq in self._data: 292 | del seq[index] 293 | 294 | def insert(self, index: int, item: Any) -> None: 295 | for field_idx, value in enumerate(item): 296 | self._data[field_idx].insert(index, value) 297 | 298 | def append(self, item: Any) -> None: 299 | for field_idx, value in enumerate(item): 300 | self._data[field_idx].append(value) 301 | 302 | def extend(self, items: Iterable[Any]) -> None: 303 | # unzip into 1D sequences for each field 304 | for field_idx, values in enumerate(zip(*items)): 305 | self._data[field_idx].extend(values) 306 | 307 | # these work because tuples typically have value semantics 308 | def index(self, item: Any, start: int = None, stop: int = None) -> int: 309 | # improve on the naive default implementation by zipping everything up front 310 | data = (s[start:stop] for s in self._data) 311 | for idx, row in enumerate(zip(*data)): 312 | if row == item: 313 | return idx 314 | raise ValueError(f'{item} is not in {self.__class__.__name__}') 315 | 316 | def remove(self, item: Any) -> None: 317 | for idx, row in enumerate(zip(*self._data)): 318 | if row == item: 319 | del self[idx] 320 | return 321 | 322 | def clear(self) -> None: 323 | for seq in self._data: 324 | seq.clear() 325 | -------------------------------------------------------------------------------- /dearpygui_obj/wrapper/drawing.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from abc import ABC, abstractmethod 4 | from typing import TYPE_CHECKING 5 | 6 | from dearpygui import dearpygui as dpgcore 7 | 8 | from dearpygui_obj import _generate_id 9 | 10 | if TYPE_CHECKING: 11 | from typing import Any, Optional, Type, Callable, Mapping 12 | from dearpygui_obj.drawing import DrawingCanvas 13 | 14 | DrawConfigData = Mapping[str, Any] 15 | GetDrawValueFunc = Callable[['DrawCommand'], Any] 16 | GetDrawConfigFunc = Callable[['DrawCommand', Any], DrawConfigData] 17 | 18 | class DrawProperty: 19 | """Descriptor used to get or set a draw command's configuration.""" 20 | 21 | def __init__(self, 22 | key: Optional[str] = None, *, 23 | doc: str = ''): 24 | """ 25 | Parameters: 26 | key: the config key to get/set with the default implementation. 27 | doc: custom docstring. 28 | """ 29 | self.owner = None 30 | self.key = key 31 | self.__doc__ = doc 32 | 33 | def __set_name__(self, owner: Type[DrawCommand], name: str): 34 | self.owner = owner 35 | self.name = name 36 | 37 | if self.key is None: 38 | self.key = name 39 | 40 | if not self.__doc__: 41 | self.__doc__ = f"Read or modify the '{self.key}' config field." 42 | 43 | def __get__(self, instance: Optional[DrawCommand], owner: Type[DrawCommand]) -> Any: 44 | if instance is None: 45 | return self 46 | return self.fvalue(instance) 47 | 48 | def __set__(self, instance: DrawCommand, value: Any) -> None: 49 | config = self.fconfig(instance, value) 50 | dpgcore.modify_draw_command(instance.canvas.id, instance.id, **config) 51 | 52 | def __call__(self, fvalue: GetDrawValueFunc): 53 | """Allows the ConfigProperty itself to be used as a decorator equivalent to :attr:`getvalue`.""" 54 | return self.getvalue(fvalue) 55 | 56 | def getvalue(self, fvalue: GetDrawValueFunc): 57 | self.fvalue = fvalue 58 | self.__doc__ = fvalue.__doc__ # use the docstring of the getter, the same way property() works 59 | return self 60 | 61 | def getconfig(self, fconfig: GetDrawConfigFunc): 62 | self.fconfig = fconfig 63 | return self 64 | 65 | ## default implementations 66 | fvalue: GetDrawValueFunc 67 | fconfig: GetDrawConfigFunc 68 | 69 | def fvalue(self, instance: DrawCommand) -> Any: 70 | return dpgcore.get_draw_command(instance.canvas.id, instance.id)[self.key] 71 | 72 | def fconfig(self, instance: DrawCommand, value: Any) -> DrawConfigData: 73 | return {self.key : value} 74 | 75 | 76 | class DrawCommand(ABC): 77 | """Base class for drawing commands.""" 78 | 79 | @classmethod 80 | def _get_draw_properties(cls) -> Mapping[str, DrawProperty]: 81 | draw_properties = cls.__dict__.get('_draw_properties') 82 | if draw_properties is None: 83 | draw_properties = {} 84 | # must match order in annotations 85 | for name in cls.__annotations__: 86 | value = getattr(cls, name) 87 | if isinstance(value, DrawProperty): 88 | draw_properties[name] = value 89 | setattr(cls, '_draw_properties', draw_properties) 90 | return draw_properties 91 | 92 | _tag_id: str = None 93 | def __init__(self, canvas: DrawingCanvas, *args, tag_id: str = None, **kwargs: Any): 94 | self._canvas = canvas 95 | if tag_id is not None: 96 | self._tag_id = tag_id 97 | else: 98 | self._tag_id = _generate_id(self) 99 | 100 | props = self._get_draw_properties() 101 | draw_data = {} 102 | for prop, value in zip(props.values(), args): 103 | draw_data.update(prop.fconfig(self, value)) 104 | 105 | for name, value in kwargs.items(): 106 | prop = props.get(name) 107 | if prop is not None: 108 | draw_data.update(prop.fconfig(self, value)) 109 | 110 | self.__draw_internal__(draw_data) 111 | 112 | @abstractmethod 113 | def __draw_internal__(self, draw_args: Mapping[str, Any]) -> None: 114 | """This should execute the draw using DearPyGui's ``draw_*()`` functions.""" 115 | 116 | def __eq__(self, other: Any) -> bool: 117 | """Two commands are equal if they share the same canvas and have the same tag.""" 118 | if isinstance(other, DrawCommand): 119 | return self.canvas == other.canvas and self.id == other.id 120 | return super().__eq__(other) 121 | 122 | @property 123 | def id(self) -> str: 124 | return self._tag_id 125 | 126 | @property 127 | def is_valid(self) -> bool: 128 | return self._tag_id is not None 129 | 130 | @property 131 | def canvas(self) -> DrawingCanvas: 132 | return self._canvas 133 | 134 | def delete(self) -> None: 135 | dpgcore.delete_draw_command(self.canvas.id, self.id) 136 | del self._tag_id 137 | 138 | def get_config(self) -> DrawConfigData: 139 | return dpgcore.get_draw_command(self.canvas.id, self.id) 140 | 141 | def set_config(self, **config: Any) -> None: 142 | dpgcore.modify_draw_command(self.canvas.id, self.id, **config) 143 | 144 | def bring_to_front(self) -> None: 145 | dpgcore.bring_draw_command_to_front(self.canvas.id, self.id) 146 | 147 | def send_to_back(self) -> None: 148 | dpgcore.send_draw_command_to_back(self.canvas.id, self.id) 149 | 150 | def move_forward(self) -> None: 151 | dpgcore.bring_draw_command_forward(self.canvas.id, self.id) 152 | 153 | def move_back(self) -> None: 154 | dpgcore.send_draw_command_back(self.canvas.id, self.id) -------------------------------------------------------------------------------- /demos/widget_status_queries_demo.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dearpygui_obj import start_gui, stop_gui, set_render_callback 4 | from dearpygui_obj.data import ColorRGBA 5 | from dearpygui_obj.window import Window 6 | from dearpygui_obj.basic import Text, Button, Separator 7 | from dearpygui_obj.input import InputText, SliderFloat 8 | from dearpygui_obj.layout import group_horizontal, Group 9 | 10 | class StatusFlags(Group): 11 | def __init__(self, target, **config): 12 | self.target = target 13 | super().__init__(**config) 14 | 15 | with self: 16 | with group_horizontal(spacing=5): 17 | self.visible_label = Text('[visible]') 18 | self.hovered_label = Text('[hovered]') 19 | self.focused_label = Text('[focused]') 20 | self.clicked_label = Text('[clicked]') 21 | 22 | with group_horizontal(spacing=5): 23 | self.active_label = Text('[active]') 24 | self.activated_label = Text('[activated]') 25 | self.deactivated_label = Text('[deactivated]') 26 | self.edited_label = Text('[edited]') 27 | self.deact_after_edit_label = Text('[deactivated after edit]') 28 | 29 | self.update() 30 | 31 | @staticmethod 32 | def _interpolate_colors(from_color, to_color, step): 33 | values = ( 34 | (1.0 - step)*from_value + step*to_value 35 | for from_value, to_value in zip(from_color, to_color) 36 | ) 37 | return ColorRGBA(*values) 38 | 39 | _true_color = ColorRGBA(1, 0, 0) 40 | _false_color = ColorRGBA(0.2, 0.2, 0.2) 41 | 42 | def _update_color(self, label, value): 43 | target_color = self._true_color if value else self._false_color 44 | if value: 45 | label.color = target_color 46 | else: 47 | label.color = self._interpolate_colors(label.color, target_color, 0.1) 48 | 49 | def update(self): 50 | self._update_color(self.active_label, self.target.active) 51 | self._update_color(self.visible_label, self.target.is_visible()) 52 | self._update_color(self.hovered_label, self.target.is_hovered()) 53 | self._update_color(self.focused_label, self.target.is_focused()) 54 | self._update_color(self.clicked_label, self.target.was_clicked()) 55 | 56 | self._update_color(self.activated_label, self.target.was_activated()) 57 | self._update_color(self.deactivated_label, self.target.was_deactivated()) 58 | self._update_color(self.edited_label, self.target.was_edited()) 59 | self._update_color(self.deact_after_edit_label, self.target.was_deactivated_after_edit()) 60 | 61 | if __name__ == '__main__': 62 | with Window("Example Window") as win: 63 | btn = Button() 64 | btn_status = StatusFlags(btn) 65 | 66 | Separator() 67 | 68 | textbox = InputText() 69 | textbox_status = StatusFlags(textbox) 70 | 71 | Separator() 72 | 73 | slider = SliderFloat() 74 | slider_status = StatusFlags(slider) 75 | 76 | @win.on_close 77 | def on_close(): 78 | stop_gui() 79 | 80 | @set_render_callback 81 | def render(): 82 | btn_status.update() 83 | textbox_status.update() 84 | slider_status.update() 85 | 86 | start_gui() 87 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/api_reference/basic.rst: -------------------------------------------------------------------------------- 1 | Basic Widgets 2 | ============= 3 | 4 | .. automodule:: dearpygui_obj.basic 5 | 6 | .. contents:: Contents 7 | :local: 8 | 9 | .. rubric:: Summary 10 | 11 | .. autosummary:: 12 | :nosignatures: 13 | 14 | Text 15 | LabelText 16 | Separator 17 | ProgressBar 18 | Button 19 | ButtonArrow 20 | Checkbox 21 | Selectable 22 | RadioButtons 23 | ListBox 24 | Combo 25 | ComboHeightMode 26 | SimplePlot 27 | 28 | 29 | Basic Content 30 | ------------- 31 | 32 | .. autoclass:: Text 33 | :members: 34 | :undoc-members: 35 | 36 | .. autoattribute:: value 37 | 38 | .. autoclass:: LabelText 39 | :members: 40 | :undoc-members: 41 | 42 | .. autoattribute:: value 43 | 44 | .. autoclass:: Separator 45 | :members: 46 | :undoc-members: 47 | 48 | .. autoclass:: ProgressBar 49 | :members: 50 | :undoc-members: 51 | 52 | .. autoattribute:: value 53 | 54 | Button-Like 55 | ----------- 56 | 57 | .. autoclass:: Button 58 | :members: 59 | :undoc-members: 60 | 61 | .. autoclass:: ButtonArrow 62 | :members: 63 | :undoc-members: 64 | 65 | .. autoclass:: Checkbox 66 | :members: 67 | :undoc-members: 68 | 69 | .. autoattribute:: value 70 | 71 | .. autoclass:: Selectable 72 | :members: 73 | :undoc-members: 74 | 75 | .. autoattribute:: value 76 | 77 | Selection List 78 | -------------- 79 | 80 | .. autoclass:: RadioButtons 81 | :members: 82 | :undoc-members: 83 | :special-members: __len__, __getitem__, __setitem__, __delitem__ 84 | 85 | .. autoattribute:: value 86 | 87 | .. autoclass:: ListBox 88 | :members: 89 | :undoc-members: 90 | :special-members: __len__, __getitem__, __setitem__, __delitem__ 91 | 92 | .. autoattribute:: value 93 | 94 | .. autoclass:: Combo 95 | :members: 96 | :undoc-members: 97 | :special-members: __len__, __getitem__, __setitem__, __delitem__ 98 | 99 | .. autoattribute:: value 100 | 101 | .. autoclass:: ComboHeightMode 102 | :members: 103 | :undoc-members: 104 | 105 | Simple Plots 106 | ------------ 107 | 108 | .. autoclass:: SimplePlot 109 | :members: 110 | :undoc-members: 111 | 112 | -------------------------------------------------------------------------------- /docs/api_reference/containers.rst: -------------------------------------------------------------------------------- 1 | Containers 2 | ========== 3 | 4 | .. automodule:: dearpygui_obj.containers 5 | 6 | .. contents:: Contents 7 | :local: 8 | 9 | .. rubric:: Summary 10 | 11 | .. autosummary:: 12 | :nosignatures: 13 | 14 | TreeNode 15 | TreeNodeHeader 16 | TabBar 17 | TabItem 18 | TabButton 19 | TabOrderMode 20 | Menu 21 | MenuItem 22 | Popup 23 | PopupInteraction 24 | 25 | 26 | Tree Nodes 27 | ---------- 28 | 29 | .. autoclass:: TreeNode 30 | :members: 31 | :undoc-members: 32 | :special-members: __exit__ 33 | 34 | .. autoattribute:: value 35 | 36 | 37 | .. autoclass:: TreeNodeHeader 38 | :members: 39 | :undoc-members: 40 | :special-members: __exit__ 41 | 42 | .. autoattribute:: value 43 | 44 | 45 | Tabs 46 | ---- 47 | 48 | .. autoclass:: TabBar 49 | :members: 50 | :undoc-members: 51 | :special-members: __exit__ 52 | 53 | .. autoclass:: TabItem 54 | :members: 55 | :undoc-members: 56 | :special-members: __exit__ 57 | 58 | .. autoclass:: TabButton 59 | :members: 60 | :undoc-members: 61 | 62 | .. autoclass:: TabOrderMode 63 | :members: 64 | :undoc-members: 65 | 66 | 67 | Menus 68 | ----- 69 | 70 | .. autoclass:: Menu 71 | :members: 72 | :undoc-members: 73 | :special-members: __exit__ 74 | 75 | .. autoclass:: MenuItem 76 | :members: 77 | :undoc-members: 78 | 79 | 80 | Popups 81 | ------ 82 | 83 | .. autoclass:: Popup 84 | :members: 85 | :undoc-members: 86 | :special-members: __exit__ 87 | 88 | 89 | .. autoclass:: PopupInteraction 90 | :members: 91 | :undoc-members: -------------------------------------------------------------------------------- /docs/api_reference/core.rst: -------------------------------------------------------------------------------- 1 | Core Functionality 2 | ================== 3 | 4 | .. automodule:: dearpygui_obj 5 | 6 | .. contents:: Contents 7 | :local: 8 | 9 | .. rubric:: Summary 10 | 11 | .. autosummary:: 12 | :nosignatures: 13 | 14 | start_gui 15 | stop_gui 16 | is_running 17 | get_item_by_id 18 | try_get_item_by_id 19 | get_active_window 20 | iter_all_items 21 | iter_all_windows 22 | set_start_callback 23 | set_exit_callback 24 | set_render_callback 25 | create_value 26 | DataValue 27 | wrap_callback 28 | unwrap_callback 29 | get_delta_time 30 | get_total_time 31 | enable_vsync 32 | 33 | Start/Stop the GUI Engine 34 | ------------------------- 35 | 36 | .. autofunction:: start_gui 37 | 38 | .. autofunction:: stop_gui 39 | 40 | .. autofunction:: is_running 41 | 42 | Get/Iterate Items 43 | ----------------- 44 | 45 | .. autofunction:: get_item_by_id 46 | 47 | .. autofunction:: try_get_item_by_id 48 | 49 | .. autofunction:: get_active_window 50 | 51 | .. autofunction:: iter_all_items 52 | 53 | .. autofunction:: iter_all_windows 54 | 55 | 56 | Value Storage System 57 | -------------------- 58 | 59 | .. autofunction:: create_value 60 | 61 | .. autoclass:: DataValue 62 | :members: 63 | :undoc-members: 64 | 65 | 66 | System-Level Callbacks 67 | ---------------------- 68 | 69 | .. autofunction:: set_start_callback 70 | 71 | .. autofunction:: set_exit_callback 72 | 73 | .. autofunction:: set_render_callback 74 | 75 | 76 | Callback Helpers 77 | ---------------- 78 | 79 | .. autofunction:: wrap_callback 80 | 81 | .. autofunction:: unwrap_callback 82 | 83 | Miscellaneous 84 | ------------- 85 | 86 | .. autofunction:: get_delta_time 87 | 88 | .. autofunction:: get_total_time 89 | 90 | .. autofunction:: enable_vsync 91 | -------------------------------------------------------------------------------- /docs/api_reference/data.rst: -------------------------------------------------------------------------------- 1 | Data 2 | ==== 3 | 4 | .. automodule:: dearpygui_obj.data 5 | 6 | .. contents:: Contents 7 | :local: 8 | 9 | .. rubric:: Summary 10 | 11 | .. autosummary:: 12 | :nosignatures: 13 | 14 | color_from_rgba8 15 | color_from_hex 16 | import_color_from_dpg 17 | export_color_to_dpg 18 | ColorRGBA 19 | MINYEAR 20 | MAXYEAR 21 | import_date_from_dpg 22 | export_date_to_dpg 23 | 24 | Colors 25 | ------ 26 | 27 | .. autofunction:: color_from_rgba8 28 | 29 | .. autofunction:: color_from_hex 30 | 31 | .. autofunction:: import_color_from_dpg 32 | 33 | .. autofunction:: export_color_to_dpg 34 | 35 | .. autoclass:: ColorRGBA 36 | :members: 37 | :undoc-members: 38 | 39 | 40 | Predefined Colors 41 | ^^^^^^^^^^^^^^^^^ 42 | 43 | A selection of predefined color values are available from the :mod:`dearpygui_obj.colors` module. 44 | 45 | Date/Time 46 | --------- 47 | 48 | .. autodata:: MINYEAR 49 | 50 | .. autodata:: MAXYEAR 51 | 52 | .. autofunction:: import_date_from_dpg 53 | 54 | .. autofunction:: export_date_to_dpg 55 | -------------------------------------------------------------------------------- /docs/api_reference/devtools.rst: -------------------------------------------------------------------------------- 1 | Developer Tools 2 | =============== 3 | 4 | .. automodule:: dearpygui_obj.devtools 5 | 6 | DearPyGui-Obj constains several useful developer tools which can help debug GUI applications. 7 | 8 | .. rubric:: Summary 9 | 10 | .. autosummary:: 11 | :nosignatures: 12 | 13 | DebugWindow 14 | MetricsWindow 15 | StyleEditorWindow 16 | DocumentationWindow 17 | AboutWindow 18 | 19 | .. autoclass:: DebugWindow 20 | :members: 21 | :undoc-members: 22 | 23 | .. autoclass:: MetricsWindow 24 | :members: 25 | :undoc-members: 26 | 27 | .. autoclass:: StyleEditorWindow 28 | :members: 29 | :undoc-members: 30 | 31 | .. autoclass:: DocumentationWindow 32 | :members: 33 | :undoc-members: 34 | 35 | .. autoclass:: AboutWindow 36 | :members: 37 | :undoc-members: 38 | -------------------------------------------------------------------------------- /docs/api_reference/drawing.rst: -------------------------------------------------------------------------------- 1 | Drawing 2 | ======= 3 | 4 | .. automodule:: dearpygui_obj.drawing 5 | 6 | .. contents:: Contents 7 | :local: 8 | 9 | .. rubric:: Summary 10 | 11 | .. autosummary:: 12 | :nosignatures: 13 | 14 | DrawingCanvas 15 | Drawing 16 | DrawLine 17 | DrawRectangle 18 | DrawCircle 19 | DrawText 20 | DrawArrow 21 | DrawPolyLine 22 | DrawTriangle 23 | DrawQuad 24 | DrawPolygon 25 | DrawBezierCurve 26 | 27 | Drawing Canvas 28 | -------------- 29 | 30 | .. autoclass:: DrawingCanvas 31 | :members: 32 | :undoc-members: 33 | 34 | .. autoclass:: WindowCanvas 35 | :members: 36 | :undoc-members: 37 | 38 | 39 | Drawing Widget 40 | -------------- 41 | 42 | .. autoclass:: Drawing 43 | :members: 44 | :undoc-members: 45 | 46 | Draw Commands 47 | ------------- 48 | 49 | .. autoclass:: DrawLine 50 | :members: 51 | :undoc-members: 52 | 53 | .. autoclass:: DrawRectangle 54 | :members: 55 | :undoc-members: 56 | 57 | .. autoclass:: DrawCircle 58 | :members: 59 | :undoc-members: 60 | 61 | .. autoclass:: DrawText 62 | :members: 63 | :undoc-members: 64 | 65 | .. autoclass:: DrawArrow 66 | :members: 67 | :undoc-members: 68 | 69 | .. autoclass:: DrawPolyLine 70 | :members: 71 | :undoc-members: 72 | 73 | .. autoclass:: DrawTriangle 74 | :members: 75 | :undoc-members: 76 | 77 | .. autoclass:: DrawQuad 78 | :members: 79 | :undoc-members: 80 | 81 | .. autoclass:: DrawPolygon 82 | :members: 83 | :undoc-members: 84 | 85 | .. autoclass:: DrawBezierCurve 86 | :members: 87 | :undoc-members: 88 | 89 | Pos2D 90 | ^^^^^ 91 | 92 | .. autoclass:: Pos2D 93 | :members: 94 | :undoc-members: 95 | 96 | Draw Wrapper 97 | ------------ 98 | 99 | .. automodule:: dearpygui_obj.wrapper.drawing 100 | 101 | .. rubric:: Summary 102 | 103 | .. autosummary:: 104 | :nosignatures: 105 | 106 | DrawCommand 107 | DrawProperty 108 | 109 | .. autoclass:: DrawCommand 110 | :members: 111 | :undoc-members: 112 | 113 | .. autoclass:: DrawProperty 114 | :members: 115 | :undoc-members: 116 | -------------------------------------------------------------------------------- /docs/api_reference/index.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | core 9 | data 10 | wrapper 11 | windows 12 | basic 13 | tables 14 | input 15 | layout 16 | containers 17 | plots 18 | drawing 19 | nodes 20 | userwidgets 21 | devtools 22 | -------------------------------------------------------------------------------- /docs/api_reference/input.rst: -------------------------------------------------------------------------------- 1 | Input Widgets 2 | ============= 3 | 4 | .. automodule:: dearpygui_obj.input 5 | 6 | .. contents:: Contents 7 | :local: 8 | 9 | 10 | Input Boxes 11 | ----------- 12 | 13 | .. rubric:: Summary 14 | 15 | .. autosummary:: 16 | :nosignatures: 17 | 18 | InputText 19 | InputFloat 20 | InputFloat2 21 | InputFloat3 22 | InputFloat4 23 | InputInt 24 | InputInt2 25 | InputInt3 26 | InputInt4 27 | 28 | Text Input 29 | ^^^^^^^^^^ 30 | 31 | .. autoclass:: InputText 32 | :members: 33 | :undoc-members: 34 | 35 | .. autoattribute:: value 36 | 37 | 38 | Number Input 39 | ^^^^^^^^^^^^ 40 | 41 | .. autoclass:: NumberInput 42 | :members: 43 | :undoc-members: 44 | 45 | .. autoattribute:: value 46 | 47 | Float Input 48 | ^^^^^^^^^^^ 49 | 50 | .. autoclass:: InputFloat 51 | :members: 52 | :undoc-members: 53 | 54 | .. autoattribute:: value 55 | 56 | .. autoclass:: InputFloat2 57 | :members: 58 | :undoc-members: 59 | 60 | .. autoattribute:: value 61 | 62 | .. autoclass:: InputFloat3 63 | :members: 64 | :undoc-members: 65 | 66 | .. autoattribute:: value 67 | 68 | .. autoclass:: InputFloat4 69 | :members: 70 | :undoc-members: 71 | 72 | .. autoattribute:: value 73 | 74 | Integer Input 75 | ^^^^^^^^^^^^^ 76 | 77 | .. autoclass:: InputInt 78 | :members: 79 | :undoc-members: 80 | 81 | .. autoattribute:: value 82 | 83 | .. autoclass:: InputInt2 84 | :members: 85 | :undoc-members: 86 | 87 | .. autoattribute:: value 88 | 89 | .. autoclass:: InputInt3 90 | :members: 91 | :undoc-members: 92 | 93 | .. autoattribute:: value 94 | 95 | .. autoclass:: InputInt4 96 | :members: 97 | :undoc-members: 98 | 99 | .. autoattribute:: value 100 | 101 | 102 | Sliders 103 | ------- 104 | 105 | .. rubric:: Summary 106 | 107 | .. autosummary:: 108 | :nosignatures: 109 | 110 | SliderFloat 111 | SliderFloat2 112 | SliderFloat3 113 | SliderFloat4 114 | SliderInt 115 | SliderInt2 116 | SliderInt3 117 | SliderInt4 118 | 119 | .. autoclass:: SliderInput 120 | :members: 121 | :undoc-members: 122 | 123 | .. autoattribute:: value 124 | 125 | Float Sliders 126 | ^^^^^^^^^^^^^ 127 | 128 | .. autoclass:: SliderFloat 129 | :members: 130 | :undoc-members: 131 | 132 | .. autoattribute:: value 133 | 134 | .. autoclass:: SliderFloat2 135 | :members: 136 | :undoc-members: 137 | 138 | .. autoattribute:: value 139 | 140 | .. autoclass:: SliderFloat3 141 | :members: 142 | :undoc-members: 143 | 144 | .. autoattribute:: value 145 | 146 | .. autoclass:: SliderFloat4 147 | :members: 148 | :undoc-members: 149 | 150 | .. autoattribute:: value 151 | 152 | Integer Sliders 153 | ^^^^^^^^^^^^^^^ 154 | 155 | .. autoclass:: SliderInt 156 | :members: 157 | :undoc-members: 158 | 159 | .. autoattribute:: value 160 | 161 | .. autoclass:: SliderInt2 162 | :members: 163 | :undoc-members: 164 | 165 | .. autoattribute:: value 166 | 167 | .. autoclass:: SliderInt3 168 | :members: 169 | :undoc-members: 170 | 171 | .. autoattribute:: value 172 | 173 | .. autoclass:: SliderInt4 174 | :members: 175 | :undoc-members: 176 | 177 | .. autoattribute:: value 178 | 179 | 180 | Drag Inputs 181 | ----------- 182 | 183 | .. rubric:: Summary 184 | 185 | .. autosummary:: 186 | :nosignatures: 187 | 188 | DragFloat 189 | DragFloat2 190 | DragFloat3 191 | DragFloat4 192 | DragInt 193 | DragInt2 194 | DragInt3 195 | DragInt4 196 | 197 | .. autoclass:: DragInput 198 | :members: 199 | :undoc-members: 200 | 201 | .. autoattribute:: value 202 | 203 | Float Drag Input 204 | ^^^^^^^^^^^^^^^^ 205 | 206 | .. autoclass:: DragFloat 207 | :members: 208 | :undoc-members: 209 | 210 | .. autoattribute:: value 211 | 212 | .. autoclass:: DragFloat2 213 | :members: 214 | :undoc-members: 215 | 216 | .. autoattribute:: value 217 | 218 | .. autoclass:: DragFloat3 219 | :members: 220 | :undoc-members: 221 | 222 | .. autoattribute:: value 223 | 224 | .. autoclass:: DragFloat4 225 | :members: 226 | :undoc-members: 227 | 228 | .. autoattribute:: value 229 | 230 | Integer Drag Input 231 | ^^^^^^^^^^^^^^^^^^ 232 | 233 | .. autoclass:: DragInt 234 | :members: 235 | :undoc-members: 236 | 237 | .. autoattribute:: value 238 | 239 | .. autoclass:: DragInt2 240 | :members: 241 | :undoc-members: 242 | 243 | .. autoattribute:: value 244 | 245 | .. autoclass:: DragInt3 246 | :members: 247 | :undoc-members: 248 | 249 | .. autoattribute:: value 250 | 251 | .. autoclass:: DragInt4 252 | :members: 253 | :undoc-members: 254 | 255 | .. autoattribute:: value 256 | 257 | Color Input 258 | ----------- 259 | 260 | .. rubric:: Summary 261 | 262 | .. autosummary:: 263 | :nosignatures: 264 | 265 | ColorPicker 266 | ColorEdit 267 | ColorButton 268 | ColorEdit 269 | ColorPicker 270 | 271 | .. autoclass:: ColorButton 272 | :members: 273 | :undoc-members: 274 | 275 | .. autoclass:: ColorPicker 276 | :members: 277 | :undoc-members: 278 | 279 | .. autoattribute:: value 280 | 281 | .. autoclass:: ColorEdit 282 | :members: 283 | :undoc-members: 284 | 285 | .. autoattribute:: value 286 | 287 | .. autoclass:: ColorFormatMode 288 | :members: 289 | :undoc-members: 290 | 291 | Date and Time Input 292 | ------------------- 293 | 294 | .. rubric:: Summary 295 | 296 | .. autosummary:: 297 | :nosignatures: 298 | 299 | DatePicker 300 | DatePickerMode 301 | TimePicker 302 | TimePickerFormat 303 | 304 | 305 | .. autoclass:: DatePicker 306 | :members: 307 | :undoc-members: 308 | 309 | .. autoclass:: DatePickerMode 310 | :members: 311 | :undoc-members: 312 | 313 | .. autoclass:: TimePicker 314 | :members: 315 | :undoc-members: 316 | 317 | .. autoclass:: TimePickerFormat 318 | :members: 319 | :undoc-members: -------------------------------------------------------------------------------- /docs/api_reference/layout.rst: -------------------------------------------------------------------------------- 1 | Layout 2 | ====== 3 | 4 | .. automodule:: dearpygui_obj.layout 5 | 6 | .. rubric:: Summary 7 | 8 | .. autosummary:: 9 | :nosignatures: 10 | 11 | VSpacing 12 | HAlignNext 13 | Dummy 14 | group_horizontal 15 | Group 16 | ColumnLayout 17 | IndentLayout 18 | ChildView 19 | 20 | Simple Layout 21 | ------------- 22 | 23 | .. autoclass:: VSpacing 24 | :members: 25 | :undoc-members: 26 | 27 | .. autoclass:: HAlignNext 28 | :members: 29 | :undoc-members: 30 | 31 | .. autoclass:: Dummy 32 | :members: 33 | :undoc-members: 34 | 35 | Containers 36 | ---------- 37 | 38 | .. autofunction:: group_horizontal 39 | 40 | .. autoclass:: Group 41 | :members: 42 | :undoc-members: 43 | :special-members: __exit__ 44 | 45 | .. autoclass:: ColumnLayout 46 | :members: 47 | :undoc-members: 48 | :special-members: __exit__ 49 | 50 | .. autoclass:: IndentLayout 51 | :members: 52 | :undoc-members: 53 | :special-members: __exit__ 54 | 55 | .. autoclass:: ChildView 56 | :members: 57 | :undoc-members: 58 | :special-members: __exit__ 59 | 60 | -------------------------------------------------------------------------------- /docs/api_reference/nodes.rst: -------------------------------------------------------------------------------- 1 | Node Editor 2 | =========== 3 | 4 | .. automodule:: dearpygui_obj.node 5 | 6 | .. contents:: Contents 7 | :local: 8 | 9 | .. rubric:: Summary 10 | 11 | .. autosummary:: 12 | :nosignatures: 13 | 14 | NodeEditor 15 | NodeLink 16 | Node 17 | input_attribute 18 | output_attribute 19 | static_attribute 20 | NodeAttribute 21 | NodeAttributeType 22 | 23 | NodeEditor 24 | ---------- 25 | 26 | .. autoclass:: NodeEditor 27 | :members: 28 | :undoc-members: 29 | 30 | .. autoclass:: NodeLink 31 | :members: 32 | :undoc-members: 33 | 34 | Node 35 | ---- 36 | 37 | .. autoclass:: Node 38 | :members: 39 | :undoc-members: 40 | 41 | 42 | NodeAttribute 43 | ------------- 44 | 45 | .. autofunction:: input_attribute 46 | 47 | .. autofunction:: output_attribute 48 | 49 | .. autofunction:: static_attribute 50 | 51 | .. autoclass:: NodeAttribute 52 | :members: 53 | :undoc-members: 54 | 55 | .. autoclass:: NodeAttributeType 56 | :members: 57 | :undoc-members: 58 | -------------------------------------------------------------------------------- /docs/api_reference/plots.rst: -------------------------------------------------------------------------------- 1 | Plots 2 | ===== 3 | 4 | .. contents:: Contents 5 | :local: 6 | 7 | Plots 8 | ----- 9 | 10 | .. automodule:: dearpygui_obj.plots 11 | 12 | .. rubric:: Summary 13 | 14 | .. autosummary:: 15 | :nosignatures: 16 | 17 | Plot 18 | PlotQueryArea 19 | PlotAxisConfig 20 | 21 | .. autoclass:: Plot 22 | :members: 23 | :undoc-members: 24 | 25 | .. autoclass:: PlotQueryArea 26 | :members: 27 | :undoc-members: 28 | 29 | Axis Configuration 30 | ^^^^^^^^^^^^^^^^^^ 31 | 32 | .. autoclass:: PlotAxisConfig 33 | :members: 34 | :undoc-members: 35 | 36 | .. autoclass:: PlotXAxisConfig 37 | :members: 38 | :undoc-members: 39 | 40 | .. autoclass:: PlotYAxisConfig 41 | :members: 42 | :undoc-members: 43 | 44 | .. autoclass:: PlotOptYAxisConfig 45 | :members: 46 | :undoc-members: 47 | 48 | Plot Markup 49 | ----------- 50 | 51 | .. rubric:: Summary 52 | 53 | .. autosummary:: 54 | :nosignatures: 55 | 56 | PlotAnnotation 57 | PlotText 58 | 59 | PlotAnnotation 60 | ^^^^^^^^^^^^^^ 61 | 62 | .. autoclass:: PlotAnnotation 63 | :members: 64 | :undoc-members: 65 | 66 | PlotText 67 | ^^^^^^^^ 68 | 69 | .. autoclass:: PlotText 70 | :members: 71 | :undoc-members: 72 | 73 | 74 | Data Series 75 | ----------- 76 | .. automodule:: dearpygui_obj.plots.dataseries 77 | 78 | .. rubric:: Summary 79 | 80 | .. autosummary:: 81 | :nosignatures: 82 | 83 | ~dearpygui_obj.wrapper.dataseries.DataSeries 84 | ~dearpygui_obj.wrapper.dataseries.DataSeriesConfig 85 | ~dearpygui_obj.wrapper.dataseries.DataSeriesCollection 86 | AreaSeries 87 | BarSeries 88 | CandleSeries 89 | ErrorSeries 90 | HeatSeries 91 | HLineSeries 92 | VLineSeries 93 | LineSeries 94 | PieSeries 95 | ScatterSeries 96 | ShadeSeries 97 | ShadeRangeSeries 98 | StairSeries 99 | StemSeries 100 | 101 | Base Class 102 | ^^^^^^^^^^ 103 | 104 | .. autoclass:: dearpygui_obj.wrapper.dataseries.DataSeries 105 | :members: 106 | :undoc-members: 107 | :special-members: __getitem__, __setitem__, __delitem__ 108 | 109 | .. autoclass:: dearpygui_obj.wrapper.dataseries.DataSeriesConfig 110 | :members: 111 | :undoc-members: 112 | 113 | 114 | .. autoclass:: dearpygui_obj.wrapper.dataseries.DataSeriesCollection 115 | :members: 116 | :undoc-members: 117 | :special-members: __len__, __iter__, __getitem__, __setitem__, __delitem__ 118 | 119 | 120 | XYData 121 | ^^^^^^ 122 | 123 | .. autoclass:: XYData 124 | :members: 125 | :undoc-members: 126 | 127 | Area Series 128 | ^^^^^^^^^^^ 129 | 130 | .. autoclass:: AreaSeries 131 | :members: 132 | :undoc-members: 133 | 134 | 135 | Bar Series 136 | ^^^^^^^^^^ 137 | 138 | .. autoclass:: BarSeries 139 | :members: 140 | :undoc-members: 141 | 142 | 143 | Candle Series 144 | ^^^^^^^^^^^^^ 145 | 146 | .. autoclass:: CandleSeries 147 | :members: 148 | :undoc-members: 149 | 150 | .. autoclass:: CandleSeriesData 151 | :members: 152 | :undoc-members: 153 | 154 | Error Series 155 | ^^^^^^^^^^^^ 156 | 157 | .. autoclass:: ErrorSeries 158 | :members: 159 | :undoc-members: 160 | 161 | .. autoclass:: ErrorSeriesData 162 | :members: 163 | :undoc-members: 164 | 165 | Heat Series 166 | ^^^^^^^^^^^ 167 | 168 | .. autoclass:: HeatSeries 169 | :members: 170 | :undoc-members: 171 | 172 | Infinite Line Series 173 | ^^^^^^^^^^^^^^^^^^^^ 174 | 175 | .. autoclass:: HLineSeries 176 | :members: 177 | :undoc-members: 178 | 179 | .. autoclass:: VLineSeries 180 | :members: 181 | :undoc-members: 182 | 183 | Line Series 184 | ^^^^^^^^^^^ 185 | 186 | .. autoclass:: LineSeries 187 | :members: 188 | :undoc-members: 189 | 190 | Pie Series 191 | ^^^^^^^^^^ 192 | 193 | .. autoclass:: PieSeriesData 194 | :members: 195 | :undoc-members: 196 | 197 | .. autoclass:: PieSeries 198 | :members: 199 | :undoc-members: 200 | 201 | Scatter Series 202 | ^^^^^^^^^^^^^^ 203 | 204 | .. autoclass:: ScatterSeries 205 | :members: 206 | :undoc-members: 207 | 208 | Shade Series 209 | ^^^^^^^^^^^^ 210 | 211 | .. autoclass:: ShadeSeries 212 | :members: 213 | :undoc-members: 214 | 215 | .. autoclass:: ShadeRangeSeries 216 | :members: 217 | :undoc-members: 218 | 219 | .. autoclass:: ShadeRangeData 220 | :members: 221 | :undoc-members: 222 | 223 | Stair Series 224 | ^^^^^^^^^^^^ 225 | 226 | .. autoclass:: StairSeries 227 | :members: 228 | :undoc-members: 229 | 230 | Stem Series 231 | ^^^^^^^^^^^ 232 | 233 | .. autoclass:: StemSeries 234 | :members: 235 | :undoc-members: 236 | -------------------------------------------------------------------------------- /docs/api_reference/tables.rst: -------------------------------------------------------------------------------- 1 | Tables 2 | ====== 3 | 4 | .. automodule:: dearpygui_obj.tables 5 | 6 | .. contents:: Contents 7 | :local: 8 | 9 | .. rubric:: Summary 10 | 11 | .. autosummary:: 12 | :nosignatures: 13 | 14 | Table 15 | TableSelection 16 | 17 | 18 | Simple Tables 19 | ------------- 20 | 21 | .. autoclass:: Table 22 | :members: 23 | :undoc-members: 24 | :special-members: __getitem__, __setitem__ 25 | 26 | .. autoclass:: TableSelection 27 | :members: 28 | :undoc-members: 29 | :special-members: __iter__, __setitem__ 30 | -------------------------------------------------------------------------------- /docs/api_reference/userwidgets.rst: -------------------------------------------------------------------------------- 1 | Custom Widgets 2 | ============== 3 | 4 | .. automodule:: dearpygui_obj.userwidget 5 | 6 | .. contents:: Contents 7 | :local: 8 | 9 | .. rubric:: Summary 10 | 11 | .. autosummary:: 12 | :nosignatures: 13 | 14 | UserWidget 15 | 16 | 17 | UserWidget 18 | ---------- 19 | 20 | .. autoclass:: UserWidget 21 | :members: 22 | :undoc-members: 23 | 24 | .. automethod:: __setup_content__ 25 | 26 | 27 | UserContainer 28 | ------------- 29 | 30 | .. autoclass:: UserContainer -------------------------------------------------------------------------------- /docs/api_reference/windows.rst: -------------------------------------------------------------------------------- 1 | Windows 2 | ======= 3 | 4 | .. automodule:: dearpygui_obj.window 5 | 6 | .. contents:: Contents 7 | :local: 8 | 9 | .. rubric:: Summary 10 | 11 | .. autosummary:: 12 | :nosignatures: 13 | 14 | MainWindow 15 | Window 16 | MenuBar 17 | 18 | 19 | Main Window 20 | ----------- 21 | 22 | .. autoclass:: MainWindow 23 | :members: 24 | :undoc-members: 25 | 26 | 27 | Window 28 | ------ 29 | 30 | .. autoclass:: Window 31 | :members: 32 | :undoc-members: 33 | :special-members: __exit__ 34 | 35 | 36 | .. autoclass:: MenuBar 37 | :members: 38 | :undoc-members: 39 | :special-members: __exit__ -------------------------------------------------------------------------------- /docs/api_reference/wrapper.rst: -------------------------------------------------------------------------------- 1 | Widget Objects 2 | ============== 3 | 4 | .. automodule:: dearpygui_obj.wrapper.widget 5 | 6 | .. contents:: Contents 7 | :local: 8 | 9 | .. rubric:: Summary 10 | 11 | .. autosummary:: 12 | :nosignatures: 13 | 14 | Widget 15 | ContainerWidgetMx 16 | ContainerFinalizedError 17 | ItemWidgetMx 18 | DefaultWidget 19 | ConfigProperty 20 | 21 | Widget 22 | ------ 23 | 24 | .. autoclass:: Widget 25 | :members: 26 | :undoc-members: 27 | :special-members: __eq__ 28 | 29 | **ID and Existence** 30 | 31 | .. autosummary:: 32 | :nosignatures: 33 | 34 | id 35 | is_valid 36 | delete 37 | 38 | **Callbacks** 39 | 40 | .. autosummary:: 41 | :nosignatures: 42 | 43 | callback 44 | get_callback 45 | set_callback 46 | callback_data 47 | 48 | **Other Properties and Status** 49 | 50 | .. autosummary:: 51 | :nosignatures: 52 | 53 | show 54 | width 55 | height 56 | size 57 | max_size 58 | min_size 59 | tooltip 60 | enabled 61 | is_visible 62 | is_hovered 63 | is_focused 64 | 65 | .. automethod:: __setup_add_widget__ 66 | .. automethod:: __setup_preexisting__ 67 | 68 | 69 | .. autoclass:: DefaultWidget 70 | :members: 71 | :undoc-members: 72 | 73 | 74 | ContainerWidgetMx 75 | ----------------- 76 | 77 | .. autoclass:: ContainerWidgetMx 78 | :members: 79 | :undoc-members: 80 | :special-members: __enter__, __exit__ 81 | 82 | .. .. automethod:: __finalize__ 83 | 84 | 85 | .. autoexception:: ContainerFinalizedError 86 | 87 | ItemWidgetMx 88 | ------------ 89 | 90 | .. autoclass:: ItemWidgetMx 91 | :members: 92 | :undoc-members: 93 | 94 | 95 | ValueWidgetMx 96 | ------------- 97 | 98 | .. autoclass:: ValueWidgetMx 99 | :members: 100 | :undoc-members: 101 | 102 | ConfigProperty 103 | -------------- 104 | 105 | .. autoclass:: ConfigProperty 106 | :members: 107 | :undoc-members: 108 | :special-members: __get__, __set__, __call__ 109 | 110 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('..')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'DearPyGui-Obj' 21 | copyright = '2021, mwerezak' 22 | author = 'mwerezak' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '0.1' 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'sphinx.ext.autosummary', 36 | 'sphinx.ext.napoleon', 37 | # 'sphinx.ext.autosectionlabel', 38 | # 'sphinx.ext.todo', 39 | 'sphinx.ext.viewcode', 40 | 'sphinx.ext.extlinks', 41 | 'sphinx.ext.intersphinx', 42 | 'sphinx_rtd_theme', 43 | ] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ['_templates'] 47 | 48 | # List of patterns, relative to source directory, that match files and 49 | # directories to ignore when looking for source files. 50 | # This pattern also affects html_static_path and html_extra_path. 51 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 52 | 53 | 54 | # -- Options for HTML output ------------------------------------------------- 55 | 56 | # The theme to use for HTML and HTML Help pages. See the documentation for 57 | # a list of builtin themes. 58 | # 59 | html_theme = 'sphinx_rtd_theme' 60 | # html_theme = 'python_docs_theme' 61 | # html_theme = 'alabaster' 62 | # html_theme = 'nature' 63 | # html_theme = 'classic' 64 | 65 | # Add any paths that contain custom static files (such as style sheets) here, 66 | # relative to this directory. They are copied after the builtin static files, 67 | # so a file named "default.css" will overwrite the builtin "default.css". 68 | html_static_path = ['_static'] 69 | 70 | # -- Extension Settings ------------------------------------------------------ 71 | 72 | autosummary_generate = False 73 | 74 | 75 | extlinks = { 76 | 'dearpygui' : ('https://github.com/hoffstadt/DearPyGui/wiki/%s', 'dearpygui'), 77 | } 78 | 79 | intersphinx_mapping = { 80 | 'python' : ('https://docs.python.org/3.8', None), 81 | 'curio' : ('https://curio.readthedocs.io/en/latest', None), 82 | } 83 | 84 | autodoc_default_options = { 85 | 'member-order' : 'bysource', 86 | 'show-inheritance' : True, 87 | } 88 | 89 | autodoc_type_aliases = { 90 | } 91 | 92 | autodoc_mock_imports = ['dearpygui'] 93 | 94 | # autosectionlabel_prefix_document = True 95 | # autosectionlabel_maxdepth = None 96 | 97 | # autoclass_content = 'both' 98 | napoleon_numpy_docstring = False 99 | 100 | 101 | # def autodoc_process_signature(app, what, name, obj, options, signature, return_annotation): 102 | # if what in ('class', 'exception'): 103 | # signature = None 104 | # return (signature, return_annotation) 105 | 106 | # def setup(app): 107 | # app.connect('autodoc-process-signature', autodoc_process_signature) 108 | -------------------------------------------------------------------------------- /docs/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwerezak/DearPyGui-Obj/de73989798c202183e400dc813e2b0b688c26f7d/docs/example.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. DearPyGui-Obj documentation master file, created by 2 | sphinx-quickstart on Wed Feb 24 09:24:24 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | DearPyGui-Obj 7 | ============= 8 | 9 | **DearPyGui-Obj** is an object-oriented interface for `DearPyGui `_. 10 | 11 | .. caution:: 12 | 13 | This documentation is under construction! 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | :caption: Contents: 18 | 19 | widget_list 20 | api_reference/index 21 | 22 | 23 | 24 | Indices and tables 25 | ================== 26 | 27 | * :ref:`genindex` 28 | * :ref:`modindex` 29 | * :ref:`search` 30 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # docs/requirements.txt 2 | sphinx>=3.5.1 3 | sphinx_rtd_theme>=0.5.1 4 | dearpygui>=0.6.213 -------------------------------------------------------------------------------- /docs/widget_list.rst: -------------------------------------------------------------------------------- 1 | Table of Widgets 2 | ================ 3 | 4 | The following tables list the widgets currently implemented in DearPyGui-Obj. 5 | 6 | Most widgets can also be imported from the :mod:`dearpygui_obj.widgets` module. 7 | 8 | .. rubric:: Windows 9 | 10 | .. autosummary:: 11 | :nosignatures: 12 | 13 | dearpygui_obj.window.MainWindow 14 | dearpygui_obj.window.Window 15 | dearpygui_obj.window.MenuBar 16 | 17 | 18 | .. rubric:: Basic Content 19 | 20 | .. autosummary:: 21 | :nosignatures: 22 | 23 | dearpygui_obj.basic.Text 24 | dearpygui_obj.basic.LabelText 25 | dearpygui_obj.basic.Separator 26 | dearpygui_obj.basic.ProgressBar 27 | dearpygui_obj.basic.Button 28 | dearpygui_obj.basic.Checkbox 29 | dearpygui_obj.basic.Selectable 30 | dearpygui_obj.basic.RadioButtons 31 | dearpygui_obj.basic.ListBox 32 | dearpygui_obj.basic.Combo 33 | dearpygui_obj.basic.SimplePlot 34 | 35 | 36 | .. rubric:: Tables 37 | 38 | .. autosummary:: 39 | :nosignatures: 40 | 41 | dearpygui_obj.tables.Table 42 | 43 | 44 | .. rubric:: Input Boxes 45 | 46 | .. autosummary:: 47 | :nosignatures: 48 | 49 | dearpygui_obj.input.InputText 50 | dearpygui_obj.input.InputFloat 51 | dearpygui_obj.input.InputFloat2 52 | dearpygui_obj.input.InputFloat3 53 | dearpygui_obj.input.InputFloat4 54 | dearpygui_obj.input.InputInt 55 | dearpygui_obj.input.InputInt2 56 | dearpygui_obj.input.InputInt3 57 | dearpygui_obj.input.InputInt4 58 | 59 | 60 | .. rubric:: Sliders 61 | 62 | .. autosummary:: 63 | :nosignatures: 64 | 65 | dearpygui_obj.input.SliderFloat 66 | dearpygui_obj.input.SliderFloat2 67 | dearpygui_obj.input.SliderFloat3 68 | dearpygui_obj.input.SliderFloat4 69 | dearpygui_obj.input.SliderInt 70 | dearpygui_obj.input.SliderInt2 71 | dearpygui_obj.input.SliderInt3 72 | dearpygui_obj.input.SliderInt4 73 | 74 | 75 | .. rubric:: Drag Inputs 76 | 77 | .. autosummary:: 78 | :nosignatures: 79 | 80 | dearpygui_obj.input.DragFloat 81 | dearpygui_obj.input.DragFloat2 82 | dearpygui_obj.input.DragFloat3 83 | dearpygui_obj.input.DragFloat4 84 | dearpygui_obj.input.DragInt 85 | dearpygui_obj.input.DragInt2 86 | dearpygui_obj.input.DragInt3 87 | dearpygui_obj.input.DragInt4 88 | 89 | 90 | .. rubric:: Colors 91 | 92 | .. autosummary:: 93 | :nosignatures: 94 | 95 | dearpygui_obj.input.ColorButton 96 | dearpygui_obj.input.ColorEdit 97 | dearpygui_obj.input.ColorPicker 98 | 99 | 100 | .. rubric:: Date/Time Input 101 | 102 | .. autosummary:: 103 | :nosignatures: 104 | 105 | dearpygui_obj.input.DatePicker 106 | dearpygui_obj.input.TimePicker 107 | 108 | 109 | .. rubric:: Layout 110 | 111 | .. autosummary:: 112 | :nosignatures: 113 | 114 | dearpygui_obj.layout.VSpacing 115 | dearpygui_obj.layout.HAlignNext 116 | dearpygui_obj.layout.group_horizontal 117 | dearpygui_obj.layout.Group 118 | dearpygui_obj.layout.ColumnLayout 119 | dearpygui_obj.layout.IndentLayout 120 | dearpygui_obj.layout.ChildView 121 | dearpygui_obj.layout.Dummy 122 | 123 | 124 | .. rubric:: Containers 125 | 126 | .. autosummary:: 127 | :nosignatures: 128 | 129 | dearpygui_obj.containers.TreeNode 130 | dearpygui_obj.containers.TreeNodeHeader 131 | dearpygui_obj.containers.TabBar 132 | dearpygui_obj.containers.TabItem 133 | dearpygui_obj.containers.TabButton 134 | dearpygui_obj.containers.Menu 135 | dearpygui_obj.containers.MenuItem 136 | dearpygui_obj.containers.Popup 137 | 138 | 139 | .. rubric:: Rich Plots 140 | 141 | .. autosummary:: 142 | :nosignatures: 143 | 144 | dearpygui_obj.plots.Plot 145 | dearpygui_obj.plots.dataseries.AreaSeries 146 | dearpygui_obj.plots.dataseries.BarSeries 147 | dearpygui_obj.plots.dataseries.CandleSeries 148 | dearpygui_obj.plots.dataseries.ErrorSeries 149 | dearpygui_obj.plots.dataseries.HeatSeries 150 | dearpygui_obj.plots.dataseries.HLineSeries 151 | dearpygui_obj.plots.dataseries.LineSeries 152 | dearpygui_obj.plots.dataseries.PieSeries 153 | dearpygui_obj.plots.dataseries.ScatterSeries 154 | dearpygui_obj.plots.dataseries.ShadeSeries 155 | dearpygui_obj.plots.dataseries.ShadeRangeSeries 156 | dearpygui_obj.plots.dataseries.StairSeries 157 | dearpygui_obj.plots.dataseries.StemSeries 158 | 159 | 160 | .. rubric:: Drawing 161 | 162 | .. autosummary:: 163 | :nosignatures: 164 | 165 | dearpygui_obj.drawing.Drawing 166 | dearpygui_obj.drawing.DrawLine 167 | dearpygui_obj.drawing.DrawRectangle 168 | dearpygui_obj.drawing.DrawCircle 169 | dearpygui_obj.drawing.DrawText 170 | dearpygui_obj.drawing.DrawArrow 171 | dearpygui_obj.drawing.DrawPolyLine 172 | dearpygui_obj.drawing.DrawTriangle 173 | dearpygui_obj.drawing.DrawQuad 174 | dearpygui_obj.drawing.DrawPolygon 175 | dearpygui_obj.drawing.DrawBezierCurve 176 | 177 | .. rubric:: Node Editor 178 | 179 | .. autosummary:: 180 | :nosignatures: 181 | 182 | dearpygui_obj.node.NodeEditor 183 | dearpygui_obj.node.Node 184 | dearpygui_obj.node.NodeAttribute 185 | 186 | 187 | .. rubric:: Developer Tool Windows 188 | 189 | .. autosummary:: 190 | :nosignatures: 191 | 192 | dearpygui_obj.devtools.DebugWindow 193 | dearpygui_obj.devtools.MetricsWindow 194 | dearpygui_obj.devtools.StyleEditorWindow 195 | dearpygui_obj.devtools.DocumentationWindow 196 | dearpygui_obj.devtools.AboutWindow 197 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel", 5 | "dearpygui>=0.6.213,<0.7", 6 | ] 7 | build-backend = "setuptools.build_meta" -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = dearpygui-obj 3 | version = 0.8.0 4 | author = Mike Werezak 5 | author_email = mwerezak@gmail.com 6 | description = An object-oriented wrapper around DearPyGui. 7 | long_description = file: README.md 8 | long_description_content_type = text/markdown 9 | url = https://github.com/mwerezak/DearPyGui-Obj 10 | project_urls = 11 | Bug Tracker = https://github.com/mwerezak/DearPyGui-Obj/issues 12 | classifiers = 13 | Programming Language :: Python :: 3 14 | License :: OSI Approved :: MIT License 15 | Development Status :: 3 - Alpha 16 | Topic :: Software Development :: User Interfaces 17 | Topic :: Software Development :: Libraries :: Python Modules 18 | Intended Audience :: Developers 19 | Intended Audience :: Education 20 | Intended Audience :: Science/Research 21 | Programming Language :: Python :: 3.8 22 | Programming Language :: Python :: 3.9 23 | Operating System :: POSIX :: Linux 24 | Operating System :: Microsoft :: Windows :: Windows 10 25 | Operating System :: MacOS :: MacOS X 26 | 27 | 28 | [options] 29 | packages = find: 30 | python_requires = >=3.8 --------------------------------------------------------------------------------