from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
from dearpygui import dearpygui as dpgcore
from dearpygui_obj import _generate_id
if TYPE_CHECKING:
from typing import Any, Optional, Type, Callable, Mapping
from dearpygui_obj.drawing import DrawingCanvas
DrawConfigData = Mapping[str, Any]
GetDrawValueFunc = Callable[['DrawCommand'], Any]
GetDrawConfigFunc = Callable[['DrawCommand', Any], DrawConfigData]
[docs]class DrawProperty:
"""Descriptor used to get or set a draw command's configuration."""
def __init__(self,
key: Optional[str] = None, *,
doc: str = ''):
"""
Parameters:
key: the config key to get/set with the default implementation.
doc: custom docstring.
"""
self.owner = None
self.key = key
self.__doc__ = doc
def __set_name__(self, owner: Type[DrawCommand], name: str):
self.owner = owner
self.name = name
if self.key is None:
self.key = name
if not self.__doc__:
self.__doc__ = f"Read or modify the '{self.key}' config field."
def __get__(self, instance: Optional[DrawCommand], owner: Type[DrawCommand]) -> Any:
if instance is None:
return self
return self.fvalue(instance)
def __set__(self, instance: DrawCommand, value: Any) -> None:
config = self.fconfig(instance, value)
dpgcore.modify_draw_command(instance.canvas.id, instance.id, **config)
def __call__(self, fvalue: GetDrawValueFunc):
"""Allows the ConfigProperty itself to be used as a decorator equivalent to :attr:`getvalue`."""
return self.getvalue(fvalue)
[docs] def getvalue(self, fvalue: GetDrawValueFunc):
self.fvalue = fvalue
self.__doc__ = fvalue.__doc__ # use the docstring of the getter, the same way property() works
return self
[docs] def getconfig(self, fconfig: GetDrawConfigFunc):
self.fconfig = fconfig
return self
## default implementations
fvalue: GetDrawValueFunc
fconfig: GetDrawConfigFunc
[docs] def fvalue(self, instance: DrawCommand) -> Any:
return dpgcore.get_draw_command(instance.canvas.id, instance.id)[self.key]
[docs] def fconfig(self, instance: DrawCommand, value: Any) -> DrawConfigData:
return {self.key : value}
[docs]class DrawCommand(ABC):
"""Base class for drawing commands."""
@classmethod
def _get_draw_properties(cls) -> Mapping[str, DrawProperty]:
draw_properties = cls.__dict__.get('_draw_properties')
if draw_properties is None:
draw_properties = {}
# must match order in annotations
for name in cls.__annotations__:
value = getattr(cls, name)
if isinstance(value, DrawProperty):
draw_properties[name] = value
setattr(cls, '_draw_properties', draw_properties)
return draw_properties
_tag_id: str = None
def __init__(self, canvas: DrawingCanvas, *args, tag_id: str = None, **kwargs: Any):
self._canvas = canvas
if tag_id is not None:
self._tag_id = tag_id
else:
self._tag_id = _generate_id(self)
props = self._get_draw_properties()
draw_data = {}
for prop, value in zip(props.values(), args):
draw_data.update(prop.fconfig(self, value))
for name, value in kwargs.items():
prop = props.get(name)
if prop is not None:
draw_data.update(prop.fconfig(self, value))
self.__draw_internal__(draw_data)
@abstractmethod
def __draw_internal__(self, draw_args: Mapping[str, Any]) -> None:
"""This should execute the draw using DearPyGui's ``draw_*()`` functions."""
def __eq__(self, other: Any) -> bool:
"""Two commands are equal if they share the same canvas and have the same tag."""
if isinstance(other, DrawCommand):
return self.canvas == other.canvas and self.id == other.id
return super().__eq__(other)
@property
def id(self) -> str:
return self._tag_id
@property
def is_valid(self) -> bool:
return self._tag_id is not None
@property
def canvas(self) -> DrawingCanvas:
return self._canvas
[docs] def delete(self) -> None:
dpgcore.delete_draw_command(self.canvas.id, self.id)
del self._tag_id
[docs] def get_config(self) -> DrawConfigData:
return dpgcore.get_draw_command(self.canvas.id, self.id)
[docs] def set_config(self, **config: Any) -> None:
dpgcore.modify_draw_command(self.canvas.id, self.id, **config)
[docs] def bring_to_front(self) -> None:
dpgcore.bring_draw_command_to_front(self.canvas.id, self.id)
[docs] def send_to_back(self) -> None:
dpgcore.send_draw_command_to_back(self.canvas.id, self.id)
[docs] def move_forward(self) -> None:
dpgcore.bring_draw_command_forward(self.canvas.id, self.id)
[docs] def move_back(self) -> None:
dpgcore.send_draw_command_back(self.canvas.id, self.id)