74.7 Dear PyGui: GPU-Accelerated Immediate Mode GUI
Now, let’s talk about Dear PyGui. If you’ve been wrestling with tkinter’s aging aesthetic or felt the sheer gravitational pull of PyQt6’s learning curve, this library is going to feel like finding a cheat code. It’s a GPU-accelerated, immediate-mode GUI framework for Python. Let’s break that down because it’s not just marketing fluff.
First, “immediate mode.” This is the core philosophical shift. In toolkits like Qt or tkinter (retained mode), you create a button object, it lives in memory, and the toolkit manages its state until you destroy it. It’s a long-term relationship. Dear PyGui is a series of one-night stands. Every frame, you redraw your entire UI from scratch. “Hey, draw a button here.” The library handles the input and gives you a boolean back for if it was clicked this frame. It sounds wildly inefficient, right? That’s where the “GPU-accelerated” part comes in. It uses the graphics card via Dear ImGui (a C++ library) to make this process absurdly fast. You’re not bogging down the CPU; you’re offloading the entire rendering workload to the GPU. The result is a buttery-smooth, highly dynamic UI that you can prototype in minutes.
The Core Loop and Basic Window
You don’t event loop .mainloop() like a retained mode GUI. You write a while loop. It feels a bit like a game loop, and that’s because it is. You start by creating a primary window and then just draw everything inside your loop.
from dearpygui.dearpygui import *
# Boilerplate setup
create_context()
create_viewport(title='Custom Title', width=600, height=300)
# The "drawing" part happens inside this function
def render_callback():
# Start a new window for this frame
with window(label="Example Window", width=500, height=200):
text("Hello, world")
add_button(label="Save", callback=lambda: print("Saving..."))
add_input_text(label="string", default_value="type here")
# Tell DPG to use our function for drawing
set_primary_window("Example Window", True)
set_render_callback(render_callback)
# The equivalent of .mainloop()
setup_dearpygui()
show_viewport()
start_dearpygui()
destroy_context()
The set_render_callback is key. That function is called on every frame, and you declare your UI within it. The with window() context manager is your best friend.
Why It’s Blazingly Fast (And a Pitfall)
The speed comes from the immediate mode paradigm. There’s no persistent widget tree to manage. This is fantastic for highly variable UIs—imagine a control panel where the number of sliders changes based on a selection. In PyQt, you’d be manually adding and removing widgets, managing layouts, and praying. In Dear PyGui, you just use an if statement in your render callback.
def render_callback():
with window(label="Dynamic Controls", width=600, height=400):
add_checkbox(label="Show Advanced Options", tag="adv_options_check")
if get_value("adv_options_check"): # Check its state *this frame*
add_slider_float(label="Super Tweak", max_value=100.0)
add_slider_float(label="Mega Tweak", max_value=100.0)
The pitfall? State persistence. Since nothing exists between frames, you are responsible for storing any data the user enters. If you type into an add_input_text widget, DPG holds that text for you internally. But if you want to do something with that data later, you must query its value with get_value() within the callback. Your application’s state must live outside the UI. This is the biggest mental shift and a common source of confusion for newcomers.
Theming and Customization: A Double-Edged Sword
Dear PyGui is themable to an almost comical degree. You can change almost any color of any element. But here’s the designers’ questionable choice: the API for it is… verbose. You don’t just set a style sheet; you create theme components and items.
with theme() as custom_theme:
with theme_component():
set_theme_color(mvThemeCol_Button, (100, 200, 100), category=mvThemeCat_Core) # Ugly green button
set_theme_color(mvThemeCol_ButtonHovered, (130, 230, 130), category=mvThemeCat_Core)
add_button(label="Themed Button")
bind_theme_style(custom_theme)
It’s powerful, but it can feel like using a cannon to kill a fly if you’re just used to CSS. The built-in themes (add_theme_color(mvThemeCol_Classic)) are a lifesaver for quick prototyping.
Best Practices and The Gotchas
Tag Everything: Every widget can have a
tag, a unique string identifier. Use it religiously. It’s how you query (get_value("my_input_tag")) and modify (configure_item("my_input_tag", show=True)) widgets after creation. If you don’t provide one, DPG generates a random internal one, which is a fast track to unmaintainable code.add_input_text(label="Name", tag="user_name_input") # Good add_input_text(label="Name") # Bad. What is this thing? Who knows!The Asyncronous Quirk: The
callbackfunctions for buttons and such are not executed in the main render loop. They are asynchronous. This is mostly fine, but if you need to modify the UI from within a callback (which you often do), you must usedefer_callback()to safely queue that change back to the main thread. Forgetting this is a classic crash.def save_callback(sender, app_data): # get_value() is safe here user_data = get_value("user_name_input") # configure_item() is NOT safe directly in a callback! defer_callback(configure_item, "status_text", default_value=f"Saved {user_data}!") add_button(label="Save", callback=save_callback) add_text("", tag="status_text")It’s Not Native: This is the big trade-off. Your app will not look like a native Windows, Mac, or Linux application. It will look like a Dear PyGui application. For tools, dashboards, and internal apps, this is often perfectly acceptable. For consumer-facing software where platform integration is key, it might not be.
Dear PyGui isn’t the right tool for every job, but when you need to whip up a complex, data-heavy, or real-time interface without the overhead of a traditional framework, it’s in a league of its own. It respects your time and intelligence, even if its state management requires a bit more of yours.