├── .gitignore ├── LICENSE ├── README.md ├── assets ├── gifs │ ├── marker_size.gif │ ├── progress_bar.gif │ └── simple_paint.gif └── pngs │ └── data_binding.png ├── buttons ├── button_border_theme.py ├── change_button_colour_when_clicked.py ├── combo_box_custom_1.py ├── combo_box_custom_2.py ├── nested_radio_buttons.py ├── slider_with_step_size.py └── tab_bar_callback.py ├── data_binding ├── data_binding_with_dict.py ├── data_binding_with_list.py └── no_data_binding.py ├── drawing ├── beach.jpg ├── erase_image.py ├── raindrops.py ├── render_loop_example.py ├── resize_image_with_viewport.py ├── simple_paint.py ├── stretch_image.py ├── update_dynamic_texture.py ├── update_fill_colour.py └── window_background_gradient.py ├── fonts ├── FiraCode-Medium.ttf ├── Inter-Bold.ttf ├── Inter-Medium.ttf ├── Inter-Regular.ttf ├── fonts_example.py ├── fonts_spacing_text.py └── license.txt ├── listbox ├── listbox_custom.py ├── listbox_custom_with_keypress.py ├── listbox_extended.py ├── listbox_key_press.py └── listbox_update_items.py ├── loggers ├── README.md ├── logger_autoscroll.py └── logger_dearpygui_ext.py ├── menubar ├── menubar_checkbox.py ├── menubar_radio_buttons.py ├── menubar_right_aligned.py └── menubar_types.py ├── misc ├── camera_capture_with_opencv.py ├── coloured_tree_node.py ├── date_picker.py ├── hex_editor.py ├── loading_indicator_on_startup.py ├── multiple_node_attributes_one_line.py ├── print_from_pdf_viewer.py └── take_screenshot.py ├── packaging ├── README.md ├── Taskfile.yml ├── app │ ├── __init__.py │ ├── constants.py │ ├── fonts.py │ ├── gui.py │ ├── textures.py │ └── utils.py ├── assets │ ├── fonts │ │ ├── Inter-Medium.ttf │ │ └── license.txt │ └── img │ │ └── beach.jpg └── main.py ├── persistence ├── persistence_of_windows.py ├── persistence_using_dataclasses.py └── persistence_using_dict.py ├── plots ├── plot_bar_series_custom_colours.py ├── plot_colormap_resizing.py ├── plot_draw_lines.py ├── plot_enforce_limits.py ├── plot_mouse_click_callback.py ├── plot_text_overlay_using_annotations.py ├── plot_text_overlay_using_drawlist.py ├── plot_update_data.py ├── plot_update_line_colour.py ├── plot_update_marker_size.py ├── plot_update_time_data.py └── plot_with_button.py ├── sizing ├── get_item_size_on_startup.py ├── resize_button_with_viewport.py └── resize_child_window.py ├── spacing ├── adjustable_separators.py ├── spacing_child_windows_using_tables.py ├── spacing_using_auto_align.py ├── spacing_using_child_window_grid.py └── spacing_using_tables.py ├── tables ├── table_cell_callback.py └── table_row_callback.py ├── threading ├── progress_bar.py ├── start_stop_button_basic.py └── start_stop_button_class.py └── window ├── child_window_clicked_handler.py ├── drag_menu_bar.py ├── drag_undecorated_viewport.py ├── pop_to_window.py ├── restrict_window_position.py ├── right_click_context_menu.py ├── status_bar.py ├── transparency ├── main.py ├── tools.py └── windoweffect │ ├── __init__.py │ ├── c_structures.py │ └── window_effect.py └── window_always_on_top.py /.gitignore: -------------------------------------------------------------------------------- 1 | wip 2 | .vscode 3 | persistence/*.json 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 my1e5 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 examples 2 | A collection of example scripts which demonstrate various features/functionality in DearPyGui. 3 | 4 |

5 |          6 |

7 |

8 |          9 |

10 | 11 | ## Examples 12 | 13 | - [Buttons](#buttons) 14 | - [Data binding](#data-binding) 15 | - [Drawing](#drawing) 16 | - [Simple paint](#simple-paint) 17 | - [Fonts](#fonts) 18 | - [Listbox](#listbox) 19 | - [Menubar](#menubar) 20 | - [Misc](#misc) 21 | - [Persistence](#persistence) 22 | - [Plots](#plots) 23 | - [Sizing](#sizing) 24 | - [Spacing](#spacing) 25 | - [Tables](#tables) 26 | - [Threading](#threading) 27 | - [Progress bar](#progress-bar) 28 | - [Window](#window) 29 | 30 | ## [Buttons](buttons/) 31 | 32 | Examples of how to implement various types of buttons. This includes how to implement a button which changes colour when clicked and how to implement nested radio buttons. 33 | 34 | ## [Data binding](data_binding/) 35 | 36 | Examples of how to link a data structure to GUI items so that changes made using the GUI are reflected in the underlying data structure. 37 | 38 | 39 | 40 | ## [Drawing](drawing/) 41 | 42 | Examples of how to use the drawing API. 43 | 44 | ### [Simple paint](drawing/simple_paint.py) 45 | 46 | A very simple implementation of a paint app. It demonstrates how you can click and drag the mouse on a `dpg.add_drawlist` and draw basic free-form lines using `dpg.draw_line`. 47 | 48 | ## [Fonts](fonts/) 49 | 50 | Examples of how to use custom fonts. And some tips on how to get the best results. 51 | 52 | ## [Listbox](listbox/) 53 | 54 | Examples of custom listbox widgets which extend the functionality of the default listbox. Includes how to implement a listbox which is unselected by default and how to respond to key presses. 55 | 56 | ## [Menubar](menubar/) 57 | 58 | Examples of how to implement all the different types of menubar and how to implement a right-aligned menubar. 59 | 60 | ## [Misc](misc/) 61 | 62 | Miscellaneous examples. 63 | 64 | ## [Persistence](persistence/) 65 | 66 | Examples of how to save and load the state of a GUI. This includes the values of GUI items, the position of windows, etc. A simple example using `dict` is shown as well as an approach using `dataclasses`. Both store the app state in a JSON file. 67 | 68 | ## [Plots](plots/) 69 | 70 | Examples of how to implement various features in plots. Such as enforcing axes limits and updating colours and marker styles. 71 | 72 | ## [Sizing](sizing/) 73 | 74 | Examples of how to size/re-size GUI items. There are some quirks with sizing - mainly to do with getting the correct size of an item on startup. 75 | 76 | ## [Spacing](spacing/) 77 | 78 | Examples of how to space GUI items using different methods. This includes automatic spacing, spacing using a grid of child windows and spacing using tables. 79 | 80 | ## [Tables](tables/) 81 | 82 | Examples of how to use tables. 83 | ## [Threading](threading/) 84 | 85 | Examples of how to use threading in DearPyGui. Includes a start/stop button which can be used to start/stop a thread and a progress bar to show the progress of a task. 86 | 87 | ### [Progress bar](threading/progress_bar.py) 88 | 89 | A basic progress bar with a start button. Once running, the start button changes to a pause button. The task can then be paused, upon which the pause button changes to a resume button and a reset button appears. 90 | 91 | ## [Window](window/) 92 | 93 | Examples of how to manage windows. This includes how to create a window which is always on top and how to drag the viewport when `decorated=False`. Also includes restricting the position of a window and how to implement a clicked handler for child windows. 94 | -------------------------------------------------------------------------------- /assets/gifs/marker_size.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/my1e5/dpg-examples/140479d3b2a255a6b78508266da097cca62f9e99/assets/gifs/marker_size.gif -------------------------------------------------------------------------------- /assets/gifs/progress_bar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/my1e5/dpg-examples/140479d3b2a255a6b78508266da097cca62f9e99/assets/gifs/progress_bar.gif -------------------------------------------------------------------------------- /assets/gifs/simple_paint.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/my1e5/dpg-examples/140479d3b2a255a6b78508266da097cca62f9e99/assets/gifs/simple_paint.gif -------------------------------------------------------------------------------- /assets/pngs/data_binding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/my1e5/dpg-examples/140479d3b2a255a6b78508266da097cca62f9e99/assets/pngs/data_binding.png -------------------------------------------------------------------------------- /buttons/button_border_theme.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | with dpg.theme(tag='button_border_theme'): 5 | with dpg.theme_component(): 6 | dpg.add_theme_color(dpg.mvThemeCol_Button, (0, 0, 0, 0)) 7 | dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, (255, 255, 255, 100)) 8 | dpg.add_theme_color(dpg.mvThemeCol_ButtonActive, (0, 0, 0, 0)) 9 | dpg.add_theme_color(dpg.mvThemeCol_Border, (255, 255, 255, 255)) 10 | dpg.add_theme_color(dpg.mvThemeCol_BorderShadow, (0, 0, 0, 0)) 11 | dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 100) 12 | dpg.add_theme_style(dpg.mvStyleVar_FrameBorderSize, 2) 13 | dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 8, 8) 14 | 15 | with dpg.window(width=500, height=300): 16 | dpg.add_button(label="Button", width=100, tag="button") 17 | dpg.bind_item_theme("button", "button_border_theme") 18 | 19 | dpg.create_viewport(width=800, height=600, title='Button Border Theme') 20 | dpg.setup_dearpygui() 21 | dpg.show_viewport() 22 | dpg.start_dearpygui() 23 | dpg.destroy_context() -------------------------------------------------------------------------------- /buttons/change_button_colour_when_clicked.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | with dpg.theme() as button_clicked_theme: 5 | with dpg.theme_component(): 6 | dpg.add_theme_color(dpg.mvThemeCol_Button, (255, 0, 0)) 7 | dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, (225, 0, 0)) 8 | dpg.add_theme_color(dpg.mvThemeCol_ButtonActive, (205, 0, 0)) 9 | 10 | 11 | def toggle_button_colour(sender): 12 | if dpg.get_item_theme(sender) == button_clicked_theme: 13 | dpg.bind_item_theme(sender, None) 14 | else: 15 | dpg.bind_item_theme(sender, button_clicked_theme) 16 | 17 | with dpg.window(): 18 | dpg.add_button(label="Click me!", callback=toggle_button_colour) 19 | 20 | 21 | dpg.create_viewport(width=800, height=600, title="Change button colour when clicked") 22 | dpg.setup_dearpygui() 23 | dpg.show_viewport() 24 | dpg.start_dearpygui() 25 | dpg.destroy_context() 26 | -------------------------------------------------------------------------------- /buttons/combo_box_custom_1.py: -------------------------------------------------------------------------------- 1 | ''' 2 | If you are using a primary window, this will work. See combo_box_custom_2.py for a 3 | version that works with floating windows. 4 | ''' 5 | 6 | import dearpygui.dearpygui as dpg 7 | dpg.create_context() 8 | 9 | def show_options(sender): 10 | x,y = dpg.get_item_pos(sender) 11 | dpg.configure_item("options_window", pos=(x,y+20)) 12 | dpg.configure_item("options_window", show=True) 13 | 14 | with dpg.window(popup=True, show=False, tag="options_window"): 15 | dpg.add_checkbox(label="Option 1") 16 | dpg.add_checkbox(label="Option 2") 17 | dpg.add_checkbox(label="Option 3") 18 | 19 | with dpg.window(width=500, height=300): 20 | dpg.set_primary_window(dpg.last_item(), True) 21 | dpg.add_button(label="Options V", width=100, callback=show_options) 22 | 23 | dpg.create_viewport(width=800, height=600, title='Custom combo box with primary window') 24 | dpg.setup_dearpygui() 25 | dpg.show_viewport() 26 | dpg.start_dearpygui() 27 | dpg.destroy_context() 28 | -------------------------------------------------------------------------------- /buttons/combo_box_custom_2.py: -------------------------------------------------------------------------------- 1 | ''' 2 | If you are not using a primary window, you need to check the position of the window 3 | the button is in and add this on. 4 | ''' 5 | 6 | import dearpygui.dearpygui as dpg 7 | dpg.create_context() 8 | 9 | def show_options(sender): 10 | wx, wy = dpg.get_item_pos("window") 11 | x,y = dpg.get_item_pos(sender) 12 | dpg.configure_item("options_window", pos=(wx+x,wy+y+20)) 13 | dpg.configure_item("options_window", show=True) 14 | 15 | with dpg.window(popup=True, show=False, tag="options_window"): 16 | dpg.add_checkbox(label="Option 1") 17 | dpg.add_checkbox(label="Option 2") 18 | dpg.add_checkbox(label="Option 3") 19 | 20 | with dpg.window(width=500, height=300, tag="window"): 21 | dpg.add_button(label="Options V", width=100, callback=show_options) 22 | 23 | dpg.create_viewport(width=800, height=600, title='Custom combo box with floating window') 24 | dpg.setup_dearpygui() 25 | dpg.show_viewport() 26 | dpg.start_dearpygui() 27 | dpg.destroy_context() 28 | -------------------------------------------------------------------------------- /buttons/nested_radio_buttons.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | buttons = { 5 | "1": {"checked": True, "nested": {"1a":False, "1b":True, "1c":False, "1d":False}}, 6 | "2": {"checked": False, "nested": {}}, 7 | "3": {"checked": False, "nested": {"3a":True}}, 8 | } 9 | 10 | def checkbox_callback(sender): 11 | for button in buttons.keys(): 12 | if not button == sender: 13 | buttons[button]["checked"] = False 14 | dpg.set_value(button, False) 15 | else: 16 | buttons[button]["checked"] = True 17 | if not dpg.get_value(sender): 18 | dpg.set_value(sender, True) 19 | 20 | def radio_button_callback(sender): 21 | for nested_button in buttons[sender.split("_")[0]]["nested"].keys(): 22 | buttons[sender.split("_")[0]]["nested"][nested_button] = False 23 | buttons[sender.split("_")[0]]["nested"][dpg.get_value(sender)] = True 24 | 25 | with dpg.window(): 26 | for button, values in buttons.items(): 27 | dpg.add_checkbox( 28 | tag=button, 29 | label=button, 30 | default_value=values["checked"], 31 | callback=checkbox_callback, 32 | ) 33 | if values["nested"]: 34 | dpg.add_radio_button( 35 | tag=button + "_nested", 36 | items=list(values["nested"].keys()), 37 | indent=24, 38 | callback=radio_button_callback, 39 | ) 40 | for nested_button, nested_values in values["nested"].items(): 41 | if nested_values: 42 | dpg.set_value(button + "_nested", nested_button) 43 | 44 | dpg.add_spacer(height=20) 45 | dpg.add_button(label="Print buttons state", callback=lambda: print(buttons)) 46 | 47 | 48 | dpg.create_viewport(width=800, height=600, title="Nested Radio Buttons Demo") 49 | dpg.setup_dearpygui() 50 | dpg.show_viewport() 51 | dpg.start_dearpygui() 52 | dpg.destroy_context() 53 | -------------------------------------------------------------------------------- /buttons/slider_with_step_size.py: -------------------------------------------------------------------------------- 1 | # Credit @Quattro https://discord.com/channels/736279277242417272/1080603804812181605/1080603804812181605 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | """As a workaround, using format="" you can disable the text in the slider, 6 | then you can add your own label next to the slider, 7 | and change the value according to the step value. 8 | """ 9 | 10 | STEP_SIZE = 2 11 | 12 | with dpg.window(): 13 | dpg.add_slider_int( 14 | tag="myslider", 15 | label=2*STEP_SIZE, 16 | default_value=2, 17 | min_value=0, 18 | max_value=5, 19 | format="", 20 | callback=lambda s, d: dpg.configure_item("myslider", label=d * STEP_SIZE), 21 | ) 22 | 23 | dpg.create_viewport(title="Slider with step size", width=400, height=400) 24 | dpg.setup_dearpygui() 25 | dpg.show_viewport() 26 | dpg.start_dearpygui() 27 | dpg.destroy_context() -------------------------------------------------------------------------------- /buttons/tab_bar_callback.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | ''' 5 | If you use a callback in directly in dpg.tab_bar, e.g. 6 | with dpg.tab_bar(tag='tab_bar', callback=tab_bar_callback): 7 | then the callback is only called when the tab changes. If you want 8 | it to be called whenever the tab bar is clicked, i.e. even when 9 | clicking the same tab, then here is a workaround. 10 | ''' 11 | 12 | def tab_bar_callback(): 13 | for child in dpg.get_item_children('tab_bar')[1]: 14 | if dpg.is_item_hovered(child): 15 | dpg.split_frame() # wait a frame for the tab to change 16 | print(f"{dpg.get_value('tab_bar')} clicked!") 17 | 18 | with dpg.window(): 19 | with dpg.tab_bar(tag='tab_bar'): 20 | with dpg.tab(label="T1", tag='T1'): 21 | dpg.add_button(label="button1") 22 | with dpg.tab(label="T2", tag='T2'): 23 | dpg.add_slider_double() 24 | with dpg.tab(label="T3", tag="T3"): 25 | pass 26 | 27 | with dpg.handler_registry(): 28 | dpg.add_mouse_click_handler(button=0, callback=tab_bar_callback) 29 | 30 | dpg.create_viewport(width=800, height=600, title="Tab bar callback") 31 | dpg.setup_dearpygui() 32 | dpg.show_viewport() 33 | dpg.start_dearpygui() 34 | dpg.destroy_context() 35 | -------------------------------------------------------------------------------- /data_binding/data_binding_with_dict.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | numbers = { 5 | dpg.generate_uuid(): 0, 6 | dpg.generate_uuid(): 0, 7 | dpg.generate_uuid(): 0, 8 | dpg.generate_uuid(): 0, 9 | } 10 | 11 | 12 | def update(_, app_data, user_data): 13 | numbers[user_data] = app_data 14 | 15 | def remove(_, app_data, user_data): 16 | if len(numbers) > 1: 17 | del numbers[user_data] 18 | dpg.delete_item(user_data) 19 | 20 | def add(_, app_data, user_data): 21 | if user_data is None: 22 | number = 0 23 | tag = dpg.generate_uuid() 24 | numbers[tag] = number 25 | else: 26 | tag, number = user_data 27 | with dpg.group(horizontal=True, tag=tag, parent="input_group"): 28 | dpg.add_input_int(default_value=number, callback=update, user_data=tag) 29 | dpg.add_button(label=" X ", callback=remove, user_data=tag) 30 | with dpg.tooltip(parent=dpg.last_item()): 31 | dpg.add_text("Remove this input") 32 | 33 | 34 | with dpg.window() as primary_window: 35 | with dpg.group(horizontal=True): 36 | dpg.add_button(label="Add number", callback=add) 37 | dpg.add_button(label="Print numbers", callback=lambda: print(numbers.values())) 38 | 39 | with dpg.group(tag="input_group"): 40 | for tag, number in numbers.items(): 41 | add(None, None, (tag, number)) 42 | 43 | 44 | dpg.set_primary_window(primary_window, True) 45 | dpg.create_viewport(width=400, height=300, title="Data binding with dict") 46 | dpg.setup_dearpygui() 47 | dpg.show_viewport() 48 | dpg.start_dearpygui() 49 | dpg.destroy_context() 50 | -------------------------------------------------------------------------------- /data_binding/data_binding_with_list.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | numbers = [0, 0, 0, 0] 5 | 6 | def update(_, app_data, user_data): 7 | numbers[user_data] = app_data 8 | 9 | def remove(): 10 | if len(numbers) > 1: 11 | numbers.pop() 12 | last_input = dpg.get_item_children('input_group',1)[-1] 13 | dpg.delete_item(last_input) 14 | 15 | def add(): 16 | numbers.append(0) 17 | dpg.add_input_int(parent='input_group', callback=update, user_data=len(numbers)-1) 18 | 19 | 20 | with dpg.window() as primary_window: 21 | 22 | with dpg.group(horizontal=True): 23 | dpg.add_button(label='Add number', callback=add) 24 | dpg.add_button(label='Remove last number', callback=remove) 25 | dpg.add_button(label="Print numbers", callback=lambda: print(numbers)) 26 | 27 | with dpg.group(tag='input_group'): 28 | for idx, number in enumerate(numbers): 29 | dpg.add_input_int(default_value=number, callback=update, user_data=idx) 30 | 31 | 32 | dpg.set_primary_window(primary_window, True) 33 | dpg.create_viewport(width=400, height=100, title="Data binding with list") 34 | dpg.setup_dearpygui() 35 | dpg.show_viewport() 36 | dpg.start_dearpygui() 37 | dpg.destroy_context() -------------------------------------------------------------------------------- /data_binding/no_data_binding.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | 5 | def add(): 6 | dpg.add_input_int(parent='input_group') 7 | 8 | def remove(): 9 | numbers_tags = dpg.get_item_children('input_group',1) 10 | if len(numbers_tags) > 1: 11 | dpg.delete_item(numbers_tags[-1]) 12 | 13 | def print_numbers(): 14 | numbers = [dpg.get_value(tag) for tag in dpg.get_item_children('input_group',1)] 15 | print(numbers) 16 | 17 | 18 | with dpg.window() as primary_window: 19 | 20 | with dpg.group(horizontal=True): 21 | dpg.add_button(label='Add number', callback=add) 22 | dpg.add_button(label='Remove last number', callback=remove) 23 | dpg.add_button(label="Print numbers", callback=print_numbers) 24 | 25 | with dpg.group(tag='input_group'): 26 | for _ in range(4): 27 | dpg.add_input_int() 28 | 29 | 30 | dpg.set_primary_window(primary_window, True) 31 | dpg.create_viewport(width=400, height=100, title="No data binding") 32 | dpg.setup_dearpygui() 33 | dpg.show_viewport() 34 | dpg.start_dearpygui() 35 | dpg.destroy_context() -------------------------------------------------------------------------------- /drawing/beach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/my1e5/dpg-examples/140479d3b2a255a6b78508266da097cca62f9e99/drawing/beach.jpg -------------------------------------------------------------------------------- /drawing/erase_image.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from math import sqrt 3 | dpg.create_context() 4 | 5 | CANVAS_WIDTH = 300 6 | CANVAS_HEIGHT = 300 7 | ERASE_COLOUR = (0,0,0,0) 8 | 9 | width, height, channels, data = dpg.load_image("beach.jpg") 10 | with dpg.texture_registry(): 11 | dpg.add_dynamic_texture(width, height, data, tag="beach") 12 | 13 | with dpg.theme() as canvas_theme, dpg.theme_component(): 14 | dpg.add_theme_style(dpg.mvStyleVar_WindowPadding, 0,0) 15 | 16 | def erase(): 17 | image_data = dpg.get_value("beach") 18 | radius = dpg.get_value(circle_thickness) 19 | while dpg.is_mouse_button_down(button=dpg.mvMouseButton_Left): 20 | mouse_x,mouse_y = dpg.get_mouse_pos() 21 | x = int(mouse_x/CANVAS_WIDTH * width) 22 | y = int(mouse_y/CANVAS_HEIGHT * height) 23 | 24 | for i in range(width): 25 | for j in range(height): 26 | distance = sqrt(((i / width) - (x / width))**2 + ((j / height) - (y / height))**2) 27 | if distance <= radius / max(width, height): 28 | index = (j * width + i) * 4 29 | image_data[index] = ERASE_COLOUR[0] 30 | image_data[index + 1] = ERASE_COLOUR[1] 31 | image_data[index + 2] = ERASE_COLOUR[2] 32 | image_data[index + 3] = ERASE_COLOUR[3] 33 | 34 | dpg.set_value("beach", image_data) 35 | 36 | 37 | def reset_image(): 38 | dpg.set_value("beach", data) 39 | 40 | with dpg.window() as window: 41 | dpg.add_text("Click and drag to erase.") 42 | with dpg.group(horizontal=True): 43 | 44 | with dpg.child_window(width=CANVAS_WIDTH, height=CANVAS_HEIGHT) as canvas: 45 | dpg.bind_item_theme(canvas, canvas_theme) 46 | 47 | with dpg.drawlist(width=CANVAS_WIDTH, height=CANVAS_HEIGHT) as drawlist: 48 | dpg.draw_image(texture_tag="beach", pmin=(0,0), pmax=(CANVAS_WIDTH,CANVAS_HEIGHT)) 49 | 50 | with dpg.item_handler_registry() as registry: 51 | dpg.add_item_clicked_handler(button=dpg.mvMouseButton_Left, callback=erase) 52 | dpg.bind_item_handler_registry(drawlist, registry) 53 | 54 | with dpg.child_window(border=False): 55 | circle_thickness = dpg.add_slider_int(label="Circle Thickness", width=200, default_value=10, min_value=1, max_value=30) 56 | dpg.add_button(label="Reset image", callback=reset_image) 57 | 58 | 59 | dpg.set_primary_window(window, True) 60 | dpg.create_viewport(width=900, height=600, title="Erase image") 61 | dpg.setup_dearpygui() 62 | dpg.show_viewport() 63 | dpg.start_dearpygui() 64 | dpg.destroy_context() -------------------------------------------------------------------------------- /drawing/raindrops.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | @dataclass 6 | class Raindrop: 7 | x: int 8 | y: int 9 | size: int 10 | color: tuple 11 | 12 | raindrops = [] 13 | 14 | 15 | def update_raindrops(raindrops): 16 | dpg.delete_item(drawlist, children_only=True) 17 | speed_value = dpg.get_value(speed) 18 | raindrops = [raindrop for raindrop in raindrops if raindrop.y <= dpg.get_item_height(drawlist)] 19 | for raindrop in raindrops: 20 | dpg.draw_circle((raindrop.x,raindrop.y), raindrop.size, parent=drawlist, fill=raindrop.color, color=raindrop.color) 21 | raindrop.y += speed_value 22 | return raindrops 23 | 24 | 25 | def create_raindrop(): 26 | x,y = dpg.get_mouse_pos() 27 | raindrops.append(Raindrop(x,y,5,dpg.get_value(color_picker))) 28 | dpg.split_frame(delay=200) 29 | while dpg.is_mouse_button_down(button=dpg.mvMouseButton_Left): 30 | dpg.split_frame(delay=100) 31 | x,y = dpg.get_mouse_pos() 32 | raindrops.append(Raindrop(x,y,5,dpg.get_value(color_picker))) 33 | 34 | 35 | with dpg.theme() as canvas_theme, dpg.theme_component(): 36 | dpg.add_theme_style(dpg.mvStyleVar_WindowPadding, 0,0) 37 | 38 | with dpg.window() as window: 39 | dpg.add_text("Click to create raindrops.") 40 | with dpg.group(horizontal=True): 41 | 42 | with dpg.child_window(width=500, height=500) as canvas: 43 | dpg.bind_item_theme(canvas, canvas_theme) 44 | drawlist = dpg.add_drawlist(width=500, height=500) 45 | with dpg.item_handler_registry() as registry: 46 | dpg.add_item_clicked_handler(button=dpg.mvMouseButton_Left, callback=create_raindrop) 47 | dpg.bind_item_handler_registry(drawlist, registry) 48 | 49 | with dpg.child_window(border=False): 50 | speed = dpg.add_slider_int(label="Speed", width=200, default_value=1, min_value=1, max_value=10) 51 | color_picker = dpg.add_color_picker(width=200, default_value=(0,0,255,255)) 52 | dpg.add_button(label="print raindrops", callback=lambda: print(raindrops)) 53 | 54 | dpg.set_primary_window(window, True) 55 | dpg.create_viewport(width=900, height=600, title="Raindrops") 56 | dpg.setup_dearpygui() 57 | dpg.show_viewport() 58 | while dpg.is_dearpygui_running(): 59 | raindrops = update_raindrops(raindrops) 60 | dpg.render_dearpygui_frame() 61 | dpg.destroy_context() 62 | -------------------------------------------------------------------------------- /drawing/render_loop_example.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | import random 3 | dpg.create_context() 4 | 5 | CANVAS_SIZE = 300 6 | 7 | width, height, channels, data = dpg.load_image("beach.jpg") 8 | with dpg.texture_registry(show=False): 9 | dpg.add_static_texture(width, height, data, tag="beach") 10 | 11 | 12 | def update_circle(): 13 | x = random.randint(0, CANVAS_SIZE) 14 | y = random.randint(0, CANVAS_SIZE) 15 | dpg.configure_item("circle", center=(x,y)) 16 | 17 | 18 | with dpg.window(): 19 | dpg.set_primary_window(dpg.last_item(), True) 20 | 21 | with dpg.drawlist(width=CANVAS_SIZE, height=CANVAS_SIZE): 22 | dpg.draw_image(texture_tag="beach", pmin=(0,0), pmax=(width,height)) 23 | dpg.draw_circle((100,100), 10, fill=(255,0,0), tag="circle") 24 | 25 | 26 | dpg.create_viewport(width=800, height=600, title="Render loop example") 27 | dpg.setup_dearpygui() 28 | dpg.show_viewport() 29 | while dpg.is_dearpygui_running(): 30 | update_circle() 31 | dpg.render_dearpygui_frame() 32 | dpg.destroy_context() -------------------------------------------------------------------------------- /drawing/resize_image_with_viewport.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | width, height, channels, data = dpg.load_image("beach.jpg") 5 | with dpg.texture_registry(): 6 | dpg.add_dynamic_texture(width, height, data, tag="mytexture") 7 | 8 | with dpg.window() as primary_window: 9 | dpg.add_text("Resize the viewport to see the image resize.") 10 | dpg.add_image(texture_tag="mytexture", tag="myimage") 11 | 12 | def resize_primary_window(): 13 | x,y = dpg.get_item_rect_size(primary_window) 14 | dpg.set_item_height("myimage", y//3) 15 | dpg.set_item_width("myimage", x//3) 16 | 17 | with dpg.item_handler_registry() as registry: 18 | dpg.add_item_resize_handler(callback=resize_primary_window) 19 | dpg.bind_item_handler_registry(primary_window, registry) 20 | 21 | dpg.set_primary_window(primary_window, True) 22 | dpg.create_viewport(width=800, height=600, title="Resize Image With Viewport") 23 | dpg.setup_dearpygui() 24 | dpg.show_viewport() 25 | dpg.start_dearpygui() 26 | dpg.destroy_context() -------------------------------------------------------------------------------- /drawing/simple_paint.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | with dpg.theme() as canvas_theme, dpg.theme_component(): 5 | dpg.add_theme_style(dpg.mvStyleVar_WindowPadding, 0,0) 6 | dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (255,255,255,255)) 7 | 8 | def draw(_, app_data): 9 | x,y = dpg.get_mouse_pos() 10 | while dpg.is_mouse_button_down(button=dpg.mvMouseButton_Left): 11 | new_x,new_y = dpg.get_mouse_pos() 12 | if new_x != x or new_y != y: 13 | dpg.draw_line((x,y), (new_x,new_y), parent=app_data[1], color=dpg.get_value(color_picker), thickness=dpg.get_value(line_thickness)) 14 | x,y = new_x,new_y 15 | 16 | with dpg.window() as window: 17 | dpg.add_text("Click and drag to draw.") 18 | with dpg.group(horizontal=True): 19 | 20 | with dpg.child_window(width=500, height=500) as canvas: 21 | dpg.bind_item_theme(canvas, canvas_theme) 22 | drawlist = dpg.add_drawlist(width=500, height=500) 23 | with dpg.item_handler_registry() as registry: 24 | dpg.add_item_clicked_handler(button=dpg.mvMouseButton_Left, callback=draw) 25 | dpg.bind_item_handler_registry(drawlist, registry) 26 | 27 | with dpg.child_window(border=False): 28 | dpg.add_button(label="Clear Canvas", callback=lambda: dpg.delete_item(drawlist, children_only=True)) 29 | line_thickness = dpg.add_slider_int(label="Line Thickness", width=200, default_value=2, min_value=1, max_value=3) 30 | color_picker = dpg.add_color_picker(width=200) 31 | 32 | dpg.set_primary_window(window, True) 33 | dpg.create_viewport(width=900, height=600, title="Simple Paint") 34 | dpg.setup_dearpygui() 35 | dpg.show_viewport() 36 | dpg.start_dearpygui() 37 | dpg.destroy_context() -------------------------------------------------------------------------------- /drawing/stretch_image.py: -------------------------------------------------------------------------------- 1 | # Credit Quattro - https://github.com/QuattroMusic/Bots-Game/blob/main/src/DPG/textures.py 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | 6 | def stretch_image(data, width: int, height: int, stretch: int) -> tuple[int, int, list[int]]: 7 | res = [0 for _ in range(width * height * 4 * stretch * stretch)] 8 | dataValues = [data[i] for i in range(len(data))] 9 | 10 | for pixelIndex in range(len(dataValues) // 4): 11 | nIndex = ((pixelIndex % width) + ((pixelIndex - (pixelIndex % width)) // width) * width * stretch) * stretch 12 | for x in range(stretch): 13 | for y in range(stretch): 14 | res[(nIndex + x + (y * width * stretch)) * 4 + 0] = dataValues[(pixelIndex * 4) + 0] 15 | res[(nIndex + x + (y * width * stretch)) * 4 + 1] = dataValues[(pixelIndex * 4) + 1] 16 | res[(nIndex + x + (y * width * stretch)) * 4 + 2] = dataValues[(pixelIndex * 4) + 2] 17 | res[(nIndex + x + (y * width * stretch)) * 4 + 3] = dataValues[(pixelIndex * 4) + 3] 18 | 19 | return width * stretch, height * stretch, res 20 | 21 | 22 | with dpg.window(width=500, height=500): 23 | 24 | width, height, channels, data = dpg.load_image("beach.jpg") 25 | width_stretched, height_stretched, data_stretched = stretch_image(data, width, height, 2) 26 | 27 | with dpg.texture_registry(): 28 | dpg.add_static_texture(width, height, data, tag="beach_original") 29 | dpg.add_static_texture(width_stretched, height_stretched, data_stretched, tag="beach_stretched") 30 | 31 | dpg.add_image(texture_tag="beach_original", width=width, height=height) 32 | dpg.add_image(texture_tag="beach_stretched", width=width_stretched, height=height_stretched) 33 | 34 | 35 | 36 | dpg.create_viewport(width=800, height=600, title="Stretch image by integer factor") 37 | dpg.setup_dearpygui() 38 | dpg.show_viewport() 39 | dpg.start_dearpygui() 40 | dpg.destroy_context() -------------------------------------------------------------------------------- /drawing/update_dynamic_texture.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from itertools import chain 3 | dpg.create_context() 4 | 5 | GREY = [128/255, 128/255, 128/255, 255/255] 6 | RED = [255/255, 0/255, 0/255, 255/255] 7 | TRANSPARENT = [0, 0, 0, 0] 8 | WIDTH = 50 9 | 10 | def create_circle_texture(fill_level): 11 | data = [] 12 | radius = WIDTH//2 13 | for x in range(0, WIDTH): 14 | for y in range(0, WIDTH): 15 | if (x-radius)**2 + (y-radius)**2 < radius**2: 16 | if (WIDTH-x)/WIDTH > fill_level/100: 17 | data.append(GREY) 18 | else: 19 | data.append(RED) 20 | else: 21 | data.append(TRANSPARENT) 22 | data = list(chain.from_iterable(data)) 23 | return data 24 | 25 | def update_circle(sender, app_data): 26 | data = create_circle_texture(app_data) 27 | dpg.set_value("circle_texture", data) 28 | 29 | data = create_circle_texture(50) 30 | with dpg.texture_registry(): 31 | dpg.add_dynamic_texture(WIDTH, WIDTH, data, tag="circle_texture") 32 | 33 | with dpg.window(width=400, height=400): 34 | dpg.add_image(texture_tag="circle_texture", width=WIDTH, height=WIDTH) 35 | dpg.add_slider_int(label="Grey/Red (%)", default_value=50, min_value=0, max_value=100, callback=update_circle) 36 | 37 | 38 | dpg.create_viewport(width=800, height=600, title="Update dynamic texture") 39 | dpg.setup_dearpygui() 40 | dpg.show_viewport() 41 | dpg.start_dearpygui() 42 | dpg.destroy_context() -------------------------------------------------------------------------------- /drawing/update_fill_colour.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | GREY = [128, 128, 128] 5 | RED = [255, 0, 0] 6 | 7 | def update_circle(sender, app_data): 8 | scale = app_data/100 9 | r = (RED[0]-GREY[0])*scale + GREY[0] 10 | g = (RED[1]-GREY[1])*scale + GREY[1] 11 | b = (RED[2]-GREY[2])*scale + GREY[2] 12 | dpg.configure_item("circle", fill=[r,g,b], color=[r,g,b]) 13 | 14 | with dpg.window(width=400, height=400): 15 | dpg.draw_circle(center=[100, 100], radius=50, fill=RED, color=RED, tag="circle") 16 | dpg.add_slider_float(label="Grey/Red (%)", default_value=100, min_value=0, max_value=100, callback=update_circle) 17 | 18 | dpg.create_viewport(width=800, height=600, title="Update fill colour") 19 | dpg.setup_dearpygui() 20 | dpg.show_viewport() 21 | dpg.start_dearpygui() 22 | dpg.destroy_context() -------------------------------------------------------------------------------- /drawing/window_background_gradient.py: -------------------------------------------------------------------------------- 1 | # Credit @v-ein (see https://discord.com/channels/736279277242417272/1184967921051635883/1184967921051635883) 2 | import dearpygui.dearpygui as dpg 3 | 4 | dpg.create_context() 5 | dpg.create_viewport(width=600, height=600) 6 | dpg.setup_dearpygui() 7 | 8 | with dpg.theme() as no_paddding_theme: 9 | with dpg.theme_component(dpg.mvWindowAppItem): 10 | dpg.add_theme_style(dpg.mvStyleVar_WindowPadding, 0, 0) 11 | 12 | with dpg.window(label="tutorial", width=500, height=500) as wnd: 13 | dpg.bind_item_theme(dpg.last_item(), no_paddding_theme) 14 | 15 | dpg.draw_rectangle( 16 | (0, 0), (500, 500), 17 | color_bottom_right=(0, 0, 0), 18 | color_bottom_left=(0, 0, 0), 19 | color_upper_right=(128, 128, 160), 20 | color_upper_left=(128, 128, 192), 21 | color=(0, 0, 0, 0), 22 | multicolor=True, 23 | fill=True 24 | ) 25 | with dpg.child_window(pos=(8, 28)): 26 | dpg.add_checkbox(label=dpg.get_dearpygui_version()) 27 | dpg.add_button(label="Lorem ipsum", callback=lambda: print("dolor sit")) 28 | 29 | dpg.show_viewport() 30 | dpg.start_dearpygui() 31 | dpg.destroy_context() -------------------------------------------------------------------------------- /fonts/FiraCode-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/my1e5/dpg-examples/140479d3b2a255a6b78508266da097cca62f9e99/fonts/FiraCode-Medium.ttf -------------------------------------------------------------------------------- /fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/my1e5/dpg-examples/140479d3b2a255a6b78508266da097cca62f9e99/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /fonts/Inter-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/my1e5/dpg-examples/140479d3b2a255a6b78508266da097cca62f9e99/fonts/Inter-Medium.ttf -------------------------------------------------------------------------------- /fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/my1e5/dpg-examples/140479d3b2a255a6b78508266da097cca62f9e99/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /fonts/fonts_example.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | FONT_SCALE = 2 5 | with dpg.font_registry(): 6 | font_regular = dpg.add_font('Inter-Regular.ttf', 16*FONT_SCALE) 7 | font_medium = dpg.add_font('Inter-Medium.ttf', 16*FONT_SCALE) 8 | font_bold = dpg.add_font('Inter-Bold.ttf', 22*FONT_SCALE) 9 | dpg.set_global_font_scale(1/FONT_SCALE) 10 | dpg.bind_font(font_medium) 11 | 12 | with dpg.window(width=700, height=500): 13 | dpg.add_text("Fonts example") 14 | dpg.bind_item_font(dpg.last_item(), font_bold) 15 | dpg.add_separator() 16 | dpg.add_text('''* If your fonts look a bit fuzzy it sometimes helps to multiply the font by a scaling factor (e.g. 2) 17 | and then multiply the global font scale by 1/factor. 18 | I find it helps on high DPI displays to make the font look 'crisper'. 19 | 20 | * On Windows this might help: 21 | import ctypes 22 | ctypes.windll.shcore.SetProcessDpiAwareness(2) 23 | 24 | # put before dpg.show_viewport() 25 | 26 | * If you have an integrated graphics card this might help: 27 | dpg.configure_app(auto_device=True) 28 | 29 | * Another thing I find looks better is to use a medium weight font as the default font. 30 | The Inter font is one of my favourites - see https://github.com/rsms/inter''' 31 | ) 32 | dpg.add_separator() 33 | dpg.add_text('''* Here is some text in the regular font (Inter-Regular.ttf) for comparison. 34 | Depending on the screen it can look a bit more fuzzy compared to the medium weight font. 35 | 36 | * Another thing to try is different font sizes. Experiment with different sizes and see what looks best. 37 | 38 | * A good source for open source fonts is google - https://fonts.google.com/''' 39 | ) 40 | dpg.bind_item_font(dpg.last_item(), font_regular) 41 | 42 | dpg.create_viewport(title='Fonts example', width=800, height=600) 43 | dpg.setup_dearpygui() 44 | dpg.show_viewport() 45 | dpg.start_dearpygui() 46 | dpg.destroy_context() 47 | -------------------------------------------------------------------------------- /fonts/fonts_spacing_text.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | 5 | TEXT_WIDTH = 140 6 | INPUT_WIDTH = 80 7 | TRANSPARENT = (0, 0, 0, 0) 8 | FONT_SCALE = 2 9 | 10 | with dpg.font_registry(): 11 | font_sans = dpg.add_font('Inter-Medium.ttf', 16*FONT_SCALE) 12 | font_mono = dpg.add_font('FiraCode-Medium.ttf', 16*FONT_SCALE) 13 | dpg.set_global_font_scale(1/FONT_SCALE) 14 | dpg.bind_font(font_sans) 15 | 16 | 17 | with dpg.theme() as button_text_theme: 18 | with dpg.theme_component(dpg.mvButton): 19 | dpg.add_theme_style(dpg.mvStyleVar_ButtonTextAlign, 1) 20 | dpg.add_theme_color(dpg.mvThemeCol_Button, TRANSPARENT) 21 | dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, TRANSPARENT) 22 | dpg.add_theme_color(dpg.mvThemeCol_ButtonActive,TRANSPARENT) 23 | 24 | 25 | def standard(): 26 | dpg.add_input_float(label="Label", width=INPUT_WIDTH, step=0) 27 | dpg.add_input_float(label="Longer Label", width=INPUT_WIDTH, step=0) 28 | dpg.add_input_float(label="Even Longer Label", width=INPUT_WIDTH, step=0) 29 | 30 | 31 | def f_string_padding(): 32 | with dpg.group(horizontal=True): 33 | dpg.add_text(f"{'Label': <20}") 34 | dpg.add_input_float(width=INPUT_WIDTH, step=0) 35 | with dpg.group(horizontal=True): 36 | dpg.add_text(f"{'Longer Label': <20}") 37 | dpg.add_input_float(width=INPUT_WIDTH, step=0) 38 | with dpg.group(horizontal=True): 39 | dpg.add_text(f"{'Even Longer Label': <20}") 40 | dpg.add_input_float(width=INPUT_WIDTH, step=0) 41 | 42 | 43 | def r_just(): 44 | with dpg.group(horizontal=True): 45 | dpg.add_text("Label".rjust(20)) 46 | dpg.add_input_float(width=INPUT_WIDTH, step=0) 47 | with dpg.group(horizontal=True): 48 | dpg.add_text("Longer Label".rjust(20)) 49 | dpg.add_input_float(width=INPUT_WIDTH, step=0) 50 | with dpg.group(horizontal=True): 51 | dpg.add_text("Even Longer Label".rjust(20)) 52 | dpg.add_input_float(width=INPUT_WIDTH, step=0) 53 | 54 | 55 | def button_method(): 56 | with dpg.group(horizontal=True): 57 | dpg.add_button(label="Label", width=TEXT_WIDTH) 58 | dpg.bind_item_theme(dpg.last_item(), button_text_theme) 59 | dpg.add_input_float(width=INPUT_WIDTH, step=0) 60 | with dpg.group(horizontal=True): 61 | dpg.add_button(label="Longer Label", width=TEXT_WIDTH) 62 | dpg.bind_item_theme(dpg.last_item(), button_text_theme) 63 | dpg.add_input_float(width=INPUT_WIDTH, step=0) 64 | with dpg.group(horizontal=True): 65 | dpg.add_button(label="Even Longer Label", width=TEXT_WIDTH) 66 | dpg.bind_item_theme(dpg.last_item(), button_text_theme) 67 | dpg.add_input_float(width=INPUT_WIDTH, step=0) 68 | 69 | 70 | with dpg.window() as primary_window: 71 | with dpg.group(horizontal=True): 72 | 73 | with dpg.child_window(width=400): 74 | dpg.add_text("Sans Serif Font Example") 75 | for method in [standard, f_string_padding, r_just, button_method]: 76 | dpg.add_spacer(height=20) 77 | method() 78 | 79 | with dpg.child_window(width=400): 80 | dpg.bind_item_font(dpg.last_item(), font_mono) 81 | dpg.add_text("Monospaced Font Example") 82 | for method in [standard, f_string_padding, r_just, button_method]: 83 | dpg.add_spacer(height=20) 84 | method() 85 | 86 | 87 | dpg.set_primary_window(primary_window, True) 88 | dpg.create_viewport(width=900, height=600, title="Fonts Spacing Text") 89 | dpg.setup_dearpygui() 90 | dpg.show_viewport() 91 | dpg.start_dearpygui() 92 | dpg.destroy_context() -------------------------------------------------------------------------------- /fonts/license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2020 The Inter Project Authors. 2 | "Inter" is trademark of Rasmus Andersson. 3 | https://github.com/rsms/inter 4 | 5 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 6 | This license is copied below, and is also available with a FAQ at: 7 | http://scripts.sil.org/OFL 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION AND CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | 96 | 97 | Copyright (c) 2014, The Fira Code Project Authors (https://github.com/tonsky/FiraCode) 98 | 99 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 100 | This license is copied below, and is also available with a FAQ at: 101 | http://scripts.sil.org/OFL 102 | 103 | 104 | ----------------------------------------------------------- 105 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 106 | ----------------------------------------------------------- 107 | 108 | PREAMBLE 109 | The goals of the Open Font License (OFL) are to stimulate worldwide 110 | development of collaborative font projects, to support the font creation 111 | efforts of academic and linguistic communities, and to provide a free and 112 | open framework in which fonts may be shared and improved in partnership 113 | with others. 114 | 115 | The OFL allows the licensed fonts to be used, studied, modified and 116 | redistributed freely as long as they are not sold by themselves. The 117 | fonts, including any derivative works, can be bundled, embedded, 118 | redistributed and/or sold with any software provided that any reserved 119 | names are not used by derivative works. The fonts and derivatives, 120 | however, cannot be released under any other type of license. The 121 | requirement for fonts to remain under this license does not apply 122 | to any document created using the fonts or their derivatives. 123 | 124 | DEFINITIONS 125 | "Font Software" refers to the set of files released by the Copyright 126 | Holder(s) under this license and clearly marked as such. This may 127 | include source files, build scripts and documentation. 128 | 129 | "Reserved Font Name" refers to any names specified as such after the 130 | copyright statement(s). 131 | 132 | "Original Version" refers to the collection of Font Software components as 133 | distributed by the Copyright Holder(s). 134 | 135 | "Modified Version" refers to any derivative made by adding to, deleting, 136 | or substituting -- in part or in whole -- any of the components of the 137 | Original Version, by changing formats or by porting the Font Software to a 138 | new environment. 139 | 140 | "Author" refers to any designer, engineer, programmer, technical 141 | writer or other person who contributed to the Font Software. 142 | 143 | PERMISSION & CONDITIONS 144 | Permission is hereby granted, free of charge, to any person obtaining 145 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 146 | redistribute, and sell modified and unmodified copies of the Font 147 | Software, subject to the following conditions: 148 | 149 | 1) Neither the Font Software nor any of its individual components, 150 | in Original or Modified Versions, may be sold by itself. 151 | 152 | 2) Original or Modified Versions of the Font Software may be bundled, 153 | redistributed and/or sold with any software, provided that each copy 154 | contains the above copyright notice and this license. These can be 155 | included either as stand-alone text files, human-readable headers or 156 | in the appropriate machine-readable metadata fields within text or 157 | binary files as long as those fields can be easily viewed by the user. 158 | 159 | 3) No Modified Version of the Font Software may use the Reserved Font 160 | Name(s) unless explicit written permission is granted by the corresponding 161 | Copyright Holder. This restriction only applies to the primary font name as 162 | presented to the users. 163 | 164 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 165 | Software shall not be used to promote, endorse or advertise any 166 | Modified Version, except to acknowledge the contribution(s) of the 167 | Copyright Holder(s) and the Author(s) or with their explicit written 168 | permission. 169 | 170 | 5) The Font Software, modified or unmodified, in part or in whole, 171 | must be distributed entirely under this license, and must not be 172 | distributed under any other license. The requirement for fonts to 173 | remain under this license does not apply to any document created 174 | using the Font Software. 175 | 176 | TERMINATION 177 | This license becomes null and void if any of the above conditions are 178 | not met. 179 | 180 | DISCLAIMER 181 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 182 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 183 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 184 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 185 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 186 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 187 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 188 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 189 | OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /listbox/listbox_custom.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | def add_custom_listbox(items: list, width: int = 250, height: int = 70, parent: int | str = None, callback: callable = None): 5 | parent = parent or dpg.last_container() 6 | 7 | with dpg.theme() as custom_listbox_theme: 8 | with dpg.theme_component(dpg.mvAll): 9 | dpg.add_theme_style(dpg.mvStyleVar_ItemSpacing, 0,0) 10 | dpg.add_theme_style(dpg.mvStyleVar_ButtonTextAlign, 0, 0.5) 11 | 12 | with dpg.theme() as button_selected_theme: 13 | with dpg.theme_component(dpg.mvButton): 14 | dpg.add_theme_color(dpg.mvThemeCol_Button, (0,119,200,153)) 15 | 16 | with dpg.theme() as button_normal_theme: 17 | with dpg.theme_component(dpg.mvButton): 18 | dpg.add_theme_color(dpg.mvThemeCol_Button, (51, 51, 55, 255)) 19 | 20 | def custom_listbox_callback(sender): 21 | if callback: 22 | callback(dpg.get_item_parent(sender), dpg.get_item_label(sender)) 23 | for button in dpg.get_item_children(dpg.get_item_parent(sender))[1]: 24 | dpg.bind_item_theme(button, button_normal_theme) 25 | dpg.bind_item_theme(sender, button_selected_theme) 26 | 27 | with dpg.child_window(height=height, width=width, border=False, parent=parent) as custom_listbox: 28 | for item in items: 29 | dpg.add_button(label=item, width=-1, callback=custom_listbox_callback) 30 | dpg.bind_item_theme(custom_listbox, custom_listbox_theme) 31 | 32 | def print_callback(sender, data): 33 | print(sender, data) 34 | 35 | with dpg.window() as primary_window: 36 | items = ["Apple", "Banana", "Cherry", "Kiwi", "Mango"] 37 | dpg.add_text("This is a listbox:") 38 | dpg.add_listbox(items=items, callback=print_callback) 39 | dpg.add_spacer(height=20) 40 | dpg.add_text("This is a custom listbox which is unselected by default:") 41 | add_custom_listbox(items=items, callback=print_callback) 42 | 43 | dpg.set_primary_window(primary_window, True) 44 | dpg.create_viewport(width=500, height=400, title="Custom Listbox") 45 | dpg.show_viewport() 46 | dpg.setup_dearpygui() 47 | dpg.start_dearpygui() 48 | dpg.destroy_context() -------------------------------------------------------------------------------- /listbox/listbox_custom_with_keypress.py: -------------------------------------------------------------------------------- 1 | import string 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | 6 | ascii_dict = {ord(char): char for char in string.ascii_uppercase} 7 | 8 | 9 | def print_selected(sender, app_data): 10 | children = dpg.get_item_children(item="custom_listbox")[1] 11 | print("Selected items:") 12 | for child in children: 13 | print(f"{dpg.get_item_label(child)}: {dpg.get_value(child)}") 14 | 15 | 16 | def add_custom_listbox(items: list[str], tag: str, height: int, width: int = -1, scroll_offset: int = 0): 17 | 18 | with dpg.theme() as theme_item_selected: 19 | with dpg.theme_component(dpg.mvSelectable): 20 | dpg.add_theme_color(dpg.mvThemeCol_Header, (0,119,200,153)) 21 | 22 | with dpg.theme() as theme_item_normal: 23 | with dpg.theme_component(dpg.mvSelectable): 24 | dpg.add_theme_color(dpg.mvThemeCol_Header, (51, 51, 55, 255)) 25 | dpg.add_theme_color(dpg.mvThemeCol_HeaderHovered, (51, 51, 55, 255)) 26 | 27 | def _key_press_handler(sender, app_data, user_data): 28 | listbox_ids, items = user_data 29 | letter = ascii_dict.get(app_data) 30 | if letter and dpg.is_item_hovered(tag): 31 | start_index = next(i for i, item_id in enumerate(listbox_ids) if dpg.get_value(item_id)) 32 | for idx, item in enumerate(items[start_index+1:]): 33 | if item.startswith(letter): 34 | _selection(listbox_ids[start_index+1+idx], None, listbox_ids) 35 | dpg.set_y_scroll(tag, max(0,dpg.get_item_pos(listbox_ids[start_index+1+idx])[1] - scroll_offset)) 36 | return 37 | for idx, item in enumerate(items[:start_index]): 38 | if item.startswith(letter): 39 | _selection(listbox_ids[idx], None, listbox_ids) 40 | dpg.set_y_scroll(tag, max(0,dpg.get_item_pos(listbox_ids[idx])[1] - scroll_offset)) 41 | return 42 | 43 | def _selection(sender, app_data, user_data): 44 | 45 | for item in user_data: 46 | dpg.set_value(item, False) 47 | dpg.bind_item_theme(item, theme_item_normal) 48 | 49 | dpg.set_value(sender, True) 50 | dpg.bind_item_theme(sender, theme_item_selected) 51 | 52 | 53 | 54 | with dpg.child_window(tag=tag, height=height, width=width): 55 | for item in items: 56 | dpg.add_selectable(label=item) 57 | 58 | listbox_ids = dpg.get_item_children(item=tag)[1] 59 | 60 | for child in listbox_ids: 61 | dpg.configure_item(child, callback=_selection, user_data=listbox_ids) 62 | 63 | _selection(listbox_ids[0], None, listbox_ids) 64 | 65 | with dpg.handler_registry(): 66 | dpg.add_key_press_handler(callback=_key_press_handler, user_data=(listbox_ids, items)) 67 | 68 | 69 | 70 | with dpg.window(tag="primary_window"): 71 | 72 | dpg.add_text("Custom listbox using selectables and with key press enabled\n(only works when the listbox is hovered)") 73 | 74 | items = ['Apple', 'Apricot', 'Avocado', 'Banana', 'Broccoli', 'Carrot', 'Cherry', 'Cucumber', 'Grape', 'Kiwi', 'Lemon', 'Mango', 'Orange', 'Papaya', 'Peach', 'Pear', 'Pepper', 'Pineapple', 'Potato', 'Raspberry', 'Strawberry', 'Tomato', 'Watermelon'] 75 | 76 | add_custom_listbox(items=items, tag="custom_listbox", height=200, scroll_offset=100) 77 | 78 | dpg.add_button(label="Print Selected in Custom Listbox", callback=print_selected) 79 | 80 | 81 | dpg.set_primary_window("primary_window", True) 82 | dpg.create_viewport(title='Custom listbox using selectables and with key press enabled', width=600, height=600) 83 | dpg.setup_dearpygui() 84 | dpg.show_viewport() 85 | dpg.start_dearpygui() 86 | dpg.destroy_context() 87 | 88 | -------------------------------------------------------------------------------- /listbox/listbox_extended.py: -------------------------------------------------------------------------------- 1 | # Credit @mangotuesday - https://discord.com/channels/736279277242417272/1078520923893805137/1085627890143600752 2 | 3 | import dearpygui.dearpygui as dpg 4 | 5 | dpg.create_context() 6 | dpg.create_viewport(title='Custom Title', width=600, height=600) 7 | 8 | # generic class that holds custom app data 9 | class Data: 10 | def __init__(self): 11 | self.last_selected = None 12 | 13 | 14 | def print_selected(sender, app_data): 15 | children = dpg.get_item_children(item="custom_listbox")[1] 16 | print("Selected items:") 17 | for child in children: 18 | print(f"{dpg.get_item_label(child)}: {dpg.get_value(child)}") 19 | 20 | 21 | def add_custom_listbox(items: list[str], tag: str, height: int, width: int = -1): 22 | 23 | with dpg.theme() as theme_item_selected: 24 | with dpg.theme_component(dpg.mvSelectable): 25 | dpg.add_theme_color(dpg.mvThemeCol_Header, (0,119,200,153)) 26 | 27 | with dpg.theme() as theme_item_normal: 28 | with dpg.theme_component(dpg.mvSelectable): 29 | dpg.add_theme_color(dpg.mvThemeCol_Header, (51, 51, 55, 255)) 30 | 31 | def _selection(sender, app_data, user_data): 32 | 33 | # shift + left click = set True to all in range between previous selection and current 34 | # selection, inclusive 35 | if dpg.is_key_down(dpg.mvKey_Shift): 36 | # if it's the first selection (no previous selection exists), assign to the 37 | # previous selection, set current selection to True, and return 38 | if not d.last_selected: 39 | d.last_selected = sender 40 | dpg.set_value(sender, True) 41 | return 42 | 43 | prev_index = user_data.index(d.last_selected) 44 | cur_index = user_data.index(sender) 45 | 46 | if prev_index < cur_index: 47 | items_to_set_true = user_data[prev_index:cur_index + 1] 48 | else: 49 | items_to_set_true = user_data[cur_index:prev_index + 1] 50 | 51 | for item in items_to_set_true: 52 | dpg.set_value(item, True) 53 | 54 | # ctrl + left click = set True to current selection, preserve previous selections 55 | # unintuitive because of how underlying selectable works, but this will toggle the 56 | # item correctly 57 | elif dpg.is_key_down(dpg.mvKey_Control): 58 | dpg.set_value(sender, dpg.get_value(sender)) 59 | 60 | # left click = erase previous selections, set True to current selection only 61 | else: 62 | for item in user_data: 63 | dpg.set_value(item, False) 64 | dpg.set_value(sender, True) 65 | 66 | for item in user_data: 67 | if dpg.get_value(item) is True: 68 | dpg.bind_item_theme(item, theme_item_selected) 69 | else: 70 | dpg.bind_item_theme(item, theme_item_normal) 71 | 72 | # store previous selection 73 | d.last_selected = sender 74 | 75 | with dpg.child_window(tag=tag, height=height, width=width): 76 | for item in items: 77 | dpg.add_selectable(label=item) 78 | 79 | listbox_ids = dpg.get_item_children(item=tag)[1] 80 | 81 | for child in listbox_ids: 82 | dpg.configure_item(child, callback=_selection, user_data=listbox_ids) 83 | 84 | # dpg.bind_item_theme(tag, theme_listbox_custom) 85 | 86 | d = Data() 87 | 88 | with dpg.window(tag="primary_window"): 89 | 90 | contents = [ 91 | "hello world 1", 92 | "hello world 2", 93 | "hello world 3", 94 | "hello world 4", 95 | "hello world 5", 96 | "hello world 6", 97 | "hello world 7", 98 | ] 99 | 100 | dpg.add_text("Normal Listbox") 101 | dpg.add_listbox(items=contents) 102 | 103 | dpg.add_text("Custom Listbox w/ Sorta Extended Selection") 104 | dpg.add_text("(Try holding CTRL or SHIFT while selecting)") 105 | add_custom_listbox(items=contents, tag="custom_listbox", height=200) 106 | 107 | dpg.add_button(label="Print Selected in Custom Listbox", callback=print_selected) 108 | 109 | dpg.setup_dearpygui() 110 | dpg.show_viewport() 111 | dpg.set_primary_window("primary_window", True) 112 | dpg.start_dearpygui() 113 | dpg.destroy_context() -------------------------------------------------------------------------------- /listbox/listbox_key_press.py: -------------------------------------------------------------------------------- 1 | import string 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | ascii_dict = {ord(char): char for char in string.ascii_uppercase} 6 | 7 | def is_pressed(sender, app_data): 8 | letter = ascii_dict.get(app_data) 9 | if letter: 10 | items = dpg.get_item_configuration('l_box')['items'] 11 | start_index = items.index(dpg.get_value('l_box')) 12 | for item in items[start_index+1:]+items[:start_index]: 13 | if item.startswith(letter): 14 | dpg.set_value('l_box', item) 15 | break 16 | 17 | with dpg.handler_registry(): 18 | dpg.add_key_press_handler(callback=is_pressed) 19 | 20 | with dpg.window(width=500, height=300): 21 | items = ['Apple', 'Apricot', 'Avocado', 'Banana', 'Orange'] 22 | dpg.add_listbox(tag='l_box', items=items) 23 | 24 | dpg.create_viewport(title='Custom Title', width=800, height=600) 25 | dpg.setup_dearpygui() 26 | dpg.show_viewport() 27 | dpg.start_dearpygui() 28 | dpg.destroy_context() -------------------------------------------------------------------------------- /listbox/listbox_update_items.py: -------------------------------------------------------------------------------- 1 | # Updating listbox after creation 2 | # from https://github.com/DataExplorerUser/tools 3 | import dearpygui.dearpygui as dpg 4 | dpg.create_context() 5 | 6 | def new_list_box_item(): 7 | items = dpg.get_item_configuration("lbox")['items'] 8 | items.append(str(len(items)+1)) 9 | dpg.configure_item("lbox", items=items) 10 | 11 | with dpg.window(): 12 | dpg.add_listbox(items=['1', '2'], tag="lbox") 13 | dpg.add_button(label="New listbox item", callback=new_list_box_item) 14 | 15 | dpg.create_viewport(width=600, height=400, title='Updating listbox items after creation') 16 | dpg.setup_dearpygui() 17 | dpg.show_viewport() 18 | dpg.start_dearpygui() 19 | dpg.destroy_context() -------------------------------------------------------------------------------- /loggers/README.md: -------------------------------------------------------------------------------- 1 | # Loggers 2 | 3 | ## Examples 4 | 5 | * https://github.com/hoffstadt/DearPyGui_Ext/ 6 | - `dearpygui_ext/logger.py` 7 | 8 | * https://github.com/DataExplorerUser/autoscroll 9 | - `autoscroll.py` 10 | 11 | * https://github.com/sistemicorp/b13-DPG_Code_patterns 12 | - `logger_example.py` 13 | - `logger_example2.py` 14 | - `logger_klass.py` 15 | 16 | -------------------------------------------------------------------------------- /loggers/logger_autoscroll.py: -------------------------------------------------------------------------------- 1 | # original code by v-ein (taken from https://github.com/DataExplorerUser/autoscroll/blob/ed8ccfb447f5fd4dd105991496972be1246154a6/autoscroll.py) 2 | import textwrap 3 | import dearpygui.dearpygui as dpg 4 | import random 5 | 6 | dpg.create_context() 7 | dpg.create_viewport(title="Automatically scrolling text", width=600, height=500) 8 | dpg.setup_dearpygui() 9 | 10 | words = ("Dear PyGui ", "Scrolling ", "Automatic ", "Toggle ", "Demo ") 11 | log_text = "".join([ words[random.randint(0, 4)] for i in range(300) ]) 12 | 13 | with dpg.window(label="Log autoscroll", width=700, height=400) as wnd: 14 | 15 | def on_load_log(): 16 | FRAME_PADDING = 3 17 | text = textwrap.fill(log_text.replace("\n", " "), width=80) 18 | dpg.set_value("log_field", text) 19 | dpg.set_item_height("log_field", dpg.get_text_size(text)[1] + (2 * FRAME_PADDING)) 20 | 21 | def toggle_auto_scroll(checkbox, checked): 22 | dpg.configure_item("log_field", tracked=checked) 23 | 24 | dpg.add_button(label="Load", callback=on_load_log) 25 | dpg.add_checkbox(label="Autoscroll", default_value=True, callback=toggle_auto_scroll) 26 | 27 | with dpg.child_window(): 28 | # autoscroll is turned on by default as tracked = True 29 | dpg.add_input_text( 30 | tag="log_field", multiline=True, readonly=True, tracked=True, track_offset=1, width=-1, height=0) 31 | 32 | dpg.set_viewport_width(750) 33 | dpg.show_viewport() 34 | dpg.start_dearpygui() 35 | dpg.destroy_context() -------------------------------------------------------------------------------- /loggers/logger_dearpygui_ext.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/hoffstadt/DearPyGui_Ext/issues/4 2 | # requires dearpygui_ext to be installed - see https://github.com/hoffstadt/DearPyGui_Ext 3 | import dearpygui.dearpygui as dpg 4 | import dearpygui.demo as demo 5 | from dearpygui_ext.logger import mvLogger 6 | 7 | dpg.create_context() 8 | dpg.create_viewport() 9 | 10 | log = mvLogger() 11 | log.log("log") 12 | log.log_debug("log debug") 13 | log.log_info("log info") 14 | log.log_warning("log warning") 15 | log.log_error("log error") 16 | log.log_critical("log critical") 17 | 18 | demo.show_demo() 19 | 20 | with dpg.window(label="tutorial", width=500, height=500, show=False): 21 | dpg.add_button(label="Press me", callback=lambda:dpg.toggle_viewport_fullscreen()) 22 | 23 | # main loop 24 | dpg.show_viewport() 25 | dpg.setup_dearpygui() 26 | dpg.start_dearpygui() 27 | dpg.destroy_context() -------------------------------------------------------------------------------- /menubar/menubar_checkbox.py: -------------------------------------------------------------------------------- 1 | # Credit @v-ein - https://discord.com/channels/736279277242417272/1219151962185142272/1219192226031210527 2 | import dearpygui.dearpygui as dpg 3 | 4 | dpg.create_context() 5 | dpg.create_viewport(title="Test", width=600, height=300) 6 | 7 | def checkbox_menu_item(label: str, **kwargs): 8 | # This is what actually toggles the checkbox. Because of this, we can't 9 | # use the checkbox's callback. If you need a callback, add it as an argument 10 | # to `checkbox_menu_item`, and forward the call to it in on_selectable. 11 | def on_selectable(sender, app_data, user_data): 12 | dpg.set_value(sender, False) 13 | # user_data is our checkbox UUID 14 | dpg.set_value(user_data, not dpg.get_value(user_data)) 15 | 16 | with dpg.group(horizontal=True, horizontal_spacing=0): 17 | selectable = dpg.add_selectable(disable_popup_close=True, callback=on_selectable) 18 | checkbox = dpg.add_menu_item(label=label, check=True, **kwargs) 19 | dpg.set_item_user_data(selectable, checkbox) 20 | 21 | # You can return a different widget if you need (e.g. the container or the selectable) 22 | return checkbox 23 | 24 | 25 | # Create the main window 26 | with dpg.window() as wnd: 27 | with dpg.menu_bar(parent=wnd): 28 | with dpg.menu(label="Test menu"): 29 | dpg.add_menu_item(label="Native menu item", check=True) 30 | checkbox_menu_item(label="Non-closing checkbox") 31 | checkbox_menu_item(label="Another non-closing") 32 | 33 | dpg.add_child_window(border=False, height=-30) # offset the help message to the bottom 34 | dpg.add_text("Go into the menu and try to click every item") 35 | 36 | 37 | dpg.setup_dearpygui() 38 | dpg.set_primary_window(wnd, True) 39 | dpg.show_viewport() 40 | dpg.start_dearpygui() 41 | dpg.destroy_context() -------------------------------------------------------------------------------- /menubar/menubar_radio_buttons.py: -------------------------------------------------------------------------------- 1 | # Credit @v-ein - see https://discord.com/channels/736279277242417272/1178380567260180541/1178380567260180541 2 | import dearpygui.dearpygui as dpg 3 | 4 | dpg.create_context() 5 | dpg.create_viewport(title='Test', width=800, height=600) 6 | dpg.setup_dearpygui() 7 | 8 | with dpg.window(label="Radio menu", width=700, height=500) as wnd: 9 | with dpg.menu_bar(): 10 | with dpg.menu(label="Examples"): 11 | dpg.add_menu_item(label="Save") 12 | with dpg.menu(label="Lang"): 13 | 14 | def on_sel_clicked(sender): 15 | # reset the selectable to unselected state 16 | dpg.set_value(sender, False) 17 | # switch the radio button 18 | dpg.set_value("lang-radio", dpg.get_item_label(sender)) 19 | 20 | with dpg.theme() as nopad_theme: 21 | with dpg.theme_component(dpg.mvAll): 22 | dpg.add_theme_style(dpg.mvStyleVar_CellPadding, 4, 0) 23 | with dpg.theme() as sel_theme: 24 | with dpg.theme_component(dpg.mvAll): 25 | dpg.add_theme_style(dpg.mvStyleVar_ItemSpacing, 0, 10) 26 | with dpg.table(header_row=False, policy=dpg.mvTable_SizingFixedFit): 27 | dpg.bind_item_theme(dpg.last_item(), nopad_theme) 28 | 29 | dpg.add_table_column() 30 | dpg.add_table_column(init_width_or_weight=20) 31 | with dpg.table_row(): 32 | with dpg.group(horizontal=True, horizontal_spacing=0): 33 | dpg.add_text() 34 | with dpg.group(): 35 | dpg.bind_item_theme(dpg.last_item(), sel_theme) 36 | dpg.add_selectable(label="en_US", span_columns=True, callback=on_sel_clicked, disable_popup_close=True) 37 | dpg.add_selectable(label="en_GB", span_columns=True, callback=on_sel_clicked, disable_popup_close=True) 38 | dpg.add_selectable(label="cn", span_columns=True, callback=on_sel_clicked, disable_popup_close=True) 39 | dpg.add_selectable(label="jp", span_columns=True, callback=on_sel_clicked, disable_popup_close=True) 40 | dpg.add_selectable(label="whatnot", span_columns=True, callback=on_sel_clicked, disable_popup_close=True) 41 | dpg.add_radio_button(("en_US", "en_GB", "cn", "jp", "whatnot"), tag="lang-radio") 42 | 43 | dpg.show_viewport() 44 | dpg.start_dearpygui() 45 | dpg.destroy_context() -------------------------------------------------------------------------------- /menubar/menubar_right_aligned.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | with dpg.window(width=500, height=500, min_size=(200,200)) as window: 5 | with dpg.menu_bar(): 6 | spacer = dpg.add_spacer() 7 | with dpg.menu(label="File"): 8 | dpg.add_menu_item(label="New") 9 | with dpg.menu(label="Edit"): 10 | dpg.add_menu_item(label="Copy") 11 | with dpg.menu(label="View"): 12 | dpg.add_menu_item(label="Maximize") 13 | 14 | def adjust_menu_bar_spacer(): 15 | dpg.configure_item(spacer, width=dpg.get_item_width(window) - 150) # adjust 150 to fit your needs 16 | 17 | with dpg.item_handler_registry() as item_handler_registry: 18 | dpg.add_item_resize_handler(callback=adjust_menu_bar_spacer) 19 | dpg.bind_item_handler_registry(window, item_handler_registry) 20 | 21 | dpg.create_viewport(width=800, height=600, title="Menubar right aligned") 22 | dpg.setup_dearpygui() 23 | dpg.show_viewport() 24 | dpg.start_dearpygui() 25 | dpg.destroy_context() 26 | -------------------------------------------------------------------------------- /menubar/menubar_types.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | with dpg.viewport_menu_bar(): 5 | with dpg.menu(label="File"): 6 | dpg.add_menu_item(label="New") 7 | dpg.add_menu_item(label="Edit") 8 | dpg.add_menu_item(label="View") 9 | 10 | with dpg.window(pos=(100,100)): 11 | with dpg.menu_bar(): 12 | with dpg.menu(label="File"): 13 | dpg.add_menu_item(label="New") 14 | dpg.add_menu_item(label="Edit") 15 | dpg.add_menu_item(label="View") 16 | 17 | with dpg.child_window(width=200, height=200, menubar=True): 18 | with dpg.menu_bar(): 19 | with dpg.menu(label="File"): 20 | dpg.add_menu_item(label="New") 21 | dpg.add_menu_item(label="Edit") 22 | dpg.add_menu_item(label="View") 23 | 24 | dpg.create_viewport(width=800, height=600, title="Menubar types") 25 | dpg.setup_dearpygui() 26 | dpg.show_viewport() 27 | dpg.start_dearpygui() 28 | dpg.destroy_context() 29 | -------------------------------------------------------------------------------- /misc/camera_capture_with_opencv.py: -------------------------------------------------------------------------------- 1 | # Credit Pcothren - example taken directly from https://github.com/Pcothren/DearPyGui-Examples/blob/d644fa2053c40c5b4dad8dd07ff2366ff2d1a356/camera_capture_with_opencv.py 2 | 3 | import dearpygui.dearpygui as dpg 4 | import cv2 as cv 5 | import numpy as np 6 | 7 | dpg.create_context() 8 | dpg.create_viewport(title='Custom Title', width=600, height=800) 9 | dpg.setup_dearpygui() 10 | 11 | vid = cv.VideoCapture(0) 12 | ret, frame = vid.read() 13 | 14 | # image size or you can get this from image shape 15 | frame_width = vid.get(cv.CAP_PROP_FRAME_WIDTH) 16 | frame_height = vid.get(cv.CAP_PROP_FRAME_HEIGHT) 17 | video_fps = vid.get(cv.CAP_PROP_FPS) 18 | print(frame_width) 19 | print(frame_height) 20 | print(video_fps) 21 | 22 | print("Frame Array:") 23 | print("Array is of type: ", type(frame)) 24 | print("No. of dimensions: ", frame.ndim) 25 | print("Shape of array: ", frame.shape) 26 | print("Size of array: ", frame.size) 27 | print("Array stores elements of type: ", frame.dtype) 28 | data = np.flip(frame, 2) # because the camera data comes in as BGR and we need RGB 29 | data = data.ravel() # flatten camera data to a 1 d stricture 30 | data = np.asfarray(data, dtype='f') # change data type to 32bit floats 31 | texture_data = np.true_divide(data, 255.0) # normalize image data to prepare for GPU 32 | 33 | print("texture_data Array:") 34 | print("Array is of type: ", type(texture_data)) 35 | print("No. of dimensions: ", texture_data.ndim) 36 | print("Shape of array: ", texture_data.shape) 37 | print("Size of array: ", texture_data.size) 38 | print("Array stores elements of type: ", texture_data.dtype) 39 | 40 | with dpg.texture_registry(show=True): 41 | dpg.add_raw_texture(frame.shape[1], frame.shape[0], texture_data, tag="texture_tag", format=dpg.mvFormat_Float_rgb) 42 | 43 | with dpg.window(label="Example Window"): 44 | dpg.add_text("Hello, world") 45 | dpg.add_image("texture_tag") 46 | 47 | dpg.show_metrics() 48 | dpg.show_viewport() 49 | while dpg.is_dearpygui_running(): 50 | 51 | # updating the texture in a while loop the frame rate will be limited to the camera frame rate. 52 | # commenting out the "ret, frame = vid.read()" line will show the full speed that operations and updating a texture can run at 53 | 54 | ret, frame = vid.read() 55 | data = np.flip(frame, 2) 56 | data = data.ravel() 57 | data = np.asfarray(data, dtype='f') 58 | texture_data = np.true_divide(data, 255.0) 59 | dpg.set_value("texture_tag", texture_data) 60 | 61 | # to compare to the base example in the open cv tutorials uncomment below 62 | #cv.imshow('frame', frame) 63 | dpg.render_dearpygui_frame() 64 | 65 | vid.release() 66 | #cv.destroyAllWindows() # when using upen cv window "imshow" call this also 67 | dpg.destroy_context() -------------------------------------------------------------------------------- /misc/coloured_tree_node.py: -------------------------------------------------------------------------------- 1 | # Credit @v-ein https://discord.com/channels/736279277242417272/1207174250604011520/1207260729560793129 2 | 3 | from contextlib import contextmanager 4 | from typing import Generator, Optional, Tuple, Union 5 | import dearpygui.dearpygui as dpg 6 | 7 | dpg.create_context() 8 | 9 | dpg.create_viewport(title="Test", width=600, height=600) 10 | 11 | dpg.setup_dearpygui() 12 | dpg.show_viewport() 13 | 14 | #=============================================================================== 15 | 16 | with dpg.window(label="Tree nodes", width=200, height=150): 17 | with dpg.tree_node(label="Root", default_open=True): 18 | dpg.add_text("Lorem") 19 | with dpg.tree_node(label="Branch", default_open=True): 20 | dpg.add_button(label="ipsum") 21 | dpg.add_tree_node(label="Leaf", leaf=True) 22 | 23 | #=============================================================================== 24 | 25 | with dpg.window(label="Headers", width=200, height=150, pos=(200, 0)): 26 | 27 | with dpg.theme() as tree_like_theme: 28 | with dpg.theme_component(dpg.mvCollapsingHeader): 29 | dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 4, 0) 30 | 31 | with dpg.collapsing_header(label="Root", default_open=True): 32 | dpg.bind_item_theme(dpg.last_item(), tree_like_theme) 33 | with dpg.group(indent=20): 34 | dpg.add_text("Lorem") 35 | with dpg.collapsing_header(label="Branch", default_open=True): 36 | with dpg.group(indent=20): 37 | dpg.add_button(label="ipsum") 38 | dpg.add_collapsing_header(label="Leaf", leaf=True) 39 | 40 | #=============================================================================== 41 | 42 | @contextmanager 43 | def colored_tree_node(label: str, color: Optional[Tuple[int, ...]] = None, leaf: bool = False, **kwargs) -> Generator[Union[int, str], None, None]: 44 | # We use a separate group to adjust padding, so that the header itself can be 45 | # customized further by binding a theme directly to it. 46 | with dpg.group(): 47 | dpg.bind_item_theme(dpg.last_item(), "tree-node-theme") 48 | with dpg.collapsing_header(label=label, leaf=leaf, indent=(21 if leaf else 0), **kwargs) as node: 49 | if color: 50 | with dpg.theme() as color_theme: 51 | with dpg.theme_component(dpg.mvCollapsingHeader): 52 | dpg.add_theme_color(dpg.mvThemeCol_Header, color) 53 | # note: you can add colors for active/hovered, too, e.g. blend them with white 54 | dpg.bind_item_theme(node, color_theme) 55 | # We need one more group in order to provide indentation AND to reset padding back to normal. 56 | # Indent in a normal tree node is determined by dpg.mvStyleVar_IndentSpacing, 57 | # which defaults to 21. 58 | with dpg.group(indent=21): 59 | dpg.bind_item_theme(dpg.last_item(), "default-padding") 60 | yield node 61 | 62 | with dpg.window(label="Wrappers", width=200, height=150, pos=(400, 0)): 63 | 64 | with dpg.theme(tag="tree-node-theme") as tree_like_theme: 65 | with dpg.theme_component(dpg.mvCollapsingHeader): 66 | dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 4, 0) 67 | # We need the default color here so that nested headers don't pick up 68 | # the parent's color. 69 | dpg.add_theme_color(dpg.mvThemeCol_Header, (51, 51, 55, 255)) 70 | 71 | with dpg.theme(tag="default-padding"): 72 | with dpg.theme_component(dpg.mvAll): 73 | dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 4, 3) 74 | 75 | with colored_tree_node(label="Root", default_open=True) as root: 76 | dpg.add_text("Lorem") 77 | with colored_tree_node(label="Branch", default_open=True, color=(192, 0, 0)): 78 | dpg.add_button(label="ipsum") 79 | with colored_tree_node(label="Leaf", leaf=True): 80 | pass 81 | 82 | 83 | dpg.start_dearpygui() 84 | dpg.destroy_context() -------------------------------------------------------------------------------- /misc/date_picker.py: -------------------------------------------------------------------------------- 1 | # Credit @Tensor - https://discord.com/channels/736279277242417272/1083377856064798781/1086329311532953652 2 | import threading 3 | import traceback 4 | from datetime import datetime, timedelta 5 | from functools import cache 6 | from typing import Callable, Self 7 | 8 | import dearpygui.dearpygui as dpg 9 | from dateutil.relativedelta import relativedelta 10 | 11 | 12 | class call_when_dpg_running: 13 | dpg_running = False 14 | worker_started = False 15 | queue = [] 16 | 17 | def __new__(cls, func): 18 | def decorator(*args, **kwargs): 19 | if not cls.worker_started: 20 | cls.worker_started = True 21 | threading.Thread(target=cls._worker, daemon=True).start() 22 | 23 | if cls.dpg_running: 24 | func(*args, **kwargs) 25 | else: 26 | cls.queue.append((func, args, kwargs)) 27 | 28 | return decorator 29 | 30 | @classmethod 31 | def _worker(cls): 32 | while dpg.get_frame_count() < 1: 33 | dpg.split_frame(delay=0) 34 | cls.dpg_running = True 35 | for data in cls.queue: 36 | func, args, kwargs = data 37 | try: 38 | func(*args, **kwargs) 39 | except Exception: 40 | traceback.print_exc() 41 | del cls.queue 42 | 43 | 44 | def cached_class_attr(f): 45 | return property(cache(f)) 46 | 47 | 48 | class DatePicker: 49 | weekdays = ('Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su') 50 | months = ( 51 | '1-January', '2-February', 52 | '3-March', '4-April', '5-May', 53 | '6-June', '7-July', '8-August', 54 | '9-September', '10-October', '11-November', 55 | '12-December', 56 | ) 57 | 58 | callback: Callable[[datetime], None] = None 59 | 60 | date: datetime 61 | min_value: datetime = datetime(year=1970, month=1, day=1) 62 | max_value: datetime = datetime(year=2999, month=12, day=31) 63 | 64 | group: int 65 | _selected_day_tag: int = None 66 | 67 | @classmethod 68 | @cached_class_attr 69 | def _theme(cls) -> int: 70 | with dpg.theme() as theme: 71 | with dpg.theme_component(dpg.mvAll, parent=theme) as theme_component: 72 | dpg.add_theme_color(dpg.mvThemeCol_FrameBg, (0, 0, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_component) 73 | dpg.add_theme_style(dpg.mvStyleVar_WindowPadding, 0, 0, category=dpg.mvThemeCat_Core, parent=theme_component) 74 | dpg.add_theme_style(dpg.mvStyleVar_CellPadding, 0, 0, category=dpg.mvThemeCat_Core, parent=theme_component) 75 | cls._frame_padding = dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 4, 3, category=dpg.mvThemeCat_Core, parent=theme_component) 76 | with dpg.theme_component(dpg.mvButton, parent=theme) as theme_component: 77 | dpg.add_theme_color(dpg.mvThemeCol_Button, (0, 0, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_component) 78 | with dpg.theme_component(dpg.mvButton, enabled_state=False, parent=theme) as theme_component: 79 | dpg.add_theme_style(dpg.mvStyleVar_Alpha, 0.3, category=dpg.mvThemeCat_Core, parent=theme_component) 80 | dpg.add_theme_color(dpg.mvThemeCol_Button, (0, 0, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_component) 81 | dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, (0, 0, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_component) 82 | dpg.add_theme_color(dpg.mvThemeCol_ButtonActive, (0, 0, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_component) 83 | with dpg.theme_component(dpg.mvInputInt, enabled_state=False, parent=theme) as theme_component: 84 | dpg.add_theme_style(dpg.mvStyleVar_Alpha, 0.75, category=dpg.mvThemeCat_Core, parent=theme_component) 85 | dpg.add_theme_color(dpg.mvThemeCol_TextSelectedBg, (0, 0, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_component) 86 | with dpg.theme_component(dpg.mvCombo, enabled_state=False, parent=theme) as theme_component: 87 | dpg.add_theme_style(dpg.mvStyleVar_Alpha, 0.75, category=dpg.mvThemeCat_Core, parent=theme_component) 88 | dpg.add_theme_color(dpg.mvThemeCol_FrameBgActive, (0, 0, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_component) 89 | dpg.add_theme_color(dpg.mvThemeCol_FrameBgHovered, (0, 0, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_component) 90 | return theme 91 | 92 | @classmethod 93 | @cached_class_attr 94 | def _weekdays_theme(cls) -> int: 95 | with dpg.theme() as theme: 96 | with dpg.theme_component(parent=theme) as theme_component: 97 | dpg.add_theme_color(dpg.mvThemeCol_Button, (0, 0, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_component) 98 | dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, (0, 0, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_component) 99 | dpg.add_theme_color(dpg.mvThemeCol_ButtonActive, (0, 0, 0, 0), category=dpg.mvThemeCat_Core, parent=theme_component) 100 | return theme 101 | 102 | @classmethod 103 | @cached_class_attr 104 | def _selected_day_theme(cls) -> int: 105 | with dpg.theme() as theme: 106 | with dpg.theme_component(parent=dpg.mvButton) as theme_component: 107 | # Set the background color of your choice here 108 | dpg.add_theme_color(dpg.mvThemeCol_Button, (51, 51, 55, 255), category=dpg.mvThemeCat_Core, parent=theme_component) 109 | return theme 110 | 111 | @classmethod 112 | @cached_class_attr 113 | def _another_month_day_theme(cls) -> int: 114 | with dpg.theme() as theme: 115 | with dpg.theme_component(parent=dpg.mvButton) as theme_component: 116 | dpg.add_theme_style(dpg.mvStyleVar_Alpha, 0.5, category=dpg.mvThemeCat_Core, parent=theme_component) 117 | return theme 118 | 119 | def __init__(self, date: datetime = None, callback: Callable[[datetime], None] = None): 120 | if date is None: 121 | date = datetime.now() 122 | self.date = datetime(date.year, date.month, date.day) 123 | self.callback = callback 124 | 125 | with dpg.group(width=100) as self.group: 126 | dpg.bind_item_theme(self.group, self._theme) 127 | with dpg.table(header_row=False, parent=self.group) as self._month_days_table: 128 | dpg.add_table_column(parent=self._month_days_table) 129 | dpg.add_table_column(width_fixed=True, parent=self._month_days_table) 130 | dpg.add_table_column(width_fixed=True, parent=self._month_days_table) 131 | 132 | with dpg.table_row(parent=self._month_days_table) as table_row: 133 | with dpg.group(horizontal=True, parent=table_row) as group: 134 | self.month_combo = dpg.add_combo(items=self.months, default_value=self.months[self.date.month - 1], 135 | parent=group, no_arrow_button=True, 136 | callback=lambda _, month_name: self._click_month(self.months.index(month_name))) 137 | self.year_input = dpg.add_input_int(default_value=self.date.year, step=0, step_fast=0, 138 | parent=group, 139 | callback=lambda _, year: self._click_year(year)) 140 | self.past_month_button = dpg.add_button(arrow=True, direction=dpg.mvDir_Left, 141 | parent=table_row, 142 | callback=lambda: self._click_month('-')) 143 | self.next_month_button = dpg.add_button(arrow=True, direction=dpg.mvDir_Right, 144 | parent=table_row, 145 | callback=lambda: self._click_month('+')) 146 | 147 | with dpg.group(): 148 | with dpg.table(header_row=False, policy=dpg.mvTable_SizingFixedFit, 149 | parent=self.group) as self._month_days_table: 150 | for _ in range(7): 151 | dpg.add_table_column(parent=self._month_days_table) 152 | 153 | self._refresh_all() 154 | 155 | def _do_callback(self): 156 | if self.callback is None: 157 | return 158 | try: 159 | self.callback(self.date) 160 | except Exception: 161 | traceback.print_exc() 162 | 163 | def _set_selected_day_tag(self, tag: int): 164 | try: 165 | dpg.bind_item_theme(self._selected_day_tag, 0) 166 | except Exception: 167 | pass 168 | finally: 169 | self._selected_day_tag = tag 170 | dpg.bind_item_theme(tag, self._selected_day_theme) 171 | 172 | @call_when_dpg_running 173 | def _refresh_all(self): 174 | self._refresh_month_and_year() 175 | self._refresh_month_days() 176 | 177 | width = dpg.get_text_size(" ".join(self.weekdays) + " ")[0] + dpg.get_value(self._frame_padding)[0] * 7 * 2 178 | dpg.set_item_width(self.group, width) 179 | 180 | def _refresh_month_and_year(self): 181 | dpg.set_value(self.year_input, self.date.year) 182 | 183 | available_months = self.months 184 | if self.date.year == self.max_value.year: 185 | available_months = available_months[:self.max_value.month:] 186 | if self.date.year == self.min_value.year: 187 | available_months = available_months[self.min_value.month - 1::] 188 | 189 | month_name = self.months[self.date.month - 1] 190 | dpg.configure_item(self.month_combo, default_value=month_name, items=available_months, 191 | width=dpg.get_text_size(month_name)[0] + dpg.get_value(self._frame_padding)[0] * 2) 192 | 193 | enabled_month_combo = True 194 | if self.min_value.month == self.max_value.month and self.min_value.year == self.max_value.year: 195 | enabled_month_combo = False 196 | dpg.configure_item(self.month_combo, enabled=enabled_month_combo) 197 | 198 | enabled_year_input = True 199 | if self.min_value.year == self.max_value.year: 200 | enabled_year_input = False 201 | dpg.configure_item(self.year_input, enabled=enabled_year_input) 202 | 203 | enabled_next_month_btn = True 204 | if self.date.month + 1 > self.max_value.month and self.date.year == self.max_value.year: 205 | enabled_next_month_btn = False 206 | dpg.configure_item(self.next_month_button, enabled=enabled_next_month_btn) 207 | 208 | enabled_past_month_btn = True 209 | if self.date.month - 1 < self.min_value.month and self.date.year == self.min_value.year: 210 | enabled_past_month_btn = False 211 | dpg.configure_item(self.past_month_button, enabled=enabled_past_month_btn) 212 | 213 | def _refresh_month_days(self): 214 | dpg.delete_item(self._month_days_table, children_only=True, slot=1) 215 | 216 | with dpg.table_row(parent=self._month_days_table): 217 | for i in range(7): 218 | btn = dpg.add_button(label=f" {self.weekdays[i]} ") 219 | dpg.bind_item_theme(btn, self._weekdays_theme) 220 | 221 | start_date = self.date.replace(day=1) 222 | start_date -= timedelta(days=start_date.weekday()) 223 | start_date -= timedelta(days=1) 224 | for _ in range(6): # rows count 225 | with dpg.table_row(parent=self._month_days_table) as table_row: 226 | for _ in range(7): 227 | start_date += timedelta(days=1) 228 | if start_date < self.min_value or start_date > self.max_value: 229 | dpg.add_text(parent=table_row) 230 | continue 231 | 232 | user_data = f"{start_date.day}" 233 | if start_date.month != self.date.month: 234 | if start_date < self.date: 235 | user_data = f"-{user_data}" 236 | else: 237 | user_data = f"+{user_data}" 238 | 239 | btn = dpg.add_button(label=f"{start_date.day}", width=-1, parent=table_row, 240 | user_data=user_data, callback=self._click_month_day) 241 | 242 | if start_date.month != self.date.month: 243 | dpg.bind_item_theme(btn, self._another_month_day_theme) 244 | elif start_date.day == self.date.day: 245 | self._set_selected_day_tag(btn) 246 | 247 | def _click_month_day(self, btn: int | str, _, day: str): 248 | if day[0] in ('+', '-'): 249 | self.date = self.date.replace(day=1) 250 | if day[0] == '+': 251 | self.date += relativedelta(months=1) 252 | else: 253 | self.date -= relativedelta(months=1) 254 | self.date = self.date.replace(day=int(day[1:])) 255 | 256 | self._refresh_all() 257 | else: 258 | self.date = self.date.replace(day=int(day)) 259 | self._set_selected_day_tag(btn) 260 | self._do_callback() 261 | 262 | def _click_month(self, month: str | int): 263 | if month == '+': 264 | self.date += relativedelta(months=1) 265 | elif month == '-': 266 | self.date += relativedelta(months=-1) 267 | else: 268 | month = int(month) + 1 269 | self.date += relativedelta(months=month - self.date.month) 270 | 271 | if self.date > self.max_value: 272 | self.date = self.max_value 273 | elif self.date < self.min_value: 274 | self.date = self.min_value 275 | 276 | self._refresh_all() 277 | self._do_callback() 278 | 279 | def _click_year(self, year: int): # click? 280 | if not (len(str(self.min_value.year)) <= len(str(year)) <= len(str(self.max_value.year))): 281 | dpg.set_value(self.year_input, self.date.year) 282 | return 283 | 284 | if year < self.min_value.year: 285 | year = self.min_value.year 286 | elif year > self.max_value.year: 287 | year = self.max_value.year 288 | 289 | if year == self.date.year: 290 | dpg.set_value(self.year_input, self.date.year) 291 | return 292 | 293 | date_with_new_year = self.date.replace(year=year) 294 | if date_with_new_year < self.min_value: 295 | self.date = self.min_value 296 | elif date_with_new_year > self.max_value: 297 | self.date = self.max_value 298 | else: 299 | self.date = date_with_new_year 300 | self._refresh_all() 301 | self._do_callback() 302 | 303 | def set_min_value(self, min_limit: datetime) -> Self: 304 | if min_limit > self.max_value: 305 | raise ValueError("`min_limit` must be less than `max_limit`") 306 | self.min_value = min_limit 307 | if self.date < self.min_value: 308 | self.date = self.min_value 309 | self._refresh_all() 310 | return self 311 | 312 | def set_max_value(self, max_limit: datetime) -> Self: 313 | if max_limit < self.min_value: 314 | raise ValueError("`max_limit` must be greater than `min_limit`") 315 | self.max_value = max_limit 316 | if self.date > self.max_value: 317 | self.date = self.max_value 318 | self._refresh_all() 319 | return self 320 | 321 | def set_value(self, date: datetime) -> Self: 322 | date = datetime(date.year, date.month, date.day) 323 | if date > self.max_value: 324 | raise ValueError("`date` must be less than `max_limit`") 325 | if date < self.min_value: 326 | raise ValueError("`date` must be greater than `min_limit`") 327 | self.date = date 328 | self._refresh_all() 329 | return self 330 | 331 | def get_value(self) -> datetime: 332 | return self.date 333 | 334 | 335 | if __name__ == '__main__': 336 | dpg.create_context() 337 | dpg.create_viewport() 338 | 339 | with dpg.window() as window: 340 | dpg_text = dpg.add_text() 341 | date_picker = DatePicker(callback=lambda date, *, _dpg_text=dpg_text: dpg.set_value(_dpg_text, date)) 342 | dpg.set_value(dpg_text, date_picker.get_value()) 343 | 344 | dpg_text = dpg.add_text() 345 | date_picker = DatePicker(callback=lambda date, *, _dpg_text=dpg_text: dpg.set_value(_dpg_text, date)) \ 346 | .set_min_value(datetime(2021, 3, 4)) \ 347 | .set_max_value(datetime(2022, 9, 15)) \ 348 | .set_value(datetime(2021, 8, 2)) 349 | dpg.set_value(dpg_text, date_picker.get_value()) 350 | 351 | dpg_text = dpg.add_text() 352 | date_picker = DatePicker(callback=lambda date, *, _dpg_text=dpg_text: dpg.set_value(_dpg_text, date)) \ 353 | .set_min_value(datetime(2021, 5, 10)) \ 354 | .set_max_value(datetime(2021, 8, 25)) \ 355 | .set_value(datetime(2021, 7, 8)) 356 | dpg.set_value(dpg_text, date_picker.get_value()) 357 | 358 | dpg_text = dpg.add_text() 359 | date_picker = DatePicker(callback=lambda date, *, _dpg_text=dpg_text: dpg.set_value(_dpg_text, date)) \ 360 | .set_min_value(datetime(2020, 8, 10)) \ 361 | .set_max_value(datetime(2020, 8, 25)) \ 362 | .set_value(datetime(2020, 8, 15)) 363 | dpg.set_value(dpg_text, date_picker.get_value()) 364 | 365 | dpg.set_primary_window(window, True) 366 | 367 | dpg.setup_dearpygui() 368 | dpg.show_viewport() 369 | dpg.start_dearpygui() 370 | dpg.destroy_context() 371 | -------------------------------------------------------------------------------- /misc/hex_editor.py: -------------------------------------------------------------------------------- 1 | # Credit @v-ein - https://discord.com/channels/736279277242417272/736279277242417275/1241101468631695513 2 | from contextlib import suppress 3 | from random import random, randrange 4 | import textwrap 5 | from typing import Any, Union 6 | import dearpygui.dearpygui as dpg 7 | 8 | dpg.create_context() 9 | dpg.create_viewport(title="Test", width=900, height=700) 10 | 11 | class HexEditor: 12 | data: bytearray 13 | start_addr: int 14 | stride: int 15 | group_size: int 16 | encoding: str 17 | unprintable_trans: Any 18 | 19 | container: Union[int, str] = 0 20 | edit_completed_handler: Union[int, str] = 0 21 | click_handler: Union[int, str] = 0 22 | # Horizontal offset, in pixels, of the first byte from the start of the line 23 | first_byte_offset: float = 0.0 24 | 25 | def __init__(self, 26 | data: bytearray, 27 | start_addr: int = 0, 28 | stride: int = 16, 29 | group_size: int = 8, 30 | encoding: str ="iso8859-1", 31 | **kwargs) -> None: 32 | 33 | self.data = data 34 | self.start_addr = start_addr 35 | self.stride = stride 36 | self.group_size = group_size 37 | self.encoding = encoding 38 | 39 | self.unprintable_trans = str.maketrans({char_code: "." for char_code in range(0, 32)}) 40 | 41 | # Now go create the UI 42 | self.create(**kwargs) 43 | 44 | def create(self, **kwargs) -> None: 45 | # Create the themes 46 | with dpg.theme() as main_theme: 47 | with dpg.theme_component(dpg.mvAll): 48 | dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 4, 0) 49 | dpg.add_theme_style(dpg.mvStyleVar_CellPadding, 4, 0) 50 | with dpg.theme_component(dpg.mvInputText): 51 | dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 0, 0) 52 | 53 | # Create the handlers 54 | with dpg.item_handler_registry() as self.edit_completed_handler: 55 | dpg.add_item_edited_handler(callback=self.on_edit_change) 56 | dpg.add_item_deactivated_handler(callback=self.on_edit_deactivated) 57 | 58 | with dpg.item_handler_registry() as self.click_handler: 59 | dpg.add_item_clicked_handler(callback=self.on_byte_clicked) 60 | 61 | # Do some calculations 62 | self.first_byte_offset = dpg.get_text_size("0000: ")[0] 63 | 64 | # Now go create the content 65 | with dpg.table( 66 | header_row=False, 67 | borders_innerV=True, 68 | policy=dpg.mvTable_SizingFixedFit, 69 | no_host_extendX=True, 70 | **kwargs) as self.container: 71 | 72 | dpg.bind_item_theme(dpg.last_item(), main_theme) 73 | 74 | dpg.add_table_column() 75 | dpg.add_table_column() 76 | 77 | for line_ofs in range(0, len(self.data), self.stride): 78 | line_addr = self.start_addr + line_ofs 79 | line_bytes = self.data[line_ofs : line_ofs + self.stride] 80 | with dpg.table_row(): 81 | # The group provides us with a container to which we'll be 82 | # adding absolutely-positioned edit fields 83 | with dpg.group(horizontal=True, horizontal_spacing=0): 84 | line = self.format_hex(line_bytes, line_addr) 85 | dpg.add_text(line, user_data=line_addr) 86 | dpg.bind_item_handler_registry(dpg.last_item(), self.click_handler) 87 | dpg.add_text(self.format_text(line_bytes)) 88 | 89 | def format_hex(self, line_bytes: bytes, line_addr: int) -> str: 90 | # `line_bytes` is only passed here for performance; we could get it from `self.data` as well. 91 | groups = [ 92 | line_bytes[addr : addr + self.group_size].hex(" ", 1) 93 | for addr in range(0, self.stride, self.group_size) 94 | ] 95 | bytes_str = " ".join(groups) 96 | # Note: the trailing space is specifically to allow input widgets some space 97 | # for the text cursor. 98 | return f"{line_addr:04X}: {bytes_str.upper()} " 99 | 100 | def format_text(self, line_bytes: bytes) -> str: 101 | # `line_bytes` is only passed here for performance; we could get it from `self.data` as well. 102 | return line_bytes.decode(self.encoding).translate(self.unprintable_trans) 103 | 104 | def on_byte_clicked(self, sender, app_data) -> None: 105 | widget = app_data[1] 106 | line_addr = dpg.get_item_user_data(widget) 107 | mouse_pos = dpg.get_mouse_pos(local=False) 108 | widget_pos = dpg.get_item_rect_min(widget) 109 | click_offset = mouse_pos[0] - widget_pos[0] - self.first_byte_offset 110 | group_width = dpg.get_text_size("00 " * self.group_size + " ")[0] 111 | clicked_group = click_offset // group_width 112 | # Adjusting for extra space between groups 113 | click_offset -= clicked_group * dpg.get_text_size(" ")[0] 114 | byte_width = dpg.get_text_size("00 ")[0] 115 | clicked_addr = line_addr + int(click_offset // byte_width) 116 | self.edit_byte(clicked_addr) 117 | 118 | def get_row_by_addr(self, addr: int) -> Union[int, str]: 119 | row_idx = (addr - self.start_addr) // self.stride 120 | return dpg.get_item_children(self.container, slot=1)[row_idx] 121 | 122 | def edit_byte(self, byte_addr: int) -> None: 123 | byte_ofs = byte_addr - self.start_addr 124 | byte_hex = self.data[byte_ofs : byte_ofs + 1].hex().upper() 125 | row = self.get_row_by_addr(byte_addr) 126 | # This gives us the group where the hex resides 127 | parent = dpg.get_item_children(row, slot=1)[0] 128 | widget_pos = dpg.get_item_pos(parent) 129 | byte_width = dpg.get_text_size("00 ")[0] 130 | byte_idx = byte_ofs % self.stride 131 | group_idx = byte_idx // self.group_size 132 | space_width = dpg.get_text_size(" ")[0] 133 | pos = (widget_pos[0] + self.first_byte_offset + byte_idx * byte_width + group_idx * space_width, widget_pos[1]) 134 | dpg.add_input_text( 135 | default_value=byte_hex, 136 | hexadecimal=True, 137 | parent=parent, 138 | pos=pos, 139 | width=byte_width, 140 | callback=self.on_edit_completed, 141 | on_enter=True, 142 | user_data=(byte_addr, byte_hex)) 143 | 144 | dpg.bind_item_handler_registry(dpg.last_item(), self.edit_completed_handler) 145 | dpg.focus_item(dpg.last_item()) 146 | 147 | def commit_change(self, edit_widget: Union[int, str]) -> None: 148 | with dpg.mutex(): 149 | byte_addr = dpg.get_item_user_data(edit_widget)[0] 150 | byte_ofs = byte_addr - self.start_addr 151 | byte_str = dpg.get_value(edit_widget) 152 | # Make sure we don't commit it again in the deactivated callback 153 | dpg.delete_item(edit_widget) 154 | with suppress(ValueError): 155 | self.data[byte_ofs] = int(byte_str[-2:], 16) 156 | # Now refresh the hex/text display 157 | line_ofs = byte_ofs - (byte_ofs % self.stride) 158 | line_bytes = self.data[line_ofs : line_ofs + self.stride] 159 | row = self.get_row_by_addr(byte_addr) 160 | cells = dpg.get_item_children(row, slot=1) 161 | line_widget = dpg.get_item_children(cells[0], slot=1)[0] 162 | dpg.set_value(line_widget, self.format_hex(line_bytes, line_ofs + self.start_addr)) 163 | dpg.set_value(cells[1], self.format_text(line_bytes)) 164 | # Edit next byte, if any 165 | if byte_addr < self.start_addr + len(self.data): 166 | self.edit_byte(byte_addr + 1) 167 | 168 | def on_edit_change(self, sender, widget) -> None: 169 | if len(dpg.get_value(widget)) >= 2: 170 | self.commit_change(widget) 171 | 172 | def on_edit_completed(self, sender, new_value, user_data) -> None: 173 | self.commit_change(sender) 174 | 175 | def on_edit_deactivated(self, sender, widget) -> None: 176 | # Unfortunately the deactivated handler gets called before the edit callback 177 | # in the same frame. To properly detect and handle Enter, we need to delay 178 | # item deletion for one frame. However, we can't use split_frame for this 179 | # because we need to give `on_edit_completed` a chance to run first. That's 180 | # why we're delaying execution in such a weird way (also, 2 frames are 181 | # specified for stability reasons). 182 | with dpg.mutex(): 183 | dpg.set_frame_callback(dpg.get_frame_count() + 2, self.handle_deactivated_event, user_data=widget) 184 | 185 | def handle_deactivated_event(self, frame, a, widget) -> None: 186 | if not dpg.does_item_exist(widget): 187 | # Nothing to do - already been committed and deleted 188 | return 189 | # Only committing if new value differs from the old one: this way we can 190 | # detect when Esc is pressed, and don't edit next byte. 191 | init_value = dpg.get_item_user_data(widget)[1] 192 | if dpg.get_value(widget) != init_value: 193 | self.commit_change(widget) 194 | dpg.delete_item(widget) 195 | 196 | 197 | def add_hex_edit( 198 | data: bytearray, 199 | start_addr: int = 0xc000, 200 | stride: int = 16, 201 | group_size: int = 8, 202 | encoding: str ="iso8859-1", 203 | **kwargs) -> Union[int, str]: 204 | 205 | editor = HexEditor(data, start_addr, stride, group_size, encoding, **kwargs) 206 | return editor.container 207 | 208 | 209 | def deferred_init(): 210 | # Create the main window 211 | with dpg.window(label="RAM", height=300) as wnd: 212 | test_text = textwrap.dedent(""" 213 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 214 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 215 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 216 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 217 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat 218 | non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 219 | """).strip().replace("\n", " ") 220 | test_bytes = bytes([(randrange(0, 256) if random() < 0.1 else 0) for i in range(0, 256)]) 221 | test_data = bytearray(test_bytes + test_text.encode("iso8859-1")) 222 | 223 | add_hex_edit(test_data) 224 | 225 | # Since the hex edit uses get_text_size, we have to wait 1 frame 226 | dpg.set_frame_callback(1, callback=deferred_init) 227 | 228 | dpg.setup_dearpygui() 229 | dpg.show_viewport() 230 | dpg.show_item_registry() 231 | dpg.start_dearpygui() 232 | dpg.destroy_context() -------------------------------------------------------------------------------- /misc/loading_indicator_on_startup.py: -------------------------------------------------------------------------------- 1 | import time 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | def long_process(): 6 | for i in range(10): 7 | time.sleep(1) 8 | dpg.add_button(label=f"Button {i}", parent=main_window) 9 | 10 | def startup(): 11 | with dpg.window(modal=True, no_move=True, no_close=True, no_title_bar=True, no_resize=True) as loading_window: 12 | dpg.add_text("Loading...") 13 | dpg.add_loading_indicator() 14 | long_process() 15 | dpg.delete_item(loading_window) 16 | 17 | with dpg.window() as main_window: 18 | pass 19 | 20 | dpg.set_frame_callback(1, startup) 21 | dpg.set_primary_window(main_window, True) 22 | dpg.create_viewport(width=800, height=600, title="Loading indicator on startup") 23 | dpg.setup_dearpygui() 24 | dpg.show_viewport() 25 | dpg.start_dearpygui() 26 | dpg.destroy_context() -------------------------------------------------------------------------------- /misc/multiple_node_attributes_one_line.py: -------------------------------------------------------------------------------- 1 | # Credit v-ein - see https://discord.com/channels/736279277242417272/1171760109270073375/1171760109270073375 2 | import dearpygui.dearpygui as dpg 3 | 4 | dpg.create_context() 5 | dpg.create_viewport(title=f"Test - {dpg.get_dearpygui_version()}", width=500, height=400) 6 | 7 | dpg.setup_dearpygui() 8 | with dpg.window(pos=(0, 30), width=500, height=350, no_title_bar=False): 9 | def on_delink(sender, link_item): 10 | link_info = dpg.get_item_configuration(link_item) 11 | node1 = link_info['attr_1'] 12 | node2 = link_info['attr_2'] 13 | print(f"Attempting to delete the link between nodes {node1} and {node2}") 14 | dpg.delete_item(link_item) 15 | 16 | def on_link(sender, app_data): 17 | dpg.add_node_link(app_data[0], app_data[1], parent=sender) 18 | 19 | with dpg.theme() as no_padding_theme: 20 | with dpg.theme_component(dpg.mvAll): 21 | dpg.add_theme_style(dpg.mvStyleVar_FramePadding, 0, 0) 22 | dpg.add_theme_style(dpg.mvStyleVar_ItemSpacing, 0, 0) 23 | 24 | with dpg.node_editor(callback=on_link, 25 | delink_callback=on_delink, minimap=True, minimap_location=dpg.mvNodeMiniMap_Location_BottomRight): 26 | 27 | with dpg.node(label="Node 1", pos=[10, 10]): 28 | 29 | FONT_SIZE = 13 30 | ITEM_SPACING_Y = 4 31 | # ImNodes has a hardcoded frame padding for attributes, 1 pixel on each side 32 | ATTR_FRAME_PADDING = 1 33 | anchor = dpg.generate_uuid() 34 | target = dpg.generate_uuid() 35 | 36 | def adjust_position(): 37 | dpg.set_item_pos(target, dpg.get_item_pos(anchor)) 38 | 39 | with dpg.item_handler_registry() as move_handler: 40 | dpg.add_item_visible_handler(callback=adjust_position) 41 | 42 | # Offsetting the dots to the supposed middle of the line 43 | with dpg.node_attribute(attribute_type=dpg.mvNode_Attr_Static): 44 | # Can't get position of a spacer, so wrapping it into a group to get pos 45 | with dpg.group(tag=anchor): 46 | dpg.bind_item_handler_registry(dpg.last_item(), move_handler) 47 | # Instead of subtracting item spacing, one could set item 48 | # spacing to zero on the entire node, but that would affect 49 | # other attributes, too. 50 | dpg.add_spacer(height=(FONT_SIZE/2 + ATTR_FRAME_PADDING - ITEM_SPACING_Y )) 51 | 52 | # An empty, zero-height input attribute 53 | with dpg.node_attribute(): 54 | dpg.bind_item_theme(dpg.last_item(), no_padding_theme) 55 | 56 | # An empty, zero-height output attribute 57 | with dpg.node_attribute(attribute_type=dpg.mvNode_Attr_Output): 58 | dpg.bind_item_theme(dpg.last_item(), no_padding_theme) 59 | 60 | with dpg.node_attribute(attribute_type=dpg.mvNode_Attr_Static): 61 | with dpg.group(horizontal=True, tag=target): 62 | dpg.add_input_float(label="F3", width=80) 63 | dpg.add_input_float(label="F4", width=80) 64 | 65 | with dpg.node(label="Node 2", pos=[300, 10]): 66 | 67 | with dpg.node_attribute() as na2: 68 | dpg.add_input_float(label="F3", width=100) 69 | 70 | with dpg.node_attribute(attribute_type=dpg.mvNode_Attr_Output): 71 | dpg.add_input_float(label="F4", width=100) 72 | 73 | dpg.show_viewport() 74 | dpg.show_item_registry() 75 | dpg.show_style_editor() 76 | dpg.start_dearpygui() 77 | dpg.destroy_context() -------------------------------------------------------------------------------- /misc/print_from_pdf_viewer.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | import keyboard # pip install keyboard 3 | from fpdf import FPDF # pip install fpdf 4 | import dearpygui.dearpygui as dpg 5 | dpg.create_context() 6 | 7 | def generate_and_save_pdf(text, filename): 8 | pdf = FPDF() 9 | pdf.add_page() 10 | pdf.set_font("Arial", size = 15) 11 | for line in text.splitlines(): 12 | pdf.cell(110, 10, txt = line, ln = 1, align = 'L') 13 | pdf.output(filename) 14 | 15 | def print_text_from_pdf_viewer(): 16 | generate_and_save_pdf(dpg.get_value(text), dpg.get_value(filename)+".pdf") 17 | webbrowser.get().open(dpg.get_value(filename)+".pdf") 18 | keyboard.press_and_release('ctrl+p') 19 | 20 | with dpg.window() as primary_window: 21 | filename = dpg.add_input_text(label="Filename", default_value="foo") 22 | text = dpg.add_input_text(label="Text", multiline=True, default_value="Hello World!") 23 | dpg.add_button(label="Print text\n(using default PDF viewer)", callback=print_text_from_pdf_viewer) 24 | 25 | dpg.set_primary_window(primary_window, True) 26 | dpg.create_viewport(width=200, height=200, title="Print from PDF Viewer") 27 | dpg.setup_dearpygui() 28 | dpg.show_viewport() 29 | dpg.start_dearpygui() 30 | dpg.destroy_context() -------------------------------------------------------------------------------- /misc/take_screenshot.py: -------------------------------------------------------------------------------- 1 | # https://discord.com/channels/736279277242417272/1105417341560438844/1105420268287033375 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | def dpg_screenshot(): 6 | dpg.output_frame_buffer('screenshot.png') 7 | 8 | with dpg.window(width=200, height=200): 9 | dpg.add_button(label='Screenshot', callback=dpg_screenshot) 10 | 11 | dpg.create_viewport(width=400, height=400) 12 | dpg.setup_dearpygui() 13 | dpg.show_viewport() 14 | dpg.start_dearpygui() 15 | dpg.destroy_context() -------------------------------------------------------------------------------- /packaging/README.md: -------------------------------------------------------------------------------- 1 | # Packaging a DearPyGui App 2 | 3 | If you want to package your app into a single distributable file (such as an .exe) there 4 | are a few different tools available, and a few things to keep in mind. 5 | 6 | Two popular tools are [PyInstaller](https://pyinstaller.org/en/stable/) and [Nuitka](https://nuitka.net/). It's fairly straightforward to use either 7 | of these tools to package a single .py file with no dependencies/resources. However, if you have 8 | resources files and code stored in a folder structure, there are some additional steps you'll need to take 9 | to ensure that your app will run correctly. 10 | 11 | In this example, I've created a simple app that uses resource files saved in `assets/` 12 | with the main codebase saved in `app/`. The entry point for the app is main.py. 13 | 14 | To run the app, you can simply run `python main.py` from the root directory. 15 | 16 | To package the app, I've saved the various packaging commands in `Taskfile.yml`. 17 | Task is a task runner / build tool that is similar to GNU Make. (see https://taskfile.dev/ for installation instructions) 18 | Alternatively, you can run the commands directly from the command line. 19 | 20 | ## PyInstaller 21 | 22 | ```bash 23 | task build-win-pyinstaller 24 | ``` 25 | ```bash 26 | task build-mac-pyinstaller # TODO: this doesn't work yet 27 | ``` 28 | 29 | ## Nuitka 30 | 31 | ```bash 32 | task build-win-nuitka 33 | ``` 34 | ```bash 35 | task build-mac-nuitka 36 | ``` 37 | 38 | ## Things to keep in mind 39 | 40 | ### Resource Files 41 | 42 | A common pitfall is to load resource files using a relative path, such as `assets/img/image.png`. This will work fine 43 | when running the app as `python main.py`, but will fail when running the packaged app. 44 | 45 | One way to solve this is to get the absolute path of the resource files, and then use that path to load the resource files. 46 | This requires getting the root directory of the app at runtime. See `app/utils.py` for an example of how to do this. -------------------------------------------------------------------------------- /packaging/Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | tasks: 4 | build-mac-nuitka: 5 | platforms: [darwin] 6 | cmds: 7 | - > 8 | python -m nuitka --standalone main.py 9 | --macos-create-app-bundle 10 | --macos-app-name=MyApp 11 | --include-data-dir=assets=assets 12 | 13 | build-win-nuitka: 14 | platforms: [windows] 15 | cmds: 16 | - > 17 | python -m nuitka --onefile main.py 18 | --include-data-dir=assets=assets 19 | --disable-console 20 | 21 | build-win-pyinstaller: 22 | platforms: [windows] 23 | cmds: 24 | - > 25 | pyinstaller --onefile --windowed main.py 26 | --add-data="assets/fonts/*;assets/fonts/" 27 | --add-data="assets/img/*;assets/img/" -------------------------------------------------------------------------------- /packaging/app/__init__.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | 3 | dpg.create_context() 4 | 5 | from .textures import load_textures 6 | from .fonts import load_fonts 7 | from .gui import create_gui 8 | from .constants import VIEWPORT_WIDTH, VIEWPORT_HEIGHT, VIEWPORT_TITLE 9 | 10 | 11 | def run(): 12 | load_textures() 13 | load_fonts() 14 | create_gui() 15 | dpg.create_viewport(width=VIEWPORT_WIDTH, height=VIEWPORT_HEIGHT, title=VIEWPORT_TITLE) 16 | dpg.show_viewport() 17 | dpg.setup_dearpygui() 18 | dpg.start_dearpygui() 19 | dpg.destroy_context() 20 | -------------------------------------------------------------------------------- /packaging/app/constants.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | 3 | VIEWPORT_WIDTH = 800 4 | VIEWPORT_HEIGHT = 600 5 | VIEWPORT_TITLE = "Packaging a DearPyGui App" 6 | 7 | FONT_SIZE = 18 8 | FONT_FILE = "assets/fonts/Inter-Medium.ttf" 9 | FONT_TAG = dpg.generate_uuid() 10 | 11 | BEACH_IMAGE_FILE = "assets/img/beach.jpg" 12 | BEACH_IMAGE_TAG = dpg.generate_uuid() 13 | -------------------------------------------------------------------------------- /packaging/app/fonts.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from .utils import absolute_path 3 | from .constants import FONT_FILE, FONT_SIZE, FONT_TAG 4 | 5 | 6 | def load_fonts(): 7 | with dpg.font_registry(): 8 | dpg.add_font(absolute_path(FONT_FILE), FONT_SIZE, tag=FONT_TAG) 9 | -------------------------------------------------------------------------------- /packaging/app/gui.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from .constants import FONT_TAG, BEACH_IMAGE_TAG 3 | 4 | 5 | class MyButton: 6 | def __init__(self, label): 7 | self.label = label 8 | self.count = 0 9 | dpg.add_button(label=label, callback=lambda s, d, u: self.callback(s, d, u)) 10 | 11 | def callback(self, sender, app_data, user_data): 12 | self.count += 1 13 | dpg.configure_item(sender, label=f"{self.label} clicked {self.count} times") 14 | 15 | 16 | def create_gui(): 17 | with dpg.window(): 18 | dpg.set_primary_window(dpg.last_item(), True) 19 | dpg.add_text("Hello world") 20 | dpg.add_button(label="Save") 21 | dpg.add_slider_float(width=200) 22 | dpg.add_image(BEACH_IMAGE_TAG) 23 | dpg.add_text("Hello world in a different font") 24 | dpg.bind_item_font(dpg.last_item(), FONT_TAG) 25 | MyButton("My button") 26 | -------------------------------------------------------------------------------- /packaging/app/textures.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from .utils import absolute_path 3 | from .constants import BEACH_IMAGE_FILE, BEACH_IMAGE_TAG 4 | 5 | def load_textures(): 6 | width, height, channels, data = dpg.load_image(absolute_path(BEACH_IMAGE_FILE)) 7 | with dpg.texture_registry(): 8 | dpg.add_static_texture(width=width, height=height, default_value=data, tag=BEACH_IMAGE_TAG) -------------------------------------------------------------------------------- /packaging/app/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | ROOT_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) 4 | 5 | def absolute_path(relative_path: str) -> str: 6 | return os.path.normpath(os.path.join(ROOT_DIR, relative_path)) -------------------------------------------------------------------------------- /packaging/assets/fonts/Inter-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/my1e5/dpg-examples/140479d3b2a255a6b78508266da097cca62f9e99/packaging/assets/fonts/Inter-Medium.ttf -------------------------------------------------------------------------------- /packaging/assets/fonts/license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2020 The Inter Project Authors. 2 | "Inter" is trademark of Rasmus Andersson. 3 | https://github.com/rsms/inter 4 | 5 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 6 | This license is copied below, and is also available with a FAQ at: 7 | http://scripts.sil.org/OFL 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION AND CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. -------------------------------------------------------------------------------- /packaging/assets/img/beach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/my1e5/dpg-examples/140479d3b2a255a6b78508266da097cca62f9e99/packaging/assets/img/beach.jpg -------------------------------------------------------------------------------- /packaging/main.py: -------------------------------------------------------------------------------- 1 | import app 2 | 3 | 4 | def main(): 5 | app.run() 6 | 7 | 8 | if __name__ == "__main__": 9 | main() 10 | -------------------------------------------------------------------------------- /persistence/persistence_of_windows.py: -------------------------------------------------------------------------------- 1 | import json 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | try: 6 | previous_state = None 7 | with open("app-state.json", "r") as f: 8 | previous_state = json.load(f) 9 | except: 10 | pass 11 | state = {"windows": {}} 12 | 13 | def create_new_window(sender, app_data, user_data): 14 | def delete_window(sender): 15 | del state["windows"][sender] 16 | tag = dpg.generate_uuid() 17 | width, height, pos, text, slider = user_data if user_data else (200,50,(10,10),'', 0) 18 | with dpg.window(width=width, height=height, pos=pos, tag=tag, on_close=delete_window): 19 | state["windows"][tag] = {} 20 | state["windows"][tag]["text"] = dpg.add_input_text(hint="Enter text here", default_value=text) 21 | state["windows"][tag]["slider"] = dpg.add_slider_float(default_value=slider) 22 | 23 | with dpg.window(width=200): 24 | dpg.add_button(label="Create new window", callback=create_new_window) 25 | dpg.add_text("Move the new windows around, resize them, enter some text, and move the slider. Then close the app and reopen it.", wrap=190) 26 | 27 | if previous_state: 28 | for values in previous_state["windows"].values(): 29 | create_new_window(None, None, (values["width"], values["height"], values["pos"], values["text"], values["slider"])) 30 | 31 | dpg.create_viewport(title="Persistence of Windows Demo", height=700, width=1200) 32 | dpg.setup_dearpygui() 33 | dpg.show_viewport() 34 | try: 35 | dpg.start_dearpygui() 36 | finally: 37 | for tag, values in state["windows"].items(): 38 | values["pos"] = dpg.get_item_pos(tag) 39 | values["width"] = dpg.get_item_width(tag) 40 | values["height"] = dpg.get_item_height(tag) 41 | values["text"] = dpg.get_value(values["text"]) 42 | values["slider"] = dpg.get_value(values["slider"]) 43 | with open("app-state.json", "w") as f: 44 | json.dump(state, f, indent=4) 45 | dpg.destroy_context() 46 | -------------------------------------------------------------------------------- /persistence/persistence_using_dataclasses.py: -------------------------------------------------------------------------------- 1 | import json 2 | from dataclasses import dataclass, field, asdict 3 | import dearpygui.dearpygui as dpg 4 | dpg.create_context() 5 | 6 | 7 | @dataclass 8 | class Settings: 9 | foo: int = 0 10 | bar: str = "DPG is awesome!" 11 | baz: float = 3.1415 12 | 13 | 14 | @dataclass 15 | class State: # Store app state in a dataclass, which can include nested dataclasses 16 | name: str = "My App" 17 | settings: Settings = field(default_factory=Settings) 18 | 19 | def __post_init__(self): 20 | if type(self.settings) is dict: # This will be a dict if loading from JSON, so need to convert it to a Settings object 21 | self.settings = Settings(**self.settings) 22 | 23 | 24 | try: 25 | with open("app-state-dataclass.json", "r") as f: 26 | state = State(**json.load(f)) 27 | except: 28 | state = State() 29 | 30 | 31 | with dpg.window(): 32 | dpg.set_primary_window(dpg.last_item(), True) 33 | dpg.add_text(state.name) 34 | dpg.add_slider_int(label="Foo", default_value=state.settings.foo, callback=lambda s, d: setattr(state.settings, "foo", d)) 35 | dpg.add_input_text(label="Bar", default_value=state.settings.bar, callback=lambda s, d: setattr(state.settings, "bar", d)) 36 | dpg.add_input_float(label="Baz", default_value=state.settings.baz, callback=lambda s, d: setattr(state.settings, "baz", d)) 37 | dpg.add_button(label="Print app state", callback=lambda: print(state)) 38 | 39 | 40 | dpg.create_viewport(title="App Persistence Demo using Dataclasses", height=400, width=500) 41 | dpg.setup_dearpygui() 42 | dpg.show_viewport() 43 | try: 44 | dpg.start_dearpygui() 45 | finally: 46 | with open("app-state-dataclass.json", "w") as f: 47 | json.dump(asdict(state), f, indent=4) 48 | dpg.destroy_context() 49 | -------------------------------------------------------------------------------- /persistence/persistence_using_dict.py: -------------------------------------------------------------------------------- 1 | import json 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | STATE_FILENAME = "app-state-dict.json" 6 | 7 | def save_state(state): 8 | with open(STATE_FILENAME, "w") as f: 9 | json.dump(state, f, indent=4) 10 | 11 | def load_state(): 12 | try: 13 | with open(STATE_FILENAME, "r") as f: 14 | state = json.load(f) 15 | except: 16 | state = { 17 | "name": "My App", 18 | "settings": { 19 | "foo": 0, 20 | "bar": "DPG is awesome!", 21 | "baz": 3.1415 22 | } 23 | } 24 | return state 25 | 26 | 27 | state = load_state() 28 | 29 | with dpg.window(): 30 | dpg.set_primary_window(dpg.last_item(), True) 31 | dpg.add_text(state["name"]) 32 | dpg.add_slider_int(label="Foo", default_value=state["settings"]["foo"], callback=lambda s, d: state["settings"].__setitem__("foo", d)) 33 | dpg.add_input_text(label="Bar", default_value=state["settings"]["bar"], callback=lambda s, d: state["settings"].__setitem__("bar", d)) 34 | dpg.add_input_float(label="Baz", default_value=state["settings"]["baz"], callback=lambda s, d: state["settings"].__setitem__("baz", d)) 35 | dpg.add_button(label="Print app state", callback=lambda: print(state)) 36 | 37 | dpg.create_viewport(title="App Persistence Demo using Dict", height=400, width=500) 38 | dpg.setup_dearpygui() 39 | dpg.show_viewport() 40 | try: 41 | dpg.start_dearpygui() 42 | finally: 43 | save_state(state) 44 | dpg.destroy_context() 45 | -------------------------------------------------------------------------------- /plots/plot_bar_series_custom_colours.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | 5 | with dpg.theme() as blue_theme: 6 | with dpg.theme_component(): 7 | dpg.add_theme_color( 8 | dpg.mvPlotCol_Fill, (0, 119, 200, 255), category=dpg.mvThemeCat_Plots 9 | ) 10 | 11 | with dpg.theme() as red_theme: 12 | with dpg.theme_component(): 13 | dpg.add_theme_color( 14 | dpg.mvPlotCol_Fill, (210, 4, 45, 255), category=dpg.mvThemeCat_Plots 15 | ) 16 | 17 | with dpg.theme() as yellow_theme: 18 | with dpg.theme_component(): 19 | dpg.add_theme_color( 20 | dpg.mvPlotCol_Fill, (253, 218, 13, 255), category=dpg.mvThemeCat_Plots 21 | ) 22 | 23 | 24 | with dpg.window(): 25 | with dpg.plot(): 26 | dpg.add_plot_legend() 27 | dpg.add_plot_axis(dpg.mvXAxis) 28 | with dpg.plot_axis(dpg.mvYAxis): 29 | dpg.add_bar_series([10, 20, 30], [30, 75, 90], label="Foo", weight=1) 30 | dpg.bind_item_theme(dpg.last_item(), blue_theme) 31 | dpg.add_bar_series([11, 21, 31], [45, 65, 72], label="Bar", weight=1) 32 | dpg.bind_item_theme(dpg.last_item(), red_theme) 33 | dpg.add_bar_series([12, 22, 32], [20, 85, 80], label="Baz", weight=1) 34 | dpg.bind_item_theme(dpg.last_item(), yellow_theme) 35 | 36 | 37 | dpg.create_viewport(width=800, height=600, title="Bar Series Custom Colours") 38 | dpg.setup_dearpygui() 39 | dpg.show_viewport() 40 | dpg.start_dearpygui() 41 | dpg.destroy_context() 42 | -------------------------------------------------------------------------------- /plots/plot_colormap_resizing.py: -------------------------------------------------------------------------------- 1 | # from https://discord.com/channels/736279277242417272/1085585441757077514/1085585441757077514 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | with dpg.colormap_registry(): 6 | dpg.add_colormap([(255, 50, 50), (50, 50, 255)], False, tag='colormap') 7 | 8 | WINDOW_PADDING_X = 8 # default window padding 9 | ITEM_SPACING_X = 8 # default item spacing 10 | COLORMAP_WIDTH = 75 11 | 12 | plots = [] 13 | 14 | def add_new_plot(): 15 | with dpg.group(horizontal=True, parent=plot_group): 16 | with dpg.plot(height=-1, width=-1) as plot: 17 | plots.append(plot) 18 | dpg.add_plot_axis(dpg.mvXAxis, label="x") 19 | y_axis = dpg.add_plot_axis(dpg.mvYAxis, label="y") 20 | dpg.add_colormap_scale(min_scale=0, max_scale=1, width=COLORMAP_WIDTH, height=-1, colormap='colormap') 21 | dpg.bind_colormap(plot, 'colormap') 22 | resize_plots() 23 | 24 | def resize_plots(): 25 | n_plots = len(plots) 26 | plot_width = (dpg.get_item_width(window) - WINDOW_PADDING_X*2 - ITEM_SPACING_X*(n_plots-1)) //n_plots 27 | for plot in plots: 28 | dpg.configure_item(plot, width=(plot_width - COLORMAP_WIDTH - ITEM_SPACING_X)) 29 | 30 | with dpg.window(width=450, height=550) as window: 31 | dpg.add_button(label="add new plot", callback=add_new_plot) 32 | with dpg.group(horizontal=True) as plot_group: 33 | add_new_plot() 34 | 35 | with dpg.item_handler_registry() as item_handler_registry: 36 | dpg.add_item_resize_handler(callback=resize_plots) 37 | dpg.bind_item_handler_registry(window, item_handler_registry) 38 | 39 | dpg.create_viewport(title='Plots with colormap sizing', width=1200, height=600) 40 | dpg.setup_dearpygui() 41 | dpg.show_viewport() 42 | dpg.start_dearpygui() 43 | dpg.destroy_context() -------------------------------------------------------------------------------- /plots/plot_draw_lines.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | def lock_axis(axis): 5 | xmin, xmax = dpg.get_axis_limits(axis) 6 | dpg.set_axis_limits(axis, xmin, xmax) 7 | 8 | def unlock_axis(axis): 9 | dpg.set_axis_limits_auto(axis) 10 | 11 | def plot_mouse_click_callback(): 12 | if not dpg.get_value(drawing_mode): 13 | return 14 | lock_axis(xaxis), lock_axis(yaxis) 15 | dpg.delete_item(xaxis, children_only=True) # delete the previous line series 16 | 17 | x,y = dpg.get_plot_mouse_pos() 18 | data_x, data_y, lines = [x],[y],[] 19 | while dpg.is_mouse_button_down(button=0): #and dpg.is_key_down(dpg.mvKey_Control): 20 | new_x,new_y = dpg.get_plot_mouse_pos() 21 | if new_x != x or new_y != y: 22 | if new_x < x: # only if the mouse is dragged left 23 | continue 24 | 25 | data_x.append(new_x), data_y.append(new_y) 26 | lines.append(dpg.draw_line([x,y], [new_x,new_y], parent=plot)) 27 | x,y = new_x, new_y 28 | 29 | for line in lines: # delete the lines we just drew 30 | dpg.delete_item(line) 31 | # at this point you could do further processing on the data, e.g. smooth it. 32 | dpg.add_line_series(data_x, data_y, parent=xaxis) # add the data as a line series 33 | 34 | unlock_axis(yaxis), unlock_axis(xaxis) 35 | 36 | with dpg.window(): 37 | dpg.add_text("Left click and drag on the plot to add a line series.\nIt only works if you drag left to right.") 38 | drawing_mode = dpg.add_checkbox(label="Drawing mode", default_value=True) 39 | with dpg.plot(anti_aliased=True) as plot: 40 | xaxis = dpg.add_plot_axis(dpg.mvXAxis) 41 | yaxis = dpg.add_plot_axis(dpg.mvYAxis) 42 | dpg.set_axis_limits(xaxis, 0, 100) 43 | dpg.set_axis_limits(yaxis, 0, 100) 44 | 45 | with dpg.item_handler_registry() as registry: 46 | dpg.add_item_clicked_handler(button=dpg.mvMouseButton_Left, callback=plot_mouse_click_callback) 47 | dpg.bind_item_handler_registry(plot, registry) 48 | 49 | dpg.create_viewport(width=800, height=600, title="Plot with mouse click callback") 50 | dpg.setup_dearpygui() 51 | dpg.show_viewport() 52 | dpg.start_dearpygui() 53 | dpg.destroy_context() 54 | -------------------------------------------------------------------------------- /plots/plot_enforce_limits.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from math import sin 3 | dpg.create_context() 4 | 5 | data_x = [i for i in range(200)] 6 | data_y = [0.5 + 0.5 * sin(50 * i / 1000) for i in data_x] 7 | 8 | Y_MAX = 1 9 | Y_MIN = 0 10 | X_MAX = 200 11 | X_MIN = 0 12 | 13 | def enforce_plot_limits(sender, app_data, user_data): 14 | plot, xaxis, yaxis = user_data 15 | while dpg.is_mouse_button_down(button=dpg.mvMouseButton_Left) or dpg.is_item_hovered(plot): 16 | xmin, xmax = dpg.get_axis_limits(xaxis) 17 | if xmin < X_MIN: 18 | xmin = X_MIN 19 | dpg.set_axis_limits(xaxis, xmin, xmax) 20 | if xmax > X_MAX: 21 | xmax = X_MAX 22 | dpg.set_axis_limits(xaxis, xmin, xmax) 23 | dpg.split_frame() 24 | dpg.set_axis_limits_auto(xaxis) 25 | 26 | ymin, ymax = dpg.get_axis_limits(yaxis) 27 | if ymin < Y_MIN: 28 | ymin = Y_MIN 29 | dpg.set_axis_limits(yaxis, ymin, ymax) 30 | if ymax > Y_MAX: 31 | ymax = Y_MAX 32 | dpg.set_axis_limits(yaxis, ymin, ymax) 33 | dpg.split_frame() 34 | dpg.set_axis_limits_auto(yaxis) 35 | 36 | with dpg.window(): 37 | with dpg.plot(height=400, width=500) as plot: 38 | xaxis = dpg.add_plot_axis(dpg.mvXAxis, label="x") 39 | yaxis = dpg.add_plot_axis(dpg.mvYAxis, label="y") 40 | dpg.add_line_series(data_x, data_y, parent=yaxis) 41 | 42 | with dpg.item_handler_registry() as item_handler_registry: 43 | dpg.add_item_hover_handler(user_data = (plot, xaxis, yaxis), callback=enforce_plot_limits) 44 | dpg.bind_item_handler_registry(plot, item_handler_registry) 45 | 46 | dpg.create_viewport(width=900, height=600, title='Plot Enforce Limits') 47 | dpg.setup_dearpygui() 48 | dpg.show_viewport() 49 | dpg.start_dearpygui() 50 | dpg.destroy_context() -------------------------------------------------------------------------------- /plots/plot_mouse_click_callback.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | def plot_mouse_click_callback(): 5 | x,y = dpg.get_plot_mouse_pos() 6 | dpg.add_scatter_series([x], [y], parent=yaxis) # different parent to the line series, makes it easy to delete all the points 7 | 8 | with dpg.window(): 9 | dpg.add_text("Right click on the plot to add a point.") 10 | with dpg.plot(no_menus=True, no_box_select=True) as plot: # note the no_menus and no_box_select, so right click doesn't do anything else 11 | xaxis = dpg.add_plot_axis(dpg.mvXAxis) 12 | yaxis = dpg.add_plot_axis(dpg.mvYAxis) 13 | dpg.add_line_series([0, 1], [0, 1], parent=xaxis) 14 | 15 | dpg.add_button(label="Delete last point", callback=lambda: dpg.delete_item(dpg.get_item_children(yaxis)[1][-1]) if len(dpg.get_item_children(yaxis)[1]) > 0 else None) 16 | dpg.add_button(label="Delete points", callback=lambda: dpg.delete_item(yaxis, children_only=True)) 17 | dpg.add_button(label="Print points x,y data", callback=lambda: print([dpg.get_value(child)[0:2] for child in dpg.get_item_children(yaxis)[1]])) 18 | 19 | with dpg.item_handler_registry() as registry: 20 | dpg.add_item_clicked_handler(button=dpg.mvMouseButton_Right, callback=plot_mouse_click_callback) 21 | dpg.bind_item_handler_registry(plot, registry) 22 | 23 | 24 | dpg.create_viewport(width=800, height=600, title="Plot with mouse click callback") 25 | dpg.setup_dearpygui() 26 | dpg.show_viewport() 27 | dpg.start_dearpygui() 28 | dpg.destroy_context() 29 | -------------------------------------------------------------------------------- /plots/plot_text_overlay_using_annotations.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from random import random 3 | dpg.create_context() 4 | 5 | def add_plot_data(): 6 | dpg.add_scatter_series([random() for i in range(10)], [random() for i in range(10)], parent="x-axis") 7 | dpg.set_axis_limits_auto("x-axis") # unlock axis limits 8 | dpg.set_axis_limits_auto("y-axis") 9 | dpg.hide_item("no_data_annotation") 10 | 11 | def clear_plot(): 12 | dpg.delete_item("x-axis", children_only=True) 13 | dpg.set_axis_limits("x-axis", 0, 1) 14 | dpg.set_axis_limits("y-axis", 0, 1) 15 | dpg.show_item("no_data_annotation") 16 | 17 | with dpg.window(): 18 | dpg.set_primary_window(dpg.last_item(), True) 19 | 20 | with dpg.group(horizontal=True): 21 | with dpg.child_window(width=200): 22 | dpg.add_button(label="Add plot data", callback=add_plot_data) 23 | dpg.add_button(label="Clear plot", callback=clear_plot) 24 | 25 | with dpg.plot(width=-1, height=-1, tag="plot"): 26 | dpg.add_plot_axis(dpg.mvXAxis, label="x-axis", tag="x-axis") 27 | dpg.add_plot_axis(dpg.mvYAxis, label="y-axis", tag="y-axis") 28 | dpg.set_axis_limits("x-axis", 0, 1) # lock axis limits so annotation is centered 29 | dpg.set_axis_limits("y-axis", 0, 1) 30 | dpg.add_plot_annotation(label="No Data Available", default_value=(0.5, 0.5), tag="no_data_annotation") 31 | 32 | dpg.create_viewport(width=800, height=600, title="Plot text annotations") 33 | dpg.setup_dearpygui() 34 | dpg.show_viewport() 35 | dpg.start_dearpygui() 36 | dpg.destroy_context() 37 | -------------------------------------------------------------------------------- /plots/plot_text_overlay_using_drawlist.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from random import random 3 | dpg.create_context() 4 | 5 | def add_plot_data(): 6 | dpg.add_scatter_series([random() for i in range(10)], [random() for i in range(10)], parent="x-axis") 7 | dpg.hide_item("overlay_text") 8 | 9 | def clear_plot(): 10 | dpg.delete_item("x-axis", children_only=True) 11 | dpg.show_item("overlay_text") 12 | 13 | with dpg.window(tag="primary_window"): 14 | dpg.set_primary_window(dpg.last_item(), True) 15 | 16 | with dpg.group(horizontal=True): 17 | with dpg.child_window(width=200): 18 | dpg.add_button(label="Add plot data", callback=add_plot_data) 19 | dpg.add_button(label="Clear plot", callback=clear_plot) 20 | 21 | with dpg.plot(width=-1, height=-1, tag="plot"): 22 | dpg.add_plot_axis(dpg.mvXAxis, label="x-axis", tag="x-axis") 23 | dpg.add_plot_axis(dpg.mvYAxis, label="y-axis", tag="y-axis") 24 | 25 | def set_overlay_text_position(): 26 | x,y = dpg.get_item_pos("plot") 27 | w,h = dpg.get_item_rect_size("plot") 28 | x_offset = -38 # needs a little manual adjustment to center 29 | y_offset = -25 30 | dpg.configure_item("overlay_text", pos=(x + w/2 + x_offset, y + h/2 + y_offset)) 31 | 32 | with dpg.viewport_drawlist(): 33 | dpg.draw_text(pos=(100, 100), text="No Data Available", size=13, tag="overlay_text") 34 | 35 | with dpg.item_handler_registry() as registry: 36 | dpg.add_item_resize_handler(callback=set_overlay_text_position) 37 | dpg.bind_item_handler_registry("primary_window", registry) 38 | 39 | 40 | dpg.create_viewport(width=800, height=600, title="Plot text using drawlist overlay") 41 | dpg.setup_dearpygui() 42 | dpg.show_viewport() 43 | dpg.start_dearpygui() 44 | dpg.destroy_context() 45 | -------------------------------------------------------------------------------- /plots/plot_update_data.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | M = 1 5 | C = 0 6 | 7 | def generate_data(m: float, c: float): 8 | data_x, data_y = [], [] 9 | for x in range(0, 100): 10 | data_x.append(x) 11 | data_y.append(m*x + c) 12 | return data_x, data_y 13 | 14 | def update_plot(): 15 | data_x, data_y = generate_data(dpg.get_value("m_slider"), dpg.get_value("c_slider")) 16 | dpg.configure_item('line', x=data_x, y=data_y) 17 | if dpg.get_value("auto_fit_checkbox"): 18 | dpg.fit_axis_data("xaxis") 19 | dpg.fit_axis_data("yaxis") 20 | 21 | with dpg.window(pos=(10,10)): 22 | with dpg.plot(label="y = mx + c", height=400, width=500): 23 | dpg.add_plot_axis(dpg.mvXAxis, label="x", tag="xaxis") 24 | dpg.add_plot_axis(dpg.mvYAxis, label="y", tag="yaxis") 25 | data_x, data_y = generate_data(M, C) 26 | dpg.add_line_series(data_x, data_y, tag='line', parent="yaxis") 27 | 28 | dpg.add_slider_float(label="m", tag="m_slider", default_value=M, min_value=0, max_value=10, callback=update_plot) 29 | dpg.add_slider_float(label="c", tag="c_slider", default_value=C, min_value=-50, max_value=50, callback=update_plot) 30 | dpg.add_checkbox(label="Auto-fit axis limits", tag="auto_fit_checkbox", default_value=False) 31 | 32 | dpg.create_viewport(width=900, height=600, title='Updating plot data') 33 | dpg.setup_dearpygui() 34 | dpg.show_viewport() 35 | dpg.start_dearpygui() 36 | dpg.destroy_context() -------------------------------------------------------------------------------- /plots/plot_update_line_colour.py: -------------------------------------------------------------------------------- 1 | # Credit @Quattro - https://discord.com/channels/736279277242417272/1034823864104005703/1035630921166110780 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | DEFAULT_LINE_COLOUR = (0, 119, 200, 153) 6 | 7 | with dpg.theme() as coloured_line_theme: 8 | with dpg.theme_component(): 9 | coloured_line_component = dpg.add_theme_color(dpg.mvPlotCol_Line, DEFAULT_LINE_COLOUR, category=dpg.mvThemeCat_Plots) 10 | 11 | def change_colour(_, rgba_values): 12 | dpg.set_value(coloured_line_component, [value*255 for value in rgba_values]) 13 | 14 | with dpg.window(): 15 | with dpg.plot(): 16 | dpg.add_plot_legend() 17 | dpg.add_plot_axis(dpg.mvXAxis) 18 | with dpg.plot_axis(dpg.mvYAxis): 19 | for i in range(4): 20 | dpg.add_line_series([0, 1], [0, i+1], label=f"line{i}") 21 | dpg.bind_item_theme(dpg.last_item(), coloured_line_theme) 22 | 23 | with dpg.window(pos=(450,0), width=300, height=300): 24 | dpg.add_color_picker(default_value=DEFAULT_LINE_COLOUR, callback=change_colour) 25 | 26 | dpg.create_viewport(width=800, height=600, title="Plot Update Line Colour") 27 | dpg.setup_dearpygui() 28 | dpg.show_viewport() 29 | dpg.start_dearpygui() 30 | dpg.destroy_context() -------------------------------------------------------------------------------- /plots/plot_update_marker_size.py: -------------------------------------------------------------------------------- 1 | import random 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | with dpg.theme() as plot_theme: 6 | with dpg.theme_component(dpg.mvScatterSeries): 7 | marker_size = dpg.add_theme_style(dpg.mvPlotStyleVar_MarkerSize, 1, category=dpg.mvThemeCat_Plots) 8 | 9 | def change_marker_size(_, app_data): 10 | dpg.set_value(marker_size, [app_data]) 11 | 12 | with dpg.window(): 13 | dpg.add_slider_int(label="Marker Size", min_value=1, max_value=10, default_value=1, callback=change_marker_size) 14 | with dpg.plot(): 15 | dpg.add_plot_legend() 16 | dpg.add_plot_axis(dpg.mvXAxis) 17 | with dpg.plot_axis(dpg.mvYAxis): 18 | for _ in range(3): 19 | dpg.add_scatter_series([random.random() for _ in range(10)],[random.random() for _ in range(10)]) 20 | dpg.bind_item_theme(dpg.last_item(), plot_theme) 21 | 22 | dpg.create_viewport(width=600, height=500) 23 | dpg.setup_dearpygui() 24 | dpg.show_viewport() 25 | dpg.start_dearpygui() 26 | dpg.destroy_context() -------------------------------------------------------------------------------- /plots/plot_update_time_data.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | import math 3 | import time 4 | import random 5 | from collections import deque 6 | dpg.create_context() 7 | 8 | DEQUE_MAX_LEN = 200 9 | data_x = deque(maxlen=DEQUE_MAX_LEN) 10 | data_y = deque(maxlen=DEQUE_MAX_LEN) 11 | 12 | def generate_data(): 13 | data_x.append(time.time()) 14 | data_y.append(math.sin(data_x[-1]) + random.uniform(-0.1, 0.1)) 15 | return list(data_x), list(data_y) 16 | 17 | def update_plot(): 18 | updated_data_x, updated_data_y = generate_data() 19 | dpg.configure_item('line', x=updated_data_x, y=updated_data_y) 20 | if dpg.get_value("auto_fit_checkbox"): 21 | dpg.fit_axis_data("xaxis") 22 | 23 | with dpg.window(): 24 | with dpg.plot(height=400, width=500): 25 | dpg.add_plot_axis(dpg.mvXAxis, label="Time", tag="xaxis", time=True, no_tick_labels=True) 26 | dpg.add_plot_axis(dpg.mvYAxis, label="Amplitude", tag="yaxis") 27 | dpg.add_line_series([], [], tag='line', parent="yaxis") 28 | dpg.set_axis_limits("yaxis", -1.5, 1.5) 29 | dpg.add_checkbox(label="Auto-fit x-axis limits", tag="auto_fit_checkbox", default_value=True) 30 | 31 | dpg.create_viewport(width=900, height=600, title='Updating plot data') 32 | dpg.setup_dearpygui() 33 | dpg.show_viewport() 34 | while dpg.is_dearpygui_running(): 35 | update_plot() # updating the plot directly from the running loop 36 | dpg.render_dearpygui_frame() 37 | dpg.destroy_context() -------------------------------------------------------------------------------- /plots/plot_with_button.py: -------------------------------------------------------------------------------- 1 | # see https://discord.com/channels/736279277242417272/1171570899888123934/1171570899888123934 2 | # and see my1e6/dpg-examples/window/pop_to_window.py 3 | import dearpygui.dearpygui as dpg 4 | dpg.create_context() 5 | 6 | def move_group(sender, app_data, user_data): 7 | group, window = user_data 8 | def close(): 9 | dpg.move_item(group, parent=window) 10 | dpg.delete_item(new_window) 11 | dpg.show_item(sender) 12 | with dpg.window(on_close=close) as new_window: 13 | dpg.move_item(group, parent=new_window) 14 | dpg.hide_item(sender) 15 | 16 | 17 | with dpg.window() as main_window: 18 | 19 | with dpg.group() as my_group: 20 | with dpg.plot(): 21 | dpg.add_plot_legend(location=dpg.mvPlot_Location_NorthEast) 22 | dpg.add_plot_axis(dpg.mvXAxis) 23 | with dpg.plot_axis(dpg.mvYAxis): 24 | dpg.add_line_series([0, 1, 2, 3, 4], [0, 3, 4, 1, 5], label="line1") 25 | 26 | with dpg.child_window(border=False, pos=(50,24), width=106, height=19): # The trick is you need to set the child window width and height to be exactly the size of the button. Any bigger and the extra space will cover the plot and prevent mouse inputs. 27 | dpg.add_button(label="Pop to window!", callback=move_group, user_data=(my_group, main_window)) 28 | 29 | dpg.set_primary_window(main_window, True) 30 | dpg.create_viewport(title='Plot buttons', width=800, height=600) 31 | dpg.setup_dearpygui() 32 | dpg.show_viewport() 33 | dpg.start_dearpygui() 34 | dpg.destroy_context() 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /sizing/get_item_size_on_startup.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | def print_item_sizes(sender): 5 | print(f"On {sender} the window size is: {dpg.get_item_rect_size('window')}") 6 | print(f"On {sender} the text size is: {dpg.get_item_rect_size('text')}") 7 | 8 | with dpg.window(autosize=True, tag='window'): 9 | dpg.add_input_text(width=400, height=200, multiline=True, tag='text') 10 | print_item_sizes('startup') 11 | 12 | dpg.set_frame_callback(1, print_item_sizes) 13 | dpg.set_frame_callback(2, print_item_sizes) 14 | dpg.create_viewport(width=800, height=600, title="Get item size on startup") 15 | dpg.setup_dearpygui() 16 | dpg.show_viewport() 17 | dpg.start_dearpygui() 18 | dpg.destroy_context() 19 | 20 | """ 21 | $ python get_item_size_on_startup.py 22 | On startup the window size is: [0, 0] 23 | On startup the text size is: [0, 0] 24 | On 1 the window size is: [100, 100] 25 | On 1 the text size is: [400, 200] 26 | On 2 the window size is: [416, 235] 27 | On 2 the text size is: [400, 200] 28 | """ -------------------------------------------------------------------------------- /sizing/resize_button_with_viewport.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | from itertools import chain 3 | dpg.create_context() 4 | 5 | texture_data = list(chain.from_iterable([(1, 0, 1, 1) for _ in range(100 * 100)])) 6 | with dpg.texture_registry(): 7 | pink_square = dpg.add_static_texture(width=100, height=100, default_value=texture_data) 8 | 9 | with dpg.window() as primary_window: 10 | dpg.add_text("Resize the viewport to see the button resize.") 11 | image_button = dpg.add_image_button(pink_square) 12 | 13 | def resize_primary_window(): 14 | x,y = dpg.get_item_rect_size(primary_window) 15 | dpg.set_item_height(image_button, y//10) 16 | dpg.set_item_width(image_button, x//10) 17 | 18 | with dpg.item_handler_registry() as registry: 19 | dpg.add_item_resize_handler(callback=resize_primary_window) 20 | dpg.bind_item_handler_registry(primary_window, registry) 21 | 22 | dpg.set_primary_window(primary_window, True) 23 | dpg.create_viewport(width=800, height=600, title="Resize Button With Viewport") 24 | dpg.setup_dearpygui() 25 | dpg.show_viewport() 26 | dpg.start_dearpygui() 27 | dpg.destroy_context() -------------------------------------------------------------------------------- /sizing/resize_child_window.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | def resize_height(sender, data): 5 | dpg.configure_item(dpg.get_item_parent(sender), height=data) 6 | 7 | with dpg.window() as primary_window: 8 | with dpg.child_window(height=100) as child_window: 9 | dpg.add_slider_int(label="Resize Child Window", default_value=dpg.get_item_height(child_window), min_value=50, max_value=300, callback=resize_height) 10 | dpg.add_separator() 11 | with dpg.child_window(): 12 | pass 13 | 14 | dpg.set_primary_window(primary_window, True) 15 | dpg.create_viewport(title='Resize Child Window', width=800, height=600) 16 | dpg.setup_dearpygui() 17 | dpg.show_viewport() 18 | dpg.start_dearpygui() 19 | dpg.destroy_context() -------------------------------------------------------------------------------- /spacing/adjustable_separators.py: -------------------------------------------------------------------------------- 1 | # Using tables it is straightforward to implement layouts with horizontally adjustable containers. 2 | # However, it is not possible to do the same vertically using tables. This is a quick experimental 3 | # implementation of a vertically adjustable separator using an image texture and a clicked handler. 4 | # It's not perfect and might be a bit buggy, but it might be useful for someone. 5 | # Limitations - you have to click accurately on the separator to adjust it, it only adjusts the 6 | # height of the child window above it, and the mouse cursor doesn't change to indicate that it is 7 | # adjustable. 8 | 9 | from itertools import chain 10 | import dearpygui.dearpygui as dpg 11 | dpg.create_context() 12 | 13 | def adjustable_separator(child_window, width=3840, height=5, colour=(255, 255, 255, 50)): 14 | with dpg.texture_registry(): 15 | data = list(chain.from_iterable([[c / 255 for c in colour] for _ in range(width*height)])) 16 | separator_texture = dpg.add_static_texture(width=width, height=height, default_value=data) 17 | separator = dpg.add_image(separator_texture) 18 | def clicked_callback(): 19 | while dpg.is_mouse_button_down(0): 20 | y_pos = dpg.get_mouse_pos()[1] 21 | dpg.split_frame(delay=10) 22 | y_delta = y_pos - dpg.get_mouse_pos()[1] 23 | height = dpg.get_item_height(child_window) - y_delta 24 | if height < 1: height = 1 25 | dpg.configure_item(child_window, height=height) 26 | with dpg.item_handler_registry() as item_handler: 27 | dpg.add_item_clicked_handler(callback=clicked_callback) 28 | dpg.bind_item_handler_registry(item=separator, handler_registry=item_handler) 29 | 30 | with dpg.window(height=500, width=600, no_scrollbar=True): 31 | with dpg.table(header_row=False, resizable=True): 32 | dpg.add_table_column(width_fixed=True, init_width_or_weight=200) 33 | dpg.add_table_column() 34 | with dpg.table_row(): 35 | with dpg.child_window() as child_window_1: 36 | dpg.add_text("Child Window 1") 37 | with dpg.group(): 38 | with dpg.child_window(height=200) as child_window_2: 39 | dpg.add_text("Child Window 2") 40 | adjustable_separator(child_window_2) 41 | with dpg.child_window(height=100) as child_window_3: 42 | dpg.add_text("Child Window 3") 43 | adjustable_separator(child_window_3) 44 | with dpg.child_window() as child_window_4: 45 | dpg.add_text("Child Window 4") 46 | 47 | dpg.create_viewport(title="Adjustable Separators", width=900, height=700) 48 | dpg.setup_dearpygui() 49 | dpg.show_viewport() 50 | dpg.start_dearpygui() 51 | dpg.destroy_context() -------------------------------------------------------------------------------- /spacing/spacing_child_windows_using_tables.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | child_window_tags = [] 5 | 6 | def resize_child_windows(sender, data): 7 | x,y = dpg.get_item_rect_size(data) # get the size of the 'main_window' 8 | for tag in child_window_tags: 9 | dpg.set_item_height(tag, y//2) 10 | 11 | def add_layout(): 12 | with dpg.table(header_row=False): 13 | dpg.add_table_column() 14 | dpg.add_table_column() 15 | with dpg.table_row(): 16 | with dpg.child_window() as child1: 17 | child_window_tags.append(child1) 18 | dpg.add_button(label="Button") 19 | with dpg.child_window() as child2: 20 | child_window_tags.append(child2) 21 | dpg.add_slider_float(label="Slider") 22 | with dpg.child_window(): 23 | pass 24 | 25 | with dpg.window(tag='main_window'): 26 | with dpg.tab_bar(tag="tabs"): 27 | with dpg.tab(label="Home", tag="home_tab"): 28 | add_layout() 29 | with dpg.tab(label="Settings", tag="settings_tab"): 30 | add_layout() 31 | 32 | with dpg.item_handler_registry() as registry: 33 | dpg.add_item_resize_handler(callback=resize_child_windows) 34 | dpg.bind_item_handler_registry('main_window', registry) 35 | 36 | dpg.create_viewport(width=1200, height=800, title='Spacing Child Windows Using Tables') 37 | dpg.set_primary_window('main_window', True) 38 | dpg.setup_dearpygui() 39 | dpg.show_viewport() 40 | dpg.start_dearpygui() 41 | dpg.destroy_context() -------------------------------------------------------------------------------- /spacing/spacing_using_auto_align.py: -------------------------------------------------------------------------------- 1 | # Credit to @Quattro - https://discord.com/channels/736279277242417272/761721971129843712/1005966507114758224 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | def auto_align(item, alignment_type: int, x_align: float = 0.5, y_align: float = 0.5): 6 | def _center_h(_s, _d, data): 7 | parent = dpg.get_item_parent(data[0]) 8 | while dpg.get_item_info(parent)['type'] != "mvAppItemType::mvWindowAppItem": 9 | parent = dpg.get_item_parent(parent) 10 | parent_width = dpg.get_item_rect_size(parent)[0] 11 | width = dpg.get_item_rect_size(data[0])[0] 12 | new_x = (parent_width // 2 - width // 2) * data[1] * 2 13 | dpg.set_item_pos(data[0], [new_x, dpg.get_item_pos(data[0])[1]]) 14 | 15 | def _center_v(_s, _d, data): 16 | parent = dpg.get_item_parent(data[0]) 17 | while dpg.get_item_info(parent)['type'] != "mvAppItemType::mvWindowAppItem": 18 | parent = dpg.get_item_parent(parent) 19 | parent_width = dpg.get_item_rect_size(parent)[1] 20 | height = dpg.get_item_rect_size(data[0])[1] 21 | new_y = (parent_width // 2 - height // 2) * data[1] * 2 22 | dpg.set_item_pos(data[0], [dpg.get_item_pos(data[0])[0], new_y]) 23 | 24 | if 0 <= alignment_type <= 2: 25 | with dpg.item_handler_registry(): 26 | if alignment_type == 0: 27 | # horizontal only alignment 28 | dpg.add_item_visible_handler(callback=_center_h, user_data=[item, x_align]) 29 | elif alignment_type == 1: 30 | # vertical only alignment 31 | dpg.add_item_visible_handler(callback=_center_v, user_data=[item, y_align]) 32 | elif alignment_type == 2: 33 | # both horizontal and vertical alignment 34 | dpg.add_item_visible_handler(callback=_center_h, user_data=[item, x_align]) 35 | dpg.add_item_visible_handler(callback=_center_v, user_data=[item, y_align]) 36 | 37 | dpg.bind_item_handler_registry(item, dpg.last_container()) 38 | 39 | with dpg.window(): 40 | item = dpg.add_button(label="I'm a button!") 41 | auto_align(item, 2, x_align=0.5, y_align=0.5) 42 | 43 | dpg.create_viewport(width=600, height=600, title="Spacing Using Auto Align") 44 | dpg.setup_dearpygui() 45 | dpg.show_viewport() 46 | dpg.start_dearpygui() 47 | dpg.destroy_context() -------------------------------------------------------------------------------- /spacing/spacing_using_child_window_grid.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | BUTTON_WIDTH = 200 5 | BUTTON_HEIGHT = 100 6 | ITEM_SPACING_X, ITEM_SPACING_Y = 8,4 # The default item spacing 7 | 8 | with dpg.window() as primary_window: 9 | with dpg.child_window(height=-BUTTON_HEIGHT-ITEM_SPACING_Y): 10 | pass 11 | with dpg.group(horizontal=True): 12 | with dpg.child_window(width=-BUTTON_WIDTH-ITEM_SPACING_X): 13 | pass 14 | with dpg.child_window(border=False): 15 | dpg.add_button(label="Connect", width=BUTTON_WIDTH, height=BUTTON_HEIGHT) 16 | 17 | dpg.set_primary_window(primary_window, True) 18 | dpg.create_viewport(width=600, height=400, title="Spacing Using Child Window Grid") 19 | dpg.setup_dearpygui() 20 | dpg.show_viewport() 21 | dpg.start_dearpygui() 22 | dpg.destroy_context() -------------------------------------------------------------------------------- /spacing/spacing_using_tables.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | with dpg.theme() as blank_button_theme: # To make a button look like text 5 | with dpg.theme_component(dpg.mvButton): 6 | dpg.add_theme_color(dpg.mvThemeCol_Button, (0, 0, 0, 0)) 7 | dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, (0, 0, 0, 0)) 8 | dpg.add_theme_color(dpg.mvThemeCol_ButtonActive, (0, 0, 0, 0)) 9 | 10 | with dpg.window(width=600, height=400): 11 | with dpg.table(header_row=False): 12 | dpg.add_table_column() 13 | dpg.add_table_column() 14 | dpg.add_table_column() 15 | with dpg.table_row(): 16 | dpg.add_spacer() 17 | dpg.add_button(label="Username", width=-1) 18 | dpg.bind_item_theme(dpg.last_item(), blank_button_theme) 19 | dpg.add_spacer() 20 | with dpg.table_row(): 21 | dpg.add_spacer() 22 | dpg.add_input_text(width=-1) 23 | dpg.add_spacer() 24 | 25 | dpg.create_viewport(width=800, height=600, title="Spacing Using Tables") 26 | dpg.setup_dearpygui() 27 | dpg.show_viewport() 28 | dpg.start_dearpygui() 29 | dpg.destroy_context() -------------------------------------------------------------------------------- /tables/table_cell_callback.py: -------------------------------------------------------------------------------- 1 | # Select table cell and get data callback 2 | # from https://github.com/DataExplorerUser/tools 3 | import dearpygui.dearpygui as dpg 4 | 5 | dpg.create_context() 6 | dpg.create_viewport(width=800, height=450) 7 | dpg.setup_dearpygui() 8 | 9 | with dpg.theme() as global_theme: 10 | with dpg.theme_component(dpg.mvTable): 11 | dpg.add_theme_color(dpg.mvThemeCol_HeaderHovered, (255, 0, 0, 100), category=dpg.mvThemeCat_Core) 12 | dpg.add_theme_color(dpg.mvThemeCol_HeaderActive, (0, 0, 0, 0), category=dpg.mvThemeCat_Core) 13 | dpg.add_theme_color(dpg.mvThemeCol_Header, (0, 0, 0, 0), category=dpg.mvThemeCat_Core) 14 | 15 | dpg.bind_theme(global_theme) 16 | 17 | 18 | def clb_selectable(sender, app_data, user_data): 19 | print(f"Content:{dpg.get_item_label(sender)}, Row and column: {user_data}") 20 | 21 | 22 | with dpg.window(tag="Table"): 23 | with dpg.table(header_row=True): 24 | dpg.add_table_column(label="First") 25 | dpg.add_table_column(label="Second") 26 | dpg.add_table_column(label="Third") 27 | 28 | for i in range(20): 29 | with dpg.table_row(): 30 | for j in range(3): 31 | dpg.add_selectable(label=f"Row{i} Column{j}", callback=clb_selectable, user_data=(i,j)) 32 | 33 | dpg.show_viewport() 34 | dpg.set_primary_window("Table", True) 35 | dpg.start_dearpygui() 36 | dpg.destroy_context() -------------------------------------------------------------------------------- /tables/table_row_callback.py: -------------------------------------------------------------------------------- 1 | # Select table row and get data callback 2 | # from https://github.com/DataExplorerUser/tools 3 | import dearpygui.dearpygui as dpg 4 | 5 | dpg.create_context() 6 | dpg.create_viewport(width=800, height=450) 7 | dpg.setup_dearpygui() 8 | 9 | with dpg.theme() as global_theme: 10 | with dpg.theme_component(dpg.mvTable): 11 | dpg.add_theme_color(dpg.mvThemeCol_HeaderHovered, (255, 0, 0, 100), category=dpg.mvThemeCat_Core) 12 | dpg.add_theme_color(dpg.mvThemeCol_HeaderActive, (0, 0, 0, 0), category=dpg.mvThemeCat_Core) 13 | dpg.add_theme_color(dpg.mvThemeCol_Header, (0, 0, 0, 0), category=dpg.mvThemeCat_Core) 14 | 15 | dpg.bind_theme(global_theme) 16 | 17 | 18 | def clb_selectable(sender, app_data, user_data): 19 | print(f"Row {user_data}") 20 | 21 | 22 | with dpg.window(tag="Table"): 23 | with dpg.table(header_row=True, callback=lambda: print("callback!!!")): 24 | dpg.add_table_column(label="First") 25 | dpg.add_table_column(label="Second") 26 | dpg.add_table_column(label="Third") 27 | 28 | for i in range(20): 29 | with dpg.table_row(): 30 | for j in range(3): 31 | dpg.add_selectable(label=f"Row{i} Column{j}", span_columns=True, callback=clb_selectable, user_data=i) 32 | 33 | dpg.show_viewport() 34 | dpg.set_primary_window("Table", True) 35 | dpg.start_dearpygui() 36 | dpg.destroy_context() -------------------------------------------------------------------------------- /threading/progress_bar.py: -------------------------------------------------------------------------------- 1 | import time 2 | import threading 3 | import dearpygui.dearpygui as dpg 4 | dpg.create_context() 5 | 6 | running = False 7 | paused = False 8 | progress = 0 9 | 10 | def run_task(): 11 | global running 12 | global paused 13 | global progress 14 | print("Running...") 15 | 16 | for i in range(1,101): 17 | while paused: 18 | time.sleep(0.1) 19 | if not running: 20 | return 21 | progress = i 22 | print(i) 23 | dpg.set_value(progress_bar, 1/100 * (i)) 24 | dpg.configure_item(progress_bar, overlay=f"{i}%") 25 | time.sleep(0.05) 26 | 27 | print("Finished") 28 | running = False 29 | dpg.set_item_label(start_pause_resume_button, "Finished") 30 | dpg.disable_item(start_pause_resume_button) 31 | dpg.show_item(reset_button) 32 | 33 | def start_stop_callback(): 34 | global running 35 | global paused 36 | if not running: 37 | print("Started") 38 | running = True 39 | paused = False 40 | thread = threading.Thread(target=run_task, args=(), daemon=True) 41 | thread.start() 42 | dpg.set_item_label(start_pause_resume_button, "Pause") 43 | else: 44 | if not paused: 45 | print("Paused...") 46 | paused = True 47 | dpg.set_item_label(start_pause_resume_button, "Resume") 48 | dpg.show_item(reset_button) 49 | return 50 | print("Resuming...") 51 | paused = False 52 | dpg.set_item_label(start_pause_resume_button, "Pause") 53 | dpg.hide_item(reset_button) 54 | 55 | def reset_callback(): 56 | global running 57 | global paused 58 | global progress 59 | running = False 60 | paused = False 61 | progress = 0 62 | dpg.set_value(progress_bar, 0) 63 | dpg.configure_item(progress_bar, overlay="0%") 64 | dpg.set_item_label(start_pause_resume_button, "Start") 65 | dpg.enable_item(start_pause_resume_button) 66 | dpg.hide_item(reset_button) 67 | 68 | with dpg.window() as primary_window: 69 | with dpg.group(horizontal=True): 70 | start_pause_resume_button = dpg.add_button(label="Start", width=70, callback=start_stop_callback) 71 | reset_button = dpg.add_button(label="Reset", width=70, callback=reset_callback) 72 | dpg.hide_item(reset_button) 73 | progress_bar = dpg.add_progress_bar(default_value=0, width=-1, overlay="0%") 74 | 75 | dpg.set_primary_window(primary_window, True) 76 | dpg.create_viewport(width=400, height=300, title="Progress Bar with Pause/Resume") 77 | dpg.setup_dearpygui() 78 | dpg.show_viewport() 79 | dpg.start_dearpygui() 80 | dpg.destroy_context() -------------------------------------------------------------------------------- /threading/start_stop_button_basic.py: -------------------------------------------------------------------------------- 1 | import time 2 | import threading 3 | import dearpygui.dearpygui as dpg 4 | dpg.create_context() 5 | 6 | running = False 7 | 8 | def run_task(): 9 | while running: 10 | print("Running...") 11 | time.sleep(1) 12 | 13 | def start_stop_callback(): 14 | global running 15 | if not running: 16 | print("Started") 17 | running = True 18 | thread = threading.Thread(target=run_task, args=(), daemon=True) 19 | thread.start() 20 | dpg.set_item_label(start_stop_button, "Stop") 21 | else: 22 | print("Stopped") 23 | running = False 24 | dpg.set_item_label(start_stop_button, "Start") 25 | 26 | with dpg.window() as primary_window: 27 | dpg.add_text("Check the terminal for output") 28 | start_stop_button = dpg.add_button(label="Start", callback=start_stop_callback) 29 | 30 | dpg.set_primary_window(primary_window, True) 31 | dpg.create_viewport(width=300, height=200, title="Basic Start/Stop Button") 32 | dpg.setup_dearpygui() 33 | dpg.show_viewport() 34 | dpg.start_dearpygui() 35 | dpg.destroy_context() -------------------------------------------------------------------------------- /threading/start_stop_button_class.py: -------------------------------------------------------------------------------- 1 | import time 2 | import threading 3 | import dearpygui.dearpygui as dpg 4 | dpg.create_context() 5 | 6 | def add_start_stop_button(target: callable, parent: int | str = None, user_data: any = None): 7 | StartStopButton(target, parent, user_data) 8 | 9 | class StartStopButton: 10 | def __init__(self, target: callable, parent: int | str = None, user_data: any = None): 11 | self.parent = parent or dpg.last_container() 12 | self.button = dpg.add_button(label="Start", parent=self.parent, callback=self.start_stop_callback) 13 | self.target = target 14 | self.user_data = user_data 15 | self.running = False 16 | 17 | def start_stop_callback(self): 18 | if not self.running: 19 | print(f"Started {self.user_data}") 20 | self.running = True 21 | thread = threading.Thread(target=self.target, args=(self,self.user_data), daemon=True) 22 | thread.start() 23 | dpg.set_item_label(self.button, "Stop") 24 | else: 25 | print(f"Stopped {self.user_data}") 26 | self.running = False 27 | dpg.set_item_label(self.button, "Start") 28 | 29 | def run_task(self, user_data): 30 | while self.running: 31 | print(f"Running... {user_data}") 32 | time.sleep(1) 33 | 34 | with dpg.window() as primary_window: 35 | dpg.add_text("Check the terminal for output") 36 | with dpg.group(horizontal=True): 37 | dpg.add_text("Task 1") 38 | add_start_stop_button(target=run_task, user_data="Task 1") 39 | with dpg.group(horizontal=True): 40 | dpg.add_text("Task 2") 41 | add_start_stop_button(target=run_task, user_data="Task 2") 42 | 43 | dpg.set_primary_window(primary_window, True) 44 | dpg.create_viewport(width=300, height=200, title="Multiple Start/Stop Buttons") 45 | dpg.setup_dearpygui() 46 | dpg.show_viewport() 47 | dpg.start_dearpygui() 48 | dpg.destroy_context() -------------------------------------------------------------------------------- /window/child_window_clicked_handler.py: -------------------------------------------------------------------------------- 1 | # Credit to @Lucifer - https://discord.com/channels/736279277242417272/852624162396831744/1006905716205965393 2 | import dearpygui.dearpygui as dpg 3 | dpg.create_context() 4 | 5 | # A child window cannot have an item clicked handler - you get this error: 6 | # Item Type: mvAppItemType::mvChildWindow 7 | # Message: Item Handler Registry includes inapplicable handler: mvClickedHandler 8 | # if you consult the source code you will see that mvAppItemType::mvChildWindow is not part of CanItemTypeBeClicked 9 | # https://github.com/hoffstadt/DearPyGui/blob/1651e65a4ac8ecaf3bda5019f2d8c8106b371820/DearPyGui/src/ui/AppItems/mvAppItem.cpp#L466 10 | # The solution is to use a global clicked handler and check if the child window is hovered. 11 | 12 | def mouse_click_callback(): 13 | if dpg.is_item_hovered(child_window): 14 | print(f"{child_window} was clicked!") 15 | 16 | with dpg.handler_registry(): 17 | dpg.add_mouse_click_handler(button=0, callback=mouse_click_callback) 18 | 19 | with dpg.window(width=500, height=500): 20 | with dpg.child_window(width=200, height=200) as child_window: 21 | dpg.add_button(label="Button") 22 | dpg.add_slider_int() 23 | 24 | dpg.create_viewport(width=600, height=600, title="Child Window Clicked Handler") 25 | dpg.setup_dearpygui() 26 | dpg.show_viewport() 27 | dpg.start_dearpygui() 28 | dpg.destroy_context() -------------------------------------------------------------------------------- /window/drag_menu_bar.py: -------------------------------------------------------------------------------- 1 | # Inspired by https://github.com/bandit-masked/raccoon/blob/main/src/gui/gui.py#L231 2 | # and credit to @Atlamillias on Discord. 3 | # see also drag_undecorated_viewport.py 4 | import dearpygui.dearpygui as dpg 5 | dpg.create_context() 6 | 7 | is_menu_bar_clicked = False 8 | 9 | def mouse_drag_callback(_, app_data): 10 | if is_menu_bar_clicked: 11 | _, drag_delta_x, drag_delta_y = app_data 12 | viewport_pos_x, viewport_pos_y = dpg.get_viewport_pos() 13 | new_pos_x = viewport_pos_x + drag_delta_x 14 | new_pos_y = max(viewport_pos_y + drag_delta_y, 0) 15 | dpg.set_viewport_pos([new_pos_x, new_pos_y]) 16 | 17 | def mouse_click_callback(): 18 | global is_menu_bar_clicked 19 | is_menu_bar_clicked = True if dpg.get_mouse_pos(local=False)[1] < 30 else False # 30 pixels is slightly more than the height of the default menu bar 20 | 21 | with dpg.handler_registry(): 22 | dpg.add_mouse_drag_handler(button=0, threshold=0, callback=mouse_drag_callback) 23 | dpg.add_mouse_click_handler(button=0, callback=mouse_click_callback) 24 | 25 | with dpg.window() as primary_window: 26 | dpg.add_text("Click and drag the menu bar to move this undecorated window.") 27 | with dpg.menu_bar(): 28 | with dpg.menu(label="File"): 29 | dpg.add_menu_item(label="New") 30 | dpg.add_menu_item(label="Open") 31 | dpg.add_menu_item(label="Save") 32 | with dpg.menu(label="View"): 33 | dpg.add_menu_item(label="Maximize viewport", callback=lambda: dpg.maximize_viewport()) 34 | dpg.add_slider_float(label="Width", default_value=600, min_value=400, max_value=1000, callback=lambda _, app_data: dpg.set_viewport_width(app_data)) 35 | dpg.add_slider_float(label="Height", default_value=600, min_value=400, max_value=1000, callback=lambda _, app_data: dpg.set_viewport_height(app_data)) 36 | dpg.add_button(label="Close", callback=lambda: dpg.destroy_context()) 37 | 38 | dpg.set_primary_window(primary_window, True) 39 | dpg.create_viewport(width=600, height=600, decorated=False) 40 | dpg.setup_dearpygui() 41 | dpg.show_viewport() 42 | dpg.start_dearpygui() 43 | dpg.destroy_context() -------------------------------------------------------------------------------- /window/drag_undecorated_viewport.py: -------------------------------------------------------------------------------- 1 | # Credit @v-ein - see https://discord.com/channels/736279277242417272/1191409079025930340/1191409079025930340 2 | # see also drag_menu_bar.py 3 | import dearpygui.dearpygui as dpg 4 | 5 | dpg.create_context() 6 | 7 | def drag_viewport(sender, app_data): 8 | FRAME_PADDING_Y = 3 9 | _, drag_dx, drag_dy = app_data 10 | 11 | # Note: at this point, the mouse has already moved off the starting point, 12 | # but to do a hit-test on the title bar, we need the starting point so we go 13 | # back to it. 14 | drag_start_y = dpg.get_mouse_pos(local=False)[1] - drag_dy 15 | title_bar_height = 2*FRAME_PADDING_Y + dpg.get_text_size("")[1] 16 | if drag_start_y < title_bar_height: # only drag the viewport when dragging the title bar 17 | x_pos, y_pos = dpg.get_viewport_pos() 18 | 19 | # We're limiting the y position so that the viewport doesn't go off the top of the screen 20 | dpg.set_viewport_pos((x_pos + drag_dx, max(0, y_pos + drag_dy))) 21 | 22 | with dpg.handler_registry(): 23 | dpg.add_mouse_drag_handler(button=0, threshold=0.0, callback=drag_viewport) 24 | 25 | window_title = "Test title bar" 26 | 27 | with dpg.window(label=window_title, on_close=lambda: dpg.stop_dearpygui()) as wnd: 28 | dpg.add_text("Window contents goes here") 29 | 30 | # Need the same title here as the title on dpg.window (otherwise the task bar 31 | # will show a different... this can be used as a feature - to control the task bar 32 | # separately :)). 33 | dpg.create_viewport(title=window_title, width=400, height=300) 34 | dpg.set_primary_window(wnd, True) 35 | dpg.configure_item(wnd, no_title_bar=False) 36 | dpg.set_viewport_decorated(False) 37 | 38 | dpg.setup_dearpygui() 39 | dpg.show_viewport() 40 | dpg.start_dearpygui() 41 | dpg.destroy_context() -------------------------------------------------------------------------------- /window/pop_to_window.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | def move_group(sender, app_data, user_data): 5 | group, window = user_data 6 | 7 | def close(): 8 | dpg.move_item(group, parent=window) 9 | dpg.delete_item(new_window) 10 | dpg.show_item(sender) 11 | 12 | 13 | with dpg.window(on_close=close) as new_window: 14 | dpg.move_item(group, parent=new_window) 15 | dpg.hide_item(sender) 16 | 17 | 18 | with dpg.window() as main_window: 19 | 20 | with dpg.child_window(width=120, height=80) as child_window: 21 | with dpg.group() as my_group: 22 | dpg.add_input_text(default_value="Hello world", width=100) 23 | dpg.add_slider_float(width=100) 24 | 25 | dpg.add_button(label="Pop to window!", callback=move_group, user_data=(my_group, child_window)) 26 | 27 | 28 | dpg.set_primary_window(main_window, True) 29 | dpg.create_viewport(title='Pop to Window', width=500, height=300) 30 | dpg.setup_dearpygui() 31 | dpg.show_viewport() 32 | dpg.start_dearpygui() 33 | dpg.destroy_context() 34 | -------------------------------------------------------------------------------- /window/restrict_window_position.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | def clamp(n, min_n, max_n): 5 | return max(min(max_n, n), min_n) 6 | 7 | def drag_callback(): 8 | if dpg.is_item_hovered(window) and dpg.get_value(checkbox): 9 | x, y = dpg.get_item_pos(window) 10 | w, h = dpg.get_item_rect_size(window) 11 | vw, vh = dpg.get_viewport_client_width(), dpg.get_viewport_client_height() 12 | cx, cy = clamp(x, 0, vw - w), clamp(y, 0, vh - h) 13 | if cx != x or cy != y: 14 | dpg.set_item_pos(window, (cx, cy)) 15 | 16 | with dpg.handler_registry(): 17 | dpg.add_mouse_drag_handler(button=0, callback=drag_callback) 18 | 19 | with dpg.window() as window: 20 | dpg.add_text("Drag this window to the\n edge of the viewport and\n it will not exceed the bounds") 21 | checkbox = dpg.add_checkbox(label="Restrict window", default_value=True) 22 | 23 | dpg.create_viewport(width=800, height=600, title="Restrict Window Position") 24 | dpg.setup_dearpygui() 25 | dpg.show_viewport() 26 | dpg.start_dearpygui() 27 | dpg.destroy_context() -------------------------------------------------------------------------------- /window/right_click_context_menu.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | import pyperclip # pip install pyperclip 3 | dpg.create_context() 4 | 5 | def right_click_context_menu(sender, app_data, user_data): 6 | def cut(): 7 | pyperclip.copy(dpg.get_value(user_data)) 8 | dpg.set_value(user_data, "") 9 | dpg.delete_item(popup) 10 | 11 | def copy(): 12 | pyperclip.copy(dpg.get_value(user_data)) 13 | dpg.delete_item(popup) 14 | 15 | def paste(): 16 | data = dpg.get_value(user_data) 17 | dpg.set_value(user_data, data+pyperclip.paste()) 18 | dpg.delete_item(popup) 19 | 20 | with dpg.window(popup=True, no_focus_on_appearing=False) as popup: 21 | dpg.add_button(label="Cut", callback=cut) 22 | dpg.add_button(label="Copy", callback=copy) 23 | dpg.add_button(label="Paste", callback=paste) 24 | 25 | def add_text_box(default_value=""): 26 | text_input = dpg.add_input_text(multiline=True, default_value=default_value) 27 | with dpg.item_handler_registry() as registry: 28 | dpg.add_item_clicked_handler(button=dpg.mvMouseButton_Right, callback=right_click_context_menu, user_data=text_input) 29 | dpg.bind_item_handler_registry(text_input, registry) 30 | 31 | with dpg.window() as primary_window: 32 | add_text_box("Right click me!") 33 | add_text_box("Right click me too!") 34 | 35 | dpg.set_primary_window(primary_window, True) 36 | dpg.create_viewport(width=400, height=300, title="Cut/Copy/Paste Context Menu") 37 | dpg.setup_dearpygui() 38 | dpg.show_viewport() 39 | dpg.start_dearpygui() 40 | dpg.destroy_context() -------------------------------------------------------------------------------- /window/status_bar.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | STATUS_BAR_HEIGHT = 20 5 | 6 | with dpg.theme() as status_bar_theme: 7 | with dpg.theme_component(): 8 | dpg.add_theme_color(dpg.mvThemeCol_WindowBg, (42, 123, 207, 255)) 9 | dpg.add_theme_style(dpg.mvStyleVar_WindowPadding, 4, 0) 10 | 11 | def resize_primary_window(): 12 | x,y = dpg.get_item_rect_size(primary_window) 13 | dpg.configure_item(status_bar, width=x) 14 | dpg.configure_item(status_bar, pos=(0, y-STATUS_BAR_HEIGHT)) 15 | 16 | with dpg.window() as primary_window: 17 | dpg.set_primary_window(primary_window, True) 18 | with dpg.item_handler_registry() as registry: 19 | dpg.add_item_resize_handler(callback=resize_primary_window) 20 | dpg.bind_item_handler_registry(primary_window, registry) 21 | 22 | with dpg.menu_bar(): 23 | with dpg.menu(label="View"): 24 | dpg.add_menu_item(label="Show/hide status bar", callback=lambda: dpg.configure_item(status_bar, show=not dpg.is_item_shown(status_bar))) 25 | 26 | with dpg.window(): 27 | dpg.add_button(label="Hello world") 28 | 29 | with dpg.window(no_title_bar=True, no_move=True, no_resize=False) as status_bar: 30 | dpg.bind_item_theme(status_bar, status_bar_theme) 31 | with dpg.group(horizontal=True): 32 | dpg.add_text("Hello") 33 | dpg.add_button(label="world") 34 | 35 | dpg.create_viewport(width=800, height=600) 36 | dpg.setup_dearpygui() 37 | dpg.show_viewport() 38 | dpg.start_dearpygui() 39 | dpg.destroy_context() -------------------------------------------------------------------------------- /window/transparency/main.py: -------------------------------------------------------------------------------- 1 | # Credit @Tensor - https://discord.com/channels/736279277242417272/1068600047090016397/1070025491895029760 2 | # Tested on Windows 11 3 | # Check that you have "Transparency effects" enabled in Windows Settings 4 | 5 | import ctypes 6 | 7 | import dearpygui.dearpygui as dpg 8 | 9 | import tools 10 | from windoweffect import window_effect 11 | 12 | dpg.create_context() 13 | dpg.create_viewport(decorated=True, resizable=True, clear_color=[0, 0, 0, 0]) 14 | dpg.setup_dearpygui() 15 | 16 | 17 | class MARGINS(ctypes.Structure): 18 | _fields_ = [ 19 | ("cxLeftWidth", ctypes.c_int), 20 | ("cxRightWidth", ctypes.c_int), 21 | ("cyTopHeight", ctypes.c_int), 22 | ("cyBottomHeight", ctypes.c_int) 23 | ] 24 | 25 | 26 | def removeBackground(): 27 | margins = MARGINS(-1, -1, -1, -1) 28 | ctypes.windll.dwmapi.DwmExtendFrameIntoClientArea(tools.get_hwnd(), margins) 29 | 30 | 31 | def restoreBackground(): 32 | margins = MARGINS(0, 0, 0, 0) 33 | ctypes.windll.dwmapi.DwmExtendFrameIntoClientArea(tools.get_hwnd(), margins) 34 | 35 | 36 | def removeBackgroundEffect(): 37 | window_effect.removeBackgroundEffect(tools.get_hwnd()) 38 | 39 | 40 | def setAeroEffect(): 41 | window_effect.setAeroEffect(tools.get_hwnd()) 42 | 43 | 44 | def setAcrylicEffect(): 45 | gradientColor = list(map(lambda item: int(item), dpg.get_value('color_picker'))) 46 | gradientColor = bytearray(gradientColor).hex().upper() 47 | enableShadow = dpg.get_value('enable_shadow') 48 | window_effect.setAcrylicEffect(tools.get_hwnd(), gradientColor=gradientColor, enableShadow=enableShadow) 49 | 50 | 51 | def setMicaEffect(): 52 | isDarkMode = dpg.get_value('is_dark_mode') 53 | window_effect.setMicaEffect(tools.get_hwnd(), isDarkMode=isDarkMode) 54 | 55 | 56 | with dpg.window(label="Background Effect Test", height=500): 57 | dpg.add_checkbox(label="decorated", default_value=True, callback=lambda _, flag: dpg.set_viewport_decorated(flag)) 58 | with dpg.group(horizontal=True): 59 | dpg.add_button(label="removeBackground (Transparency)", callback=removeBackground) 60 | dpg.add_button(label="Restore", callback=restoreBackground) 61 | dpg.add_button(label="removeBackgroundEffect", callback=removeBackgroundEffect) 62 | dpg.add_button(label="setAeroEffect", callback=setAeroEffect) 63 | with dpg.group(horizontal=True): 64 | dpg.add_button(label="setAcrylicEffect", callback=setAcrylicEffect) 65 | dpg.add_checkbox(label="enableShadow", default_value=False, tag="enable_shadow") 66 | with dpg.group(horizontal=True): 67 | dpg.add_button(label="setMicaEffect", callback=setMicaEffect) 68 | dpg.add_checkbox(label="isDarkMode", default_value=False, tag="is_dark_mode") 69 | dpg.add_color_picker(display_type=dpg.mvColorEdit_uint8, picker_mode=dpg.mvColorPicker_bar, alpha_bar=True, tag='color_picker') 70 | 71 | dpg.show_viewport() 72 | dpg.start_dearpygui() 73 | dpg.destroy_context() 74 | -------------------------------------------------------------------------------- /window/transparency/tools.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ctypes 4 | import ctypes.wintypes 5 | import os 6 | 7 | user32 = ctypes.windll.user32 8 | WNDENUMPROC = ctypes.WINFUNCTYPE(ctypes.wintypes.BOOL, 9 | ctypes.wintypes.HWND, 10 | ctypes.wintypes.LPARAM) 11 | user32.EnumWindows.argtypes = [WNDENUMPROC, 12 | ctypes.wintypes.LPARAM] 13 | 14 | 15 | def get_hwnd_from_pid(pid: int) -> int | None: 16 | result = None 17 | 18 | def callback(hwnd, _): 19 | nonlocal result 20 | lpdw_PID = ctypes.c_ulong() 21 | user32.GetWindowThreadProcessId(hwnd, ctypes.byref(lpdw_PID)) 22 | hwnd_PID = lpdw_PID.value 23 | 24 | if hwnd_PID == pid: 25 | result = hwnd 26 | return False 27 | return True 28 | 29 | cb_worker = WNDENUMPROC(callback) 30 | user32.EnumWindows(cb_worker, 0) 31 | return result 32 | 33 | 34 | def get_hwnd() -> int | None: 35 | return get_hwnd_from_pid(os.getpid()) 36 | -------------------------------------------------------------------------------- /window/transparency/windoweffect/__init__.py: -------------------------------------------------------------------------------- 1 | # https://github.com/zhiyiYo/PyQt-Frameless-Window/tree/master/qframelesswindow/windows 2 | 3 | from .window_effect import WindowsWindowEffect 4 | 5 | window_effect = WindowsWindowEffect() 6 | -------------------------------------------------------------------------------- /window/transparency/windoweffect/c_structures.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from ctypes import POINTER, Structure, c_int 3 | from ctypes.wintypes import DWORD, HWND, ULONG, POINT, RECT, UINT 4 | from enum import Enum 5 | 6 | 7 | class WINDOWCOMPOSITIONATTRIB(Enum): 8 | WCA_UNDEFINED = 0 9 | WCA_NCRENDERING_ENABLED = 1 10 | WCA_NCRENDERING_POLICY = 2 11 | WCA_TRANSITIONS_FORCEDISABLED = 3 12 | WCA_ALLOW_NCPAINT = 4 13 | WCA_CAPTION_BUTTON_BOUNDS = 5 14 | WCA_NONCLIENT_RTL_LAYOUT = 6 15 | WCA_FORCE_ICONIC_REPRESENTATION = 7 16 | WCA_EXTENDED_FRAME_BOUNDS = 8 17 | WCA_HAS_ICONIC_BITMAP = 9 18 | WCA_THEME_ATTRIBUTES = 10 19 | WCA_NCRENDERING_EXILED = 11 20 | WCA_NCADORNMENTINFO = 12 21 | WCA_EXCLUDED_FROM_LIVEPREVIEW = 13 22 | WCA_VIDEO_OVERLAY_ACTIVE = 14 23 | WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15 24 | WCA_DISALLOW_PEEK = 16 25 | WCA_CLOAK = 17 26 | WCA_CLOAKED = 18 27 | WCA_ACCENT_POLICY = 19 28 | WCA_FREEZE_REPRESENTATION = 20 29 | WCA_EVER_UNCLOAKED = 21 30 | WCA_VISUAL_OWNER = 22 31 | WCA_HOLOGRAPHIC = 23 32 | WCA_EXCLUDED_FROM_DDA = 24 33 | WCA_PASSIVEUPDATEMODE = 25 34 | WCA_USEDARKMODECOLORS = 26 35 | WCA_CORNER_STYLE = 27 36 | WCA_PART_COLOR = 28 37 | WCA_DISABLE_MOVESIZE_FEEDBACK = 29 38 | WCA_LAST = 30 39 | 40 | 41 | class ACCENT_STATE(Enum): 42 | """ Client area status enumeration class """ 43 | ACCENT_DISABLED = 0 44 | ACCENT_ENABLE_GRADIENT = 1 45 | ACCENT_ENABLE_TRANSPARENTGRADIENT = 2 46 | ACCENT_ENABLE_BLURBEHIND = 3 # Aero effect 47 | ACCENT_ENABLE_ACRYLICBLURBEHIND = 4 # Acrylic effect 48 | ACCENT_ENABLE_HOSTBACKDROP = 5 # Mica effect 49 | ACCENT_INVALID_STATE = 6 50 | 51 | 52 | class ACCENT_POLICY(Structure): 53 | """ Specific attributes of client area """ 54 | 55 | _fields_ = [ 56 | ("AccentState", DWORD), 57 | ("AccentFlags", DWORD), 58 | ("GradientColor", DWORD), 59 | ("AnimationId", DWORD), 60 | ] 61 | 62 | 63 | class WINDOWCOMPOSITIONATTRIBDATA(Structure): 64 | _fields_ = [ 65 | ("Attribute", DWORD), 66 | # Pointer() receives any ctypes type and returns a pointer type 67 | ("Data", POINTER(ACCENT_POLICY)), 68 | ("SizeOfData", ULONG), 69 | ] 70 | 71 | 72 | class DWMNCRENDERINGPOLICY(Enum): 73 | DWMNCRP_USEWINDOWSTYLE = 0 74 | DWMNCRP_DISABLED = 1 75 | DWMNCRP_ENABLED = 2 76 | DWMNCRP_LAS = 3 77 | 78 | 79 | class DWMWINDOWATTRIBUTE(Enum): 80 | DWMWA_NCRENDERING_ENABLED = 1 81 | DWMWA_NCRENDERING_POLICY = 2 82 | DWMWA_TRANSITIONS_FORCEDISABLED = 3 83 | DWMWA_ALLOW_NCPAINT = 4 84 | DWMWA_CAPTION_BUTTON_BOUNDS = 5 85 | DWMWA_NONCLIENT_RTL_LAYOUT = 6 86 | DWMWA_FORCE_ICONIC_REPRESENTATION = 7 87 | DWMWA_FLIP3D_POLICY = 8 88 | DWMWA_EXTENDED_FRAME_BOUNDS = 9 89 | DWMWA_HAS_ICONIC_BITMAP = 10 90 | DWMWA_DISALLOW_PEEK = 11 91 | DWMWA_EXCLUDED_FROM_PEEK = 12 92 | DWMWA_CLOAK = 13 93 | DWMWA_CLOAKED = 14 94 | DWMWA_FREEZE_REPRESENTATION = 15 95 | DWMWA_PASSIVE_UPDATE_MODE = 16 96 | DWMWA_USE_HOSTBACKDROPBRUSH = 17 97 | DWMWA_USE_IMMERSIVE_DARK_MODE = 18 98 | DWMWA_WINDOW_CORNER_PREFERENCE = 19 99 | DWMWA_BORDER_COLOR = 20 100 | DWMWA_CAPTION_COLOR = 21 101 | DWMWA_TEXT_COLOR = 22 102 | DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 23 103 | DWMWA_LAST = 24 104 | 105 | 106 | class MARGINS(Structure): 107 | _fields_ = [ 108 | ("cxLeftWidth", c_int), 109 | ("cxRightWidth", c_int), 110 | ("cyTopHeight", c_int), 111 | ("cyBottomHeight", c_int), 112 | ] 113 | 114 | 115 | class MINMAXINFO(Structure): 116 | _fields_ = [ 117 | ("ptReserved", POINT), 118 | ("ptMaxSize", POINT), 119 | ("ptMaxPosition", POINT), 120 | ("ptMinTrackSize", POINT), 121 | ("ptMaxTrackSize", POINT), 122 | ] 123 | 124 | 125 | class PWINDOWPOS(Structure): 126 | _fields_ = [ 127 | ('hWnd', HWND), 128 | ('hwndInsertAfter', HWND), 129 | ('x', c_int), 130 | ('y', c_int), 131 | ('cx', c_int), 132 | ('cy', c_int), 133 | ('flags', UINT) 134 | ] 135 | 136 | 137 | class NCCALCSIZE_PARAMS(Structure): 138 | _fields_ = [ 139 | ('rgrc', RECT*3), 140 | ('lppos', POINTER(PWINDOWPOS)) 141 | ] 142 | 143 | 144 | LPNCCALCSIZE_PARAMS = POINTER(NCCALCSIZE_PARAMS) -------------------------------------------------------------------------------- /window/transparency/windoweffect/window_effect.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | import sys 3 | import warnings 4 | from ctypes import POINTER, byref, c_bool, c_int, cdll, pointer, sizeof 5 | from ctypes.wintypes import DWORD, LONG, LPCVOID 6 | from platform import platform 7 | 8 | import win32api 9 | import win32con 10 | import win32gui 11 | 12 | from .c_structures import (ACCENT_POLICY, ACCENT_STATE, DWMNCRENDERINGPOLICY, 13 | DWMWINDOWATTRIBUTE, MARGINS, 14 | WINDOWCOMPOSITIONATTRIB, 15 | WINDOWCOMPOSITIONATTRIBDATA) 16 | 17 | 18 | class WindowsWindowEffect: 19 | """ Windows window effect """ 20 | 21 | def __init__(self): 22 | # Declare the function signature of the API 23 | self.user32 = cdll.LoadLibrary("user32") 24 | self.dwmapi = cdll.LoadLibrary("dwmapi") 25 | self.SetWindowCompositionAttribute = self.user32.SetWindowCompositionAttribute 26 | self.DwmExtendFrameIntoClientArea = self.dwmapi.DwmExtendFrameIntoClientArea 27 | self.DwmSetWindowAttribute = self.dwmapi.DwmSetWindowAttribute 28 | self.SetWindowCompositionAttribute.restype = c_bool 29 | self.DwmExtendFrameIntoClientArea.restype = LONG 30 | self.DwmSetWindowAttribute.restype = LONG 31 | self.SetWindowCompositionAttribute.argtypes = [ 32 | c_int, 33 | POINTER(WINDOWCOMPOSITIONATTRIBDATA), 34 | ] 35 | self.DwmSetWindowAttribute.argtypes = [c_int, DWORD, LPCVOID, DWORD] 36 | self.DwmExtendFrameIntoClientArea.argtypes = [c_int, POINTER(MARGINS)] 37 | 38 | # Initialize structure 39 | self.accentPolicy = ACCENT_POLICY() 40 | self.winCompAttrData = WINDOWCOMPOSITIONATTRIBDATA() 41 | self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value 42 | self.winCompAttrData.SizeOfData = sizeof(self.accentPolicy) 43 | self.winCompAttrData.Data = pointer(self.accentPolicy) 44 | 45 | def setAcrylicEffect(self, hWnd, gradientColor="F2F2F299", enableShadow=True, animationId=0): 46 | """ Add the acrylic effect to the window 47 | 48 | Parameters 49 | ---------- 50 | hWnd: int or `sip.voidptr` 51 | Window handle 52 | 53 | gradientColor: str 54 | Hexadecimal acrylic mixed color, corresponding to four RGBA channels 55 | 56 | isEnableShadow: bool 57 | Enable window shadows 58 | 59 | animationId: int 60 | Turn on matte animation 61 | """ 62 | if "Windows-7" in platform(): 63 | warnings.warn("The acrylic effect is only available on Win10+") 64 | return 65 | 66 | hWnd = int(hWnd) 67 | gradientColor = ''.join(gradientColor[i:i + 2] for i in range(6, -1, -2)) 68 | gradientColor = DWORD(int(gradientColor, base=16)) 69 | animationId = DWORD(animationId) 70 | accentFlags = DWORD(0x20 | 0x40 | 0x80 | 0x100) if enableShadow else DWORD(0) 71 | self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_ACRYLICBLURBEHIND.value 72 | self.accentPolicy.GradientColor = gradientColor 73 | self.accentPolicy.AccentFlags = accentFlags 74 | self.accentPolicy.AnimationId = animationId 75 | self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value 76 | self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) 77 | 78 | def setMicaEffect(self, hWnd, isDarkMode=False): 79 | """ Add the mica effect to the window (Win11 only) 80 | 81 | Parameters 82 | ---------- 83 | hWnd: int or `sip.voidptr` 84 | Window handle 85 | 86 | isDarkMode: bool 87 | whether to use dark mode mica effect 88 | """ 89 | if sys.getwindowsversion().build < 22000: 90 | warnings.warn("The mica effect is only available on Win11") 91 | return 92 | 93 | hWnd = int(hWnd) 94 | margins = MARGINS(-1, -1, -1, -1) 95 | self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) 96 | 97 | self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value 98 | self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_HOSTBACKDROP.value 99 | self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) 100 | 101 | if isDarkMode: 102 | self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_USEDARKMODECOLORS.value 103 | self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) 104 | 105 | if sys.getwindowsversion().build < 22523: 106 | self.DwmSetWindowAttribute(hWnd, 1029, byref(c_int(1)), 4) 107 | else: 108 | self.DwmSetWindowAttribute(hWnd, 38, byref(c_int(2)), 4) 109 | 110 | def setAeroEffect(self, hWnd): 111 | """ Add the aero effect to the window 112 | 113 | Parameters 114 | ---------- 115 | hWnd: int or `sip.voidptr` 116 | Window handle 117 | """ 118 | hWnd = int(hWnd) 119 | self.winCompAttrData.Attribute = WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY.value 120 | self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_ENABLE_BLURBEHIND.value 121 | self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) 122 | 123 | def removeBackgroundEffect(self, hWnd): 124 | """ Remove background effect 125 | 126 | Parameters 127 | ---------- 128 | hWnd: int or `sip.voidptr` 129 | Window handle 130 | """ 131 | hWnd = int(hWnd) 132 | self.accentPolicy.AccentState = ACCENT_STATE.ACCENT_DISABLED.value 133 | self.SetWindowCompositionAttribute(hWnd, pointer(self.winCompAttrData)) 134 | 135 | @staticmethod 136 | def moveWindow(hWnd): 137 | """ Move the window 138 | 139 | Parameters 140 | ---------- 141 | hWnd: int or `sip.voidptr` 142 | Window handle 143 | """ 144 | hWnd = int(hWnd) 145 | win32gui.ReleaseCapture() 146 | win32api.SendMessage( 147 | hWnd, win32con.WM_SYSCOMMAND, win32con.SC_MOVE + win32con.HTCAPTION, 0 148 | ) 149 | 150 | def addShadowEffect(self, hWnd): 151 | """ Add DWM shadow to window 152 | 153 | Parameters 154 | ---------- 155 | hWnd: int or `sip.voidptr` 156 | Window handle 157 | """ 158 | hWnd = int(hWnd) 159 | margins = MARGINS(-1, -1, -1, -1) 160 | self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) 161 | 162 | def addMenuShadowEffect(self, hWnd): 163 | """ Add DWM shadow to menu 164 | 165 | Parameters 166 | ---------- 167 | hWnd: int or `sip.voidptr` 168 | Window handle 169 | """ 170 | hWnd = int(hWnd) 171 | self.DwmSetWindowAttribute( 172 | hWnd, 173 | DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value, 174 | byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_ENABLED.value)), 175 | 4, 176 | ) 177 | margins = MARGINS(-1, -1, -1, -1) 178 | self.DwmExtendFrameIntoClientArea(hWnd, byref(margins)) 179 | 180 | def removeShadowEffect(self, hWnd): 181 | """ Remove DWM shadow from the window 182 | 183 | Parameters 184 | ---------- 185 | hWnd: int or `sip.voidptr` 186 | Window handle 187 | """ 188 | hWnd = int(hWnd) 189 | self.DwmSetWindowAttribute( 190 | hWnd, 191 | DWMWINDOWATTRIBUTE.DWMWA_NCRENDERING_POLICY.value, 192 | byref(c_int(DWMNCRENDERINGPOLICY.DWMNCRP_DISABLED.value)), 193 | 4, 194 | ) 195 | 196 | @staticmethod 197 | def removeMenuShadowEffect(hWnd): 198 | """ Remove shadow from pop-up menu 199 | 200 | Parameters 201 | ---------- 202 | hWnd: int or `sip.voidptr` 203 | Window handle 204 | """ 205 | hWnd = int(hWnd) 206 | style = win32gui.GetClassLong(hWnd, win32con.GCL_STYLE) 207 | style &= ~0x00020000 # CS_DROPSHADOW 208 | win32api.SetClassLong(hWnd, win32con.GCL_STYLE, style) 209 | 210 | @staticmethod 211 | def addWindowAnimation(hWnd): 212 | """ Enables the maximize and minimize animation of the window 213 | 214 | Parameters 215 | ---------- 216 | hWnd : int or `sip.voidptr` 217 | Window handle 218 | """ 219 | hWnd = int(hWnd) 220 | style = win32gui.GetWindowLong(hWnd, win32con.GWL_STYLE) 221 | win32gui.SetWindowLong( 222 | hWnd, 223 | win32con.GWL_STYLE, 224 | style 225 | | win32con.WS_MINIMIZEBOX 226 | | win32con.WS_MAXIMIZEBOX 227 | | win32con.WS_CAPTION 228 | | win32con.CS_DBLCLKS 229 | | win32con.WS_THICKFRAME, 230 | ) 231 | -------------------------------------------------------------------------------- /window/window_always_on_top.py: -------------------------------------------------------------------------------- 1 | import dearpygui.dearpygui as dpg 2 | dpg.create_context() 3 | 4 | with dpg.window(label="Background Window", width=200, height=200, no_bring_to_front_on_focus=True): 5 | dpg.add_button(label="Open info window", callback=lambda: dpg.show_item(info_window)) 6 | 7 | with dpg.window(label="Info", show=False) as info_window: 8 | dpg.add_text("I'm always on top") 9 | 10 | dpg.create_viewport(width=400, height=300, title="Window Always On Top") 11 | dpg.setup_dearpygui() 12 | dpg.show_viewport() 13 | dpg.start_dearpygui() 14 | dpg.destroy_context() --------------------------------------------------------------------------------