├── .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()
--------------------------------------------------------------------------------