Skip to main content

An object-oriented, lightweight, modular framework and toolkit for Dear PyGui.

Project description

Dear PyPixl

Dear PyPixl is an object-oriented, lightweight, modular framework and toolkit for Dear PyGui with minimal dependencies and zero use restrictions (MIT license).

Features

Dear PyPixl provides object-oriented interfaces for Dear PyGui items and systems. In addition, it ships with an arrangement of optional extensions and tools to help tackle common hurdles.

  • Extensible object-oriented API for creating and/or interfacing with Dear PyGui items
  • A strongly-typed core library to help get the most out of your development tools
  • A functional API for theme color and styles
  • Patches a few holes in within Dear PyGui's API, such as allowing users to access the application-level font and theme and manage "on-frame" callbacks
  • Organizes DearPyGui's global constants into individual enum.IntEnum classes and exposes more of ImGUI's key code constants
  • Exposes various item type information, such as parent-child relationships between item types
  • An optional layout system and a widget emulating an embedded Python console
  • Provides a near drop-in substitute for Dear PyGui's API

Requirements

  • Operating System; Windows 8.1+, MacOS, Linux*
  • Python: 3.14 x64 (or newer)
  • dearpygui 2.2 (or newer)

[!NOTE] Dear PyPixl is available on all platforms already supported by Dear PyGui. Availability for 32-bit systems and Raspberry Pi OS is loosely supported (at the time of writing), but requires building Dear PyGui from the source. Please visit their wiki for more information.

[!IMPORTANT] This project prioritizes supporting the most recent version of Dear PyGui available, but tries to allow for some backwards compatability when possible.


Installation

Using pip;

python3 -m pip install dearpypixl

Alternatively, build the wheel and install locally. Download/clone the source;
git clone https://github.com/Atlamillias/DearPyPixl

Then, from the project directory;

python3 -m pip install build
python3 -m build <path_to_dearpypixl_dir>
python3 -m pip install <path_to_generated_whl>

Overview

Dear PyPixl's core library is very similar to Dear PyGui, so a lot of existing code will work with a simple change of imports:

import dearpypixl as dpg

dpg.create_context()
dpg.setup_dearpygui()
dpg.create_viewport(title='Custom Title', width=600, height=300)

with dpg.window(label="Example Window"):
    dpg.add_text("Hello, world")
    dpg.add_button(label="Save")
    dpg.add_input_text(label="string", default_value="Quick brown fox")
    dpg.add_slider_float(label="float", default_value=0.273, max_value=1)

dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

Example: "First Run" example code from Dear PyGui's documentation that imports dearpypixl instead of dearpygui.dearpygui.

import dearpypixl as dpx
from dearpygui import demo

dpx.create_context()
dpx.setup_dearpygui()
dpx.create_viewport()
dpx.show_viewport()

with open(demo.__file__, "r") as file:
    # replace the import
    source = file.read().replace(f"import dearpygui.dearpygui as dpg", "import dearpypixl as dpg", 1)

# rebuild the module
demo = type(demo)(demo.__name__)
exec(source, demo.__dict__)

demo.show_demo()
dpx.start_dearpygui()

Example: Running Dear PyGui's demo that has been patched to import dearpypixl.

As such, most of Dear PyGui's documentation applies to Dear PyPixl as well. APIs unique to Dear PyPixl are typed and documented — your type-checker, language server, and other development tools will typically display this information for you.

Item Interfaces

In Dear PyGui, functions such as window() and add_button() return the tag of the item they create. A tag is a unique identifier representing that item in Dear PyGui's registry. Similar functions exist in Dear PyPixl as well, but return item interface objects in place of normal integer or string identifiers. Item interfaces can be used anywhere expecting that item's tag. In addition, interfaces expose various methods and properties that operate on that item:

import dearpypixl as dpx

# DPG setup code
...

with dpx.window(autosize=True) as window:
    text = dpx.add_text("The next test is impossible.")

    def update_text(button_id: int | str, value: bool, text: dpx.mvText) -> None:
        button = dpx.mvButton(button_id)
        text.value = button.label

    labels = (
        "Cake and grief counseling will be available at the conclusion of the test.",
        "The Enrichment Center is required to remind you that you will be baked, and then there will be cake.",
        "Uh oh. Somebody cut the cake.",
    )
    for label in labels:
        dpx.add_button(label=label, user_data=text, callback=update_text)

dpx.start_dearpygui()

[!NOTE] Further code examples may omit the typical Dear PyGui boilerplate code e.g. create_context(), setup_dearpygui(), etc. However, they probably won't run without it.

The above code shouldn't be too unfamiliar for those familiar with Dear PyGui. The snippet creates a window item, a text item, and three button items. When a button is clicked, the text item's value is updated to mirror the button's label. The part to unpack is the update_text() callback function:

    def update_text(sender: int | str, app_data: bool, user_data: dpx.mvText) -> None:
        button = dpx.mvButton(sender)
        user_data.value = button.label

While creating each button, its user_data was assigned to the item whose value they'll update (text in this case). Since functions like add_text() in Dear PyPixl return interfaces, text is not a typical integer but an instance of the mvText class. Dear PyGui will send this along with the usual suite of other arguments to update_text() when our buttons are clicked.

However, sender is still an item tag. You can use the functional API to operate on it as you typically would via Dear PyGui e.g. set_value(), or you can wrap it in an interface and use Dear PyPixl's object-oriented API. Interface objects of built-in interface types are stateless and do not have instance dictionaries. Properties and methods operate on their associated items using Dear PyGui's functions. This means that as long as we have access to an existing item's tag, we can get an interface for it simply by calling the appropriate class' constructor:

        button = dpx.mvButton(sender)

Once we have our button interface, we update the target text item's value to match the label of our button. This line:

        user_data.value = button.label

... is functionally equivelent to set_value(user_data, get_item_configuration(sender)["label"]), which would work even though user_data is an interface object.

[!NOTE] When creating interfaces by calling the class directly, the tag argument optional. If omitted, the constructor will make one using dearpygui.generate_uuid(). However, keep in mind that most instance members will raise SystemError while an item does not exist with that tag. If you create an item with this tag later, the interface will work properly.

[!NOTE] At runtime, the most appropriate concrete interface type for an existing item can be obtained using getattr(dearpypixl, get_item_info(item)["type"].removeprefix("mvAppItemType::")), or the Dear PyPixl function dearpypixl.get_interface_type(). Alternatively, the dearpygui.AppItem class can be used to create basic interfaces for any item.


Interface Members

Properties like label, use_internal_label, and user_data are available on any interface regardless of its type. However, the most appropriate interface for an item will expose others that may be unique to items of that type. These properties typically fall into one of three categories based on the system or function(s) used to manage the item setting of the same name: configuration, information, or state. For example, users of Dear PyGui will likely recognize label, use_internal_label, and user_data as common item settings that are managed using the get_item_configuration() and configure_item() functions. If an item's setting could be initially set when the item was created and that setting is included in the dictionary returned by get_item_configuration(item), a configuration property for that setting is available on the interface. If that setting can later be updated using configure_item(), that property will also be writable. A similar relationship exists for settings returned via get_item_info() and get_item_state() as well, but most are read-only unlike configuration-related settings.

In addition to properties, all interfaces have a method named after each system/function:

System Dear PyPyxl Interface Method Dear PyGui Function
configuration item.configure(**kwargs) configure_item(item, **kwargs)
configuration item.configuration() get_item_configuration(item)
information item.information() get_item_info(item)
state item.state() get_item_state(item)

There are a few exceptions:

  • configuration: We glossed over this one — properties for configuration-related item settings are only available if that setting could be set when creating the item and later obtained via get_item_configuration(). This is because get_item_configuration() packs a few "common default" settings for every item, even if the item doesn't support them. An interface type won't implement a property for a setting that is redundant or useless e.g. mvButton does not implement a source property even though get_item_configuration(button) includes a "source" key. These settings will still be included via item.configuration() though, since that method simply returns the result of get_item_configuration(item).
  • information:
    • Interface types do not implement *_handler_applicable properties even though they're included in the dictionary returned by get_item_info(). The rationale for not doing so is opinionated — I don't believe they are useful enough at runtime to warrant the API bloat (it's an opinion that can easily change). However, those settings are still returned via the information() method.
    • All items have writable theme, font, and handlers properties. Accessing these settings via named property returns all non-null values (ones that typically would be item tags) as mvTheme, mvFont, and mvItemHandlerRegistry objects, respectively. On set, the appropriate bind_item_*() Dear PyGui function is used to update the item with the new setting. Additionally, deleting the value e.g. del item.theme is equivelent to setting it to 0 or None.
  • state:
    • Interfaces do not implement the ok property. The the exists() method, which calls does_item_exist(), roughly serves the same purpose and is more performant as Dear PyGui does not need to pack a new Python dictionary.
    • pos is a very special case. All interfaces have a readable pos property as get_item_state(item)["pos"] is valid for all items, even for those without geometry. If configure_item(item, pos=...) is valid for that item, the appropriate interface will have a writable variant instead. Additionally, the writable implementation supports deletion which will restore the associated item to its default position (roughly equivelent to configure_item(item, pos=())).

[!NOTE] HTML-friendly docs and API reference coming soon! In the meantime, please refer to the source code definitions and/or type stub symbols for a complete view of available members for each interface class.


Interface Types

Dear PyGui exposes over 150 different types of items and widgets. In Dear PyPixl, every type of item is represented by an AppItem subclass available in the dearpypixl namespace. Concrete interface types are named after an item's internal Dear PyGui type name and always have the mv prefix (mvButton, mvChildWindow, etc). They have a fairly simple structure and can be subclassed with a couple considerations in mind. Just like any other Python class, calling an interface class creates an interface object. The associated Dear PyGui item is not coupled to the interface's creation. Typically, you would create the Dear PyGui item first, then create an interface for it by providing the constructor that item's tag. This is what the create() class method does — by calling a function like dearpypixl.add_button(), you are actually calling mvButton.create(). This is particularly important when extending or subclassing an existing interface class, as the constructor and destructor methods you implement or override may limit how and when those objects can be created and used.

The easiest way to extend an existing interface class in a way that keeps their behavior consistent with that of built-in types is by deriving from both the interface class of choice and the CompositeItem mixin class, in addition to overridding the create() class method:

import typing
import dearpypixl as dpx


class LogWindow(dpx.CompositeItem, dpx.mvChildWindow):

    @classmethod
    def create(self, /, maxsize: int = 0, *, auto_scroll: bool = False, **kwargs) -> typing.Self:  # override
        self = super().create(**kwargs)

        # initialize the object like you would in `__init__` or `__new__`
        self.maxsize = maxsize
        self.auto_scroll = auto_scroll
        self._overflow = dpx.add_stage()

        return self

    # other properties/methods

    def get_value(self, *, include_clipped: bool = False) -> str:
        if include_clipped:
            children = self._overflow.children(1)
            children.extend(self.children(1))
        else:
            children = self.children(1)

        value = '\n'.join(v for v in dpx.get_values(children) if v is not None)

        return value

    def move_overflow_text(self) -> None:
        children = self.children(1)

        item_count = len(children)
        while item_count > self.maxsize:
            item = children.pop(0)
            dpx.move_item(item, parent=self._overflow)

            item_count = len(children)

        if self.auto_scroll:
            self.y_scroll_pos = -1.0

    def add_text(self, value: str) -> dpx.mvText:
        item = dpx.add_text(value, parent=self)
        self.move_overflow_text()
        return item

    def write(self, value: str) -> None:
        self.add_text(value)

    @property
    def maxsize(self) -> int:
        return self._maxsize
    @maxitems.setter
    def maxsize(self, value: int) -> None:
        if value == self.maxsize:
            return

        if value < 0:
            value = 0

        self.maxitems = value
        self.move_overflow_text()

    def __exit__(self, *args) -> None:
        super().__enter__(*args)
        self.move_overflow_text()

# optional — alias the `create()` method
log_window = add_log_window = LogWindow.create

# typical DPG boilerplate
...

with dpx.window():
    with log_window(maxsize=3, auto_scroll=True, tag="log-window") as log:
        log.write("Cake and grief counseling will be available at the conclusion of the test.")
        log.write("The Enrichment Center is required to remind you that you will be baked, and then there will be cake.")
        log.write("Uh oh. Somebody cut the cake.")
        log.write("The cake is a lie!")

LogWindow objects manage a widget for displaying lines of text from a file or log. Calling LogWindow.create() creates the underlying Dear PyGui item and interface, initializes the interface, then returns it. So, what happens if we override __new__() instead?

import typing
import dearpypixl as dpx
import dearpygui.dearpygui as dpg

class LogWindow(dpx.mvChildWindow):

    def __new__(cls, /, maxsize: int = 0, *, auto_scroll: bool = False, tag: int | str = 0, **kwargs) -> typing.Self:
        item = dpg.add_child_window(tag=tag, **kwargs)
        self = super().__new__(item)

        self.maxsize = maxsize
        self.auto_scroll = auto_scroll
        self._overflow = dpx.add_stage()

        return self

    ...

# optional — alias the constructor
log_window = add_log_window = LogWindow

Well, this code still works:

with dpx.window():
    with log_window(maxsize=3, auto_scroll=True, tag="log-window") as log:
        log.write("Cake and grief counseling will be available at the conclusion of the test.")
        log.write("The Enrichment Center is required to remind you that you will be baked, and then there will be cake.")
        log.write("Uh oh. Somebody cut the cake.")
        log.write("The cake is a lie!")

But now you can't create a new interface without also creating an item. Additionally, each interface object has its own state. This isn't necessarily a bad thing, but it puts them in a weird spot because only some of our state is object-bound, while the rest is stored in Dear PyGui. If this is design is intentional, you're better off not subclassing mvChildWindow and managing these two entities seperately. By deriving from CompositeItem and using create() as the primary constructor, you effectively extend your additional state and API to the item itself — different interface objects created with the same tag become views of the original:

with dpx.window():
    log = LogWindow.create(maxsize=3, auto_scroll=True, tag="log-window")

# different interface objects, same state
LogWindow("log-window").maxsize = 8
LogWindow("log-window").auto_scroll = False

log.maxsize == 8          # True
log.auto_scroll == False  # True

Another benefit of deriving from CompositeItem is item dependency management. Custom widgets usually involve creating several items. But, what happens to those other items when your item is deleted? This is a non-issue if they are its direct or indirect children as dearpygui.delete_item() will take care of it for you. That is not the case for items like themes and handler registries, which linger around in Dear PyGui's item registry until they are explicitly deleted:

# this deletes the underlying `mvChildWindow` item, but not the `mvStage`
# assigned to `log._overflow`
log.destroy()

Just like how interfaces have a dedicated constructor method in the form of create(), the destroy() method acts as their destructor. We can override it, but that isn't necessary to fix our problem here — we just need to add a line of code to create():

class LogWindow(dpx.CompositeItem, dpx.mvChildWindow):

    @classmethod
    def create(self, /, maxsize: int = 0, *, auto_scroll: bool = False, **kwargs) -> typing.Self:
        self = super().create(**kwargs)

        self.maxsize = maxsize
        self.auto_scroll = auto_scroll
        self._overflow = dpx.add_stage()

        self.components = [self._overflow]

        return self

The components attribute is special-cased by CompositeItem.destroy(). When destroy() is called, it checks to see if item.components is a sequence of items (either item interfaces or normal item identifiers). Every item interface will have its destroy() method called while every other item is destroyed via delete_item(). Additionally, the procedure puts the interface object in a near-unusable state similar to calling a normal object's __del__() method.

[!NOTE] HTML-friendly docs and API reference coming soon! In the meantime, please refer to the source code definitions and/or type stub symbols for a complete view of available members for each interface class.


Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dearpypixl-2.1.2.tar.gz (160.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dearpypixl-2.1.2-py3-none-any.whl (170.0 kB view details)

Uploaded Python 3

File details

Details for the file dearpypixl-2.1.2.tar.gz.

File metadata

  • Download URL: dearpypixl-2.1.2.tar.gz
  • Upload date:
  • Size: 160.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for dearpypixl-2.1.2.tar.gz
Algorithm Hash digest
SHA256 cfbf2f672a84f856a4f109ed1945b67e36211c7ac6dc319eef559da8666b8333
MD5 1835458ff86787a8f1c5411d8c4d5176
BLAKE2b-256 42745eedbe16ea1e5ee29061a6ff1adcf327f38df2c97afe9bab14da753a0ecc

See more details on using hashes here.

File details

Details for the file dearpypixl-2.1.2-py3-none-any.whl.

File metadata

  • Download URL: dearpypixl-2.1.2-py3-none-any.whl
  • Upload date:
  • Size: 170.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for dearpypixl-2.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 382ea870aff86fdca7faa205bda63303f2c2d4a7081aa53fd3ff334593b93acc
MD5 d68f5bcb8fe239f2dc85aa21d4b1f28e
BLAKE2b-256 6a9e2d6f25be3f6da30eaa229e909e418a771a38325f0b94b5405d85bbea6440

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page